#ifndef ONLINE_JUDGE
#include "default.h"
#else
#include<bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#endif

using namespace std;
using namespace __gnu_pbds;


#pragma GCC target("popcnt")  //https://godbolt.org/z/yQv94Y 

typedef long long int ll;
typedef unsigned long long int  ull;
typedef long double ld;

const ld pi = 4.0*atan(1.0);
#define xlr8 ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cout.precision(10); cout<<fixed


typedef tree<
int,
null_type,
less<int>,
rb_tree_tag,
tree_order_statistics_node_update>
ordered_set;
// T.insert(a), T.erase(a), T.size()
// T.order_of_key(a) -- number of elements strictly less than a
// *T.find_by_order(k) -- k-th element in increasing order

struct Timer
{   chrono::time_point<chrono::high_resolution_clock> start , end; chrono:: duration<float> dur;
    Timer(){start = chrono::high_resolution_clock::now();}
    ~Timer(){   end = chrono::high_resolution_clock::now(); dur = end - start;  cout<<"\nTime Elapsed : " <<dur.count() << " s\n";  }
};


template<typename T>
void getVec(vector<T>& a , int st, int en)
{
    int dif = (en > st ? 1 : -1);
    for(int  i = st ; i!= en ; i+= dif)
        cin>>a[i];
}
template<typename T>
void getVecPair(vector<pair<T , T > >& a , int st, int en)
{
    int dif = (en > st ? 1 : -1);
    for(int  i = st ; i!= en ; i+= dif)
        cin>>a[i].first>>a[i].second;
}


//76149
template<int D, typename T> 
struct Vec : public vector<Vec<D - 1, T>> {
  static_assert(D >= 1, "Vector dimension must be greater than zero!");
  template<typename... Args>
  Vec(int n = 0, Args... args) : vector<Vec<D - 1, T>>(n, Vec<D - 1, T>(args...)) {
  }
};
template<typename T>
struct Vec<1, T> : public vector<T> {
  Vec(int n = 0, T val = T()) : vector<T>(n, val) {
  }
};


// const int MAX = 1000;
// Vec<2 , int > dp(MAX + 1, MAX + 1, -1);

struct DisjointSets
{
    int n;
    vector<int> parent, rank, sz;
    DisjointSets(int n, bool Rank= true)
    {
        this->n = n;
        parent.resize(n+1,0);
        iota(parent.begin(), parent.end(), 0);
        (Rank ? rank.resize(n+1,1) : sz.resize(n+1, 1));
    }
    int find(int i)
    {
        if(i!=parent[i])
            parent[i] = find(parent[i]);
        return parent[i];
    }
    void UnionByRank(int i , int j)
    {
        i = find(i);j = find(j);
        if(i==j)    return;
        if(rank[i]<rank[j])
            swap(i,j);
        parent[j] = i;
        rank[i]+=rank[j];
    }
    void UnionBySize(int i ,int j)
    {
        i = find(i);j = find(j);
        if(i==j)    return;
        if(sz[i] < sz[j])
            swap(i,j);
        parent[j] = i;
        sz[i]+=sz[j];
    }
    void solve()
    {
        set<int> s;
        for(int i = 1 ; i <= n ; i++)
            s.insert(find(i));
        cout<< (int)s.size() - 1<<"\n";
    }
};

int main()
{
    xlr8;
    int testCases  = 1;
    // cin>>testCases;
    while (testCases--)
    {
        int n , m;
        cin>>n>>m;
        DisjointSets DS(n);
        for(int i =  0 ; i< m ; i++)
        {
            int a , b;
            cin>>a>>b;
            DS.UnionByRank(a , b);
        }
        DS.solve();

        
    }
}


Measured memory_bytes: 7868