#include <bits/stdc++.h>
using namespace std;
const int N = 400005;
int p[N];
bool bj[N];
struct edge {
  int v, next;
} e[N * 2];
int head[N], eid;
// int find(int x){
//	if (x==p[x]) return x;
//	return find(p[x]);
// }
// void Union(int x,int y){
//	x=find(x);
//	y=find(y);
//	if (x!=y) p[x]=y;
// }
// int ans;
void addEdge(int u, int v) {
  e[eid] = {v, head[u]};
  head[u] = eid++;
}
void dfs(int u, int fa) {
  bj[u] = 1;
  //	cout<<u<<endl;
  for (int i = head[u]; ~i; i = e[i].next) {
    int v = e[i].v;
    //	cout<<v<<endl;
    if (v == fa)
      continue;
    dfs(v, u);
  }
}
bool pa[N];
int main() {
  memset(head, -1, sizeof head);
  //	freopen("sample.txt","r",stdin);
  int n, m;
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= m; i++) {
    int a, b;
    scanf("%d %d", &a, &b);
    addEdge(a, b);
    addEdge(b, a);
  }
  int cnt = 0;
  for (int i = 1; i <= n; i++) {
    if (bj[i] == 0) {
      cnt++;
      dfs(i, 0);
    }
  }
  printf("%d\\n", cnt - 1);
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int p[N], cnt;
int find(int x) {
  if (x == p[x])
    return x;
  return find(p[x]);
}
void Union(int x, int y) {
  x = find(x);
  y = find(y);
  if (x == y)
    return;
  p[x] = p[y];
  cnt--;
}
int ans;
bool pa[N];
int main() {
  int n, m;
  scanf("%d %d", &n, &m);
  cnt = n;
  for (int i = 1; i <= n; i++)
    p[i] = i;
  for (int i = 1; i <= m; i++) {
    int a, b;
    scanf("%d %d", &a, &b);
    Union(a, b);
  }
  printf("%d\\n", cnt - 1);
  return 0;
}


#include <bits/stdc++.h>
using namespace std;

int par[100010];

int parent(int i) {
  if (par[i] == i)
    return i;
  else {
    return par[i] = parent(par[i]);
  }
}

int main() {
  int N, M;
  cin >> N >> M;

  for (int i = 0; i < N; i++)
    par[i] = i;

  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    A--;
    B--;
    int pa = par[A], pb = par[B];
    if (pa != pb) {
      par[pa] = pb;
    }
  }

  set<int> S;
  for (int i = 0; i < N; i++) {
    S.insert(parent(par[i]));
  }
  cout << S.size() - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;

int par[100010];

int parent(int i) {
  if (par[i] == i)
    return i;
  else {
    return par[i] = parent(par[i]);
  }
}

int main() {
  int N, M;
  cin >> N >> M;

  for (int i = 0; i < N; i++)
    par[i] = i;

  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    A--;
    B--;
    int pa = parent(A), pb = parent(B);
    if (pa == pb)
      continue;
    else {
      if (pa > pb) {
        par[pa] = pb;
      } else {
        par[pb] = pa;
      }
    }
  }

  set<int> S;
  for (int i = 0; i < N; i++) {
    S.insert(parent(par[i]));
  }
  cout << S.size() - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;
#define int int64_t
#define REP(i, n) FOR(i, 0, n)
#define FOR(i, s, n) for (int i = (s), i##_len = (n); i < i##_len; ++i)
#define ALL(obj) (obj).begin(), (obj).end()
#define ALLR(obj) (obj).rbegin(), (obj).rend()
#define CEIL(a, b) ((a - 1) / b + 1)

struct UnionFind {
  vector<int> par; // 親の番号
  UnionFind(int size) : par(size, -1) {}
  // xとyの木を併合
  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x != y) {
      if (par[y] < par[x])
        swap(x, y);
      par[x] += par[y];
      par[y] = x;
      return true;
    } else {
      return false;
    }
  }
  // 2つのデータx, yが属する木が同じならtrueを返す
  bool same(int x, int y) { return root(x) == root(y); }
  // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
  int root(int x) { return par[x] < 0 ? x : par[x] = root(par[x]); }
  // 頂点 v が属するグループと同じグループに属する頂点数を得る
  int size(int x) { return -par[root(x)]; }
};

void solve() {
  int N, M;
  cin >> N >> M;
  UnionFind uf(N);
  REP(i, M) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    uf.unite(a, b);
  }
  int ans = 0;
  REP(i, N) {
    REP(j, i) {
      // 同じグループでなければ接続
      if (uf.root(i) != uf.root(j)) {
        uf.unite(i, j);
        ans++;
      }
    }
  }
  cout << ans << endl;
}

signed main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  solve();
}

#include <bits/stdc++.h>
using namespace std;
#define int int64_t
#define REP(i, n) FOR(i, 0, n)
#define FOR(i, s, n) for (int i = (s), i##_len = (n); i < i##_len; ++i)
#define ALL(obj) (obj).begin(), (obj).end()
#define ALLR(obj) (obj).rbegin(), (obj).rend()
#define CEIL(a, b) ((a - 1) / b + 1)

struct UnionFind {
  vector<int> par; // 親の番号
  UnionFind(int size) : par(size, -1) {}
  // xとyの木を併合
  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x != y) {
      if (par[y] < par[x])
        swap(x, y);
      par[x] += par[y];
      par[y] = x;
      return true;
    } else {
      return false;
    }
  }
  // 2つのデータx, yが属する木が同じならtrueを返す
  bool same(int x, int y) { return root(x) == root(y); }
  // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
  int root(int x) { return par[x] < 0 ? x : par[x] = root(par[x]); }
  // 頂点 v が属するグループと同じグループに属する頂点数を得る
  int size(int x) { return -par[root(x)]; }
};

void solve() {
  int N, M;
  cin >> N >> M;
  UnionFind uf(N);
  // N個の島に対して、N-1個の橋で全て連結できる
  int ans = N - 1;
  REP(i, M) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    ans -= uf.unite(a, b);
  }
  cout << ans << endl;
}

signed main() {
  cin.tie(nullptr)->sync_with_stdio(false);
  solve();
}


#include <bits/stdc++.h>
using namespace std;
int main() {
  int n, m;
  cin >> n >> m;
  vector<int> a(m);
  vector<int> b(m);
  vector<int> c(n, 0);
  for (int i = 0; i < m; i++) {
    cin >> a.at(i) >> b.at(i);
  }

  for (int i = 0; i < m; i++) {
    int aa, bb;
    aa = a.at(i);
    bb = b.at(i);
    c.at(aa)++;
    c.at(bb)++;
  }
  int count = 0;
  for (int i = 0; i < m; i++) {
    if (c.at(i) == 0)
      count++;
  }
  if (count == 1) {
    cout << count << endl;
  } else {
    cout << count - 1 << endl;
  }
}

#include <bits/stdc++.h>
using namespace std;

vector<int> a[100005];
vector<bool> b(100005, false);

void c(int i) {
  if (!b[i]) {
    b[i] = true;
    for (auto v : a[i]) {
      c(v);
    }
  }
}

void solve() {
  int n, m;
  int cnt = 0;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int aa, bb;
    cin >> aa >> bb;
    aa--;
    bb--;
    a[aa].push_back(bb);
    a[bb].push_back(aa);
  }

  for (int i = 0; i < n; i++) {
    if (!b[i]) {
      cnt++;
      c(i);
    }
  }
  cout << cnt - 1 << endl;
}

int main() { solve(); }


//@formatter:off
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < int(n); ++i)
#define rrep(i, n) for (int i = int(n) - 1; i >= 0; i--)
#define rep2(i, s, n) for (int i = int(s); i < int(n); ++i)
#define all(a) a.begin(), a.end()
#define rall(a) a.rbegin(), a.rend()
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define vvi vector<vector<int>>
#define vl vector<ll>
#define vvl vector<vector<ll>>
#define vd vector<double>
#define vvd vector<vector<double>>
#define vs vector<string>
#define vc vector<char>
#define vvc vector<vector<char>>
#define vb vector<bool>
#define vvb vector<vector<bool>>
#define vp vector<P>
#define vvp vector<vector<P>>
using namespace std;
using ll = long long;
using P = pair<int, int>;
using LP = pair<ll, ll>;
template <class S, class T> istream &operator>>(istream &is, pair<S, T> &p) {
  return is >> p.first >> p.second;
}
template <class S, class T>
ostream &operator<<(ostream &os, const pair<S, T> &p) {
  return os << '{' << p.first << "," << p.second << '}';
}
template <class T> istream &operator>>(istream &is, vector<T> &v) {
  for (T &t : v) {
    is >> t;
  }
  return is;
}
template <class T> ostream &operator<<(ostream &os, const vector<T> &v) {
  os << '[';
  rep(i, v.size()) os << v[i] << (i == int(v.size() - 1) ? "" : ",");
  return os << ']';
}
void Yes(bool b) { cout << (b ? "Yes" : "No") << '\\n'; }
void YES(bool b) { cout << (b ? "YES" : "NO") << '\\n'; }
template <class T> bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
template <class T> bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
const int inf = 1001001001;
const ll linf = 1001001001001001001;
//@formatter:on

class unionfind {
  int n;
  vector<int> par, rank;

public:
  unionfind(int n) : n(n), par(n, -1), rank(n, 0) {}

  int root(int x) {
    if (par[x] < 0)
      return x;
    else
      return par[x] = root(par[x]);
  }

  bool same(int x, int y) { return root(x) == root(y); };

  bool merge(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (rank[x] < rank[y])
      swap(x, y);
    if (rank[x] == rank[y])
      rank[x]++;
    par[x] += par[y];
    par[y] = x;
    return true;
  }

  int size(int x) { return -par[root(x)]; };

  bool connected() {
    rep(i, n - 1) if (!same(i, i + 1)) return false;
    return true;
  }
};

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  int n, m;
  cin >> n >> m;
  unionfind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    uf.merge(a, b);
  }
  int ans = -1;
  rep(i, n) {
    if (uf.root(i) == i)
      ans++;
  }
  cout << ans << endl;
}

//@formatter:off
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < int(n); ++i)
#define rrep(i, n) for (int i = int(n) - 1; i >= 0; i--)
#define rep2(i, s, n) for (int i = int(s); i < int(n); ++i)
#define all(a) a.begin(), a.end()
#define rall(a) a.rbegin(), a.rend()
#define pb push_back
#define eb emplace_back
#define vi vector<int>
#define vvi vector<vector<int>>
#define vl vector<ll>
#define vvl vector<vector<ll>>
#define vd vector<double>
#define vvd vector<vector<double>>
#define vs vector<string>
#define vc vector<char>
#define vvc vector<vector<char>>
#define vb vector<bool>
#define vvb vector<vector<bool>>
#define vp vector<P>
#define vvp vector<vector<P>>
using namespace std;
using ll = long long;
using P = pair<int, int>;
using LP = pair<ll, ll>;
template <class S, class T> istream &operator>>(istream &is, pair<S, T> &p) {
  return is >> p.first >> p.second;
}
template <class S, class T>
ostream &operator<<(ostream &os, const pair<S, T> &p) {
  return os << '{' << p.first << "," << p.second << '}';
}
template <class T> istream &operator>>(istream &is, vector<T> &v) {
  for (T &t : v) {
    is >> t;
  }
  return is;
}
template <class T> ostream &operator<<(ostream &os, const vector<T> &v) {
  os << '[';
  rep(i, v.size()) os << v[i] << (i == int(v.size() - 1) ? "" : ",");
  return os << ']';
}
void Yes(bool b) { cout << (b ? "Yes" : "No") << '\\n'; }
void YES(bool b) { cout << (b ? "YES" : "NO") << '\\n'; }
template <class T> bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
template <class T> bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
const int inf = 1001001001;
const ll linf = 1001001001001001001;
//@formatter:on

class unionfind {
  int n;
  vector<int> par, rank;

public:
  unionfind(int n) : n(n), par(n, -1), rank(n, 0) {}

  int root(int x) {
    if (par[x] < 0)
      return x;
    else
      return par[x] = root(par[x]);
  }

  bool same(int x, int y) { return root(x) == root(y); };

  bool merge(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (rank[x] < rank[y])
      swap(x, y);
    if (rank[x] == rank[y])
      rank[x]++;
    par[x] += par[y];
    par[y] = x;
    return true;
  }

  int size(int x) { return -par[root(x)]; };

  bool connected() {
    rep(i, n - 1) if (!same(i, i + 1)) return false;
    return true;
  }
};

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  cout.tie(nullptr);
  int n, m;
  cin >> n >> m;
  unionfind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    uf.merge(a, b);
  }
  int ans = -1;
  rep(i, n) {
    if (uf.root(i) == i)
      ans++;
  }
  cout << ans << endl;
}


#include <bits/stdc++.h>
using namespace std;

int cities, roads, a, b, group;
vector<int> adj[10100];
bool visited[100100];

void dfs(int c) {
  if (!visited[c]) {
    visited[c] = true;
    for (int i = 0; i < adj[c].size(); i++) {
      int d = adj[c][i];
      dfs(d);
    }
  }
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  cin >> cities >> roads;
  for (int i = 1; i <= roads; i++) {
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  for (int i = 1; i <= cities; i++) {
    if (!visited[i])
      group++;
    dfs(i);
  }
  cout << group - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;

int cities, roads, a, b, group;
vector<int> adj[100100];
bool visited[100100];

void dfs(int c) {
  if (!visited[c]) {
    visited[c] = true;
    for (int i = 0; i < adj[c].size(); i++) {
      int d = adj[c][i];
      dfs(d);
    }
  }
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  cin >> cities >> roads;
  for (int i = 1; i <= roads; i++) {
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  for (int i = 1; i <= cities; i++) {
    if (!visited[i])
      group++;
    dfs(i);
  }
  cout << group - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;
vector<int> rec;
int find(int u) { return u == rec[u] ? u : rec[u] = find(rec[u]); }
int main() {
  int n, m;
  cin >> n >> m;
  rec.resize(n);
  for (int i = 0; i < n; ++i) {
    rec[i] = i;
  }
  int c = n - 1;
  for (int i = 0; i < m; ++i) {
    int u, v;
    cin >> u >> v;
    int _u = find(u), _v = find(v);
    if (_u != _v) {
      rec[_v] = _u;
      --c;
    }
  }
  cout << c << endl;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
vector<int> rec;
int find(int u) { return u == rec[u] ? u : rec[u] = find(rec[u]); }
int main() {
  int n, m;
  cin >> n >> m;
  rec.resize(n);
  for (int i = 0; i < n; ++i) {
    rec[i] = i;
  }
  int c = n - 1;
  for (int i = 0; i < m; ++i) {
    int u, v;
    cin >> u >> v;
    --u;
    --v;
    int _u = find(u), _v = find(v);
    if (_u != _v) {
      rec[_v] = _u;
      --c;
    }
  }
  cout << c << endl;
  return 0;
}


#include <bits/stdc++.h>
#define pb push_back
#define pii pair<int, int>
#define int long long int
#define vec vector<int>
#define inf 1e18
using namespace std;
vector<int> adj[100001];
int vis[100001];
int dfs(int node) {
  vis[node] = 1;
  for (int ch : adj[node]) {
    if (!vis[ch])
      dfs(ch);
  }
}
int32_t main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  int tt = 1;
  // cin>>tt;
  while (tt--) {
    int n, m, i;
    cin >> n >> m;
    for (i = 0; i < m; i++) {
      int u, v;
      cin >> u >> v;
      adj[u].pb(v);
      adj[v].pb(u);
    }
    int c = 0;
    for (i = 1; i <= n; i++) {
      if (!vis[i])
        dfs(i), c++;
    }
    cout << c - 1;
  }
}

#include <bits/stdc++.h>
#define pb push_back
#define pii pair<int, int>
#define int long long int
#define vec vector<int>
#define inf 1e18
using namespace std;
vector<int> adj[100001];
int vis[100001];
void dfs(int node) {
  vis[node] = 1;
  for (int ch : adj[node]) {
    if (!vis[ch])
      dfs(ch);
  }
}
int32_t main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  int tt = 1;
  // cin>>tt;
  while (tt--) {
    int n, m, i;
    cin >> n >> m;
    for (i = 0; i < m; i++) {
      int u, v;
      cin >> u >> v;
      adj[u].pb(v);
      adj[v].pb(u);
    }
    int c = 0;
    for (i = 1; i <= n; i++) {
      if (!vis[i])
        dfs(i), c++;
    }
    cout << c - 1;
  }
}


#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const long double PI = (acos(-1));
const long long MOD = 1000000007;
struct Edge {
  long long to;
  long long cost;
};
using Graph = vector<vector<Edge>>;
using P = pair<ll, ll>;
const long long INF = 1LL << 60;
#define ALL(a) (a).begin(), (a).end()
#define rep(i, n) for (int i = 0; i < n; i++)

bool is_prime(ll x) {
  if (x == 1) {
    return false;
  }
  for (ll i = 2; i * i <= x; i++) {
    if (x % i == 0)
      return false;
  }
  return true;
}

long long modpow(long long a, long long n, long long mod) {
  long long res = 1;
  while (n > 0) {
    if (n & 1)
      res = res * a % mod;
    a = a * a % mod;
    n >>= 1;
  }
  return res;
}

long long combi(long long n, long long a) {
  long long ans = 1, ans1 = 1;
  for (long long i = n - a + 1; i <= n; i++) {
    ans *= i % MOD;
    ans %= MOD;
  }

  for (long long i = 2; i <= a; i++)
    ans1 = (ans1 * i) % MOD;
  ans1 = modpow(ans1, MOD - 2, MOD);
  return ((ans % MOD) * ans1) % MOD;
}

template <typename T>
bool next_combination(const T first, const T last, int k) {
  const T subset = first + k;
  // empty container | k = 0 | k == n
  if (first == last || first == subset || last == subset) {
    return false;
  }
  T src = subset;
  while (first != src) {
    src--;
    if (*src < *(last - 1)) {
      T dest = subset;
      while (*src >= *dest) {
        dest++;
      }
      iter_swap(src, dest);
      rotate(src + 1, dest + 1, last);
      rotate(subset, subset + (last - dest) - 1, last);
      return true;
    }
  }
  // restore
  rotate(first, subset, last);
  return false;
}

void dfs(vector<ll> s, ll mi, ll mx, ll N, vector<vector<ll>> &arr) {
  if (s.size() == (size_t)N) {
    // cout << s.c_str() << endl;
    arr.push_back(s);
  } else {
    for (ll c = s.size() > 0 ? 0 : mi; c <= mx; c++) {
      s.push_back(c);
      dfs(s, mi, mx, N, arr);
      s.pop_back();
    }
  }

  return;
}

int bfs(int sx, int sy, int gx, int gy, int h, int w,
        vector<vector<char>> map) {
  queue<pair<int, int>> s;

  vector<vector<ll>> ans;

  for (int i = 0; i < h; i++) {
    vector<ll> aa(w);
    ans.push_back(aa);
  }

  s.push(make_pair(sx, sy));
  while (s.size() > 0) {

    pair<int, int> tmp = s.front();
    s.pop();
    map[tmp.first][tmp.second] = '#';
    for (int i = -1; i <= 1; i++) {
      for (int j = -1; j <= 1; j++) {
        if (tmp.first + i < 0 || tmp.first + i >= h) {
          continue;
        }
        if (tmp.second + j < 0 || tmp.second + j >= w) {
          continue;
        }
        if (i != 0 && j != 0) {
          continue;
        }
        if (i == 0 && j == 0) {
          continue;
        }
        if (map[tmp.first + i][tmp.second + j] == '#') {
          continue;
        }
        map[tmp.first + i][tmp.second + j] = '#';
        if (ans[tmp.first + i][tmp.second + j] == 0) {
          ans[tmp.first + i][tmp.second + j] = ans[tmp.first][tmp.second] + 1;
        } else {
          ans[tmp.first + i][tmp.second + j] =
              min(ans[tmp.first + i][tmp.second + j],
                  ans[tmp.first][tmp.second] + 1);
        }
        s.push(make_pair(tmp.first + i, tmp.second + j));
      }
    }
  }

  return ans[gy][gx];
}

ll modfactorial(ll a) {
  if (a == 1)
    return 1;

  return (a % MOD) * (modfactorial(a - 1) % MOD);
}

ll gcd(ll a, ll b) {
  if (a % b == 0) {
    return (b);
  } else {
    return (gcd(b, a % b));
  }
}

ll lcm(ll a, ll b) { return (a / gcd(a, b)) * b; }

map<ll, ll> prime_factor(ll n) {
  map<ll, ll> ret;
  for (ll i = 2; i * i <= n; i++) {
    while (n % i == 0) {
      ret[i]++;
      n /= i;
    }
  }
  if (n != 1)
    ret[n] = 1;
  return ret;
}

struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }

  int size(int x) { return -r[root(x)]; }
};

/* dijkstra(G,s,dis)
    入力：グラフ G, 開始点 s, 距離を格納する dis
    計算量：O(|E|log|V|)
    副作用：dis が書き換えられる
*/
void dijkstra(const Graph &G, int s, vector<ll> &dis, vector<ll> &prev) {
  int N = G.size();
  dis.resize(N, INF);
  prev.resize(N, -1); // 初期化
  priority_queue<P, vector<P>, greater<P>> pq;
  dis[s] = 0;
  pq.emplace(dis[s], s);
  while (!pq.empty()) {
    P p = pq.top();
    pq.pop();
    int v = p.second;
    if (dis[v] < p.first) {
      continue;
    }
    for (auto &e : G[v]) {
      if (dis[e.to] > dis[v] + e.cost) {
        dis[e.to] = dis[v] + e.cost;
        prev[e.to] = v; // 頂点 v を通って e.to にたどり着いた
        pq.emplace(dis[e.to], e.to);
      }
    }
  }
}

vector<ll> get_path(const vector<ll> &prev, ll t) {
  vector<ll> path;
  for (ll cur = t; cur != -1; cur = prev[cur]) {
    path.push_back(cur);
  }
  reverse(path.begin(), path.end()); // 逆順なのでひっくり返す
  return path;
}

vector<string> split(string &s, char delim) {
  vector<string> elems;
  stringstream ss(s);
  string item;
  while (getline(ss, item, delim)) {
    if (!item.empty()) {
      elems.push_back(item);
    }
  }
  return elems;
}

vector<ll> divisor(ll n) {
  vector<ll> ret;
  for (ll i = 1; i * i <= n; i++) {
    if (n % i == 0) {
      ret.push_back(i);
      if (i * i != n)
        ret.push_back(n / i);
    }
  }
  sort(begin(ret), end(ret));
  return (ret);
}

int main() {
  ll n, m;

  cin >> n >> m;

  Graph g(n);

  rep(i, m) {
    ll a, b;
    cin >> a >> b;
    a--;
    b--;
    Edge e;

    e.to = a;
    e.cost = 1;

    g[b].push_back(e);

    e.to = b;
    e.cost = 1;

    g[a].push_back(e);
  }

  ll ans = n;
  rep(i, n) {
    vector<ll> dis;
    vector<ll> prev;
    dijkstra(g, i, dis, prev);
    sort(ALL(dis));

    ans = min(ans, dis[1]);
  }

  cout << ans << endl;

  return 0;
}
// cout << std::fixed << std::setprecision(15)

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
const long double PI = (acos(-1));
const long long MOD = 1000000007;
struct Edge {
  long long to;
  long long cost;
};
using Graph = vector<vector<Edge>>;
using P = pair<ll, ll>;
const long long INF = 1LL << 60;
#define ALL(a) (a).begin(), (a).end()
#define rep(i, n) for (int i = 0; i < n; i++)

bool is_prime(ll x) {
  if (x == 1) {
    return false;
  }
  for (ll i = 2; i * i <= x; i++) {
    if (x % i == 0)
      return false;
  }
  return true;
}

long long modpow(long long a, long long n, long long mod) {
  long long res = 1;
  while (n > 0) {
    if (n & 1)
      res = res * a % mod;
    a = a * a % mod;
    n >>= 1;
  }
  return res;
}

long long combi(long long n, long long a) {
  long long ans = 1, ans1 = 1;
  for (long long i = n - a + 1; i <= n; i++) {
    ans *= i % MOD;
    ans %= MOD;
  }

  for (long long i = 2; i <= a; i++)
    ans1 = (ans1 * i) % MOD;
  ans1 = modpow(ans1, MOD - 2, MOD);
  return ((ans % MOD) * ans1) % MOD;
}

template <typename T>
bool next_combination(const T first, const T last, int k) {
  const T subset = first + k;
  // empty container | k = 0 | k == n
  if (first == last || first == subset || last == subset) {
    return false;
  }
  T src = subset;
  while (first != src) {
    src--;
    if (*src < *(last - 1)) {
      T dest = subset;
      while (*src >= *dest) {
        dest++;
      }
      iter_swap(src, dest);
      rotate(src + 1, dest + 1, last);
      rotate(subset, subset + (last - dest) - 1, last);
      return true;
    }
  }
  // restore
  rotate(first, subset, last);
  return false;
}

void dfs(vector<ll> s, ll mi, ll mx, ll N, vector<vector<ll>> &arr) {
  if (s.size() == (size_t)N) {
    // cout << s.c_str() << endl;
    arr.push_back(s);
  } else {
    for (ll c = s.size() > 0 ? 0 : mi; c <= mx; c++) {
      s.push_back(c);
      dfs(s, mi, mx, N, arr);
      s.pop_back();
    }
  }

  return;
}

int bfs(int sx, int sy, int gx, int gy, int h, int w,
        vector<vector<char>> map) {
  queue<pair<int, int>> s;

  vector<vector<ll>> ans;

  for (int i = 0; i < h; i++) {
    vector<ll> aa(w);
    ans.push_back(aa);
  }

  s.push(make_pair(sx, sy));
  while (s.size() > 0) {

    pair<int, int> tmp = s.front();
    s.pop();
    map[tmp.first][tmp.second] = '#';
    for (int i = -1; i <= 1; i++) {
      for (int j = -1; j <= 1; j++) {
        if (tmp.first + i < 0 || tmp.first + i >= h) {
          continue;
        }
        if (tmp.second + j < 0 || tmp.second + j >= w) {
          continue;
        }
        if (i != 0 && j != 0) {
          continue;
        }
        if (i == 0 && j == 0) {
          continue;
        }
        if (map[tmp.first + i][tmp.second + j] == '#') {
          continue;
        }
        map[tmp.first + i][tmp.second + j] = '#';
        if (ans[tmp.first + i][tmp.second + j] == 0) {
          ans[tmp.first + i][tmp.second + j] = ans[tmp.first][tmp.second] + 1;
        } else {
          ans[tmp.first + i][tmp.second + j] =
              min(ans[tmp.first + i][tmp.second + j],
                  ans[tmp.first][tmp.second] + 1);
        }
        s.push(make_pair(tmp.first + i, tmp.second + j));
      }
    }
  }

  return ans[gy][gx];
}

ll modfactorial(ll a) {
  if (a == 1)
    return 1;

  return (a % MOD) * (modfactorial(a - 1) % MOD);
}

ll gcd(ll a, ll b) {
  if (a % b == 0) {
    return (b);
  } else {
    return (gcd(b, a % b));
  }
}

ll lcm(ll a, ll b) { return (a / gcd(a, b)) * b; }

map<ll, ll> prime_factor(ll n) {
  map<ll, ll> ret;
  for (ll i = 2; i * i <= n; i++) {
    while (n % i == 0) {
      ret[i]++;
      n /= i;
    }
  }
  if (n != 1)
    ret[n] = 1;
  return ret;
}

struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }

  int size(int x) { return -r[root(x)]; }
};

/* dijkstra(G,s,dis)
    入力：グラフ G, 開始点 s, 距離を格納する dis
    計算量：O(|E|log|V|)
    副作用：dis が書き換えられる
*/
void dijkstra(const Graph &G, int s, vector<ll> &dis, vector<ll> &prev) {
  int N = G.size();
  dis.resize(N, INF);
  prev.resize(N, -1); // 初期化
  priority_queue<P, vector<P>, greater<P>> pq;
  dis[s] = 0;
  pq.emplace(dis[s], s);
  while (!pq.empty()) {
    P p = pq.top();
    pq.pop();
    int v = p.second;
    if (dis[v] < p.first) {
      continue;
    }
    for (auto &e : G[v]) {
      if (dis[e.to] > dis[v] + e.cost) {
        dis[e.to] = dis[v] + e.cost;
        prev[e.to] = v; // 頂点 v を通って e.to にたどり着いた
        pq.emplace(dis[e.to], e.to);
      }
    }
  }
}

vector<ll> get_path(const vector<ll> &prev, ll t) {
  vector<ll> path;
  for (ll cur = t; cur != -1; cur = prev[cur]) {
    path.push_back(cur);
  }
  reverse(path.begin(), path.end()); // 逆順なのでひっくり返す
  return path;
}

vector<string> split(string &s, char delim) {
  vector<string> elems;
  stringstream ss(s);
  string item;
  while (getline(ss, item, delim)) {
    if (!item.empty()) {
      elems.push_back(item);
    }
  }
  return elems;
}

vector<ll> divisor(ll n) {
  vector<ll> ret;
  for (ll i = 1; i * i <= n; i++) {
    if (n % i == 0) {
      ret.push_back(i);
      if (i * i != n)
        ret.push_back(n / i);
    }
  }
  sort(begin(ret), end(ret));
  return (ret);
}

int main() {
  ll n, m;

  cin >> n >> m;

  UnionFind u(n);

  rep(i, m) {
    ll a, b;
    cin >> a >> b;
    a--;
    b--;
    u.unite(a, b);
  }

  set<ll> s;
  rep(i, n) { s.insert(u.root(i)); }

  cout << s.size() - 1 << endl;

  return 0;
}
// cout << std::fixed << std::setprecision(15)


#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int n, m, cnt, father[N];

int find(int x) { return x == father[x] ? x : father[x] = find(father[x]); }

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++)
    father[i] = i;
  for (int i = 1, x, y; i <= m; i++) {
    scanf("%d%d", &x, &y);
    father[find(x)] = y;
  }
  for (int i = 1; i <= n; i++)
    if (find(i) == i)
      cnt++;
  printf("%d\\n", cnt - 1);
  return 0;
}

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int n, m, cnt, father[N];

int find(int x) { return x == father[x] ? x : father[x] = find(father[x]); }

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++)
    father[i] = i;
  for (int i = 1, x, y; i <= m; i++) {
    scanf("%d%d", &x, &y);
    father[find(x)] = find(y);
  }
  for (int i = 1; i <= n; i++)
    if (find(i) == i)
      cnt++;
  printf("%d\\n", cnt - 1);
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;

void dfs(int i, bool *visited, vector<vector<int>> v) {
  visited[i] = true;
  vector<int> temp = v[i];
  for (int j = 0; j < temp.size(); j++) {
    if (!visited[temp[j]]) {
      dfs(temp[j], visited, v);
    }
  }
}

int main() {
  int n, m;
  set<int> s;
  cin >> n >> m;
  int a, b;
  vector<vector<int>> v(n + 1);
  int count = 0;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    v[a].push_back(b);
    v[b].push_back(a);
  }
  bool *visited = new bool[n + 1]();
  for (int i = 1; i <= n; i++) {
    if (visited[i] == false) {
      count++;
      dfs(i, visited, v);
    }
  }
  cout << count - 1 << endl;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;

void dfs(ll i, bool *visited, ll n, vector<ll> *v) {
  visited[i] = true;
  vector<ll> temp = v[i];
  for (int j = 0; j < temp.size(); j++) {
    if (!visited[temp[j]]) {
      dfs(temp[j], visited, n, v);
    }
  }
}

int main() {
  ll n, m;
  // set<int> s;
  cin >> n >> m;
  ll a, b;
  vector<ll> *v = new vector<ll>[n + 1];
  ll count = 0;
  for (ll i = 0; i < m; i++) {
    cin >> a >> b;
    v[a].push_back(b);
    v[b].push_back(a);
  }
  bool *visited = new bool[n + 1]();
  for (ll i = 1; i <= n; i++) {
    if (visited[i] == false) {
      count++;
      dfs(i, visited, n, v);
    }
  }
  cout << count - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
typedef int64_t ll;
typedef long double ld;
const ll MOD = 1000000007;
const ll MODA = 998244353;
#define rep(i, n) for (ll i = 0; i < (ll)(n); i++)
long long gcd(long long a, long long b) {
  ll gcdmax = max(a, b);
  ll gcdmin = min(a, b);
  while (true) {
    if (gcdmax % gcdmin == 0)
      break;
    else
      gcdmax %= gcdmin;
    swap(gcdmin, gcdmax);
  }
  return gcdmin;
}
ll powerup(ll N, ll P, ll M) {
  if (P == 0)
    return 1;
  else if (P % 2 == 0) {
    ll t = powerup(N, P / 2, M);
    return t * t % M;
  } else
    return N * powerup(N, P - 1, M) % M;
}
vector<ll> find_divisor(ll N) {
  ll k = 1;
  while (k * k <= N) {
    k++;
  }
  vector<ll> A(1);
  rep(i, k) {
    if (i == 1)
      A.at(0) = 1;
    else if (i >= 2) {
      if (N % i == 0)
        A.push_back(i);
    }
  }
  ll t = 0;
  t = A.size();
  rep(i, t) {
    if (A.at(t - i - 1) * A.at(t - i - 1) != N)
      A.push_back(N / A.at(t - 1 - i));
  }
  return A;
}
vector<ll> fac;
vector<ll> finv;
vector<ll> inv;
void COMinit(ll N, ll P) {
  rep(i, N + 1) {
    if (i == 0) {
      fac.push_back(1);
      finv.push_back(1);
      inv.push_back(1);
    } else if (i == 1) {
      fac.push_back(1);
      finv.push_back(1);
      inv.push_back(1);
    } else {
      fac.push_back(fac.at(i - 1) * i % P);
      inv.push_back(P - inv.at(P % i) * (P / i) % P);
      finv.push_back(finv.at(i - 1) * inv.at(i) % P);
    }
  }
}

ll COM(ll n, ll k, ll P) {
  if (n < k)
    return 0;
  if (n < 0 || k < 0)
    return 0;
  return fac.at(n) * (finv.at(k) * finv.at(n - k) % P) % P;
}

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(ll N) : par(N) { // 最初は全てが根であるとして初期化
    for (ll i = 0; i < N; i++)
      par[i] = i;
  }

  ll root(ll x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(ll x, ll y) { // xとyの木を併合
    ll rx = root(x);       // xの根をrx
    ll ry = root(y);       // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(ll x, ll y) { // 2つのデータx, yが属する木が同じならtrueを返す
    ll rx = root(x);
    ll ry = root(y);
    return rx == ry;
  }

  ll COUNT(ll N) {
    vector<ll> count(N);
    ll ans = 0;
    rep(i, N) {
      if (count.at(root(i)) == 0) {
        ans++;
        count.at(root(i))++;
      }
    }
    return ans;
  }
};

int main() {
  ll N, M;
  cin >> N >> M;
  UnionFind tree(N);
  rep(i, M) {
    ll A, B;
    cin >> A >> B;
    tree.unite(A, B);
  }
  cout << tree.COUNT(N) - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;
typedef int64_t ll;
typedef long double ld;
const ll MOD = 1000000007;
const ll MODA = 998244353;
#define rep(i, n) for (ll i = 0; i < (ll)(n); i++)
long long gcd(long long a, long long b) {
  ll gcdmax = max(a, b);
  ll gcdmin = min(a, b);
  while (true) {
    if (gcdmax % gcdmin == 0)
      break;
    else
      gcdmax %= gcdmin;
    swap(gcdmin, gcdmax);
  }
  return gcdmin;
}
ll powerup(ll N, ll P, ll M) {
  if (P == 0)
    return 1;
  else if (P % 2 == 0) {
    ll t = powerup(N, P / 2, M);
    return t * t % M;
  } else
    return N * powerup(N, P - 1, M) % M;
}
vector<ll> find_divisor(ll N) {
  ll k = 1;
  while (k * k <= N) {
    k++;
  }
  vector<ll> A(1);
  rep(i, k) {
    if (i == 1)
      A.at(0) = 1;
    else if (i >= 2) {
      if (N % i == 0)
        A.push_back(i);
    }
  }
  ll t = 0;
  t = A.size();
  rep(i, t) {
    if (A.at(t - i - 1) * A.at(t - i - 1) != N)
      A.push_back(N / A.at(t - 1 - i));
  }
  return A;
}
vector<ll> fac;
vector<ll> finv;
vector<ll> inv;
void COMinit(ll N, ll P) {
  rep(i, N + 1) {
    if (i == 0) {
      fac.push_back(1);
      finv.push_back(1);
      inv.push_back(1);
    } else if (i == 1) {
      fac.push_back(1);
      finv.push_back(1);
      inv.push_back(1);
    } else {
      fac.push_back(fac.at(i - 1) * i % P);
      inv.push_back(P - inv.at(P % i) * (P / i) % P);
      finv.push_back(finv.at(i - 1) * inv.at(i) % P);
    }
  }
}

ll COM(ll n, ll k, ll P) {
  if (n < k)
    return 0;
  if (n < 0 || k < 0)
    return 0;
  return fac.at(n) * (finv.at(k) * finv.at(n - k) % P) % P;
}

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(ll N) : par(N + 1) { // 最初は全てが根であるとして初期化
    for (ll i = 0; i < N + 1; i++)
      par[i] = i;
  }

  ll root(ll x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(ll x, ll y) { // xとyの木を併合
    ll rx = root(x);       // xの根をrx
    ll ry = root(y);       // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(ll x, ll y) { // 2つのデータx, yが属する木が同じならtrueを返す
    ll rx = root(x);
    ll ry = root(y);
    return rx == ry;
  }

  ll COUNT(ll N) {
    vector<ll> count(N + 1);
    ll ans = 0;
    rep(i, N + 1) {
      if (count.at(root(i)) == 0) {
        ans++;
        count.at(root(i))++;
      }
    }
    return ans;
  }
};

int main() {
  ll N, M;
  cin >> N >> M;
  UnionFind tree(N);
  rep(i, M) {
    ll A, B;
    cin >> A >> B;
    tree.unite(A, B);
  }
  cout << tree.COUNT(N) - 2 << endl;
}


#include <bits/stdc++.h>
using namespace std;
#define FIO                                                                    \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(0);                                                                  \\
  cout.tie(0)
#define mod 1000000007
#define test                                                                   \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define loop(i, n) for (int i = 0; i < n; i++)
#define pb push_back
#define mp make_pair
typedef long long int ll;
#define MAX 100000
#define debug(i) cout << i < "\\n"
#define f(t1, t2) min(max(a[t1], a[t2]), max(b[t1], b[t2]));

void print_arr(int a[], int n) {
  loop(i, n) { cout << a[i] << " "; }
  cout << "\\n";
}

void print_arr(ll a[], int n) {
  loop(i, n) { cout << a[i] << " "; }
  cout << "\\n";
}

void init(int a[], int n) { loop(i, n) a[i] = 0; }

void visit(vector<vector<int>> g, int i, int vis[]) {
  if (vis[i] == 1)
    return;
  vis[i] = 1;
  loop(j, g[i].size()) { visit(g, g[i][j], vis); }
}

int dfs(vector<vector<int>> g, int n) {
  int color = 0;
  int vis[n];
  init(vis, n);
  for (int i = 0; i < n; i++) {
    if (vis[i] == 0) {
      ++color;
      visit(g, i, vis);
    }
  }
  return color;
}

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> em;
  vector<vector<int>> g(n, em);
  loop(i, m) {
    int t1, t2;
    cin >> t1 >> t2;
    g[t1 - 1].push_back(t2 - 1);
    g[t2 - 1].push_back(t1 - 1);
  }
  cout << dfs(g, n) - 1 << "\\n";
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define FIO                                                                    \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(0);                                                                  \\
  cout.tie(0)
#define mod 1000000007
#define test                                                                   \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define loop(i, n) for (int i = 0; i < n; i++)
#define pb push_back
#define mp make_pair
typedef long long int ll;
#define MAX 100000
#define debug(i) cout << i < "\\n"
#define f(t1, t2) min(max(a[t1], a[t2]), max(b[t1], b[t2]));

void print_arr(int a[], int n) {
  loop(i, n) { cout << a[i] << " "; }
  cout << "\\n";
}

void print_arr(ll a[], int n) {
  loop(i, n) { cout << a[i] << " "; }
  cout << "\\n";
}

void init(int a[], int n) { loop(i, n) a[i] = 0; }

void visit(vector<vector<int>> &g, int i, vector<int> &vis) {
  vis[i] = 1;
  for (int j = 0; j < g[i].size(); j++) {
    if (vis[g[i][j]] == 0)
      visit(g, g[i][j], vis);
  }
}

int dfs(vector<vector<int>> &g, int n) {
  int color = 0;
  vector<int> vis(n, 0);
  for (int i = 0; i < n; i++) {
    if (vis[i] == 0) {
      ++color;
      // cout<<i<<" "<<color<<"\\n";
      visit(g, i, vis);
    }
  }
  return color;
}

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> em;
  vector<vector<int>> g(n, em);
  for (int i = 0; i < m; i++) {
    int t1, t2;
    cin >> t1 >> t2;
    g[t1 - 1].push_back(t2 - 1);
    g[t2 - 1].push_back(t1 - 1);
  }
  cout << dfs(g, n) - 1 << "\\n";
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
#define int long long

vector<int> graph[1000005];
int color[1000005];

int dfs(int v) {
  color[v] = 1;
  for (auto i : graph[v]) {
    if (color[i] == 0)
      dfs(i);
  }
}
signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);

  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    graph[a].push_back(b);
    graph[b].push_back(a);
  }
  dfs(1);
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (color[i] == 0) {
      ans++;
      dfs(i);
    }
  }
  cout << ans << endl;
}

#include <bits/stdc++.h>
using namespace std;
#define int long long

vector<int> graph[1000005];
int color[1000005];

int dfs(int v) {
  color[v] = 1;
  for (auto i : graph[v]) {
    if (color[i] == 0)
      dfs(i);
  }
  return 0;
}
signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);

  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    graph[a].push_back(b);
    graph[b].push_back(a);
  }
  dfs(1);
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (color[i] == 0) {
      ans++;
      dfs(i);
    }
  }
  cout << ans << endl;
}


/*Author : Krishna*/
#include <algorithm>
#include <cmath>
#include <iostream>
#include <map>
#include <queue>
#include <sstream>
#include <stack>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#define ll long long
using namespace std;

vector<int> adj[100005];
vector<bool> vis(100005, false);
void dfs(int i) {
  if (!vis[i]) {
    vis[i] = true;
    for (auto v : adj[i])
      dfs(v);
  }
}

void solve() {

  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    --x;
    --y;
    adj[x].push_back(y);
  }
  int comp = 0;
  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      comp++;
      dfs(i);
    }
  }
  cout << comp - 1;
}

int main() {
  int t;
  cin >> t;
  while (t--) {
    solve();
  }
}

/*
 *Check for the edge cases(n=1)?
 *Is there any another approach?
 *check for data types
 *Keep on thinking
 */

/*Author : Krishna*/
#include <algorithm>
#include <cmath>
#include <iostream>
#include <map>
#include <queue>
#include <sstream>
#include <stack>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#define ll long long
using namespace std;

vector<int> adj[100005];
vector<bool> vis(100005, false);
void dfs(int i) {
  if (!vis[i]) {
    vis[i] = true;
    for (auto v : adj[i])
      dfs(v);
  }
}

void solve() {

  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    --x;
    --y;
    adj[x].push_back(y);
    adj[y].push_back(x);
  }
  int comp = 0;
  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      comp++;
      dfs(i);
    }
  }
  cout << comp - 1;
}

int main() {
  int t;
  //    cin>>t;
  t = 1;
  while (t--) {
    solve();
  }
}

/*
 *Check for the edge cases(n=1)?
 *Is there any another approach?
 *check for data types
 *Keep on thinking
 */


#include "bits/stdc++.h"

using namespace std;

#define endl "\\n"
#define sz(x) (int)(x.size())
#define all(v) v.begin(), v.end()
#define isOdd(n) (n & 1)
#define pow(n, m) (ll) powl(n, m)
#define clr(x, val) memset(x, val, sizeof(x))

typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 1e5 + 2, M = 1e5 + 5;

int ne, head[N], nxt[M], to[M], vis[N];

void initGraph(int n) { // reset graph
  memset(head, -1, n * sizeof(head[0]));
  ne = 0;
}
void addEdge(int f, int t) {
  to[ne] = t;
  nxt[ne] = head[f];
  head[f] = ne++;
}

void addBidirectedEdge(int u, int v) {
  addEdge(u, v);
  addEdge(v, u);
}

void dfs(int u) {
  vis[u] = 1;
  for (int k = head[u]; ~k; k = nxt[k]) {
    int v = to[k];
    if (!vis[v])
      dfs(v);
  }
}
int main() {
  ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

  int n, m;
  cin >> n >> m;
  initGraph(n);

  while (m--) {
    int u, v;
    cin >> u >> v;
    v--, u--;
    addBidirectedEdge(u, v);
  }
  int comp = 0;
  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      dfs(i);
      comp++;
    }
  }

  cout << comp - 1;
  return 0;
}

#include "bits/stdc++.h"

using namespace std;

#define endl "\\n"
#define sz(x) (int)(x.size())
#define all(v) v.begin(), v.end()
#define isOdd(n) (n & 1)
#define pow(n, m) (ll) powl(n, m)
#define clr(x, val) memset(x, val, sizeof(x))

typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef vector<int> vi;

const int N = 1e5 + 2, M = 3e5 + 5;

int ne, head[N], nxt[M], to[M], vis[N];

void initGraph(int n) { // reset graph
  memset(head, -1, n * sizeof(head[0]));
  ne = 0;
}
void addEdge(int f, int t) {
  to[ne] = t;
  nxt[ne] = head[f];
  head[f] = ne++;
}

void addBidirectedEdge(int u, int v) {
  addEdge(u, v);
  addEdge(v, u);
}

void dfs(int u) {
  vis[u] = 1;
  for (int k = head[u]; ~k; k = nxt[k]) {
    int v = to[k];
    if (!vis[v])
      dfs(v);
  }
}
int main() {
  ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);

  int n, m;
  cin >> n >> m;

  initGraph(n);

  while (m--) {
    int u, v;
    cin >> u >> v;
    v--, u--;
    addBidirectedEdge(u, v);
  }
  int comp = 0;
  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      dfs(i);
      comp++;
    }
  }

  cout << comp - 1;
  return 0;
}


#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <iostream>
#include <map>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define int long long
#define double long double
#define rep(i, n) for (int i = 0; i < n; i++)
#define srep(i, n, m) for (int i = n; i < m; i++)
#define elif else if
#define pi 3.141592653589793
#define prique priority_queue
constexpr auto mod = 1000000007;
using namespace std;
int gcd(int a, int b) {
  if (b == 0)
    return a;
  return gcd(b, a % b);
}
int lcm(int a, int b) { return a * b / gcd(a, b); }
int cutup(int a, int b) { return (a + b - 1) / b; }
bool prime(int n) {
  int cnt = 0;
  for (int i = 1; i <= sqrt(n); i++) {
    if (n % 1 == 0)
      cnt++;
  }
  if (cnt != 1)
    return false;
  else
    return n != 1;
}
struct UnionFind {
  vector<int> r;
  UnionFind(int N) { r = vector<int>(N, -1); }
  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }
  bool oya(int x) {
    if (r[root(x)] < 0) {
      return true;
    } else {
      return false;
    }
  }
  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }
  int size(int x) { return -r[root(x)]; }
};
vector<int> v;
int n, m, cnt = 0, ans = 0, aa[220000], ab[220000];
pair<int, int> pp[220000];
string s, ss[220000];
queue<int> que;
signed main() {
  cin >> n >> m;
  UnionFind tree(n);
  rep(i, m) {
    cin >> pp[i].first >> pp[i].second;
    tree.unite(pp[i].first, pp[i].second);
  }
  rep(i, m + 1) { cnt += tree.oya(i); }
  cout << cnt - 1;
}

#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <iostream>
#include <map>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define int long long
#define double long double
#define rep(i, n) for (int i = 0; i < n; i++)
#define srep(i, n, m) for (int i = n; i < m; i++)
#define elif else if
#define pi 3.141592653589793
#define prique priority_queue
constexpr auto mod = 1000000007;
using namespace std;
int gcd(int a, int b) {
  if (b == 0)
    return a;
  return gcd(b, a % b);
}
int lcm(int a, int b) { return a * b / gcd(a, b); }
int cutup(int a, int b) { return (a + b - 1) / b; }
bool prime(int n) {
  int cnt = 0;
  for (int i = 1; i <= sqrt(n); i++) {
    if (n % 1 == 0)
      cnt++;
  }
  if (cnt != 1)
    return false;
  else
    return n != 1;
}
struct UnionFind {
  vector<int> r;
  UnionFind(int N) { r = vector<int>(N, -1); }
  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }
  int oya(int x) {
    if (r[x] < 0) {
      return 1;
    } else {
      return 0;
    }
  }
  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }
  int size(int x) { return -r[root(x)]; }
};
vector<int> v;
int n, m, cnt = 0, ans = 0, aa[220000], ab[220000];
pair<int, int> pp[220000];
string s, ss[220000];
queue<int> que;
signed main() {
  cin >> n >> m;
  UnionFind tree(n);
  rep(i, m) {
    cin >> pp[i].first >> pp[i].second;
    tree.unite(pp[i].first, pp[i].second);
  }
  rep(i, n + 1) { cnt += tree.oya(i); }
  cout << cnt - 1;
}


#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < n; i++)
#define vv(T) vector<vector<T>>
#define coa                                                                    \\
  cout << ans << endl;                                                         \\
  return 0;
using namespace std;
using ll = long long;
template <typename T> using vec = vector<T>;
template <typename T> using vvec = vector<vector<T>>;
using qint = queue<int>;
using sint = stack<int>;
using pii = pair<int, int>;

class UnionFind {
  vector<int> parent;
  // UnionFind木の実装
public:
  UnionFind(int N) : parent(N, -1) {} // コンストラクタ
  int root(int x) {
    // 根を求める。
    if (parent[x] < 0)
      return x;
    return parent[x] = root(parent[x]);
  }
  void unite(int x, int y) {
    // xとyの木を結合する。
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return;
    if (-parent[rx] > -parent[ry]) {
      // 小さい方の木を大きい方につなげる。
      parent[rx] += parent[ry];
      parent[ry] = rx;
    } else {
      parent[ry] += parent[rx];
      parent[rx] = ry;
    }
    return;
  }
  bool isSame(int x, int y) {
    // 同じ木にあるか?
    return root(x) == root(y);
  }
  int size(int x) {
    // xの木に属する要素はいくつか?
    return -parent[root(x)];
  }
};

int main() {
  int n, m;
  cin >> n >> m;

  UnionFind uf(n);

  rep(i, m) {
    int a, b;
    cin >> a >> b;
    uf.unite(a, b);
  }

  int ans = -1;
  rep(i, n) {
    int ri = uf.root(i);
    if (ri == i)
      ans++;
  }

  coa;
}

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < n; i++)
#define vv(T) vector<vector<T>>
#define coa                                                                    \\
  cout << ans << endl;                                                         \\
  return 0;
using namespace std;
using ll = long long;
template <typename T> using vec = vector<T>;
template <typename T> using vvec = vector<vector<T>>;
using qint = queue<int>;
using sint = stack<int>;
using pii = pair<int, int>;

class UnionFind {
  vector<int> parent;
  // UnionFind木の実装
public:
  UnionFind(int N) : parent(N, -1) {} // コンストラクタ
  int root(int x) {
    // 根を求める。
    if (parent[x] < 0)
      return x;
    return parent[x] = root(parent[x]);
  }
  void unite(int x, int y) {
    // xとyの木を結合する。
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return;
    if (-parent[rx] > -parent[ry]) {
      // 小さい方の木を大きい方につなげる。
      parent[rx] += parent[ry];
      parent[ry] = rx;
    } else {
      parent[ry] += parent[rx];
      parent[rx] = ry;
    }
    return;
  }
  bool isSame(int x, int y) {
    // 同じ木にあるか?
    return root(x) == root(y);
  }
  int size(int x) {
    // xの木に属する要素はいくつか?
    return -parent[root(x)];
  }
};

int main() {
  int n, m;
  cin >> n >> m;

  UnionFind uf(n);

  rep(i, m) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    uf.unite(a, b);
  }

  int ans = -1;
  rep(i, n) {
    int ri = uf.root(i);
    if (ri == i)
      ans++;
  }

  coa;
}


#include <bits/stdc++.h>

using namespace std;

#define INF 1e9
#define rep(i, a, n) for (ll i = (a); i < (n); i++)
#define repv(i, a, n) for (ll i = (n)-1; i >= a; i--)
#define ll long long int
#define endl "\\n"

vector<int> adj[10005];
int vis[10005];

void dfs(int i) {
  vis[i] = 1;
  for (auto child : adj[i]) {
    if (vis[child] == 0)
      dfs(child);
  }
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

  int n, m;
  cin >> n >> m;
  while (m--) {
    int a, b;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  int c = 0;
  for (int i = 0; i < n; i++) {
    if (vis[i] == 0) {
      c++;
      dfs(i);
    }
  }
  cout << c - 1;
}

#include <bits/stdc++.h>

using namespace std;

#define INF 1e9
#define rep(i, a, n) for (ll i = (a); i < (n); i++)
#define repv(i, a, n) for (ll i = (n)-1; i >= a; i--)
#define ll long long int
#define endl "\\n"
#define pb push_back

vector<int> adj[1000005];
bool vis[1000005];

void dfs(int i) {
  vis[i] = true;
  for (auto child : adj[i]) {
    if (!vis[child])
      dfs(child);
  }
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);

  int n, m;
  cin >> n >> m;
  rep(i, 0, m) {
    int a, b;
    cin >> a >> b;
    adj[a].pb(b);
    adj[b].pb(a);
  }
  int c = 0;
  rep(i, 1, n + 1) {
    if (!vis[i]) {
      c++;
      dfs(i);
    }
  }
  c--;
  cout << c;
}


#include <bits/stdc++.h>
#define rep(i, a, b) for (ll i = ll(a); i < ll(b); i++)
#define irep(i, a, b) for (ll i = ll(a); i >= ll(b); i--)
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
#define pb push_back
#define mp make_pair
#define F .first
#define S .second
#define pll pair<ll, ll>
using ll = long long;
using ld = long double;
const ll INF = 1LL << 60;
const ll mod = 1e9 + 7;
const ll mod2 = 998244353;
using namespace std;
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }
  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }

  int size(int x) { return -r[root(x)]; }
};

ll a[110000], b[110000];
int main() {
  ll N, M, ans = 0;
  cin >> N >> M;
  UnionFind UF(N);
  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    UF.unite(A, B);
  }
  rep(i, 1, N + 1) {
    if (UF.same(i, 1) == false) {
      ans++;
      UF.unite(i, 1);
    }
  }
  cout << ans << endl;
  // rep(i,1,N+1)cout<<UF.root(i)<<endl;
}

#include <bits/stdc++.h>
#define rep(i, a, b) for (ll i = ll(a); i < ll(b); i++)
#define irep(i, a, b) for (ll i = ll(a); i >= ll(b); i--)
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
#define pb push_back
#define mp make_pair
#define F .first
#define S .second
#define pll pair<ll, ll>
using ll = long long;
using ld = long double;
const ll INF = 1LL << 60;
const ll mod = 1e9 + 7;
const ll mod2 = 998244353;
using namespace std;
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

const ll N = 1100000;
struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }
  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }

  int size(int x) { return -r[root(x)]; }
};

ll a[110000], b[110000];
int main() {
  ll n, M, ans = 0;
  cin >> n >> M;
  UnionFind UF(N);
  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    UF.unite(A, B);
  }
  rep(i, 1, n + 1) {
    if (UF.same(i, 1) == false) {
      ans++;
      UF.unite(i, 1);
    }
  }
  cout << ans << endl;
  // rep(i,1,N+1)cout<<UF.root(i)<<endl;
}


/*
        @uthor: Kashish Gilhotra
        user: CodeChef, CodeForces, HackerEarth, HackerRank, SPOJ: kashish001
*/

#include <bits/stdc++.h>
using namespace std;

#define int long long int
typedef vector<int> vi;
typedef vector<pair<int, int>> vpi;
typedef vector<vi> vvi;
const int mod = 1e9 + 7;

#define FAST                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL)
#define debug(...) cerr << "[" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "
#define EB emplace_back
#define ALL(v) v.begin(), v.end()
#define size(v) (int)v.size()
#define endl "\\n"
#define UMO unordered_map
#define USO unordered_set
#define TC                                                                     \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)

void dfs(vvi in, int n, int sv, vector<bool> &visited) {
  visited[sv] = true;
  for (auto i : in[sv]) {
    if (!visited[i]) {
      dfs(in, n, i, visited);
    }
  }
}

void Panda() {
  int v, e;
  cin >> v >> e;
  vvi in(v);
  for (int i = 0, x, y; i < e; i++) {
    cin >> x >> y;
    x--, y--;
    in[x].EB(y), in[y].EB(x);
  }
  vector<bool> visited(v);
  int cnt = 0;
  for (int i = 0; i < v; i++)
    if (!visited[i]) {
      dfs(in, v, i, visited);
      cnt++;
    }
  cout << cnt - 1 << endl;
}

int32_t main() {

  FAST;

  // freopen("input.txt", "r", stdin);
  // freopen("output.txt", "w", stdout);

  Panda();

  return 0;
}

#include <bits/stdc++.h>
#define mod 1000000007
#define int long long int
#define phi 1.6180339
#define rep(i, n) for (auto i = 0; i < (n); i++)
#define endl "\\n"
#define For(i, a, b) for (auto i = (a); i < (b); i++)
#define op1 cin.tie(NULL)
#define op2 cout.tie(NULL)
#define op3 std::ios_base::sync_with_stdio(false)
#define MAXX 100001
using namespace std;
vector<int> edges[MAXX];
bool visited[MAXX];
void dfs(int sv) {
  visited[sv] = true;
  for (auto i : edges[sv])
    if (!visited[i])
      dfs(i);
}
void trozen() {
  int n, m;
  cin >> n >> m;
  rep(i, m) {
    int x, y;
    cin >> x >> y;
    edges[x].push_back(y);
    edges[y].push_back(x);
  }
  memset(visited, false, sizeof(visited));
  int cnt = -1;
  for (int i = 1; i <= n; i++)
    if (!visited[i]) {
      dfs(i);
      cnt++;
    }
  cout << cnt << endl;
}
int32_t main() {
  op1;
  op2;
  op3;
  /*#ifndef ONLINE_JUDGE
  freopen("input.txt","r",stdin);
  freopen("output.txt","w",stdout);
  #endif*/
  trozen();
  return 0;
}


#include <algorithm>
#include <cctype>
#include <climits>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;

struct UnionFind {
  vector<int> par;
  vector<int> sz;
  map<int, int> szmp; // map<size, cnt>

  // n : 要素数
  UnionFind(int n = 0) {
    if (n > 0)
      initialize(n);
  }

  void initialize(int n) {
    par.resize(n);
    sz.resize(n);
    for (int i = 0; i < n; i++) {
      par[i] = i;
      sz[i] = 1;
    }
    szmp[1] = n;
  }

  // 0-index
  int find(int x) {
    if (par[x] == x) {
      return x;
    } else {
      return par[x] = find(par[x]);
    }
  }

  // 0-index
  void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return;
    if (sz[x] > sz[y])
      swap(x, y);

    for (int s : {sz[x], sz[y]}) {
      szmp[s]--;
      if (szmp[s] == 0)
        szmp.erase(s);
    }
    par[x] = y;
    sz[y] += sz[x];
    szmp[sz[y]]++;
  }

  // 0-index
  bool same(int x, int y) { return find(x) == find(y); }

  // 0-index
  int size(int x) { return sz[find(x)]; }

  // 最小要素サイズ
  int minsz() { return szmp.begin()->first; }

  // 連結成分個数
  int unitenum() { return szmp.size(); }
};

int main() {
  int n, m;
  cin >> n >> m;

  UnionFind uf(n);

  while (m--) {
    int a, b;
    cin >> a >> b;
    uf.unite(a, b);
  }
  cout << uf.unitenum() - 1 << endl;
  return 0;
}

#include <algorithm>
#include <cctype>
#include <climits>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;

struct UnionFind {
  vector<int> par;
  vector<int> sz;
  map<int, int> szmp; // map<size, cnt>

  // n : 要素数
  UnionFind(int n = 0) {
    if (n > 0)
      initialize(n);
  }

  void initialize(int n) {
    par.resize(n);
    sz.resize(n);
    for (int i = 0; i < n; i++) {
      par[i] = i;
      sz[i] = 1;
    }
    szmp[1] = n;
  }

  // 0-index
  int find(int x) {
    if (par[x] == x) {
      return x;
    } else {
      return par[x] = find(par[x]);
    }
  }

  // 0-index
  void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return;
    if (sz[x] > sz[y])
      swap(x, y);

    for (int s : {sz[x], sz[y]}) {
      szmp[s]--;
      if (szmp[s] == 0)
        szmp.erase(s);
    }
    par[x] = y;
    sz[y] += sz[x];
    szmp[sz[y]]++;
  }

  // 0-index
  bool same(int x, int y) { return find(x) == find(y); }

  // 0-index
  int size(int x) { return sz[find(x)]; }

  // 最小要素サイズ
  int minsz() { return szmp.begin()->first; }

  // 連結成分個数
  int unitenum() { return szmp.size(); }
};

int main() {
  int n, m;
  cin >> n >> m;

  UnionFind uf(n);

  while (m--) {
    int a, b;
    cin >> a >> b;
    uf.unite(a - 1, b - 1);
  }
  int cnt = 0;
  for (auto u : uf.szmp) {
    cnt += u.second;
  }
  cout << cnt - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mp make_pair
#define ff first
#define ss second
#define ll long long int
#define ull unsigned long long int
#define ld long double

int n;
vector<vector<int>> arr(n + 1);
vector<bool> vis(n + 1);
int cnt;

void dfs(int v) {
  vis[v] = true;
  for (int i = 1; i < n + 1; i++) {
    if (vis[i] == false && arr[v][i] == 1) {
      dfs(i);
    }
  }
}

int main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);

#ifndef ONLINE_JUDGE
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
#endif

  int m, a, b;
  cin >> n >> m;
  for (int i = 1; i < n + 1; i++) {
    vector<int> ar(n + 1, 0);
    arr.pb(ar);
    vis[i] = false;
  }
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    arr[a][b] = 1;
    arr[b][a] = 1;
  }
  cnt = 0;
  for (int i = 1; i < n + 1; i++) {
    if (!vis[i]) {
      cnt += 1;
      dfs(i);
    }
  }
  cout << cnt - 1;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mp make_pair
#define ff first
#define ss second
#define ll long long int
#define ull unsigned long long int
#define ld long double

int n, m, ans = 0;
vector<vector<int>> g(200010);
bool vis[200010];

void dfs(int v) {
  vis[v] = true;
  for (int i = 0; i < g[v].size(); i++) {
    if (!vis[g[v][i]]) {
      dfs(g[v][i]);
    }
  }
}

int main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);

#ifndef ONLINE_JUDGE
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
#endif

  int a, b;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  for (int i = 1; i <= n; i++) {
    if (!vis[i]) {
      ans += 1;
      dfs(i);
    }
  }
  cout << ans - 1;
  return 0;
}


#include <iostream>
#include <vector>

using namespace std;

class UnionFind {
public:
  explicit UnionFind(int N) : root(N, -1), size(N, 1) {}
  int getRoot(int u) { return root[u] == -1 ? u : root[u] = getRoot(root[u]); }
  int getSize(int u) { return size[getRoot(u)]; }
  bool same(int a, int b) { return getRoot(a) == getRoot(b); }
  bool merge(int a, int b) {
    int u = getRoot(a);
    int v = getRoot(b);
    if (u == v)
      return false;
    root[u] = v;
    size[v] += size[u];
    return true;
  }

private:
  vector<int> root;
  vector<int> size;
};

int main() {
  int N, M;
  cin >> N >> M;
  UnionFind uf(N);
  for (int i = 0; i < M; i++) {
    int a, b;
    cin >> a >> b;
    uf.merge(a, b);
  }
  int res = -1;
  for (int i = 0; i < N; i++)
    if (uf.getRoot(i) == i)
      ++res;
  cout << res << endl;
}

#include <iostream>
#include <vector>

using namespace std;

class UnionFind {
public:
  explicit UnionFind(int N) : root(N, -1), size(N, 1) {}
  int getRoot(int u) { return root[u] == -1 ? u : root[u] = getRoot(root[u]); }
  int getSize(int u) { return size[getRoot(u)]; }
  bool same(int a, int b) { return getRoot(a) == getRoot(b); }
  bool merge(int a, int b) {
    int u = getRoot(a);
    int v = getRoot(b);
    if (u == v)
      return false;
    root[u] = v;
    size[v] += size[u];
    return true;
  }

private:
  vector<int> root;
  vector<int> size;
};

int main() {
  int N, M;
  cin >> N >> M;
  UnionFind uf(N);
  for (int i = 0; i < M; i++) {
    int a, b;
    cin >> a >> b;
    --a;
    --b;
    uf.merge(a, b);
  }
  int res = -1;
  for (int i = 0; i < N; i++)
    if (uf.getRoot(i) == i)
      ++res;
  cout << res << endl;
}


#include <bits/stdc++.h>
using namespace std;

struct union_find {
  vector<int> r;
  union_find(int n) : r(n, -1) {}
  int root(int a) {
    if (r[a] < 0) {
      return a;
    } else {
      return root(r[a]);
    }
  }
  void add(int a, int b) {
    int ra = root(a);
    int rb = root(b);
    if (r[ra] > r[rb]) {
      swap(ra, rb);
    }
    r[ra] += r[rb];
    r[rb] = ra;
  }
  int sum() {
    int s = 0;
    for (int i = 0; i < r.size(); i++) {
      if (r[i] < 0) {
        s++;
      }
    }
    return s;
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  union_find u(n);
  int a, b;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    a--;
    b--;
    u.add(a, b);
  }
  cout << u.sum() - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;

struct union_find {
  vector<int> r;
  union_find(int n) : r(n, -1) {}
  int root(int a) {
    if (r[a] < 0) {
      return a;
    } else {
      return root(r[a]);
    }
  }
  void add(int a, int b) {
    int ra = root(a);
    int rb = root(b);
    if (r[ra] > r[rb]) {
      swap(ra, rb);
    } else if (ra == rb) {
      return;
    }
    r[ra] += r[rb];
    r[rb] = ra;
  }
  int sum() {
    int s = 0;
    for (int i = 0; i < r.size(); i++) {
      if (r[i] < 0) {
        s++;
      }
    }
    return s;
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  union_find u(n);
  int a, b;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    a--;
    b--;
    u.add(a, b);
  }
  cout << u.sum() - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;

#define IOS                                                                    \\
  ios::sync_with_stdio(false);                                                 \\
  cin.tie(0);                                                                  \\
  cout.tie(0);
#define endl "\\n"
const int N = 1e9 + 7;
#define int long long
#define double long double
#define pb push_back
#define vi vector<int>
#define rep(i, n) for (int i = 1; i <= n; i++)
vi ar[100001];
int vis[100000];
void dfs(int node) {
  vis[node] = 1;
  for (int child : ar[node]) {
    if (!vis[child])
      dfs(child);
  }
}
int32_t main() {
#ifndef ONLINE_JUDGE
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
#endif
  IOS;
  int n, m, a, b;
  cin >> n >> m;
  while (m--) {
    cin >> a >> b;
    ar[a].pb(b), ar[b].pb(a);
  }
  int cc = 0;
  for (int i = 1; i <= n; i++) {
    if (!vis[i]) {
      cc++;
      dfs(i);
    }
  }
  cout << cc - 1;

  return 0;
}

#include <bits/stdc++.h>
using namespace std;

#define IOS                                                                    \\
  ios::sync_with_stdio(false);                                                 \\
  cin.tie(0);                                                                  \\
  cout.tie(0);
#define endl "\\n"
const int N = 1e9 + 7;
#define int long long
#define double long double
#define pb push_back
#define vi vector<int>
#define rep(i, n) for (int i = 1; i <= n; i++)
vi ar[100001];
int vis[100001];
void dfs(int node) {
  vis[node] = 1;
  for (int child : ar[node]) {
    if (!vis[child])
      dfs(child);
  }
}
int32_t main() {
#ifndef ONLINE_JUDGE
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
#endif
  IOS;
  int n, m, a, b;
  cin >> n >> m;
  while (m--) {
    cin >> a >> b;
    ar[a].pb(b), ar[b].pb(a);
  }
  int cc = 0;
  for (int i = 1; i <= n; i++) {
    if (!vis[i]) {
      cc++;
      dfs(i);
    }
  }
  cout << cc - 1;

  return 0;
}


#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
// #include <atcoder/all>
// using namespace atcoder;
using namespace std;
using ll = long long;
using p = pair<int, int>;
const long long INF = 1ll << 60;
using Graph = vector<vector<int>>;

struct UnionFind {
  vector<int> d;
  // UnionFind(int n=0): d(n,-1) {}
  UnionFind(int n) { d.resize(n, -1); }
  int find(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }
  bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  bool same(int x, int y) { return find(x) == find(y); }
  int size(int x) { return -d[find(x)]; }
};

int main() {
  int N, M, A, B;
  cin >> N >> M;
  UnionFind uf(N);
  for (int i = 0; i < M; i++) {
    cin >> A >> B;
    uf.unite(A, B);
  }
  int ans = 0;
  set<int> st;
  rep(i, N) { st.insert(uf.find(i)); }
  ans = st.size() / 2 + st.size() % 2;

  cout << ans << endl;
  return 0;
}

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
// #include <atcoder/all>
// using namespace atcoder;
using namespace std;
using ll = long long;
using p = pair<int, int>;
const long long INF = 1ll << 60;
using Graph = vector<vector<int>>;

struct UnionFind {
  vector<int> d;
  // UnionFind(int n=0): d(n,-1) {}
  UnionFind(int n) { d.resize(n, -1); }
  int find(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }
  bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  bool same(int x, int y) { return find(x) == find(y); }
  int size(int x) { return -d[find(x)]; }
};

set<int> st;

int main() {
  int N, M, A, B;
  cin >> N >> M;
  UnionFind uf(N);
  for (int i = 0; i < M; i++) {
    cin >> A >> B;
    A--;
    B--;
    uf.unite(A, B);
  }
  for (int i = 0; i < N; i++) {
    int p;
    p = uf.find(i);
    st.insert(p);
  }

  int ans = 0;
  int s = st.size();
  if (s >= 2)
    ans = s - 1;

  cout << ans << endl;
  return 0;
}


#include <iostream>

using namespace std;
#define Inf 1e6 + 7;
const int N = 1e7 + 7;
int s[N];
int Find(int i) {
  if (s[i] == i) {
    return i;
  } else {
    return s[i] = Find(i);
  }
}
void Union(int i, int j) {
  int x = Find(i);
  int y = Find(j);
  if (x != y) {
    s[y] = x;
  }
}
int main() {
  int n, m;
  cin >> n >> m;
  int ans = 0;
  for (int i = 0; i < n; i++) {
    s[i] = i;
  }
  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    Union(u, v);
  }
  for (int i = 0; i < n; i++) {
    if (s[i] == i) {
      ans++;
    }
  }
  cout << ans - 1 << endl;
  return 0;
}

#include <iostream>

using namespace std;
#define Inf 1e6 + 7;
const int N = 1e7 + 7;
int s[N];
int Find(int i) {
  int r = i;
  while (s[r] != r) {
    r = s[r];
  }
  int x = i, j;
  while (x != r) {
    j = s[x];
    s[x] = r;
    x = j;
  }
  return r;
}
void Union(int i, int j) {
  int x = Find(i);
  int y = Find(j);
  if (x != y) {
    s[y] = x;
  }
}
int main() {
  int n, m;
  cin >> n >> m;
  int ans = 0;
  for (int i = 0; i < n; i++) {
    s[i] = i;
  }
  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    Union(u, v);
  }
  for (int i = 0; i < n; i++) {
    if (s[i] == i) {
      ans++;
    }
  }
  cout << ans - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;

struct union_find {
  vector<int> par, sz;
  union_find(int n) : par(n + 1), sz(n + 1) {
    for (int i = 1; i <= n; i++)
      par[i] = -1, sz[i] = 1;
  }
  int find(int x) { return par[x] == -1 ? x : par[x] = find(par[x]); }
  void unite(int a, int b) {
    a = find(a), b = find(b);
    if (sz[b] > sz[a])
      swap(a, b);
    sz[a] += sz[b];
    par[b] = a;
  }
};

int n, m;

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cin >> n >> m;
  union_find dsu(n);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    dsu.unite(a, b);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++)
    ans += (dsu.find(i) == i);
  cout << ans - 1 << '\\n';
  return 0;
}

#include <bits/stdc++.h>
using namespace std;

int n, m;
vector<int> adj[100001];
bool vis[100001];

void dfs(int vtx) {
  vis[vtx] = true;
  for (int i : adj[vtx])
    if (!vis[i])
      dfs(i);
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  int ans = -1;
  for (int i = 1; i <= n; i++)
    if (!vis[i])
      dfs(i), ans++;
  cout << ans << '\\n';
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
int father(vector<int> &f, int x) {
  return (f[x] == x) ? x : (father(f, f[x]));
}
int merge(vector<int> &f, int x, int y) {
  x = father(f, x);
  y = father(f, y);
  if (x == y)
    return 0;
  f[x] = y;
  return 1;
}
int main() {
  int n;
  int m;
  cin >> n >> m;
  vector<int> f(n + 1, 0);
  for (int i = 1; i <= n; i++)
    f[i] = i;
  int ans = n - 1;
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    if (merge(f, x, y))
      ans--;
  }
  cout << ans << endl;
}

#include <bits/stdc++.h>
using namespace std;
int father(vector<int> &f, int x) {
  return (f[x] == x) ? x : (f[x] = father(f, f[x]));
}
int merge(vector<int> &f, int x, int y) {
  x = father(f, x);
  y = father(f, y);
  if (x == y)
    return 0;
  f[x] = y;
  return 1;
}
int main() {
  int n;
  int m;
  cin >> n >> m;
  vector<int> f(n + 1, 0);
  for (int i = 1; i <= n; i++)
    f[i] = i;
  int ans = n - 1;
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    if (merge(f, x, y))
      ans--;
  }
  cout << ans << endl;
}


#include <bits/stdc++.h>
#define ll long long
#define fastio                                                                 \\
  cin.sync_with_stdio(false);                                                  \\
  cin.tie(NULL)
#define all(x) x.begin(), x.end()
using namespace std;
int n, m;
void dfs(int x, vector<vector<int>> &adj, vector<bool> &visited) {
  if (x >= n)
    return;
  visited[x] = true;
  for (auto &a : adj[x]) {
    if (visited[a])
      continue;
    dfs(a, adj, visited);
  }
}
int main() {
  fastio;
  int numCon = 0;
  vector<bool> visited(n, false);
  cin >> n >> m;
  vector<vector<int>> adj(n, vector<int>());
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    x--;
    y--;
    assert(x >= 0 && x < n);
    assert(y >= 0 && y < n);
    adj[x].push_back(y);
    adj[y].push_back(x);
  }

  for (int i = 0; i < n; i++) {
    if (!visited[i]) {
      dfs(i, adj, visited);
      numCon += 1;
    }
  }
  cout << numCon - 1 << endl;
  return 0;
}

#include <bits/stdc++.h>
#define ll long long
#define fastio                                                                 \\
  cin.sync_with_stdio(false);                                                  \\
  cin.tie(NULL)
#define all(x) x.begin(), x.end()
using namespace std;
void dfs(int x, vector<vector<int>> &adj, vector<bool> &visited) {
  visited[x] = true;
  for (auto &a : adj[x]) {
    if (visited[a])
      continue;
    dfs(a, adj, visited);
  }
}
int main() {
  fastio;
  int n, m;
  int numCon = 0;
  cin >> n >> m;
  vector<bool> visited(n, false);
  vector<vector<int>> adj(n, vector<int>());
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    x--;
    y--;
    assert(x >= 0 && x < n);
    assert(y >= 0 && y < n);
    adj[x].push_back(y);
    adj[y].push_back(x);
  }

  for (int i = 0; i < n; i++) {
    if (!visited[i]) {
      dfs(i, adj, visited);
      numCon += 1;
    }
  }
  cout << numCon - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
#define FASTio                                                                 \\
  ios::sync_with_stdio(false);                                                 \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);
#define ll long long int
#define endl '\\n'
ll mod = 1e9 + 7;

class Graph {
  int V;

  list<int> *adj;

  void DFSUtil(int v, bool visited[]);

public:
  // Constructor
  Graph(int V);

  void addEdge(int v, int w);
  int NumberOfconnectedComponents();
};

// Function to return the number of
// connected components in an undirected graph
int Graph::NumberOfconnectedComponents() {

  // Mark all the vertices as not visited
  bool *visited = new bool[V];

  // To store the number of connected components
  int count = 0;
  for (int v = 0; v < V; v++)
    visited[v] = false;

  for (int v = 0; v < V; v++) {
    if (visited[v] == false) {
      DFSUtil(v, visited);
      count += 1;
    }
  }

  return count;
}

void Graph::DFSUtil(int v, bool visited[]) {

  // Mark the current node as visited
  visited[v] = true;

  // Recur for all the vertices
  // adjacent to this vertex
  list<int>::iterator i;

  for (i = adj[v].begin(); i != adj[v].end(); ++i)
    if (!visited[*i])
      DFSUtil(*i, visited);
}

Graph::Graph(int V) {
  this->V = V;
  adj = new list<int>[V];
}

// Add an undirected edge
void Graph::addEdge(int v, int w) {
  adj[v].push_back(w);
  adj[w].push_back(v);
}

int main() {
  FASTio;
#ifndef ONLINE_JUDGE // file start
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
#endif // file end
  int n, m, x, y, i;
  cin >> n >> m;
  Graph g(n);
  for (i = 0; i < m; i++) {
    cin >> x >> y;
    g.addEdge(x, y);
  }
  cout << g.NumberOfconnectedComponents() - 1 << "\\n";
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define FASTio                                                                 \\
  ios::sync_with_stdio(false);                                                 \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);
#define ll long long int
#define endl '\\n'
ll mod = 1e9 + 7;

class Graph {
  // No. of vertices
  int V;

  // Pointer to an array containing adjacency lists
  list<int> *adj;

  // A function used by DFS
  void DFSUtil(int v, bool visited[]);

public:
  // Constructor
  Graph(int V);

  void addEdge(int v, int w);
  int NumberOfconnectedComponents();
};

// Function to return the number of
// connected components in an undirected graph
int Graph::NumberOfconnectedComponents() {

  // Mark all the vertices as not visited
  bool *visited = new bool[V];

  // To store the number of connected components
  int count = 0;
  for (int v = 1; v <= V; v++)
    visited[v] = false;

  for (int v = 1; v <= V; v++) {
    if (visited[v] == false) {
      DFSUtil(v, visited);
      count += 1;
    }
  }

  return count;
}

void Graph::DFSUtil(int v, bool visited[]) {

  // Mark the current node as visited
  visited[v] = true;

  // Recur for all the vertices
  // adjacent to this vertex
  list<int>::iterator i;

  for (i = adj[v].begin(); i != adj[v].end(); ++i)
    if (!visited[*i])
      DFSUtil(*i, visited);
}

Graph::Graph(int V) {
  this->V = V;
  adj = new list<int>[V + 1];
}

// Add an undirected edge
void Graph::addEdge(int v, int w) {
  adj[v].push_back(w);
  adj[w].push_back(v);
}

int main() {
  FASTio;
#ifndef ONLINE_JUDGE // file start
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
#endif // file end
  int n, m, x, y, i;
  cin >> n >> m;
  Graph g(n);
  for (i = 0; i < m; i++) {
    cin >> x >> y;
    g.addEdge(x, y);
  }
  cout << g.NumberOfconnectedComponents() - 1 << "\\n";
  return 0;
}


#include <bits/stdc++.h>
typedef long long ll;

using namespace std;

vector<int> cities[100000];
bool visited[100000];

void dfs(int i) {
  visited[i] = true;
  for (int x : cities[i]) {
    if (not(visited[x])) {
      dfs(x);
    }
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  while (m--) {
    int a, b;
    cin >> a >> b;
    cities[a].push_back(b);
    cities[b].push_back(a);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (not(visited[i])) {
      ans++;
      dfs(i);
    }
  }
  cout << ans - 1;
}

#include <bits/stdc++.h>
typedef long long ll;

using namespace std;

vector<int> cities[100005];
bool visited[100005];

void dfs(int i) {
  visited[i] = true;
  for (int x : cities[i]) {
    if (not(visited[x])) {
      dfs(x);
    }
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  while (m--) {
    int a, b;
    cin >> a >> b;
    cities[a].push_back(b);
    cities[b].push_back(a);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (not(visited[i])) {
      ans++;
      dfs(i);
    }
  }
  cout << ans - 1;
}


#include <bits/stdc++.h>
using namespace std;
#define rep(i, N) for (int i = 0; i < int(N); ++i)
using ll = long long;
const int MOD = 1e9 + 7;
const int INF = 2147000000;
// const ll INF = 1000000000000000000LL;

struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }

  int size(int x) { return -r[root(x)]; }
};

int main() {
  ll n, m;
  cin >> n >> m;
  vector<ll> a(m);
  vector<ll> b(m);
  rep(i, m) { cin >> a[i] >> b[i]; }
  UnionFind tree(n);
  for (int i = 0; i < m; i++) {
    int A, B;
    A = a[i]--;
    B = b[i]--;
    tree.unite(A, B);
  }
  int ans = 0;
  for (int i = 0; i < n; i++) {
    if (tree.r[i] < 0)
      ans++;
  }
  cout << ans << endl;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define rep(i, N) for (int i = 0; i < int(N); ++i)
using ll = long long;
const int MOD = 1e9 + 7;
const int INF = 2147000000;
// const ll INF = 1000000000000000000LL;

struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

  int root(int x) {
    if (r[x] < 0)
      return x;
    return r[x] = root(r[x]);
  }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }

  int size(int x) { return -r[root(x)]; }
};

int main() {
  ll n, m;
  cin >> n >> m;
  vector<ll> a(m);
  vector<ll> b(m);
  rep(i, m) { cin >> a[i] >> b[i]; }
  UnionFind tree(n);
  for (int i = 0; i < m; i++) {
    int A, B;
    A = a[i];
    A -= 1;
    B = b[i];
    B -= 1;
    tree.unite(A, B);
  }
  int ans = 0;
  for (int i = 0; i < n; i++) {
    if (tree.r[i] < 0)
      ans++;
  }
  cout << ans - 1 << endl;
  return 0;
}


#ifndef ATCODER_DSU_HPP
#define ATCODER_DSU_HPP 1

#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

namespace atcoder {

// Implement (union by size) + (path compression)
// Reference:
// Zvi Galil and Giuseppe F. Italiano,
// Data structures and algorithms for disjoint set union problems
struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

} // namespace atcoder

#endif // ATCODER_DSU_HPP

int main() {
  // input(sunippets: inpv, inpn, inps)
  ll N, M, A, B;
  cin >> N >> M;
  atcoder::dsu d(N);
  for (int i = 0; i < M; ++i) {
    cin >> A >> B;
    d.merge(A, B);
    //  cout << d.size(i) << endl;
  }

  int cnt = 0;

  for (int i = 0; i < N; ++i) {
    if (d.leader(i) == i)
      ++cnt;
    // cout << i  << "  " << d.leader(i) << endl; 連結成分の各頂点の確認
  }

  // calculation
  // REP(i, Q) {
  //     cin >> N >> M;
  //     if (P == 0) {
  //     } else {
  //         if (d.issame(A, B)) cout << "Yes" << "\\n";
  //         else cout << "No" << "\\n";
  //     }
  // // }
  cout << cnt - 1 << endl;
  return 0;
}

#ifndef ATCODER_DSU_HPP
#define ATCODER_DSU_HPP 1

#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

namespace atcoder {

// Implement (union by size) + (path compression)
// Reference:
// Zvi Galil and Giuseppe F. Italiano,
// Data structures and algorithms for disjoint set union problems
struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

} // namespace atcoder

#endif // ATCODER_DSU_HPP

int main() {
  // input(sunippets: inpv, inpn, inps)
  ll N, M, A, B;
  cin >> N >> M;
  atcoder::dsu d(N);
  for (int i = 0; i < M; ++i) {
    cin >> A >> B;
    --A;
    --B;
    d.merge(A, B);
    //  cout << d.size(i) << endl;
  }

  int cnt = 0;

  for (int i = 0; i < N; ++i) {
    if (d.leader(i) == i)
      ++cnt;
    // cout << i  << "  " << d.leader(i) << endl; 連結成分の各頂点の確認
  }

  cout << cnt - 1 << endl;

  return 0;
}


#include <bits/stdc++.h>
using namespace std;
using Graph = vector<vector<int>>;
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
const long long MOD = 1000000007;
#define chmax(x, y) x = max(x, y)
#define chmin(x, y) x = min(x, y)

int main(void) {
  int n, m;
  cin >> n >> m;
  vector<vector<int>> G(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    G[a].push_back(b);
    G[b].push_back(a);
  }
  vector<int> seen(n, -1);
  rep(count, n + 1) {
    queue<int> que;
    rep(i, n) {
      if (seen[i] == -1) {
        que.push(i);
        seen[i] = count;
        break;
      }
    }
    // 探索完了時
    if (que.empty()) {
      // 連結部の個数-1が答え
      cout << count - 1 << endl;
      return 0;
    }
    // BFSする時
    while (!que.empty()) {
      int v = que.front();
      que.pop();
      for (auto nv : G[v]) {
        if (seen[nv] != -1)
          continue;
        que.push(nv);
        seen[nv] = count;
      }
    }
  }
  cout << 0 << endl;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
using Graph = vector<vector<int>>;
#define rep(i, n) for (int i = 0; i < (int)(n); i++)
const long long MOD = 1000000007;
#define chmax(x, y) x = max(x, y)
#define chmin(x, y) x = min(x, y)

int main(void) {
  int n, m;
  cin >> n >> m;
  vector<vector<int>> G(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    G[a].push_back(b);
    G[b].push_back(a);
  }
  vector<int> seen(n, -1);
  int count = 0;
  rep(i, n) {
    if (seen[i] != -1)
      continue;
    queue<int> que;
    que.push(i);
    seen[i] = count;
    while (!que.empty()) {
      int v = que.front();
      que.pop();
      for (auto nv : G[v]) {
        if (seen[nv] != -1)
          continue;
        que.push(nv);
        seen[nv] = count;
        // printf("%d %d %d\\n",count,v,nv);
      }
    }
    count++;
  }
  // 連結部の個数-1が答え
  cout << count - 1 << endl;
  return 0;
}


#include "bits/stdc++.h"
#define rep(i, b) for (ll i = 0; i < b; i++)
#define ll long long
using namespace std;
/*--Input//////////////////////////////////////////////////*/
inline void IN(void) { return; }
template <typename First, typename... Rest>
void IN(First &first, Rest &...rest) {
  cin >> first;
  IN(rest...);
  return;
}
#define SS(T, ...)                                                             \\
  T __VA_ARGS__;                                                               \\
  IN(__VA_ARGS__);
#define SV(type, c, n)                                                         \\
  vector<type> c(n);                                                           \\
  for (auto &i : c)                                                            \\
    cin >> i;
/*--Output/////////////////////////////////////////////////*/
#define Yes(n) cout << ((n) ? "Yes" : "No") << endl
inline int p(void) {
  cout << endl;
  return 0;
}
template <class Head> int p(Head &&head) {
  cout << head;
  p();
  return 0;
}
template <class Head, class... Tail> int p(Head &&head, Tail &&...tail) {
  cout << head << " ";
  p(forward<Tail>(tail)...);
  return 0;
}
struct ProconInit {
  static constexpr int IOS_PREC = 15;
  static constexpr bool AUTOFLUSH = false;

  ProconInit() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(IOS_PREC);
    if (AUTOFLUSH)
      cout << unitbuf;
  }
} PROCON_INIT;

#define vl vector<ll>
#define vvl vector<vector<ll>>
#define vvvl vector<vector<vector<ll>>>
#define debug(x) cerr << #x << " " << x << '\\n';
#define all(x) (x).begin(), (x).end()
#define bit(n) (1LL << (n))

template <class T> bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> bool chmin(T &a, const T &b) {
  if (b < a) {
    a = b;
    return 1;
  }
  return 0;
}
bool cmp(pair<ll, ll> a, pair<ll, ll> b) { return a.second < b.second; }

ll MOD = 1000000007;

struct UnionFind {
  vector<int> par;

  UnionFind(int n) : par(n, -1) {}
  void init(int n) { par.assign(n, -1); }

  int root(int x) {
    if (par[x] < 0)
      return x;
    else
      return par[x] = root(par[x]);
  }

  bool same(int x, int y) { return root(x) == root(y); }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (par[x] > par[y])
      swap(x, y); // merge technique
    par[x] += par[y];
    par[y] = x;
    return true;
  }

  int size(int x) { return -par[root(x)]; }
};
int main() {
  vector<pair<int, int>> Q;
  SS(int, n, m)
  UnionFind uf(n);
  rep(i, m) {
    SS(int, a, b);
    uf.unite(a, b);
  }
  int now = 0;
  for (int i = 1; i <= n; i++)
    Q.push_back(make_pair(uf.root(i), uf.size(i)));
  sort(rbegin(Q), rend(Q));
  for (auto V : Q) {
    if (now != V.first)
      n -= V.second - 1;
    now = V.first;
  }
  p(n - 1);
}

#include "bits/stdc++.h"
#define rep(i, b) for (ll i = 0; i < b; i++)
#define ll long long
using namespace std;
/*--Input//////////////////////////////////////////////////*/
inline void IN(void) { return; }
template <typename First, typename... Rest>
void IN(First &first, Rest &...rest) {
  cin >> first;
  IN(rest...);
  return;
}
#define SS(T, ...)                                                             \\
  T __VA_ARGS__;                                                               \\
  IN(__VA_ARGS__);
#define SV(type, c, n)                                                         \\
  vector<type> c(n);                                                           \\
  for (auto &i : c)                                                            \\
    cin >> i;
/*--Output/////////////////////////////////////////////////*/
#define Yes(n) cout << ((n) ? "Yes" : "No") << endl
inline int p(void) {
  cout << endl;
  return 0;
}
template <class Head> int p(Head &&head) {
  cout << head;
  p();
  return 0;
}
template <class Head, class... Tail> int p(Head &&head, Tail &&...tail) {
  cout << head << " ";
  p(forward<Tail>(tail)...);
  return 0;
}
struct ProconInit {
  static constexpr int IOS_PREC = 15;
  static constexpr bool AUTOFLUSH = false;

  ProconInit() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(IOS_PREC);
    if (AUTOFLUSH)
      cout << unitbuf;
  }
} PROCON_INIT;

#define vl vector<ll>
#define vvl vector<vector<ll>>
#define vvvl vector<vector<vector<ll>>>
#define debug(x) cerr << #x << " " << x << '\\n';
#define all(x) (x).begin(), (x).end()
#define bit(n) (1LL << (n))

template <class T> bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> bool chmin(T &a, const T &b) {
  if (b < a) {
    a = b;
    return 1;
  }
  return 0;
}
bool cmp(pair<ll, ll> a, pair<ll, ll> b) { return a.second < b.second; }

ll MOD = 1000000007;

struct UnionFind {
  vector<int> par;

  UnionFind(int n) : par(n, -1) {}

  int root(int x) {
    if (par[x] < 0)
      return x;
    else
      return par[x] = root(par[x]);
  }

  bool same(int x, int y) { return root(x) == root(y); }

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (par[x] > par[y])
      swap(x, y); // merge technique
    par[x] += par[y];
    par[y] = x;
    return true;
  }
  int size(int x) { return -par[root(x)]; }
};
int main() {
  vector<pair<int, int>> Q;
  SS(int, n, m)
  UnionFind uf(n);
  rep(i, m) {
    SS(int, a, b);
    a--;
    b--;
    uf.unite(a, b);
  }
  int now = 0;
  rep(i, n) Q.push_back(make_pair(uf.root(i), uf.size(i)));
  sort(rbegin(Q), rend(Q));
  for (auto V : Q) {
    if (now != V.first)
      n -= V.second - 1;
    now = V.first;
  }
  p(n - 1);
}


#include <iostream>
#include <stdlib.h>
#include <string>
#include <vector>

using namespace std;

struct UnionFind {
  vector<int> par;

  UnionFind(int N) : par(N) {
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) {
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return;
    par[rx] = ry;
  }
};

int main() {

  int N, M, count = 0;
  cin >> N >> M;
  UnionFind tree(N);

  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    tree.unite(A, B);
  }
  for (int i = 0; i < N; i++) {
    if (tree.root(i) == i)
      count++;
  }

  cout << count - 1 << endl;
  return 0;
}

#include <iostream>
#include <stdlib.h>
#include <string>
#include <vector>

using namespace std;

struct UnionFind {
  vector<int> par;

  UnionFind(int N) : par(N) {
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) {
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return;
    par[rx] = ry;
  }
};

int main() {

  int N, M, count = 0;
  cin >> N >> M;
  UnionFind tree(N);

  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    A--;
    B--;
    tree.unite(A, B);
  }
  for (int i = 0; i < N; i++) {
    if (tree.root(i) == i)
      count++;
  }

  cout << count - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

using Graph = vector<vector<int>>;

// 深さ優先探索
vector<bool> seen;
void dfs(const Graph &G, int v) {
  seen[v] = true; // v を訪問済にする
  // v から行ける各頂点 next_v について
  for (auto next_v : G[v]) {
    if (seen[next_v])
      continue;     // next_v が探索済だったらスルー
    dfs(G, next_v); // 再帰的に探索
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  Graph g(n);
  for (int i = 0; i < m; ++i) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  seen.resize(n);
  seen.assign(n, false);
  int cnt = 0;
  for (int i = 0; i < n; ++i) {
    if (seen[i]) {
      continue;
    }
    dfs(g, i);
    // for(int j=0; j<n; ++j){cout << seen[i] << ",";} cout << endl;
    for (int j = 0; j < n; ++j) {
      if (!seen[j]) {
        cnt++;
        break;
      }
    }
  }
  // cnt++;
  cout << cnt << endl;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

using Graph = vector<vector<int>>;

// 深さ優先探索
vector<bool> seen;
void dfs(const Graph &G, int v) {
  seen[v] = true; // v を訪問済にする
  // v から行ける各頂点 next_v について
  for (auto next_v : G[v]) {
    if (seen[next_v])
      continue;     // next_v が探索済だったらスルー
    dfs(G, next_v); // 再帰的に探索
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  Graph g(n);
  for (int i = 0; i < m; ++i) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  seen.resize(n);
  seen.assign(n, false);
  int cnt = 0;
  int size = 0;
  for (int i = 0; i < n; ++i) {
    if (seen[i]) {
      continue;
    }
    dfs(g, i);
    // for(int j=0; j<n; ++j){cout << seen[i] << ",";} cout << endl;

    for (int j = i; j < n; ++j) {
      if (seen[j]) {
        size++;
      } // cout << i << "," << j << "," << size << endl;
      else {
        break;
      }
    }
    if (size != n) {
      cnt++;
    }
  }
  // cnt++;
  cout << cnt << endl;
  return 0;
}