1. 程式人生 > >小米麵試題---朋友圈問題(並查集)

小米麵試題---朋友圈問題(並查集)

    假如已知有n個人和m對好友關係(存於數字r)。如果兩個人是直接或間接的好友(好友的好友的好友...),則認為他們屬於同一個朋友圈,請寫出程式求出這n個人裡一共有多少朋友圈。

   例如:n=5,m=3,r={{1,2},{2,3},{4,5}},表示有5個人,1和2是好友,2和3是好友,4和5是好友。則1,2,3屬於一個朋友圈,4,5屬於另一個朋友圈,結果為兩個朋友圈。

    這道題有很多種解法,首先想到的就是定義一個數組,陣列元素也是陣列。使用STL庫,定義這樣的一個結構:

	vector<vector<int>> _v;

      然後遍歷每對好友關係,如果陣列中有相同元素(好友),就在當前陣列中新增該好友,最後,遍歷最外層陣列元素個數就可以知道有多少個朋友圈了。

     作為小米麵試題,它不僅要求正確性還有效率,上面給出的解法也能做出這道題,但是效率太低,所以,在這裡,我使用一種更高效,快捷的資料結構-----並查集。

並查集:

並查集是一種樹型的資料結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。常常在使用中以森林來表示。集就是讓每個元素構成一個單元素的集合,也就是按一定順序將屬於同一組的元素所在的集合合併。在一些有N個元素的集合應用問題中,通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查詢一個元素在哪個集合中。

如圖所示:

查詢根:

int GetRoot(int root)
	{
		if(_unionset[root] >= 0)	//_unionset[]為負數時找到
		{
			root = _unionset[root];
		}

		return root;
	}

合併朋友圈(判斷x1與x2是否已經為好友關係,不是則併合併到所在朋友圈):

void Union(int x1,int x2)
	{
		int root1 = GetRoot(x1);
		int root2 = GetRoot(x2);

		if(root1 != root2)
		{
			_unionset[root1] += _unionset[root2];
			_unionset[root2] = root1;
		}
	}

計算朋友圈個數:

int Count()
{
	int count = 0;
	for(int i = 0; i<_n; ++i)
	{
		if(_unionset[i] < 0)
			count++;
	}

	return count-1;//因為0號位置不用,但初始化時初始化為了-1,需要減去多算的這一個
}

整體程式碼:

#pragma once

#include <iostream>
using namespace std;
#include <vector>
#include <cassert>
class UnionSet
{
public:
	UnionSet(int n)
		:_n(n)
	{
		_unionset = new int[n];

		//memset(_unionset,-1,sizeof(int)*n);memset按位元組處理,只能初始化為0,1,-1
		//安全起見,都用for迴圈處理
		for(int i = 0; i<n; ++i)
		{
			_unionset[i] = -1;
		}

	}

	int GetRoot(int root)
	{
		if(_unionset[root] >= 0)
		{
			root = _unionset[root];
		}

		return root;
	}

	void Union(int x1,int x2)
	{
		int root1 = GetRoot(x1);
		int root2 = GetRoot(x2);

		if(root1 != root2)
		{
			_unionset[root1] += _unionset[root2];
			_unionset[root2] = root1;
		}
	}

	bool Find(int x1,int x2)
	{
		int root1 = GetRoot(x1);
		int root2 = GetRoot(x2);

		return root1 == root2;
	}

	int Count()
	{
		int count = 0;
		for(int i = 0; i<_n; ++i)
		{
			if(_unionset[i] < 0)
				count++;
		}

		return count-1;
	}

	

protected:
	//vector<int> v;
	int* _unionset;
	int _n;
};

int Friend(int n,int m,int r[][2])
{
	assert(r);

	UnionSet u(n+1);//多開一個空間,0號位置不用
	for(int i = 0;i<m;++i)
	{
		int r1 = r[i][0];
		int r2 = r[i][1];

		u.Union(r1,r2);
	}
	return u.Count();
}

Test.cpp

void Test1()
{
	int r[4][2] = {{1,2},{2,3},{4,5},{1,3}};//n=總人數,m=多少對好友關係
	cout<<"朋友圈?"<<Friend(5,4,r)<<endl;
}
void Test2()
{
	int r[][2] = {{1,2},{2,3},{4,5},{5,9},{6,2},{7,8}};//n=總人數,m=多少對好友關係
	cout<<"朋友圈?"<<Friend(9,6,r)<<endl;
}