// Slower C++ solution:
#define LOCAL

#include <algorithm>

#include <array>

#include <bitset>

#include <cassert>

#include <cmath>

#include <complex>

#include <cstdio>

#include <cstring>

#include <ctime>

#include <functional>

#include <iomanip>

#include <iostream>

#include <list>

#include <map>

#include <numeric>

#include <queue>

#include <random>

#include <set>

#include <sstream>

#include <stack>

#include <string>

#include <unordered_map>

#include <unordered_set>

#include <vector>



using namespace std;

using ll = long long;

using P = pair<int, int>;



#define REP(i, x) for (int i = 0; i < (int) (x); i++)

#define REPS(i, x) for (int i = 1; i <= (int) (x); i++)

#define RREP(i, x) for (int i = ((int) (x) -1); i >= 0; i--)

#define RREPS(i, x) for (int i = ((int) (x)); i > 0; i--)

#define ALL(x) ((x).begin(), (x).end())

#define SIZE(x) ((int) (x).size())

#define BITS(x) (1LL << (x))



constexpr int INF = 1001001001;

constexpr ll LINF = 1001001001001001001LL;

constexpr int MOD = 1000000007;

constexpr double EPS = 1e-10;

constexpr double PI = 3.141592653589793;

constexpr int dx4[4] = {1, 0, -1, 0};

constexpr int dy4[4] = {0, 1, 0, -1};

constexpr int dx8[8] = {1, 1, 1, 0, 0, -1, -1, -1};

constexpr int dy8[8] = {1, 0, -1, 1, -1, 1, 0, -1};



random_device seed_gen;

mt19937 mrand(seed_gen());

mt19937_64 mrand64(seed_gen());

int rand32(int x) {

	return mrand() % x;

}

ll rand64(long x) {

	return mrand64() % x;

}



template<typename A, typename B> ostream& operator<<(ostream& out, const pair<A, B>& a) {

	out << "(" << a.first << "," << a.second << ")";

	return out;

}

template<typename T, size_t N> ostream& operator<<(ostream& out, const array<T, N>& a) {

	out << "[";

	bool first = true;

	for (auto& v : a) {

		out << (first ? "" : ",");

		out << v;

		first = 0;

	}

	out << "]";

	return out;

}

template<typename T> ostream& operator<<(ostream& out, const vector<T>& a) {

	out << "[";

	bool first = true;

	for (auto& v : a) {

		out << (first ? "" : ",");

		out << v;

		first = 0;

	}

	out << "]";

	return out;

}

template<typename T, class Cmp> ostream& operator<<(ostream& out, const set<T, Cmp>& a) {

	out << "{";

	bool first = true;

	for (auto& v : a) {

		out << (first ? "" : ",");

		out << v;

		first = 0;

	}

	out << "}";

	return out;

}

template<typename U, typename T, class Cmp> ostream& operator<<(ostream& out, const map<U, T, Cmp>& a) {

	out << "{";

	bool first = true;

	for (auto& p : a) {

		out << (first ? "" : ",");

		out << p.first << ": " << p.second;

		first = 0;

	}

	out << "}";

	return out;

}

#ifdef LOCAL

#define trace(...) __f(#__VA_ARGS__, __VA_ARGS__)

#else

#define trace(...) 42

#endif

template<typename Arg1> void __f(const char* name, Arg1&& arg1) {

	cerr << name << ": " << arg1 << endl;

}

template<typename Arg1, typename... Args> void __f(const char* names, Arg1&& arg1, Args&&... args) {

	const char* comma = strchr(names + 1, ',');

	cerr.write(names, comma - names) << ": " << arg1 << " |";

	__f(comma + 1, args...);

}



struct fast_ios {

	static constexpr int IOS_PREC = 15;

	static constexpr bool AUTOFLUSH = false;



	fast_ios() {

		cin.tie(nullptr);

		ios::sync_with_stdio(false);

		cout << fixed << setprecision(IOS_PREC);

		if (AUTOFLUSH) cout << unitbuf;

	}

} fast_ios_;



int cnt = 0;

vector<int> edges[220000];

bool visited[220000];



void dfs(int v) {

	cnt++;

	visited[v] = true;

	for (auto x : edges[v]) {

		if (visited[x]) continue;

		dfs(x);

	}

}



int main() {

	int n, m;

	cin >> n >> m;

	for (int i = 0; i < m; i++) {

		int a, b;

		cin >> a >> b;

		a--, b--;

		edges[a].push_back(b);

		edges[b].push_back(a);

	}

	int ans = 0;

	for (int i = 0; i < n; i++) {

		if (visited[i]) continue;

		cnt = 0;

		dfs(i);

		ans = max(ans, cnt);

	}

	cout << ans << endl;

	return 0;

}

// Faster C++ solution for the same problem (measured speedup: 6.884613837454348x):
#define LOCAL

#include <algorithm>

#include <array>

#include <bitset>

#include <cassert>

#include <cmath>

#include <complex>

#include <cstdio>

#include <cstring>

#include <ctime>

#include <functional>

#include <iomanip>

#include <iostream>

#include <list>

#include <map>

#include <numeric>

#include <queue>

#include <random>

#include <set>

#include <sstream>

#include <stack>

#include <string>

#include <unordered_map>

#include <unordered_set>

#include <vector>



using namespace std;

using ll = long long;

using P = pair<int, int>;



#define REP(i, x) for (int i = 0; i < (int) (x); i++)

#define REPS(i, x) for (int i = 1; i <= (int) (x); i++)

#define RREP(i, x) for (int i = ((int) (x) -1); i >= 0; i--)

#define RREPS(i, x) for (int i = ((int) (x)); i > 0; i--)

#define ALL(x) ((x).begin(), (x).end())

#define SIZE(x) ((int) (x).size())

#define BITS(x) (1LL << (x))



constexpr int INF = 1001001001;

constexpr ll LINF = 1001001001001001001LL;

constexpr int MOD = 1000000007;

constexpr double EPS = 1e-10;

constexpr double PI = 3.141592653589793;

constexpr int dx4[4] = {1, 0, -1, 0};

constexpr int dy4[4] = {0, 1, 0, -1};

constexpr int dx8[8] = {1, 1, 1, 0, 0, -1, -1, -1};

constexpr int dy8[8] = {1, 0, -1, 1, -1, 1, 0, -1};



random_device seed_gen;

mt19937 mrand(seed_gen());

mt19937_64 mrand64(seed_gen());

int rand32(int x) {

	return mrand() % x;

}

ll rand64(long x) {

	return mrand64() % x;

}



template<typename A, typename B> ostream& operator<<(ostream& out, const pair<A, B>& a) {

	out << "(" << a.first << "," << a.second << ")";

	return out;

}

template<typename T, size_t N> ostream& operator<<(ostream& out, const array<T, N>& a) {

	out << "[";

	bool first = true;

	for (auto& v : a) {

		out << (first ? "" : ",");

		out << v;

		first = 0;

	}

	out << "]";

	return out;

}

template<typename T> ostream& operator<<(ostream& out, const vector<T>& a) {

	out << "[";

	bool first = true;

	for (auto& v : a) {

		out << (first ? "" : ",");

		out << v;

		first = 0;

	}

	out << "]";

	return out;

}

template<typename T, class Cmp> ostream& operator<<(ostream& out, const set<T, Cmp>& a) {

	out << "{";

	bool first = true;

	for (auto& v : a) {

		out << (first ? "" : ",");

		out << v;

		first = 0;

	}

	out << "}";

	return out;

}

template<typename U, typename T, class Cmp> ostream& operator<<(ostream& out, const map<U, T, Cmp>& a) {

	out << "{";

	bool first = true;

	for (auto& p : a) {

		out << (first ? "" : ",");

		out << p.first << ": " << p.second;

		first = 0;

	}

	out << "}";

	return out;

}

#ifdef LOCAL

#define trace(...) __f(#__VA_ARGS__, __VA_ARGS__)

#else

#define trace(...) 42

#endif

template<typename Arg1> void __f(const char* name, Arg1&& arg1) {

	cerr << name << ": " << arg1 << endl;

}

template<typename Arg1, typename... Args> void __f(const char* names, Arg1&& arg1, Args&&... args) {

	const char* comma = strchr(names + 1, ',');

	cerr.write(names, comma - names) << ": " << arg1 << " |";

	__f(comma + 1, args...);

}



struct fast_ios {

	static constexpr int IOS_PREC = 15;

	static constexpr bool AUTOFLUSH = false;



	fast_ios() {

		cin.tie(nullptr);

		ios::sync_with_stdio(false);

		cout << fixed << setprecision(IOS_PREC);

		if (AUTOFLUSH) cout << unitbuf;

	}

} fast_ios_;



/** UnionFind木 */

class UnionFind {

  private:

	vector<int> data;



  public:

	/**

	 * @brief 初期化

	 * @param sz データサイズ

	 */

	UnionFind(int sz) : data(sz, -1) {}

	/**

	 * @brief 併合

	 * @param node1 併合する集合1のノード

	 * @param node2 併合する集合2のノード

	 * @return bool node1とnode2が同じ集合であるか

	 */

	bool unionSet(int node1, int node2) {

		node1 = root(node1);

		node2 = root(node2);

		if (node1 != node2) {

			if (data[node2] < data[node1]) swap(node1, node2);

			data[node1] += data[node2];

			data[node2] = node1;

		}

		return node1 != node2;

	}

	/**

	 * @brief 同じ集合に含まれるかを求める

	 * @param node1 検索する集合1のノード

	 * @param node2 検索する集合2のノード

	 * @return bool node1とnode2が同じ集合に含まれるか

	 */

	bool findSet(int node1, int node2) { return root(node1) == root(node2); }

	/**

	 * @brief 集合の親ノードを求める

	 * @param node 検索する集合のノード

	 * @return 集合の親ノード

	 */

	int root(int node) { return data[node] < 0 ? node : data[node] = root(data[node]); }

	/**

	 * @brief ノードが含まれる集合のサイズを求める

	 * @param node 検索するノード

	 * @return 集合のサイズ

	 */

	int size(int node) { return -data[root(node)]; }

};



int main() {

	int n, m;

	cin >> n >> m;

	UnionFind uf(n);

	for (int i = 0; i < m; i++) {

		int a, b;

		cin >> a >> b;

		a--, b--;

		uf.unionSet(a, b);

	}

	int ans = -1;

	for (int i = 0; i < n; i++) {

		ans = max(ans, uf.size(i));

	}

	cout << ans << endl;

	return 0;

}