#![allow(unused_macros)]
#![allow(dead_code)]
#![allow(unused_imports)]

use itertools::Itertools;

const U_INF: usize = 1 << 60;
const I_INF: isize = 1 << 60;

fn main() {
    let mut sc = Scanner::new();
    let n = sc.next_usize();
    let m = sc.next_usize();

    let mut uf = UnionFindTree::new(n);
    for _ in 0..m {
        let a = sc.next_usize() - 1;
        let b = sc.next_usize() - 1;
        uf.unite(a, b);
    }

    let mut ans = 0;
    for i in 0..n {
        if uf.is_parent(i) {
            ans += 1;
        }
    }

    println!("{}", ans - 1);
}

/// UnionFind構造体
pub struct UnionFindTree {
    /// 頂点`i`の親を格納する配列
    parents: Vec<usize>,
    /// 頂点`i`が親であるときのその木の頂点数
    sizes: Vec<usize>,
    /// 重み付きUnionFindを使う際の重みの格納配列
    weights: Vec<isize>,
    /// 頂点`i`が属する木がループを持っているかどうか
    has_loops: Vec<bool>,
}

impl UnionFindTree {
    /// UnionFind初期化
    /// 計算量はO(n)
    pub fn new(n: usize) -> Self {
        let parents = (0..n).collect();
        let sizes = vec![1; n];
        let weights = vec![0; n];
        let has_loops = vec![false; n];
        UnionFindTree {
            parents,
            sizes,
            weights,
            has_loops,
        }
    }

    /// 親を再帰的に求め、途中の計算結果をもとに親の書き換えを行う関数
    /// 計算量はO(a(n)))
    pub fn root(&mut self, x: usize) -> usize {
        if self.parents[x] == x {
            x
        } else {
            let tmp = self.root(self.parents[x]);
            self.weights[x] += self.weights[self.parents[x]];
            self.parents[x] = tmp;
            tmp
        }
    }

    pub fn size(&self, x: usize) -> usize {
        self.sizes[x]
    }

    pub fn has_loop(&self, x: usize) -> bool {
        self.has_loops[x]
    }

    /// 2つの頂点が同じ木に属しているかの判定
    /// `self.root()`を呼び出すため、`&mut self`を引数に取る。そのため、命名に`is_`を使っていない
    /// 計算量はO(a(n))
    pub fn same(&mut self, x: usize, y: usize) -> bool {
        self.root(x) == self.root(y)
    }

    /// 重み付きUnionFindを考える際のUnite関数
    /// 計算量はO(a(n))
    pub fn unite_with_weight(&mut self, x: usize, y: usize, w: isize) {
        let root_x = self.root(x);
        let root_y = self.root(y);

        if self.same(x, y) {
            self.has_loops[root_x] = true;
            self.has_loops[root_y] = true;
        } else if self.sizes[root_x] >= self.sizes[root_y] {
            self.parents[root_y] = root_x;
            self.sizes[root_x] += self.sizes[root_y];
            self.weights[root_y] = -w - self.weights[y] + self.weights[x];
        } else {
            self.parents[root_x] = root_y;
            self.sizes[root_y] += self.sizes[root_x];
            self.weights[root_x] = w + self.weights[y] - self.weights[x];
        }
    }

    /// 重みを考慮しない際のUnite関数
    /// 重みとして0を与えているだけであり、計算量は同じくO(a(n))
    pub fn unite(&mut self, x: usize, y: usize) {
        self.unite_with_weight(x, y, 0);
    }

    /// 重み付きUnionFindにおいて、2つの頂点の距離を返す関数
    /// 2つの頂点が同じ木に属していない場合は`None`を返す
    pub fn diff(&mut self, x: usize, y: usize) -> Option<isize> {
        if self.same(x, y) {
            Some(self.weights[x] - self.weights[y])
        } else {
            None
        }
    }

    pub fn is_parent(&self, x: usize) -> bool {
        self.parents[x] == x
    }
}

pub struct Scanner {
    buf: std::collections::VecDeque<String>,
}

impl Scanner {
    pub fn new() -> Self {
        Self {
            buf: std::collections::VecDeque::new(),
        }
    }

    fn scan_line(&mut self) {
        let mut flag = 0;
        while self.buf.is_empty() {
            let mut s = String::new();
            std::io::stdin().read_line(&mut s).unwrap();
            let mut iter = s.split_whitespace().peekable();
            if iter.peek().is_none() {
                if flag >= 5 {
                    panic!("There is no input!");
                }
                flag += 1;
                continue;
            }

            for si in iter {
                self.buf.push_back(si.to_string());
            }
        }
    }

    pub fn next<T: std::str::FromStr>(&mut self) -> T {
        self.scan_line();
        self.buf
            .pop_front()
            .unwrap()
            .parse()
            .unwrap_or_else(|_| panic!("Couldn't parse!"))
    }

    pub fn next_usize(&mut self) -> usize {
        self.next()
    }

    pub fn next_isize(&mut self) -> isize {
        self.next()
    }

    pub fn next_chars(&mut self) -> Vec<char> {
        self.next::<String>().chars().collect()
    }

    pub fn next_string(&mut self) -> String {
        self.next()
    }

    pub fn next_char(&mut self) -> char {
        self.next()
    }

    pub fn next_float(&mut self) -> f64 {
        self.next()
    }
}


Measured memory_bytes: 4448