TopoSort 實現之不用高端靜態鏈棧算法

和fawks大神探討之後,理解了大神的思維方式,收穫不少。感覺當年DS學的靜態鏈棧實在坑爹,而且麻煩,好像和我的本質沒有太大的區別哦。時間複雜度O(n+e)其實和O(n^2)是鄰接矩陣和臨界表的區別。
我的理解就是其實好像循環N次,{每次先找任意一個入度爲0的點,然後從圖中刪除}。思路非常簡單,但是coding實現還是有很多變化,我已開始不用indegree,直接matrix寫了
一個O(n^3)的,就是不斷在matrix上折騰,用了indegree數組之後,可以降到O(n^2)。這裏需要注意的是,用什麼樣的DS可以時間複雜度儘可能小,如使用array,那麼需要找值爲0的數N次,同時還要找到這個點指向的幾個點集,
然後對應indegree分量減一,看似sort,其實不需要,因爲只需要找爲0的,不需要後面的都有序,如果最小的如果不爲0就不能TopoSort了,用sort會複雜度從內層O(n)到O(nlogn)上。好像heap可以O(logn),但是heap會打亂順序,使得從matrix上找對應點在indegree上的index變得麻煩,因爲還要高效的
隨機訪問 graph[top][j]==true, j的indegree--,所以還是數組方便,遍歷一遍,indgree--的同時,再找=0的。


代碼的算法過程中遇到一個bug,就是內層循環找graph[top][j]==true的,但是後面top又可能立馬被賦值,使得每次找的未必是一個點的臨界點,我看代碼愣是沒看出來,debug才得知,而且new的數組查看變量值又蛋疼。
所以感覺bug都出在左值上,以後查看奇怪的bug都去看左值,頂住循環的左值,一般循環都容易出問題,因爲會不斷操作。


代碼風格上,感謝fawks大神給出的建議,變量用了全局確實很簡單,不用new來new去,不用擔心忘delete。然後還要判斷bool值不用完整判斷語句。
同時在探討過程中熟悉了下二維數組傳遞的問題,new的數組可以用bool**,棧的數組用bool a[][b] 不能Bool* a[]這種,而且不能bool a[][]這也必須制定第二維長度,一直費解,後來從二維的最終實現上理解了,二維a[i][j] 本質是a[i*n+j] 這個是編譯器做的事情,本質是一維連續數組,而n正是第二維長度,試想不知道n編譯器怎麼定位數據,第一維無所謂,編譯器纔不管你越不越界勒。所以到了更高位也可以根據這個知道那幾位必須制定了,例如三維數組,看成很多[][] 第一維表示第幾個[][]  所以a[i][j][k]=i*(d2*d3)+j*d3+k,因此必須指定d2 d3的維度,d1可以忽略,總結好像只有第一維可以忽略,不論多少維的。

附上fawks大神查閱後修改過代碼風格的代碼:
#include <iostream>
using namespace std;
const int MAXNUM=10000;
bool graph[MAXNUM][MAXNUM];
int indegree[MAXNUM];
void TopoSort(int n)
{
	//initial indegree
	int top=-1;
	for(int j=0;j<n;j++)
	{
		int indegreesum=0;
		for(int i=0;i<n;i++)
		{
			if(graph[i][j])
				indegreesum++;
		}
		if(indegreesum==0)
			top=j;
		indegree[j]=indegreesum;
	}
	if(top==-1){cout<<"No Topo Sort"<<endl;return;}


	for(int i=0;i<n;i++)
	{
		cout<<top<<" ";
		indegree[top]=-1;//-1 means has been output already
		if(i==n-1) break;//output n vertexes, finish
		int lastoutput=top;
		for(int j=0;j<n;j++)
		{
			if(indegree[j]==-1)continue;//has been output
			if(graph[lastoutput][j])//indegree==0, can not be true
			{
				indegree[j]--;
			}
			if(indegree[j]==0)//find any one indegree[j]==0
				top=j;
		}
		if(top==lastoutput)
		{
			cout<<"No Topo Sort"<<endl;
			break;
		}


	}
}
int main()
{
	int n;
	//const int m=6;
	while(cin>>n)
	{
		
		
		/*
		bool** graph=new bool*[n];
		for(int i=0;i<n;i++)
			graph[i]=new bool[n];
		//*/
	//	int* indegree=new int[n];
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				cin>>graph[i][j];
			}
		}
		TopoSort(graph, indegree, n);
		
		/*
		delete[] indegree;
		for(int i=0;i<n;i++)
			delete[] graph[i];
		delete[] graph;*/
	}
	return 0;
}

最後其實有一點總結,就是一定要請思考。因爲原來DS學的後來看看很費解,還要一遍一遍手動模擬。其實自己想想,就是一個array,但是和書本的殊途同歸。然後看書本的就好多了,這也印證了張一博大神”代碼只有作者和上帝知道在幹嘛“的說法

後來看到別處可以用DFS實現TS,而且教材PPT上也是這麼說DFS重要性的,於是乎DFS搞搞TS,有一個問題,如果遞歸進去,發現找不到度爲0,也即不存在TS時,可能需要另外輸出count或者什麼的來判斷是否無TS,因爲中間是不好print NO TS的。之前也是先建立Indegree,中間遍歷的”剪枝“也是一樣判斷Indegree,同樣用-1表示已經print

void dfs(int i, int n)
{
	cout<<i<<" ";
	indegree[i]=-1;
	for(int w=0;w<n;w++)//matrix: n loop, list: traverse linklist, max n 
	{
		//ajaacnet indegree--
		if(graph[i][w]==true)//w is i's adjanct vertex
		{
			if(indegree[w]!=-1)//has been output, can not be indegree 0, as graph[i][w] is true
			{
				indegree[w]--;
			}
		}
		
		//dfs
		if(indegree[w]==0)//
		{
			dfs(w,n);//matrix add if not -1, list in for point not null
		}
	}
}

所以遍歷部分,其實都可以用DFS或者BFS實現。

和曹旻老師討論後,有了更透徹的理解。如果用鄰接表的話,是有區別的,靜態鏈棧只要訪問剛纔print的點對應的邊鏈表,因爲top記錄了當前indegree爲0鏈接成的一個靜態鏈,如果用鄰接矩陣的話,則是遍歷N個頂點,所以和用普通的數組實現是差不多的。


但其實時間複雜度在用鄰接矩陣的時候有降低,數組還是O(n^2), 因爲遍歷數組找0,靜態戀戰 O(n+e) 因爲是訪問頂點的linklist (注:原來的說法:“只是說常數因子減少了,就好比多個頂點對的最短路徑用FLoyd而不用N次Dijstra一樣。”是有誤的,我後來想清楚了)但是matrix 兩者都一樣的。

另外一個數組兩用是說如果要用  把入度爲0的點鏈接起來的方式   實現,比單獨再開一個棧要節省空間

今天看到曹博的DPPPT裏寫DP算法過程是拓撲排序,很有意思,感覺是對DP算法的理解

順便補上一個超讚,精簡的典型基於DS的算法,附上http://cyukang.com/2013/11/20/topological-sort.html#comment-1458797806
用棧來模擬出拓撲的先後關係,代碼看得讓人爽到爆!
PS:代碼要GCC編譯器,某軟的編譯器不認識數組長度未知的數組定義,即使是在類的成員函數裏!


發佈了98 篇原創文章 · 獲贊 0 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章