#if local
#define _GLIBCXX_DEBUG
#endif

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

#define rep(i,n) for(ll i=0;i<ll(n);++i)
#define repr(i,n) for(ll i=ll(n);i-->0;)
using ll = int_fast64_t;
using pll = pair<ll,ll>;
constexpr ll INF = 1LL<<60;
constexpr ll MOD = 1e9+7;
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(a>b){a=b;return 1;}return 0;}
#if local
template<class T>void dump(T&& t){cerr<<t<<endl;}
template<class T,class... Ts> void dump(T&& h, Ts&&... t){cerr<<h<<", ";dump(forward<Ts>(t)...);}
#else
void dump(){}
template<class T,class... Ts> void dump(T&& h, Ts&&... t){}
#endif
template<class T> istream &operator>>(istream&is,vector<T>&v){for(auto &elemnt:v)is>>elemnt;return is;}
template<class T,class U> istream &operator>>(istream&is,pair<T,U>&p){is>>p.first>>p.second;return is;}
template<class T> ostream &operator<<(ostream& os,vector<T>const& v){for(auto const& vi:v)os<<vi<<" ";return os;}
template<class T,class U> ostream &operator<<(ostream& os,pair<T,U>const& p){os<<p.first<<","<<p.second;return os;}
template<class T>vector<T> vec(size_t a){return vector<T>(a);}
template<class T, class... Ts>auto vec(size_t a, Ts... ts){return vector<decltype(vec<T>(ts...))>(a, vec<T>(ts...));}

template<class T,class F>
struct segment_tree{
  std::vector<T> data;int n;F f;T identity;segment_tree(){}
  segment_tree(int sz,T ide,F _f):f(_f),identity(ide){n=1;while(n<sz)n<<=1;data.resize(2*n,identity);}
  segment_tree(std::vector<T> const& v,T ide,F _f):f(_f),identity(ide){int sz=v.size();n=1;while(n<sz)n<<=1;data.resize(2*n,identity);for(int i=0;i<sz;++i)data[i+n]=v[i];for(int i=n-1;i>0;--i)data[i]=f(data[i<<1|0],data[i<<1|1]);}
  void set(int i,T x){i+=n;data[i]=x;while(i,i>>=1)data[i]=f(data[i<<1|0],data[i<<1|1]);}
  T fold(int l,int r)const{l+=n,r+=n;T fold_l=identity,fold_r=identity;while(l<r){if(l&1)fold_l=f(fold_l,data[l++]);if(r&1)fold_r=f(data[--r],fold_r);l>>=1,r>>=1;}return f(fold_l,fold_r);}
  T fold()const{int l=n,r=2*n;T fold_l=identity,fold_r=identity;while(l<r){if(l&1)fold_l=f(fold_l,data[l++]);if(r&1)fold_r=f(data[--r],fold_r);l>>=1,r>>=1;}return f(fold_l,fold_r);}
  T operator[](int i)const{return data[i+n];}
  template<class Cond>int bserch(int l,Cond& cond){l+=n;T x=identity;do{while(!l&1)l>>=1;if(cond(f(x,data[l]))){while(l<n){l<<=1;if(!cond(f(x,data[l])))x=f(x,data[l++]);}return l-n;}x=f(x,data[l++]);}while((l&-l)!=l);return -1;}
};
template<class T,class F>auto make_segtree(int n,T ide,F f){return segment_tree<T,F>(n,ide,f);}
template<class T,class F>auto make_segtree(std::vector<T>const& v,T ide,F f){return segment_tree<T,F>(v,ide,f);}
auto RMQ(int n=2e5){return make_segtree<int64_t>(n,-(1ll<<60),[](auto a,auto b){return a>b?a:b;});}auto rmq(int n=2e5){return make_segtree<int64_t>(n,1ll<<60,[](auto a,auto b){return a<b?a:b;});}auto rsq(int n=2e5){return make_segtree<int64_t>(n,0,[](auto a,auto b){return a+b;});}

struct union_find{
  size_t n;std::vector<int> par;size_t group;union_find(){}
  union_find(size_t _n):n(_n),group(_n){par.resize(n,-1);}
  void reset      (size_t _n)     {n = _n;group = n;par.clear();par.resize(n,-1);}
  int  root       (int k)         {if(par[k]<0)return k;return par[k]=root(par[k]);}
  int  size       (int k)         {k=root(k);return -par[k];}
  bool unite      (int a,int b)   {a=root(a);b=root(b);if(a==b)return false;if(size(a)<size(b))std::swap(a,b);group--;par[a]+=par[b];par[b]=a;return true;}
  bool same       (int a,int b)   {return root(a) == root(b);}
  int  operator[] (int k)         {return size(k);}
  bool operator() (int a,int b)   {return unite(a,b);}
};

signed main(){
  std::cin.tie(nullptr);
  std::ios_base::sync_with_stdio(false);
  constexpr char newl = '\n';

  int n,m;
  cin>>n>>m;
  union_find uni(n);
  rep(i,m){
    int a,b;
    cin>>a>>b;
    a--;b--;
    uni(a,b);
  }
  int ans = uni.group-1;
  cout<<(ans)<<endl;

}

Measured memory_bytes: 3708