#include <bits/stdc++.h>
// #include <boost/multiprecision/cpp_int.hpp>
using namespace std;

#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define MIN(a) *std::min_element((a).begin(), (a).end())
#define MAX(a) *std::max_element((a).begin(), (a).end())
#define SUM(a) std::accumulate((a).begin(), (a).end(), 0LL)
#define REP(i, n) for (int (i) = 0; (i) < (n); (i)++)
#define RREP(i, n) for (int (i) = (n)-1; (i) >= 0; (i)--)
#define FOR(i, m, n) for (int (i) = (m); (i) < (n); i++)
#define FORR(i, m, n) for (int (i) = (n)-1; (i) >= (m); i--)
#define debug(x) std::cerr << #x << " = " << x << '\n'
// #define int long long

using ll = long long;
using ull = unsigned long long;
using ld = long double;
using vl = std::vector<long long>;
using vvl = std::vector<std::vector<long long>>;
// using bint = boost::multiprecision::cpp_int;
// using rat = boost::rational<bint>;

constexpr int inf = (1 << 30) - 1;
constexpr long long INF = 1LL << 60;
constexpr double eps = 1e-9;

inline void ios_() {std::cin.tie(nullptr); std::ios::sync_with_stdio(false);}
template<typename T> inline T Div(const T& a, const T& b) {return (a + b - 1) / b;}
template<typename T> bool chmin(T& a, const T& b) {if(a > b){a = b; return true;} return false;}
template<typename T> bool chmax(T& a, const T& b) {if(a < b){a = b; return true;} return false;}

template<typename T>
inline void finish(const T& msg) {
    std::cout << msg << std::endl;
    std::exit(0);
}

template<typename T>
long long binarySearch(long long ng, long long ok, const T& fn) {
    while (std::abs(ok - ng) > 1) {
        long long mid = (ok + ng) / 2;
        if (fn(mid)) ok = mid;
        else ng = mid;
    }
    return ok;
}

struct ModInt {
    long long value;
    static constexpr long long mod = 1e9+7;
    // static constexpr long long mod = 998244353;

    constexpr ModInt(long long v = 0) : value(((v % mod) + mod) % mod) {}

    constexpr ModInt pow(long long n) const {
        long long x = value, res = 1;
        while (n > 0) {
            if (n & 1) {
                res = res * x % mod;
            }
            x = x * x % mod;
            n >>= 1;
        }
        return ModInt(res);
    }

    constexpr ModInt inverse() const {
        return pow(mod - 2);
    }

    constexpr ModInt operator-() const {
        return ModInt((mod - value) % mod);
    }

    constexpr ModInt& operator+=(const ModInt& a) {
        value += a.value;
        value %= mod;
        return *this;
    }

    constexpr ModInt& operator-=(const ModInt& a) {
        value += (-a).value;
        value %= mod;
        return *this;
    }

    constexpr ModInt& operator*=(const ModInt& a) {
        value *= a.value;
        value %= mod;
        return *this;
    }

    constexpr ModInt& operator/=(const ModInt& a) {
        return (*this) *= a.inverse();
    }

    constexpr ModInt operator+(const ModInt& a) const {
        return ModInt(*this) += a;
    }

    constexpr ModInt operator-(const ModInt& a) const {
        return ModInt(*this) -= a;
    }

    constexpr ModInt operator*(const ModInt& a) const {
        return ModInt(*this) *= a;
    }

    constexpr ModInt operator/(const ModInt& a) const {
        return ModInt(*this) /= a;
    }

    constexpr bool operator==(const ModInt& a) const {
        return value == a.value;
    }

    constexpr bool operator!=(const ModInt& a) const {
        return value != a.value;
    }

    friend std::istream& operator>>(std::istream& is, ModInt& a) {
        long long v;
        is >> v;
        a = v;
        return is;
    }

    friend std::ostream& operator<<(std::ostream& os, const ModInt& a) {
        os << a.value;
        return os;
    }

    static ModInt factorial(int n) {
        static std::vector<ModInt> fact(1 << 18);

        if (fact.size() <= n) fact.resize(n + 1);

        if (n < 0) return 0;
        if (n == 0) return 1;
        if (fact[n] != 0) return fact[n];

        return fact[n] = factorial(n - 1) * n;
    }

    static ModInt combin(int n, int k) {
        if (n < 0 || k < 0 || n < k) return 0;
        return factorial(n) / (factorial(k) * factorial(n - k));
    }
};

using mint = ModInt;

//Union-Find Tree
class UnionFind{
public:
    int N;              //頂点数
    int groupnum;       //グループの総数
    vector<int> par;    //par[i]:iの親の番号
    vector<int> size;   //size[i]:iが属するグループと同じグループに属する頂点数
    vector<int> rank;   //ランク

    UnionFind(int n) : N(n), groupnum(n), par(n), size(n, 1), rank(n, 0){  //初期化時はすべて根とする
        for(int i=0; i<n; i++){
            par[i] = i;
        }
    }

    int root(int x){    //データxが属する木の根を再帰で得る
        if(par[x] == x){
            return x;
        }else{
            return par[x] = root(par[x]); //経路圧縮を同時に実行
        }
    }

    void unite(int x, int y){   //xの属する木とyの属する木を併合
        int rx = root(x), ry = root(y);
        if(rx == ry) return;    //xとyの属する木が同じなら何もしない

        if(rank[rx] < rank[ry]) swap(rx, ry);   //rank[rx] >= rank[ry]とする

        par[ry] = rx;   //yの木の根をxの根につける

        if(rank[rx] == rank[ry]) rank[rx]++;    //rankを更新
        size[rx] += size[ry];   //sizeを更新

        groupnum--;     //グループの総数は1減る
    }

    int getSize(int x){         //xの属する木のサイズを返す
        return size[x] = size[root(x)];
    }

    bool same(int x, int y){    //x,yが同じ木に属するときtrue, そうでなければfalse
        return root(x) == root(y);
    }
};


//Weighted Union-Find Tree
class WeightedUnionFind : public UnionFind{
public:
    vector<ll> weight;  //差分重み

    WeightedUnionFind(int n) : UnionFind(n){
        weight.resize(n, 0);
    }

    int root(int x){    //差分重みも同時に更新する
        if(par[x] == x){
            return x;
        }else{
            int r = root(par[x]);
            weight[x] += weight[par[x]];    //  累積和
            return par[x] = r;  //経路圧縮
        }
    }

    ll getWeight(int x){    //xのrootからの差分重みを取得
        root(x);
        return weight[x];
    }

    ll diff(int x, int y){  //yとxの差分を取得
        return getWeight(y) - getWeight(x);
    }

    //weight(y) - weight(x) = w となるようにマージ
    bool unite(int x, int y, ll w){
        w -= diff(x, y);    //rootとの重み差分を補正

        int rx = root(x), ry = root(y);
        if(rx == ry) return false;  //xとyがすでにマージされていたらfalseを返す

        if(rank[rx] < rank[ry]){    //rank[rx] >= rank[ry]とする
            swap(rx, ry);
            w *= -1;                //合わせてwも符号反転
        }

        par[ry] = rx;   //yの木の根をxの根につける

        if(rank[rx] == rank[ry]) rank[rx]++;    //rankを更新
        size[rx] += size[ry];   //sizeを更新
        
        groupnum--;     //グループの総数は1減る

        weight[y] = w;  //xとyの差分をweight[y]に記録

        return true;
    }
};

signed main() {
    int n, m;
    cin >> n >> m;
    UnionFind UF(n);
    REP(i, m){
        int a, b;
        cin >> a >> b;
        UF.unite(a-1, b-1);
    }

    cout << UF.groupnum - 1 << endl;
    return 0;
}


Measured memory_bytes: 4568