#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <utility>
#include <tuple>
#include <cstdint>
#include <cstdio>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <deque>
#include <unordered_map>
#include <unordered_set>
#include <bitset>
#include <cctype>
#include <cmath>
#include <ctime>
#include <stdlib.h>
#include <math.h>
#include <numeric>
#include <fstream>

//#include "atcoder/segtree.hpp"
using namespace std;

/*
#include <boost/multiprecision/cpp_int.hpp>
namespace mp = boost::multiprecision;
*/
/*
#pragma GCC target("avx")
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")
*/
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

#define int long long
#define endl "\n"
#define ALL(v)  v.begin(),v.end()
#define m_p make_pair
#define m_t make_tuple
#define rep(i,n)  for(int i=0; i<(int) (n); i++)
using P = pair<int, int>;
using ld = long double;
using ll = long long;
using VI = vector<int>;
using VVI = vector<vector<int>>;
using  VVC=vector<vector<char>>;
template<class T> using PQ_inv = priority_queue<T, vector<T>, greater<T>>;

const double pai = 3.1415926535897;
const int INF = 1000000021;
const ll LINF = 2000000000000000000;
const ll mod = 1000000007;
const int MAX = 2010000;
const ll MOD = 1000000007;
long long fac[MAX], finv[MAX], inv[MAX];

struct edge {
	int to, cost;
};
edge m_e(int xx, int yy) {
	edge ed;
	ed.to = xx;
	ed.cost = yy;
	return ed;
}

const auto identify_element = LINF;//単位元
template<class T>T op(T xx, T yy) {//セグ木、BITで扱う関数
	return min(xx, yy);
}
template<class T>class segment_tree {
	int root_size;//根の大きさ root_size+i-1でv[i]に対応する添え字を取得
	vector<T> data;
public:
	segment_tree(vector<T> v) {
		int a = 0;
		while (v.size() > (1ll << a))a++;
		root_size = 1ll << a;
		data.resize((root_size << 1) - 1, identify_element);
		rep(i, v.size()) {
			data[i + root_size - 1] = v[i];
		}
		for (int i = root_size - 2; i >= 0; i--) {
			data[i] = op(data[i * 2 + 1], data[i * 2 + 2]);
		}
	}

	//seg[p]=xxにする
	void update(int p, T xx) {
		int a = p + root_size - 1;
		data[a] = xx;
		while (a != 0) {
			a = (a - 1) / 2;
			data[a] = op(data[a * 2 + 2], data[a * 2 + 1]);
		}
	}
	//0-indexedでxx番目の値を返す
	T get(int xx) {
		return data[root_size + xx - 1];
	}
	//0-indexed 閉区間でlからrのopの和
	T get_sum(int l, int r) {//0-indexed
		int a = 1/*2乗*/, p = l + root_size - 1/*足す場所の添え字*/;
		T ret = identify_element;/*答え*/
		bool bo = 1;
		while (r + 1 > l) {
			if (a > r - l + 1) {
				bo = 0;
				a >>= 1;
				p = p * 2 + 1;
				continue;
			}
			if (bo) {
				if (l & a) {
					ret = op(ret, data[p]);
					l += a;
					p++;
				}
				a <<= 1;
				p = (p - 1) / 2;
			}
			else {
				if (r - l + 1 & a) {
					ret = op(ret, data[p]);
					l += a;
					p++;
				}
				a >>= 1;
				p = p * 2 + 1;
			}
		}
		return ret;
	}
	//全体和
	T all() {
		return data[0];
	}
};

class BIT { //0-indexedで与える opは群   
	vector<int>data;

public:;
	  BIT(vector<int>v) {
		  data.resize(v.size(), identify_element);
		  rep(i, v.size()) {
			  add(i, v[i]);
		  }
	  }
	  //data[p]にxxを加算
	  void add(int p, int xx) {
		  for (int i = p + 1; i <= (int)data.size(); i += (i & -i)) {
			  data[i - 1] += xx;
		  }
	  }
	  //0からrまでの総和
	  int get_sum(int r) {
		  int ret = identify_element;
		  for (int i = r + 1; i > 0; i -= (i & -i)) {
			  ret += data[i - 1];
		  }
		  return ret;
	  }
};

class union_find { //0-indexedで与える
	VI union_size;
public:;
	  VI table;
	  union_find(int xx) {
		  table.resize(xx);
		  rep(i, xx) table[i] = i;
		  union_size.resize(xx, 1);
	  }

	  int leader(int xx) {
		  if (table[xx] == xx)return xx;
		  return table[xx] = leader(table[xx]);
	  }
	  void marge(int xx, int yy) {
		  int a = leader(xx), b = leader(yy);
		  if (union_size[a] > union_size[b]) {
			  table[b] = a;
			  union_size[a] += union_size[b];
		  }
		  else {
			  table[a] = b;
			  union_size[b] += union_size[a];
		  }
	  }
	  int same(int xx, int yy) {
		  if (leader(xx) == leader(yy))return true;
		  else return false;
	  }
	  int size(int xx) {
		  return union_size[leader(xx)];
	  }
};

// テーブルを作る前処理
void COMinit() {
	fac[0] = fac[1] = 1;
	finv[0] = finv[1] = 1;
	inv[1] = 1;
	for (int i = 2; i < MAX; i++) {
		fac[i] = fac[i - 1] * i % MOD;
		inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;
		finv[i] = finv[i - 1] * inv[i] % MOD;
	}
}
//n個を1個以上のx組のグループに分ける重複組み合わせはcom(n-1,x-1)
long long COM(int n, int k) {
	if (n < k) return 0;
	if (n < 0 || k < 0) return 0;
	return fac[n] * (finv[k] * finv[n - k] % MOD) % MOD;
}
ll PER(int n, int k) {
	if (n < 0 || k < 0) return 0;
	if (n < k) return fac[n];
	return fac[n] * finv[n - k] % mod;
}

long long modinv(long long a, long long m) {
	long long b = m, u = 1, v = 0;
	while (b) {
		long long t = a / b;
		a -= t * b; swap(a, b);
		u -= t * v; swap(u, v);
	}
	u %= m;
	if (u < 0) u += m;
	return u;
}
long long modpow(int a, int n) {
	int res = 1;
	while (n > 0) {
		if (n & 1) res = res * a % mod;
		a = a * a % mod;
		n >>= 1;
	}
	return res;
}
long long fix_mod(int &xx) {
	if (xx >= mod * 2||xx<-mod*2) {
		xx %= mod;
	}
	if (xx >= mod)xx -= mod;
	//if (xx < 0)xx += mod;
	return xx;
}
//ax + by = gcd(a,b)を満たすx,yにする
long long extGCD(long long a, long long b, long long& x, long long& y) {
	if (b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	long long d = extGCD(b, a % b, y, x);
	y -= a / b * x;
	return d;
}

template<class T>void vecout(vector<T>& vec) { for (T t : vec) cout << t << " "; cout << endl; }
template<class T>void vecout(vector<vector<T>>& vec) {for (vector<T>tvec : vec) {for (T t : tvec)cout << t << " "; cout << endl;}}
template<class T>void vecout(vector<pair<T, T>>& vec) {for (pair<T, T>p : vec) {cout << p.first << " " << p.second << endl;}}
template<class T>void vecin(vector<T>& vec) { for (int i = 0; i < (int)vec.size(); i++) cin >> vec[i]; }
bool chmax(int& xx, int yy) { if (xx < yy) { xx = yy; return true; }return false; }
bool chmin(int& xx, int yy) { if (xx > yy) { xx = yy; return true; }return false; }
//x未満の要素数を返す二分探索関数
int b_s(vector<int>& vec, int xx) { return lower_bound(ALL(vec), xx) - vec.begin(); }
int gcd(int xx, int yy) {
	if (yy > xx)swap(xx, yy);
	while (xx % yy != 0) {
		xx %= yy;
		swap(xx, yy);
	}
	return yy;
}
int lcm(int xx, int yy) {return (xx / gcd(xx, yy)) * yy;}
bool prime(int xx) {
	if (xx <= 1) return 0;
	if (xx == 2)return 1;
	for (int i = 3; i * i <= xx; i+=2) {
		if (xx % i == 0) {
			return 0;
		}
	}
	return 1;
}



signed main() {
	int n, m,a,b;
	cin >> n >> m;
	union_find UF(n);
	rep(i, m) {
		cin >> a >> b;
		a--;
		b--;
		UF.marge(a, b);
	}
	set<int>se;
	rep(i, n) {
		se.insert(UF.leader(i));
	}
	cout << se.size()-1 << endl;
}



Measured memory_bytes: 8364