1. 程式人生 > >最小公共祖先lca

最小公共祖先lca

null 找到 else 中間 div 我們 www win bsp

3、神秘國度的愛情故事
題目要求:某個太空神秘國度中有很多美麗的小村,從太空中可以想見,小村間有路相連,更精確一點說,任意兩村之間有且僅有一條路徑。小村 A 中有位年輕人愛上了自己村裏的美麗姑娘。
每天早晨,姑娘都會去小村 B 裏的面包房工作,傍晚 6 點回到家。年輕人終於決定要向姑娘表白,他打算在小村 C 等著姑娘路過的時候把愛慕說出來。問題是,他不能確定小村 C 是否在小村 B 到小村 A 之間的路徑上。你可以幫他解決這個問題嗎? 輸入要求:輸入由若幹組測試數據組成。每組數據的第 1 行包含一正整數 N ( l 《 N 《 50000 ) ,
代表神秘國度中小村的個數,每個小村即從0到 N - l 編號。接下來有 N -1 行輸入,每行包含一條雙向道路的兩個端點小村的編號,中間用空格分開。
之後一行包含一正整數 M ( l 《 M 《 500000 ) ,代表著該組測試問題的個數。接下來 M 行,每行給出 A 、 B 、 C 三個小村的編號,中間用空格分開。當 N 為 O 時,表示全部測試結束,不要對該數據做任何處理。 輸出要求:對每一組測試給定的 A 、 B 、C,在一行裏輸出答案,即:如果 C 在 A 和 B 之間的路徑上,輸出 Yes ,否則輸出 No。

這是我們一個期末的數據結構課設題。

雖然很簡單但是我們老師要求還蠻多的,不準手動輸入要求隨機輸入而且還有滿足最大極限條件。最後一天下午才告訴這是拿優的必須條件。

然後肝了一晚上還要補完實驗報告直接通宵。

主要用的是LCA(最近公共祖先)的概念:簡單來說就是把村子的路徑變成一棵樹,然後算A村和C村的最小公共祖先是不是B村,如果不是,我們再看看B村是不是僅為A村或僅為C村的祖先,如果是則B村在A、C村的路徑上,小夥子有救了,否則我們就說小夥子他已經涼了

關於LCA更具體的可以去這個博客看:https://www.cnblogs.com/JVxie/p/4854719.html

貼部分主要公式與備註:(運行TarjanDFS

前需要先執行ALG_DFSvisited[0] = true;)

int find(int x) 
{
	while (x != f[x]) 
	{
		x = f[x];
	}
	return x;
}
void unite(int u, int v)//合並集合
{
	int x = find(u);
	int y = find(v);
	if (x != y)
		f[x] = y;
}
//找最近公共祖先
void TarjanDFS(ALGraph &G_Full,ALGraph &G_Search, int root, int anc) 
{
	ArcNode *p = G_Full.vertices[root].firstarc;
	while (p) 
	{
		if (ALG_DFSvisited[p->adjvex] == false && p->adjvex != anc) 
		{
			TarjanDFS(G_Full, G_Search,p->adjvex, root);
			unite(p->adjvex,root);//將子節點並入到父節點所在的集合中
		}
		p = p->nextarc;
	}
	ALG_DFSvisited[root] = true; //如果退出遍歷則標記為訪問
	ArcNode *q = G_Search.vertices[root].firstarc; //在搜索圖中查找該結點是否有查詢(查找有無出度)
	while (q) 
	{
		if (ALG_DFSvisited[q->adjvex] == true) //查找到且已被標記
		{
			q->anc = find(q->adjvex);//將q的祖先即當前結點的祖先保存在anc中
			ArcNode *r = G_Search.vertices[q->adjvex].firstarc;//將r的祖先即另一對應結點的祖先
			while (r) {
				if (r->adjvex == root) //如果另一結點的祖先為該結點當前的祖先
				{
					r->anc = q->anc;
					break;
				}
				r = r->nextarc;
			}
		}
		q = q->nextarc;
	}
}

主要是通過一次DFS遍歷把村莊的路徑圖G_Full的同時把已遍歷的結點歸入並查集的同時檢查該結點G_Search是否有存在查詢關系,有的話把當前的公共祖先存入對應查詢關系結果中。

輸入方面:

在路徑輸入通過隨機函數分別通過兩個數組的判斷處理能否組成一棵樹,這裏通過加多了一個數組簡單優化了一下把這部分運行時間提高了一點點,查詢輸入就是在n的範圍呢完全隨機了

queue<int>r_u;
	queue<int>r_v;
	int r=1;
	bool rand1[MVNum],rand2[MVNum];
	memset(rand1, false, sizeof(rand1));
	memset(rand2, false, sizeof(rand2));
	rand1[0] = true;
	srand((unsigned)time(NULL));
	int num1 = 0;
	rr[0] = 0;
	int num2;
	while (r_u.size() < n - 1)
	{
		num2 = rand() % n;
		while (rand1[num1] && rand2[num2])
		{
			num2 = rand() % n;
		}
		if (!rand1[num2])
		{
			rand1[num2] = true;
			rr[r] = num2;
			r++;
		}
		if (!rand2[num2])rand2[num2] = true;
		r_u.push(num1);
		r_v.push(num2);
		srand((unsigned)time(NULL));
		num1 = rr[(rand() % r)];
	}  

結果的判斷主要通過一個巧妙的方式:如果ab的最近公共祖先即b本身是ac的公共祖先或者bc的最近公共祖先即b本身是ac的公共祖先即滿足條件b在ac的路徑上

//獲得ABC任意兩者的公共祖先
void GetLca(ALGraph &G_Search, int a, int b, int c, int i)
{
	ArcNode *ab = G_Search.vertices[a].firstarc;
	ArcNode *bc = G_Search.vertices[b].firstarc;
	ArcNode *ac = G_Search.vertices[a].firstarc;
	while (ab)
	{
		if (ab->adjvex == b)
		{
			lca[i].lca_ab = ab->anc;
			break;
		}
		else ab = ab->nextarc;
	}
	while (bc)
	{
		if (bc->adjvex == c)
		{
			lca[i].lca_bc = bc->anc;
			break;
		}
		else bc = bc->nextarc;
	}
	while (ac)
	{
		if (ac->adjvex == c)
		{
			lca[i].lca_ac = ac->anc;
			break;
		}
		else ac = ac->nextarc;
	}
}
//檢查C是否在AB的路徑上
void CanMeet(ALGraph &G_Search)
{
	for (int i = 0; i < question_number; i++)
	{
		if ((lca[i].lca_ab == lca[i].lca_ac&&lca[i].lca_bc == lca[i].C) || (lca[i].lca_ab == lca[i].lca_bc&&lca[i].lca_ac == lca[i].C))
		{
			cout << "Yes!" << endl;
		}
		else
		{
			cout << "No!" << endl;
		}
	}
}

最後直接輸出

技術分享圖片

小例子測試成功,因為數據量小運行時間可以忽略不計。

在結點數量為5000時:(時間由於每次樹的結構的不同可能會有不同幅度的波動)

技術分享圖片

大例子數據太多無法截圖,最後在測試在5W個村子是一樣可以運行,但是在時間上運行會比較久。

運行時間可以通過下列代碼實現:

#include<windows.h>	

DWORD start_time = GetTickCount();
DWORD end_time = GetTickCount();

總體來說我在這方面就是犧牲了大量空間來換取時間的做法了,看大佬用了倍增的方法似乎還更快,自己還是太渣了呀。

ps:最後運行了幾十遍花了快一小時還得在excel上把曲線列出來分析寫總結才完事真的是累人呀,50W的數據等得真的是久。

最小公共祖先lca