func readTwoInts(separator: String.Element = " ") -> (a: Int, b: Int) {
    let ints = readLine()!.split(separator: separator).map { Int($0)! }
    return (a: ints[0], b: ints[1])
}

/// 値が-なのがルート
struct UnionFind {

    // rootなら要素数*-1
    // それ以外はrootのindex
    var points: [Int]

    init(size: Int) {
        points = .init(repeating: -1, count: size)
    }

    /// a.k.a find
    /// rootのindex検索する
    /// find時にrootを繋ぎ直すことで構造を最適化
    mutating func getRoot(index: Int) -> Int {
        if points[index] < 0 { return index }

        // rootを繋ぎ直す
        let rootIndex = getRoot(index: points[index])
        points[index] = rootIndex
        return rootIndex
    }

    /// xとyのrootをつなぐ
    mutating func union(_ x: Int, and y: Int) {
        let xRoot = getRoot(index: x)
        let yRoot = getRoot(index: y)

        // すでに繋がれていたら何もしない
        if xRoot == yRoot { return }

        // 超点数が多い方につなぐ
        if points[xRoot] > points[yRoot] {
            points[yRoot] += points[xRoot]
            points[xRoot] = yRoot
        } else {
            points[xRoot] += points[yRoot]
            points[yRoot] = xRoot
        }
    }

    mutating func getSize(index: Int) -> Int {
        return -points[getRoot(index: index)]
    }

    mutating func isSameTree(_ x: Int, and y: Int) -> Bool {
        return getRoot(index: x) == getRoot(index: y)
    }
}

func mainD2() {
    let (N, M) = readTwoInts()
    let pairs = (0..<M).map { _ in readTwoInts() }

    var unionFind = UnionFind(size: N)
    for pair in pairs {
        unionFind.union(pair.a - 1, and: pair.b - 1)
    }

    print(-unionFind.points.min()!)
}
mainD2()