1. 程式人生 > >六、C++實現圖(鄰接矩陣)資料結構

六、C++實現圖(鄰接矩陣)資料結構

本文旨採用C++實現圖資料結構,具體地以鄰接矩陣的方式實現圖資料結構。圖結構可以描述一組物件之間的二元關係,例如在城市交通中,聯接於各個公交車站之間的道侶,或者在網際網路中個網路節點之間的路由都可以很方便地用圖結構來表述,另外在之前介紹的樹結構也屬於圖的一種,此類一般性的二元關係,屬於圖論

在將一些物件之間的關係用圖結構表示後,那麼我們就可以使用圖演算法方便地解決這些物件的某些問題,比如拓撲排序,最小支撐樹,最短路徑問題等。事實上,對圖結構的處理策略,也可以通過遍歷將圖轉換為半線性結構,進而藉助樹結構已有的處理方法和機巧解決問題

圖有些基本術語,如有向圖,無向圖,混合圖,鄰接,關聯,簡單圖,度,環路,帶權網路等,可以參考課本。

在這裡,圖資料結構的實現通過graph.h類和graphMatrix.h兩個類實現,其中graph.h類主要是描述圖結構的基本屬性,比如頂點總數,邊總數,並提供了一些對頂點和邊的操作介面和一些圖演算法操作介面,這些介面以純虛擬函式的形式宣告,因為圖結構的具體描述有很多種,如鄰接矩陣和鄰接表等,這些虛擬函式需要在圖結構的具體實現時進行覆蓋。本文采用鄰接矩陣這一最基本的實現方式,鄰接矩陣在graphMatrix.h中進行實現,其中對graph.h中的虛擬函式進行了覆蓋。

graph介面列表
操作 功能 物件
reset() 復位所有頂點,邊的輔助資訊
BFS(int v, int& clock) (單個連通域)廣度優先演算法
DFS(int v, int& clock) (單個連通域)深度優先演算法
BCC(int, int&, stack<int>&) (單個連通域)基於DFS的雙連通分量分解演算法
TSort(int v, int& clock, stack<Tv>* S) (單個連通域)基於DFS的拓撲排序演算法
PFS(int s, PU prioUpdater) (單個連通域)優先順序搜尋框架
insert(const Tv&) 插入頂點,返回編號
remove(int) 刪除頂點及其關聯邊,返回該頂點資訊
vdata(int) 返回頂點v的資料
inDegree(int) 返回頂點v的入度
outDegree(int) 返回頂點v的出度
firstNbr(int) 返回頂點v的首個鄰接頂點
nextNbr(int, int) 返回頂點v(相對於頂點j的)下一鄰接頂點
status(int) 返回頂點v的狀態
dTime(int) 返回頂點v的時間標籤dTime
fTime(int) 返回頂點v的時間標籤fTime
parent(int) 返回頂點v在遍歷樹中的父親
priority(int) 頂點v在遍歷樹中的優先順序數
exists(int, int) 邊(v,u)是否存在
insert(Te const&, int, int, int) 在頂點v和u之間插入權重為w的邊e
remove(int, int) 刪除頂點v和u之間的邊,並返回該邊的資訊
type(int, int) 邊(v,u)的資料
weight(int, int) 返回邊(u,v)的權重
bfs(int s) 廣度優先搜尋演算法
dfs(int s) 深度優先搜尋演算法
bcc(int) 基於DFS的雙連通分量分解演算法
tSort(int s) 基於DFS的拓撲排序演算法
prim(int s) 最小支撐樹Prim演算法
dijkstra(int s) 最短路徑Dijkstra演算法
pfs(int s, PU prioUpdater) 優先順序搜尋框架
graphMatrix介面列表
操作 功能 物件
graphMatrix() 預設建構函式
~graphMatrix 解構函式,釋放所有邊所佔的記憶體空間
vdata(int i) 返回資料
inDegree(int i) 返回入度
outDegree(int i) 返回出度
firstNbr(int i) 返回首個鄰接頂點
nextNbr(int i, int j) 返回相對於頂點j的下一鄰接頂點
status(int i) 返回狀態
dTime(int i) 返回時間標籤dTime
fTime(int i) 返回時間標籤fTime
parent(int i) 返回在遍歷樹中的父親
priority(int i) 返回在遍歷樹中的優先順序數
insert(const Tv& v) 插入頂點,返回標號
remove(int i) 刪除第i個頂點及其關連邊
exists(int i, int j) 判斷從頂點i到j的邊是否存在
type(int i, int j) 返回邊(i,j)的型別
edata(int i, int j) 返回邊(i,j)的資料
weight(int i, int j) 返回邊(i,j)的權重
insert(const Te& ed, int w, int i, int j) 插入權重為w的邊e=(i,j)
remove(int i, int j) 刪除頂點i到頂點j的邊e=(i,j)

(1) graph.h

#pragma once
#include"stack.h"
#include"queue.h"

typedef enum { UNDISCOVERED, DISCOVERED, VISITED } VStatus;         //頂點狀態(未發現,發現,已遍歷)
typedef enum { UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD }EType; //邊在遍歷樹中的型別(未確定,樹,跨邊,向前,向後)


//graph模板類
template<typename Tv, typename Te> class graph
{
public:
	void reset();  //復位所有頂點,邊的輔助資訊
	void BFS(int v, int& clock);  //(單個連通域)廣度優先演算法
	void DFS(int v, int& clock);  //(單個連通域)深度優先演算法
	void BCC(int, int&, stack<int>&);//(單個連通域)基於DFS的雙連通分量分解演算法
	bool TSort(int v, int& clock, stack<Tv>* S);//(單個連通域)基於DFS的拓撲排序演算法
	template<typename PU> void PFS(int s, PU prioUpdater);//(單個連通域)優先順序搜尋框架

public:
	//頂點相關
	int n;  //頂點總數
	virtual int insert(const Tv&) = 0;//插入頂點,返回編號
	virtual Tv remove(int) = 0;       //刪除頂點及其關聯邊,返回該頂點資訊
	virtual Tv& vdata(int) = 0;      //返回頂點v的資料
	virtual int inDegree(int) = 0;    //返回頂點v的入度
	virtual int outDegree(int) = 0;   //返回頂點v的出度
	virtual int firstNbr(int) = 0;    //返回頂點v的首個鄰接頂點
	virtual int nextNbr(int, int) = 0;    // 返回頂點v(相對於頂點j的)下一鄰接頂點
	virtual VStatus& status(int) = 0; //返回頂點v的狀態
	virtual int& dTime(int) = 0;      //返回頂點v的時間標籤dTime
	virtual int& fTime(int) = 0;      //返回頂點v的時間標籤fTime
	virtual int& parent(int) = 0;     //返回頂點v在遍歷樹中的父親
	virtual int& priority(int) = 0;   //頂點v在遍歷樹中的優先順序數

	//邊相關(無向邊也轉換成有向邊)
	int e;  //邊總數
	virtual bool exists(int, int) = 0;  //邊(v,u)是否存在
	virtual void insert(Te const&, int, int, int) = 0;   //在頂點v和u之間插入權重為w的邊e
	virtual Te remove(int, int) = 0;   //刪除頂點v和u之間的邊,並返回該邊的資訊
	virtual EType& type(int, int) = 0;  //邊(v,u)的資料
	virtual int& weight(int, int) = 0;  //返回邊(u,v)的權重
	
	//演算法
	void bfs(int s);   //(可處理多個獨立連通域)廣度優先搜尋演算法
	void dfs(int s);   //深度優先搜尋演算法
	void bcc(int);   //基於DFS的雙連通分量分解演算法
	stack<Tv>* tSort(int s);   //基於DFS的拓撲排序演算法
	void prim(int s);  //最小支撐樹Prim演算法
	void dijkstra(int s);   //最短路徑Dijkstra演算法
	template<typename PU> void pfs(int s, PU prioUpdater);   //優先順序搜尋框架
};

template<typename V, typename E> struct PrimPU
{
	void operator()(graph<V, E>* g, int uk, int v)
	{
		if (g->status(v) == UNDISCOVERED)   //若頂點v尚未被發現
			if ((g->weight(uk, v)) < (g->priority(v)))  //比較當前邊的權重和之前遍歷時設定的優先順序數
			{
				g->priority(v) = g->weight(uk, v);   //根據權重設定優先順序數
				g->parent(v) = uk;   //更新父節點
				cout << "尋找頂點(uk,v)" << "(" << uk << "," << v << ")" <<"----w---"<<g->weight(uk,v)<< endl;
			}
	}
};

template<typename V, typename E> struct DijkstraPU
{
	void operator()(graph<V, E>* g, int uk, int v)
	{
		if (g->status(v) == UNDISCOVERED)   //如果發現頂點v尚未被發現
		{
			if ((g->weight(uk, v) + g->priority(uk)) < (g->priority(v)))
			{
				g->priority(v) = g->weight(uk, v) + g->priority(uk);   //更新優先順序數
				g->parent(v) = uk;
			}
		}
	}
};

template<typename Tv, typename Te> void graph<Tv, Te>::reset()
{
	for (int i = 0; i < n; i++)
	{
		status(i) = UNDISCOVERED;   //狀態
		dTime(i) = fTime(i) = -1;   //時間標籤
		parent(i) = -1;
		priority(i) = INT_MAX;
		for (int j = 0; j < n; j++)
		{
			if (exists(i, j))
				type(i, j) = UNDETERMINED;  //復位存在的邊的型別

		}
	}
}

template<typename Tv, typename Te> void graph<Tv, Te>::BFS(int v, int& clock)   //遍歷單個
{
	queue<int> Q;  //頂點快取佇列
	status(v) = DISCOVERED; //標記頂點為已發現
	Q.enqueue(v);  //將當前頂點入隊
	
	while (!Q.empty())   //只要佇列非空,則繼續
	{
		v = Q.dequeue();  //每次選擇一個頂點出隊,遍歷其所有鄰居,檢查是否存在關聯邊
		dTime(v) = ++clock;
		for (int u = firstNbr(v); u >= 0; u = nextNbr(v, u))   //對於頂點v,從頂點集V的最後一個元素開始尋找鄰居(與v存在關聯邊)
		{

			if (status(u) == UNDISCOVERED)  //如果此鄰居頂點尚未發現
			{			cout << "(v,u)" << "(" << v<<"," << u << ")" << endl;
				status(u) = DISCOVERED;  //設定該頂點為已被發現
				type(v, u) = TREE;   //設定邊e(v,u)為TREE(遍歷樹)
				parent(u) = v;       //設定在遍歷樹中頂點u的父親為v
				Q.enqueue(u);        //頂點u入隊
			}
			else  //如果此鄰居頂點已經被發現
			{
				type(v, u) = CROSS;   //設定邊e(v,u)為CROSS(跨邊),不是遍歷樹枝幹
			}
		}
		status(v) = VISITED;   //設定頂點v為已遍歷
	}
}

template<typename Tv, typename Te> void graph<Tv, Te>::bfs(int s)
{
	reset();   //復位所有頂點和已存在邊的狀態為未被發現,未確定
	int clock = 0;  //時間標籤
	int v = s;
	do
	{
		if(status(v)==UNDISCOVERED)
			BFS(v,clock);   //對每個頂點都進行一次單連通域廣度優先搜尋
		v++;
		cout << "v----" << v << endl;
	} while ((v = (++v%n)) != s);
}

template<typename Tv, typename Te> void graph<Tv, Te>::DFS(int v, int& clock)
{
	status(v) = DISCOVERED;    //標記當前節點為發現
	dTime(v) = ++clock;

	for (int u = firstNbr(v); u > -1; u = nextNbr(v, u))  //遍歷所有鄰居頂點
	{
		switch (status(u))
		{
		case UNDISCOVERED:   //尚未發現的頂點,繼續深入遍歷
			status(u) = DISCOVERED;  //標記為已發現
			type(v, u) = TREE;
			parent(u) = v;
			DFS(u, clock);
			break;
		case DISCOVERED:     //已被發現但是尚未遍歷完成的頂點,那就是祖先啊
			type(v, u) = BACKWARD;
			break;
		default:   //VISITED  已經遍歷完成,根據dTime判斷是FORWARD還是CROSS
			type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;
			break;
		}
	}
	status(v) = VISITED;
	fTime(v) = ++clock;
}

template<typename Tv, typename Te> void graph<Tv, Te>::dfs(int s)
{
	reset();   //復位所有頂點和已存在邊的狀態為未被發現,未確定
	int clock = 0;  //時間標籤
	int v = s;
	do
	{
		if (status(v) == UNDISCOVERED)
			DFS(v, clock);   //對每個頂點都進行一次單連通域深度優先搜尋
		cout << "v----" << v << endl;
	} while ((v=(++v%n)) != s);
}


template<typename Tv, typename Te> bool graph<Tv, Te>::TSort(int v, int& clock, stack<Tv>* S)   //(基於DFS)單個連通域的拓撲排序,從連通域的任一頂點開始即可,因為會有外層tSort函式排查尚未發現的頂點
{
	status(v) = DISCOVERED;       
	dTime(v) = ++clock;
	for (int u = firstNbr(v); u > -1; u = nextNbr(v, u))
	{
		switch (status(u))
		{
		case UNDISCOVERED:   //若此頂點尚未被發現
			status(u) = DISCOVERED;  //標記為已被發現
			type(v, u) = TREE;     //標記邊e(v,u)為遍歷樹的枝幹
			parent(u) = v;
			if (!TSort(u, clock, S))  //繼續深入遍歷
				return false;
			break;
		case DISCOVERED:    //若此頂點已被發現但尚未完成遍歷,則為迴環
			type(v, u) = BACKWARD;
			return false;  //發現此連通域為有環圖,不能生成拓撲序列
		default:   //VISITED  發現已經遍歷完畢的頂點
			type(v, u) = (dTime(v) < dTime(u)) ? FORWARD : CROSS;  //根據頂點最開始遍歷的時間標籤dTime判斷是前向邊還是兩個分支的跨邊
			break;
		}
	}  
	//此頂點已經完成遍歷
	status(v) = VISITED;
	S->push(vdata(v));
	return true;
}

template<typename Tv, typename Te> stack<Tv>* graph<Tv, Te>::tSort(int s)
{
	reset();
	stack<Tv>* S=new stack<Tv>;
	int v = s;
	int clock = 0;
	do
	{
		if (status(v) == UNDISCOVERED)
		{
			if (!TSort(v, clock, S))   //如果發現是有環圖不能生成拓撲序列,則返回
			{
				while (!(S->empty()))
				{
					S->pop(); 
				}
				break;
			}
		}
	} while ((v=(++v)%n)!=s);
	return S;
}

template<typename Tv, typename Te> template<typename PU> void graph<Tv, Te>::PFS(int s, PU prioUpdater)   //(單個連通域)優先順序搜尋
{
	priority(s) = 0;      //設定s的優先順序最高
	status(s) = VISITED;  //標記頂點s為已經便利的
	cout << "選中的頂點:" << vdata(s) << endl;
	parent(s) = -1;       //這句可以不要,因為reset()已經置-1
	while (true)
	{
		for (int w = firstNbr(s); w > -1; w = nextNbr(s, w))   //遍歷頂點s的所有鄰居
		{
			prioUpdater(this, s, w);  //更新頂點w的優先順序和父頂點
		}
		for (int shortest = INT_MAX, w = 0; w < n; w++)
		{
			if (status(w) == UNDISCOVERED)   //如果頂點w尚未被遍歷
				if (priority(w) < shortest)
				{
					shortest = priority(w);
					s = w;       //更新級數最小的點
				}
		}
		if (status(s) == VISITED) break;   //如果所有頂點均已經訪問,則結束
		status(s) = VISITED; 
		cout << "選中的頂點:"<<vdata(s) << endl;
		cout << parent(s) << endl;
		type(parent(s), s) = TREE;
	}
}

template<typename Tv, typename Te> template<typename PU> void graph<Tv, Te>::pfs(int s, PU prioUpdater)
{
	reset();
	int v = s;
	do
	{
		if (status(v) == UNDISCOVERED)
			PFS(v, prioUpdater);
	} while ((v = (++v%n)) != s);
}



template<typename Tv, typename Te> void graph<Tv, Te>::prim(int s)
{
	PrimPU<Tv, Te> prioUpdater;
	pfs(s, prioUpdater);
}

template<typename Tv, typename Te> void graph<Tv,Te>::dijkstra(int s)
{
	DijkstraPU<Tv, Te> prioUpdater;
	pfs(s, prioUpdater);
}

(2) graphMatrix.h

#pragma once
#include"graph.h"
#include"vector.h"
template<typename Tv> struct vertex  //頂點類
{
	Tv data;
	int inDegree, outDegree;  //入度,出度
	VStatus status;           //狀態
	int dTime, fTime;         //時間標籤
	int parent;               //在遍歷樹中的父節點
	int priority;             //在遍歷樹中的優先順序數

	//建構函式
	vertex(const Tv& d = Tv(0)) :data(d), inDegree(0), outDegree(0), status(UNDISCOVERED), dTime(-1), fTime(-1), parent(-1), priority(INT_MAX) {}
};

template<typename Te> struct edge
{
	Te data;
	int weight;   //權重
	EType type;   //型別

	//建構函式
	edge(const Te& d, int w) :data(d), weight(w), type(UNDETERMINED) {}
};

template<typename Tv, typename Te> class graphMatrix :public graph<Tv,Te>
{
public:
	vector<vertex<Tv>> V;  //頂點集
	vector<vector<edge<Te>*>> E;//邊集

public:
	//建構函式
	graphMatrix() { n = e = 0; }
	//解構函式
	~graphMatrix()
	{
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				delete E[i][j]; //清除所有邊
	}
	//頂點的基本操作
	virtual Tv& vdata(int i) { return V[i].data; }    //返回資料
	virtual int inDegree(int i) { return V[i].inDegree; }  //返回入度
	virtual int outDegree(int i) { return V[i].outDegree; }  //返回出度
	virtual int firstNbr(int i) { return nextNbr(i, n); }  //首個鄰接頂點
	virtual int nextNbr(int i, int j)  //相對於頂點j的下一鄰接頂點(鄰接表更好)
	{
		while ((j > -1) && (!exists(i, --j))); return j;
	}
	virtual VStatus& status(int i) { return V[i].status; }  //返回狀態
	virtual int& dTime(int i) { return V[i].dTime; }   //返回時間標籤dTime
	virtual int& fTime(int i) { return V[i].fTime; }   //返回時間標籤fTime
	virtual int& parent(int i) { return V[i].parent; } //返回在遍歷樹中的父親
	virtual int& priority(int i) { return V[i].priority; } //在遍歷樹中的優先順序數

	//頂點的動態操作
	virtual int insert(const Tv& v);   //插入頂點,返回標號
	virtual Tv remove(int i);    //刪除第i個頂點及其關連邊

	//邊的確認操作
	virtual bool exists(int i, int j);   //判斷從頂點i到j的邊是否存在

	//邊的基本操作
	virtual EType& type(int i, int j) {return E[i][j]->type; }   //返回邊(i,j)的型別
	virtual Te& edata(int i, int j) { return E[i][j]->data; }     //返回邊(i,j)的資料
	virtual int& weight(int i, int j) { return E[i][j]->weight; } //返回邊(i,j)的權重

	//邊的動態操作
	virtual void insert(const Te& ed, int w, int i, int j);  //插入權重為w的邊e=(i,j)
	virtual Te remove(int i, int j);   //刪除頂點i到頂點j的邊e=(i,j)
};

template<typename Tv,typename Te> int graphMatrix<Tv, Te>::insert(const Tv& v)
{
	for (int j = 0; j < n; j++)   //為每個頂點預留一個潛在的關聯邊
		E[j].insert(NULL);
	n++;
	E.insert(vector<edge<Te>*>(n, n, (edge<Te>*)NULL));   //建立新頂點對應的邊向量
	return V.insert(vertex<Tv>(v));  //頂點向量增加一個頂點
	return 0;
}

template<typename Tv, typename Te> Tv graphMatrix<Tv,Te>::remove(int i)
{
	//清除所有出邊
	for (int j = 0; j < n; j++)
	{
		if (exists(i, j))
		{
			delete E[i][j];
			V[j].inDegree--;
		}
	}
	E.remove(i);   //刪掉i頂點對應的關連邊這一行
	Tv temp = vdata(i);   //快取資料
	V.remove(i);   //刪除頂點
	for (int j = 0; j < n; j++)
	{
		if (edge<Te>* e = E[j].remove(i))
		{
			delete e;
			V[j].outDegree--;
		}
	}
	return temp;
}

template<typename Tv, typename Te> bool graphMatrix<Tv, Te>::exists(int i, int j)
{
	return (0 <= i) && (i < n) && (0 <= j) && (j < n) && (E[i][j] != NULL);
}


template<typename Tv, typename Te>  void graphMatrix<Tv, Te>::insert(const Te& ed, int w, int i, int j)
{
	if (exists(i, j)) return;
	E[i][j] = new edge<Te>(ed, w); //建立新邊
	e++;
	V[i].outDegree++;
	V[j].inDegree++;
}

template<typename Tv, typename Te> Te graphMatrix<Tv, Te>::remove(int i, int j)
{
	Te temp = edata(i, j);
	delete E[i][j];
	E[i][j] = NULL;
	e--;
	V[i].outDegree--;
	V[j].inDegree--;
	return temp;
}