import sys

sys.setrecursionlimit(1 << 25)

readline = sys.stdin.buffer.readline

read = sys.stdin.readline  # 文字列読み込む時はこっち

ra = range

enu = enumerate





def exit(*argv, **kwarg):

    print(*argv, **kwarg)

    sys.exit()





def mina(*argv, sub=1): return list(map(lambda x: x - sub, argv))

# 受け渡されたすべての要素からsubだけ引く.リストを*をつけて展開しておくこと





def a_int(): return int(readline())





def ints(): return list(map(int, readline().split()))





def read_col(H):

    '''H is number of rows

    A列、B列が与えられるようなとき

    ex1)A,B=read_col(H)    ex2) A,=read_col(H) #一列の場合'''

    ret = []

    for _ in range(H):

        ret.append(list(map(int, readline().split())))

    return tuple(map(list, zip(*ret)))





def read_tuple(H):

    '''H is number of rows'''

    ret = []

    for _ in range(H):

        ret.append(tuple(map(int, readline().split())))

    return ret





def read_matrix(H):

    '''H is number of rows'''

    ret = []

    for _ in range(H):

        ret.append(list(map(int, readline().split())))

    return ret

    # return [list(map(int, read().split())) for _ in range(H)] # 内包表記はpypyでは遅いため





class UnionFind:

    def __init__(self, N):

        self.N = N  # ノード数

        self.n_groups = N  # グループ数

        # 親ノードをしめす。負は自身が親ということ。

        self.parent = [-1] * N  # idxが各ノードに対応。



    def root(self, A):

        # print(A)

        # ノード番号を受け取って一番上の親ノードの番号を帰す

        if (self.parent[A] < 0):

            return A

        self.parent[A] = self.root(self.parent[A])  # 経由したノードすべての親を上書き

        return self.parent[A]



    def size(self, A):

        # ノード番号を受け取って、そのノードが含まれている集合のサイズを返す。

        return -self.parent[self.root(A)]



    def unite(self, A, B):

        # ノード番号を2つ受け取って、そのノード同士をつなげる処理を行う。

        # 引数のノードを直接つなぐ代わりに、親同士を連結する処理にする。

        A = self.root(A)

        B = self.root(B)



        # すでにくっついている場合

        if (A == B):

            return False



        # 大きい方に小さい方をくっつけたほうが処理が軽いので大小比較

        if (self.size(A) < self.size(B)):

            A, B = B, A



        # くっつける

        self.parent[A] += self.parent[B]  # sizeの更新

        self.parent[B] = A  # self.rootが呼び出されればBにくっついてるノードもすべて親がAだと上書きされる

        self.n_groups -= 1



        return True



    def is_in_same(self, A, B):

        return self.root(A) == self.root(B)





MOD = 10**9 + 7

INF = 2**31  # 2147483648 > 10**9

# default import

from collections import defaultdict, Counter, deque

from operator import itemgetter, xor, add

from itertools import product, permutations, combinations

from bisect import bisect_left, bisect_right  # , insort_left, insort_right

from functools import reduce

from math import gcd





def lcm(a, b):

    # 最小公倍数

    g = gcd(a, b)

    return a // g * b





N, M = ints()

uf = UnionFind(N)

for _ in range(M):

    a, b = mina(*ints())

    uf.unite(a, b)



# あとはなるべく別々になるように

# →サイズを1ずつ引いて1グループ

# いやmaxのsizeか



print(-min(uf.parent))

mycode = r'''

# distutils: language=c++

# cython: language_level=3, boundscheck=False, wraparound=False

# cython: cdivision=True

# False:Cython はCの型に対する除算・剰余演算子に関する仕様を、(被演算子間の符号が異なる場合の振る舞いが異なる)Pythonのintの仕様に合わせ、除算する数が0の場合にZeroDivisionErrorを送出します。この処理を行わせると、速度に 35% ぐらいのペナルティが生じます。 True:チェックを行いません。

ctypedef long long LL



import numpy as np

from functools import partial

array=partial(np.array, dtype=np.int64)

zeros=partial(np.zeros, dtype=np.int64)

full=partial(np.full, dtype=np.int64)



import sys

readline = sys.stdin.buffer.readline

read = sys.stdin.readline #文字列読み込む時はこっち



cdef ints(): return np.fromstring(readline(), sep=' ', dtype=np.int64)



cdef read_matrix(LL H,LL W):

    #return np.ndarray shape=(H,W) matrix

    lines=[]

    cdef LL _

    for _ in range(H): 

        lines.append(read())

    lines=' '.join(lines) #byte同士の結合ができないのでreadlineでなくreadで

    return np.fromstring(lines, sep=' ',dtype=np.int64).reshape(H,W)



cdef class UnionFind:

    cdef:

        public LL N,n_groups

        public LL[::1] parent



    def __init__(self, LL N):

        self.N = N  # ノード数

        self.n_groups = N  # グループ数

        # 親ノードをしめす。負は自身が親ということ。

        self.parent = full(N,-1)  # idxが各ノードに対応。



    cdef LL root(self, LL A):

        # print(A)

        # ノード番号を受け取って一番上の親ノードの番号を帰す

        if (self.parent[A] < 0):

            return A

        self.parent[A] = self.root(self.parent[A])  # 経由したノードすべての親を上書き

        return self.parent[A]



    cdef LL size(self, LL A):

        # ノード番号を受け取って、そのノードが含まれている集合のサイズを返す。

        return -self.parent[self.root(A)]



    cdef bint unite(self,LL A,LL B):

        # ノード番号を2つ受け取って、そのノード同士をつなげる処理を行う。

        # 引数のノードを直接つなぐ代わりに、親同士を連結する処理にする。

        A = self.root(A)

        B = self.root(B)



        # すでにくっついている場合

        if (A == B):

            return False



        # 大きい方に小さい方をくっつけたほうが処理が軽いので大小比較

        if (self.size(A) < self.size(B)):

            A, B = B, A



        # くっつける

        self.parent[A] += self.parent[B]  # sizeの更新

        self.parent[B] = A  # self.rootが呼び出されればBにくっついてるノードもすべて親がAだと上書きされる

        self.n_groups -= 1



        return True



    cdef bint is_in_same(self,LL A,LL B):

        return self.root(A) == self.root(B)





cdef LL N,M



N, M = ints()

AB=read_matrix(M,2)-1

cdef UnionFind uf = UnionFind(N)

cdef LL a,b

for a,b in AB:

    uf.unite(a, b)



print(-np.min(uf.parent))

'''



import sys

import os

if sys.argv[-1] == 'ONLINE_JUDGE':  # コンパイル時

    with open('mycode.pyx', 'w') as f:

        f.write(mycode)

    os.system('cythonize -i -3 -b mycode.pyx')



import mycode

import sys

sys.setrecursionlimit(1 << 25)
readline = sys.stdin.buffer.readline
read = sys.stdin.readline  # 文字列読み込む時はこっち
ra = range
enu = enumerate


def exit(*argv, **kwarg):
    print(*argv, **kwarg)
    sys.exit()


def mina(*argv, sub=1):
    return list(map(lambda x: x - sub, argv))


# 受け渡されたすべての要素からsubだけ引く.リストを*をつけて展開しておくこと
def a_int():
    return int(readline())


def ints():
    return list(map(int, readline().split()))


def read_col(H):
    """H is number of rows
    A列、B列が与えられるようなとき
    ex1)A,B=read_col(H)    ex2) A,=read_col(H) #一列の場合"""
    ret = []
    for _ in range(H):
        ret.append(list(map(int, readline().split())))
    return tuple(map(list, zip(*ret)))


def read_tuple(H):
    """H is number of rows"""
    ret = []
    for _ in range(H):
        ret.append(tuple(map(int, readline().split())))
    return ret


def read_matrix(H):
    """H is number of rows"""
    ret = []
    for _ in range(H):
        ret.append(list(map(int, readline().split())))
    return ret
    # return [list(map(int, read().split())) for _ in range(H)] # 内包表記はpypyでは遅いため


class UnionFind:
    def __init__(self, N):
        self.N = N  # ノード数
        self.n_groups = N  # グループ数
        # 親ノードをしめす。負は自身が親ということ。
        self.parent = [-1] * N  # idxが各ノードに対応。

    def root(self, A):
        # print(A)
        # ノード番号を受け取って一番上の親ノードの番号を帰す
        if self.parent[A] < 0:
            return A
        self.parent[A] = self.root(self.parent[A])  # 経由したノードすべての親を上書き
        return self.parent[A]

    def size(self, A):
        # ノード番号を受け取って、そのノードが含まれている集合のサイズを返す。
        return -self.parent[self.root(A)]

    def unite(self, A, B):
        # ノード番号を2つ受け取って、そのノード同士をつなげる処理を行う。
        # 引数のノードを直接つなぐ代わりに、親同士を連結する処理にする。
        A = self.root(A)
        B = self.root(B)
        # すでにくっついている場合
        if A == B:
            return False
        # 大きい方に小さい方をくっつけたほうが処理が軽いので大小比較
        if self.size(A) < self.size(B):
            A, B = B, A
        # くっつける
        self.parent[A] += self.parent[B]  # sizeの更新
        self.parent[B] = A  # self.rootが呼び出されればBにくっついてるノードもすべて親がAだと上書きされる
        self.n_groups -= 1
        return True

    def is_in_same(self, A, B):
        return self.root(A) == self.root(B)


MOD = 10**9 + 7
INF = 2**31  # 2147483648 > 10**9
# default import
from collections import defaultdict, Counter, deque
from operator import itemgetter, xor, add
from itertools import product, permutations, combinations
from bisect import bisect_left, bisect_right  # , insort_left, insort_right
from functools import reduce
from math import gcd


def lcm(a, b):
    # 最小公倍数
    g = gcd(a, b)
    return a // g * b


N, M = ints()
uf = UnionFind(N)
for _ in range(M):
    a, b = mina(*ints())
    uf.unite(a, b)
# あとはなるべく別々になるように
# →サイズを1ずつ引いて1グループ
# いやmaxのsizeか
print(-min(uf.parent))

mycode = r"""
# distutils: language=c++
# cython: language_level=3, boundscheck=False, wraparound=False
# cython: cdivision=True
# False:Cython はCの型に対する除算・剰余演算子に関する仕様を、(被演算子間の符号が異なる場合の振る舞いが異なる)Pythonのintの仕様に合わせ、除算する数が0の場合にZeroDivisionErrorを送出します。この処理を行わせると、速度に 35% ぐらいのペナルティが生じます。 True:チェックを行いません。
ctypedef long long LL
import numpy as np
from functools import partial
array=partial(np.array, dtype=np.int64)
zeros=partial(np.zeros, dtype=np.int64)
full=partial(np.full, dtype=np.int64)
import sys
readline = sys.stdin.buffer.readline
read = sys.stdin.readline #文字列読み込む時はこっち
cdef ints(): return np.fromstring(readline(), sep=' ', dtype=np.int64)
cdef read_matrix(LL H,LL W):
    #return np.ndarray shape=(H,W) matrix
    lines=[]
    cdef LL _
    for _ in range(H): 
        lines.append(read())
    lines=' '.join(lines) #byte同士の結合ができないのでreadlineでなくreadで
    return np.fromstring(lines, sep=' ',dtype=np.int64).reshape(H,W)
cdef class UnionFind:
    cdef:
        public LL N,n_groups
        public LL[::1] parent
    def __init__(self, LL N):
        self.N = N  # ノード数
        self.n_groups = N  # グループ数
        # 親ノードをしめす。負は自身が親ということ。
        self.parent = full(N,-1)  # idxが各ノードに対応。
    cdef LL root(self, LL A):
        # print(A)
        # ノード番号を受け取って一番上の親ノードの番号を帰す
        if (self.parent[A] < 0):
            return A
        self.parent[A] = self.root(self.parent[A])  # 経由したノードすべての親を上書き
        return self.parent[A]
    cdef LL size(self, LL A):
        # ノード番号を受け取って、そのノードが含まれている集合のサイズを返す。
        return -self.parent[self.root(A)]
    cdef bint unite(self,LL A,LL B):
        # ノード番号を2つ受け取って、そのノード同士をつなげる処理を行う。
        # 引数のノードを直接つなぐ代わりに、親同士を連結する処理にする。
        A = self.root(A)
        B = self.root(B)
        # すでにくっついている場合
        if (A == B):
            return False
        # 大きい方に小さい方をくっつけたほうが処理が軽いので大小比較
        if (self.size(A) < self.size(B)):
            A, B = B, A
        # くっつける
        self.parent[A] += self.parent[B]  # sizeの更新
        self.parent[B] = A  # self.rootが呼び出されればBにくっついてるノードもすべて親がAだと上書きされる
        self.n_groups -= 1
        return True
    cdef bint is_in_same(self,LL A,LL B):
        return self.root(A) == self.root(B)
cdef LL N,M
N, M = ints()
AB=read_matrix(M,2)-1
cdef UnionFind uf = UnionFind(N)
cdef LL a,b
for a,b in AB:
    uf.unite(a, b)
print(-np.min(uf.parent))
"""
import sys
import os

if sys.argv[-1] == "ONLINE_JUDGE":  # コンパイル時
    with open("mycode.pyx", "w") as f:
        f.write(mycode)
    os.system("cythonize -i -3 -b mycode.pyx")
import mycode

+mycode = r""" +# distutils: language=c++ +# cython: language_level=3, boundscheck=False, wraparound=False +# cython: cdivision=True +# False:Cython はCの型に対する除算・剰余演算子に関する仕様を、(被演算子間の符号が異なる場合の振る舞いが異なる)Pythonのintの仕様に合わせ、除算する数が0の場合にZeroDivisionErrorを送出します。この処理を行わせると、速度に 35% ぐらいのペナルティが生じます。 True:チェックを行いません。 +ctypedef long long LL +import numpy as np +from functools import partial +array=partial(np.array, dtype=np.int64) +zeros=partial(np.zeros, dtype=np.int64) +full=partial(np.full, dtype=np.int64) - -sys.setrecursionlimit(1 << 25) -read = sys.stdin.readline  # 文字列読み込む時はこっち -ra = range -enu = enumerate - - -def exit(*argv, **kwarg): -    print(*argv, **kwarg) -    sys.exit() - - -def mina(*argv, sub=1): -    return list(map(lambda x: x - sub, argv)) - - -# 受け渡されたすべての要素からsubだけ引く.リストを*をつけて展開しておくこと -def a_int(): -    return int(readline()) - - -def ints(): -    return list(map(int, readline().split())) - - -def read_col(H): -    """H is number of rows -    A列、B列が与えられるようなとき -    ex1)A,B=read_col(H)    ex2) A,=read_col(H) #一列の場合""" -    ret = [] -    for _ in range(H): -        ret.append(list(map(int, readline().split()))) -    return tuple(map(list, zip(*ret))) - - -def read_tuple(H): -    """H is number of rows""" -    ret = [] -    for _ in range(H): -        ret.append(tuple(map(int, readline().split()))) -    return ret - - -def read_matrix(H): -    """H is number of rows""" -    ret = [] -    for _ in range(H): -        ret.append(list(map(int, readline().split()))) -    return ret -    # return [list(map(int, read().split())) for _ in range(H)] # 内包表記はpypyでは遅いため - - -class UnionFind: -    def __init__(self, N): +read = sys.stdin.readline #文字列読み込む時はこっち +cdef ints(): return np.fromstring(readline(), sep=' ', dtype=np.int64) +cdef read_matrix(LL H,LL W): +    #return np.ndarray shape=(H,W) matrix +    lines=[] +    cdef LL _ +    for _ in range(H): +        lines.append(read()) +    lines=' '.join(lines) #byte同士の結合ができないのでreadlineでなくreadで +    return np.fromstring(lines, sep=' ',dtype=np.int64).reshape(H,W) +cdef class UnionFind: +    cdef: +        public LL N,n_groups +        public LL[::1] parent +    def __init__(self, LL N): -        self.parent = [-1] * N  # idxが各ノードに対応。 - -    def root(self, A): +        self.parent = full(N,-1)  # idxが各ノードに対応。 +    cdef LL root(self, LL A): -        if self.parent[A] < 0: +        if (self.parent[A] < 0): - -    def size(self, A): +    cdef LL size(self, LL A): - -    def unite(self, A, B): +    cdef bint unite(self,LL A,LL B): -        if A == B: +        if (A == B): -        if self.size(A) < self.size(B): +        if (self.size(A) < self.size(B)): +    cdef bint is_in_same(self,LL A,LL B): +        return self.root(A) == self.root(B) +cdef LL N,M +N, M = ints() +AB=read_matrix(M,2)-1 +cdef UnionFind uf = UnionFind(N) +cdef LL a,b +for a,b in AB: +    uf.unite(a, b) +print(-np.min(uf.parent)) +""" +import sys +import os -    def is_in_same(self, A, B): -        return self.root(A) == self.root(B) - - -MOD = 10**9 + 7 -INF = 2**31  # 2147483648 > 10**9 -# default import -from collections import defaultdict, Counter, deque -from operator import itemgetter, xor, add -from itertools import product, permutations, combinations -from bisect import bisect_left, bisect_right  # , insort_left, insort_right -from functools import reduce -from math import gcd - - -def lcm(a, b): -    # 最小公倍数 -    g = gcd(a, b) -    return a // g * b - - -N, M = ints() -uf = UnionFind(N) -for _ in range(M): -    a, b = mina(*ints()) -    uf.unite(a, b) -# あとはなるべく別々になるように -# →サイズを1ずつ引いて1グループ -# いやmaxのsizeか -print(-min(uf.parent)) +if sys.argv[-1] == "ONLINE_JUDGE":  # コンパイル時 +    with open("mycode.pyx", "w") as f: +        f.write(mycode) +    os.system("cythonize -i -3 -b mycode.pyx") +import mycode