#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <iostream>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>

using namespace std;
using lint = long long;
using P = pair<lint, lint>;

#define rep(i, n) for (lint i = 0; i < (n); ++i)
#define rep1(i, n) for (lint i = 1; i < (n); ++i)
#define repn(i, a, b) for(lint i = (a); i < (b); ++i)
#define rep_inv(i, n) for (lint i = (n); i >= 0; --i)
#define rep_inv1(i, n) for(lint i = (n); i >= 1; --i)
#define all(vec) (vec).begin(), (vec).end()
#define cend printf("\n")

constexpr lint Mod = /**/ 1000'000'007LL /*/ 998'244'353LL /**/;
constexpr lint Inf = 4'500'000'000'000'000'007LL; //4.5e18+7
constexpr double Pi = 3.141592653589793;

template<class T> using prique     = priority_queue<T>;
template<class T> using prique_inv = priority_queue<T, vector<T>, greater<T>>;
template<class T> inline bool chmin(T& a, const T& b) {
	if (a > b) {
		a = b;
		return true;
	}
	return false;
}
template<class T> inline bool chmax(T& a, const T& b) {
	if (a < b) {
		a = b;
		return true;
	}
	return false;
}
template<class T> inline istream& operator>>(istream& is, vector<T>& v) { for (auto& e : v) is >> e; return is; }
template<class T> inline ostream& operator<<(ostream& os, const vector<T>& v) {
	size_t i = 0, n = v.size();
	for (const auto& e : v) {
		os << e;
		if (i + 1 < n) os << " ";
		++i;
	}
	return os;
}
template<class T, class U> inline istream& operator>>(istream& is, pair<T, U>& rhs) { return is >> rhs.first >> rhs.second; }
template<class T, class U> inline ostream& operator<<(ostream& os, const pair<T, U>& rhs) { return os << "{" << rhs.first << ", " << rhs.second << "}"; }
template<class T, class U> inline void print(const T& a, const U& b, bool f) {
	if (f) cout << a << "\n";
	else cout << b << "\n";
}
template<class InputIterator> void arrin(InputIterator first, InputIterator last) { for (; first != last; ++first) cin >> (*first); }
template<class InputIterator> void arrout(InputIterator first, InputIterator last) {
	for (; first != last; ++first) {
		cout << (*first) << ((first + 1) != last ? " " : "\n");
	}
}

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

lint fact[3'000'000];
void fact_init(lint n, lint m = Mod) {
	if (3'000'000 <= n) return;
	fact[0] = fact[1] = 1;
	for (lint i = 2; i <= n; ++i) {
		fact[i] = i * fact[i - 1] % m;
	}
}

// a * first + b * second = gcd(a, b)
P extgcd(lint a, lint b) {
	lint s = a, sx = 1, sy = 0, t = b, tx = 0, ty = 1;

	while (s % t != 0) {
		lint tmp = s / t;
		lint u = s - t * tmp;
		lint ux = sx - tx * tmp;
		lint uy = sy - ty * tmp;

		s = t;
		sx = tx;
		sy = ty;
		t = u;
		tx = ux;
		ty = uy;
	}

	return { tx, ty };
}

lint modinv(lint a, lint m = Mod) {
	lint b = m, u = 1, v = 0;
	while (b != 0) {
		lint t = a / b;
		a -= t * b; swap(a, b);
		u -= t * v; swap(u, v);
	}
	u %= m;
	if (u < 0) u += m;
	return u;
}

lint modpow(lint x, lint n, lint m = Mod) {
	lint res = 1;
	while (n > 0) {
		if (n & 1) res = res * x % m;
		x = x * x % m;
		n >>= 1;
	}
	return res;
}

lint intpow(lint x, lint n) {
	lint res = 1;
	while (n > 0) {
		if (n & 1) res *= x;
		x *= x;
		n >>= 1;
	}
	return res;
}

lint comb(lint n, lint r, lint m = Mod) {
	if (r == 0 || r == n) return 1;
	lint res = fact[n] * modinv(fact[n - r], m) % m * modinv(fact[r], m) % m;
	return res < 0 ? res + m : res;
}

class Factring {
private:

	const lint max_n;
	vector<lint> sieve;

public:

	explicit Factring(lint n) :
		max_n(n), sieve(n)
	{
		iota(sieve.begin(), sieve.end(), 0);

		for (lint i = 2; i * i < n; ++i) {
			if (sieve[i] < i) continue;

			for (lint j = i * i; j < n; j += i) {
				if (sieve[j] == j) sieve[j] = i;
			}
		}
	}

	unordered_map<lint, lint> calc(lint x) const {
		assert(x < max_n);

		unordered_map<lint, lint> res;
		while (x > 1) {
			++res[sieve[x]];
			x /= sieve[x];
		}
		return res;
	}
};

namespace geometry {

	constexpr double EPS = 1e-12;

	constexpr int sgn(double a) { return (a < -EPS ? -1 : (a > EPS ? +1 : 0)); }

	constexpr bool same(double a, double b) { return sgn(a - b) == 0; }

	struct Vec2 {
		double x, y;

		Vec2() = default;
		constexpr Vec2(double x, double y) :x(x), y(y) {}

		constexpr bool operator<(const Vec2& v) const {
			if (!same(x, v.x)) return x < v.x;
			return y < v.y;
		}
		constexpr bool operator>(const Vec2& v) const {
			return v < *this;
		}

		constexpr Vec2 operator+() const { return *this; }
		constexpr Vec2 operator-() const { return { -x,-y }; }

		constexpr Vec2 operator+(const Vec2& v) const { return { x + v.x,y + v.y }; }
		constexpr Vec2 operator-(const Vec2& v) const { return { x - v.x,y - v.y }; }
		constexpr Vec2 operator*(double s) const { return { x * s,y * s }; }
		constexpr Vec2 operator/(double s) const { return { x / s,y / s }; }

		constexpr Vec2& operator+=(const Vec2& v) {
			x += v.x; y += v.y;
			return *this;
		}
		constexpr Vec2& operator-=(const Vec2& v) {
			x -= v.x; y -= v.y;
			return *this;
		}
		constexpr Vec2& operator*=(double s) {
			x *= s; y *= s;
			return *this;
		}
		constexpr Vec2& operator/=(double s) {
			x /= s; y /= s;
			return *this;
		}

		constexpr bool isZero() const {
			return x == 0.0 && y == 0.0;
		}
		bool hasNan() const {
			return isnan(x) || isnan(y);
		}

		constexpr double dot(const Vec2& v) const { return x * v.x + y * v.y; }
		constexpr double cross(const Vec2& v) const { return x * v.y - y * v.x; }

		double length() const {
			return sqrt(lengthSq());
		}
		constexpr double lengthSq() const {
			return dot(*this);
		}

		double distanceFrom(const Vec2& v) const {
			return (v - *this).length();
		}
		constexpr double distanceFromSq(const Vec2& v) const {
			return (v - *this).lengthSq();
		}

		Vec2 normalized() const {
			return *this / length();
		}
		Vec2& normalize() {
			return *this /= length();
		}

		Vec2 rotated(double angle) const {
			const double s = sin(angle);
			const double c = cos(angle);
			return { x * c - y * s,x * s + y * c };
		}
		Vec2& rotate(double angle) {
			return *this = rotated(angle);
		}

		double angle() const { return atan2(y, x); }
		double angle(const Vec2& v) const {
			if (isZero() || v.isZero()) {
				return numeric_limits<double>::signaling_NaN();
			}

			return atan2(cross(v), dot(v));
		}
		double angle(const Vec2& a, const Vec2& b) const {
			return (a - *this).angle(b - *this);
		}

		constexpr Vec2 lerp(const Vec2& v, double f) const {
			return { x + (v.x - x) * f,y + (v.y - y) * f };
		}

		constexpr friend Vec2 operator*(double s, const Vec2& v) {
			return { s * v.x,s * v.y };
		}
		friend istream& operator>>(istream& is, Vec2& v) {
			return is >> v.x >> v.y;
		}
		friend ostream& operator<<(ostream& os, const Vec2& v) {
			return os << "(" << v.x << ", " << v.y << ")";
		}
	};

	/*
	ABから見てBCは左に曲がるのなら +1
	ABから見てBCは右に曲がるのなら -1
	ABC(CBA)の順番で一直線上に並ぶなら +2
	BAC(CAB)の順番で一直線上に並ぶなら -2
	CがAB上なら 0
	*/
	constexpr int iSP(const Vec2& a, const Vec2& b, const Vec2& c) {
		int flag = sgn((b - a).cross(c - a));

		if (flag != 0) return flag;
		if (sgn((b - a).dot(c - b)) > 0) return 2;
		if (sgn((a - b).dot(c - a)) > 0) return -2;
		return 0;
	}

	struct Line {
		Vec2 begin, end;

		Line() = default;
		constexpr Line(const Vec2& begin, const Vec2& end) :begin(begin), end(end) {}

		// ax + by + c = 0
		Line(double a, double b, double c) {
			assert(sgn(a) != 0 && sgn(b) != 0);

			if (sgn(b) == 0) {
				begin = Vec2(-c / a, 0.0);
				end = Vec2(-c / a, 1.0);
			}
			else {
				begin = Vec2(0, -c / b);
				end = Vec2(1.0, -(a + c) / b);
			}
		}

		constexpr Vec2 vector() const { return end - begin; }

		constexpr bool isParallel(const Line& line) const {
			return sgn(vector().cross(line.vector())) == 0;
		}
		constexpr bool isOrthogonal(const Line& line) const {
			return sgn(vector().dot(line.vector())) == 0;
		}

		double length() const { return vector().length(); }
		constexpr double lengthSq() const { return vector().lengthSq(); }

		double distanceFrom(const Vec2& v) const {
			return fabs(vector().cross(v - begin)) / length();
		}

		optional<Vec2> intersectsAt(const Line& line) const {
			if (sgn(vector().cross(line.vector())) == 0) return nullopt;

			return begin + vector() *
				fabs((line.end - begin).cross(line.vector()) / vector().cross(line.vector()));
		}

		Vec2 projection(const Vec2& v) const {
			return begin + vector().normalized() *
				(v - begin).dot(vector()) / length();
		}
		Vec2 reflection(const Vec2& v) const {
			return 2 * projection(v) - v;
		}

		friend istream& operator>>(istream& is, Line& rhs) {
			return is >> rhs.begin >> rhs.end;
		}
	};

	struct Polygon {
		int n;
		vector<Vec2>vertex;

		Polygon() = default;
		Polygon(int n) : n(n), vertex(n) {}
		Polygon(const vector<Vec2>& vec) : n(vertex.size()), vertex(vec) {}

		bool isConvex() const {
			for (int i = 0; i < n; ++i) {
				if (iSP(vertex[i], vertex[(i + 1) % n], vertex[(i + 2) % n]) == -1) return false;
			}
			return true;
		}

		bool isOnEdge(const Vec2& v) const {
			for (int i = 0; i < n; ++i) {
				if (iSP(vertex[i], vertex[(i + 1) % n], v) == 0) return true;
			}
			return false;
		}

		bool intersects(const Vec2& v) const {
			double sum = 0.0;
			
			for (int i = 0; i < n; ++i) {
				sum += v.angle(vertex[i], vertex[(i + 1) % n]);
			}

			return sgn(sum - 2.0 * Pi) == 0;
		}

		double area() const {
			double sum = 0.0;

			for (int i = 0; i < n; ++i) {
				sum += vertex[i].cross(vertex[(i + 1) % n]);
			}

			if (sum < 0) sum = -sum;

			return 0.5 * sum;
		}

		Polygon convexFull() const {
			int k = 0;
			vector<Vec2> vec(vertex), res(n * 2);

			stable_sort(vec.begin(), vec.end());
			
			for (int i = 0; i < n; ++i) {
				while (k > 1 && (res[k - 1] - res[k - 2]).cross(vec[i] - res[k - 1]) < 0) k--;
				res[k++] = vec[i];
			}

			for (int i = n - 2, t = k; i >= 0; --i) {
				while (k > t && (res[k - 1] - res[k - 2]).cross(vec[i] - res[k - 1]) < 0) k--;
				res[k++] = vec[i];
			}

			res.resize(k - 1);
			return res;
		}

		double diameter() const {
			const auto& convex = (isConvex() ? (*this) : convexFull()).vertex;

			const size_t n = convex.size();

			if (n == 2) return convex[0].distanceFrom(convex[1]);
			else {
				size_t i = 0, j = 0;

				for (size_t k = 0; k < n; ++k) {
					if (!(convex[i] < convex[k])) i = k;
					if (convex[j] < convex[k]) j = k;
				}

				const size_t si = i, sj = j;
				while (i != sj || j != si) {
					if ((convex[(i + 1) % n] - convex[i]).cross(convex[(j + 1) % n] - convex[j]) < 0) {
						i = (i + 1) % n;
					}
					else j = (j + 1) % n;
				}

				return convex[i].distanceFrom(convex[j]);
			}
		}

		friend istream& operator>>(istream& is, Polygon& rhs) {
			for (Vec2& e : rhs.vertex) is >> e;
			return is;
		}
	};

	namespace segment {

		using Segment = Line;

		constexpr bool intersect(const Segment& a, const Segment& b) {
			if (iSP(a.begin, a.end, b.begin) * iSP(a.begin, a.end, b.end) <= 0 &&
				iSP(b.begin, b.end, a.begin) * iSP(b.begin, b.end, a.end) <= 0) {
				return true;
			}
			return false;
		}

		double distance(const Segment& a, const Vec2& v) {
			if (sgn(a.vector().dot(v - a.begin)) < 0 ||
				sgn((-a.vector()).dot(v - a.end) < 0)) {
				return min(v.distanceFrom(a.begin), v.distanceFrom(a.end));
			}
			return a.distanceFrom(v);
		}

		double distance(const Segment& a, const Segment& b) {
			if (intersect(a, b)) return 0.0;

			return min({
				distance(a,b.begin),
				distance(a,b.end),
				distance(b,a.begin),
				distance(b,a.end)
				}
			);
		}
	}
}

class UnionFind {
private:

	vector<int>par, rank, size;

public:

	explicit UnionFind(int n)
		:par(n), rank(n), size(n, 1)
	{
		iota(par.begin(), par.end(), 0);
	}

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

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

	void unite(int x, int y) {
		x = root(x); y = root(y);
		if (x == y) return;
		if (rank[x] < rank[y]) {
			par[x] = y;
			size[y] += size[x];
		}
		else {
			par[y] = x;
			if (rank[x] == rank[y]) ++rank[x];
			size[x] += size[y];
		}
	}
};

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

	UnionFind uf(n + 10);

	set<int>st;

	rep(i, m) {
		int a, b;
		cin >> a >> b;
		--a; --b;

		uf.unite(a, b);
	}

	rep(i, n) {
		st.insert(uf.root(i));
	}

	cout << st.size() - 1 << endl;
}

Measured memory_bytes: 8136