1. 程式人生 > >圖的鄰接表儲存(C++模板實現)

圖的鄰接表儲存(C++模板實現)

一,鄰接表表示法

圖的鄰接矩陣儲存方法跟樹的孩子連結串列示法相類似,是一種順序分配和鏈式分配相結合的儲存結構。鄰接表由表頭結點和表結點兩部分組成,其中圖中每個頂點均對應一個儲存在陣列中的表頭結點。如這個表頭結點所對應的頂點存在相鄰頂點,則把相鄰頂點依次存放於表頭結點所指向的單向連結串列中。如圖8.12所示,表結點存放的是鄰接頂點在陣列中的索引。對於無向圖來說,使用鄰接表進行儲存也會出現資料冗餘,表頭結點A所指連結串列中存在一個指向C的表結點的同時,表頭結點C所指連結串列也會存在一個指向A的表結點。 有向圖的鄰接表有出邊表和入邊表(又稱逆鄰接表)之分。出邊表的表結點存放的是從表頭結點出發的有向邊所指的尾頂點;入邊表的表結點存放的則是指向表頭結點的某個頭頂點。如圖所示,圖(b)和(c)分別為有向圖(a)的出邊表和入邊表。

以上所討論的鄰接表所表示的都是不帶權的圖,如果要表示帶權圖,可以在表結點中增加一個存放權的欄位,其效果如圖8.14所示
注意:觀察圖8.14可以發現,當刪除儲存表頭結點的陣列中的某一元素,有可能使部分表頭結點索引號的改變,從而導致大面積修改表結點的情況發生。可以在表結點中直接存放指向表頭結點的指標以解決這個 問題。在實際建立鄰接表時,甚至可以使用連結串列代替陣列存放表頭結點或使用順序表代替連結串列存放表結點。對所學的資料結構知識應當根據實際情況及所使用語言的特點靈活應用,切不可生搬硬套。

二,C++模板類實現

1,Graph.h的程式碼實現

(以下鄰結表實現與以上並不一致):

#include "windows.h"
#include <stdio.h> 
#include "iostream"
#include "vector"
#include "algorithm"
#include "math.h"  
#include <thread>
#include <mutex>
#include <condition_variable>
#include <stdio.h>   

using namespace std;

template<class DistType/*邊的權值的型別*/>
class Edge//邊的定義
{
public:
	Edge(int dest, DistType weight)
	{
		m_nposTable = dest;
		m_distWeight = weight;
		m_pnext = NULL;
	}
	~Edge()
	{

	}
public:
	int m_nposTable;//該邊的目的頂點在頂點集中的位置
	DistType m_distWeight;//邊的權重值
	Edge<DistType> *m_pnext;//下一條邊(注意不是下一個頂點,因為m_nposTable已經知道了這個頂點的位置)
};
//宣告
template<class NameType/*頂點集名字型別*/, class DistType/*距離的資料型別*/> class Graph;

template<class NameType/*頂點集名字型別*/, class DistType/*距離的資料型別*/>
class Vertex//頂點的定義
{
public:
	Vertex()
	{
		padjEdge = NULL;
		m_vertexName = 0;
	}
	~Vertex()
	{
		Edge<DistType> *pmove = padjEdge;
		while (pmove)
		{
			padjEdge = pmove->m_pnext;
			delete pmove;
			pmove = padjEdge;
		}
	}

private:
	friend class Graph<NameType, DistType>;//允許Graph類任意訪問
	NameType m_vertexName;//頂點中的資料內容
	Edge<DistType> *padjEdge;//頂點的鄰邊

};


template<class NameType/*頂點集名字型別*/, class DistType/*距離的資料型別*/>
class Graph
{
public:
	Graph(int size = m_nDefaultSize/*圖頂點集的規模*/)
	{
		m_pVertexTable = new Vertex<NameType, DistType>[size];  //為頂點集分配記憶體
		if (m_pVertexTable == NULL)
		{
			exit(1);
		}
		m_numVertexs = 0;
		m_nmaxSize = size;
		m_nnumEdges = 0;
	}

	~Graph()
	{
		Edge<DistType> *pmove;
		for (int i = 0; i < this->m_numVertexs; i++)
		{
			pmove = this->m_pVertexTable[i].padjEdge;
			if (pmove){
				this->m_pVertexTable[i].padjEdge = pmove->m_pnext;
				delete pmove;
				pmove = this->m_pVertexTable[i].padjEdge;
			}
		}
		delete[] m_pVertexTable;
	}
	int GetNumEdges()
	{
		return m_nnumEdges / 2;
	}
	int GetNumVertexs()
	{
		return m_numVertexs;
	}
	bool IsGraphFull() const
	{     //圖滿的?
		return m_nmaxSize == m_numVertexs;
	}
	//在頂點集中位置為v1和v2的頂點之間插入邊
	bool InsertEdge(int v1, int v2, DistType weight = m_Infinity);
	bool InsertVertex(const NameType vertex);   //插入頂點名字為vertex的頂點
	void PrintGraph();   //列印圖
private:
	Vertex<NameType, DistType> *m_pVertexTable;   //頂點集
	int m_numVertexs;//圖中當前的頂點數量
	int m_nmaxSize;//圖允許的最大頂點數
	static const int m_nDefaultSize = 10;       //預設的最大頂點集數目
	static const DistType m_Infinity = 65536;  //邊的預設權值(可以看成是無窮大)
	int m_nnumEdges;//圖中邊的數目
	int GetVertexPosTable(const NameType vertex);    //用該頂點的名字來尋找其在頂點集中的位置
};


//返回頂點vertexname在m_pVertexTable(頂點集)中的位置
//如果不在頂點集中就返回-1
template<class NameType, class DistType>
int Graph<NameType, DistType>::GetVertexPosTable(const NameType vertexname)
{
	for (int i = 0; i < this->m_numVertexs; i++)
	{
		if (vertexname == m_pVertexTable[i].m_vertexName)
		{
			return i;
		}
	}
	return -1;
}

//列印圖中的各個頂點及其連結的邊的權重
template<class NameType, class DistType>
void Graph<NameType, DistType>::PrintGraph()
{
	Edge<DistType> *pmove;
	for (int i = 0; i<this->m_numVertexs; i++)
	{
		cout << this->m_pVertexTable[i].m_vertexName << "--->";
		pmove = this->m_pVertexTable[i].padjEdge;
		while (pmove)
		{
			cout << pmove->m_distWeight << "--->" << this->m_pVertexTable[pmove->m_nposTable].m_vertexName << "--->";
			pmove = pmove->m_pnext;
		}
		cout << "NULL" << endl;
	}
}


//頂點依次插入到分配好的頂點集中
template<class NameType, class DistType>
bool Graph<NameType, DistType>::InsertVertex(const NameType vertexname)
{
	if (IsGraphFull())
	{
		cerr << "圖已經滿,請勿再插入頂點!" << endl;
		return false;
	}
	else
	{
		this->m_pVertexTable[this->m_numVertexs].m_vertexName = vertexname;
		this->m_numVertexs++;
	}

	return true;
}

//在頂點集位置為v1和v2的頂點之間插入權值為weght的邊(務必保持輸入的準確性,否則.....)
template<class NameType, class DistType>
bool Graph<NameType, DistType>::InsertEdge(int v1, int v2, DistType weight)
{
	if (v1 < 0 && v1 > this->m_numVertexs && v2 < 0 && v2 > this->m_numVertexs)
	{
		cerr << "邊的位置引數錯誤,請檢查! " << endl;
		return false;
	}
	else
	{
		Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge;
		if (pmove == NULL)//如果頂點v1沒有鄰邊
		{ //建立頂點v1的第一個鄰邊(該鄰邊指明瞭目的頂點)
			m_pVertexTable[v1].padjEdge = new Edge<DistType>(v2, weight);
			m_nnumEdges++;//圖中邊的數目
			return true;
		}
		else//如果有鄰邊
		{
			while (pmove->m_pnext)
			{
				pmove = pmove->m_pnext;
			}
			pmove->m_pnext = new Edge<DistType>(v2, weight);
			m_nnumEdges++;//圖中邊的數目
			return true;
		}
	}
}



2,主測試程式碼:

// ConsoleAppMyGraph.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include "Graph.h"
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	Graph<char *, int> graph(7);
	char *vertex[7] = {"【地大】", "【武大】", "【華科】", "【交大】", "【北大】", "【清華】", "【復旦】"};//頂點集
	for (int i=0; i<7; i++)
	{
		graph.InsertVertex(vertex[i]);
	}
	cout<<"一,圖的初始化(鄰結表儲存):======================================"<<endl;
	graph.PrintGraph();
	cout<<"圖中頂點的數目:"<<graph.GetNumVertexs()<<endl;
	cout <<endl;


	int edge[7][3] = {{0, 1, 43}/*地大到武大的距離*/, {0, 2, 12}, {1, 2, 38}, {2, 3 ,1325},
		{3, 6, 55}, {4, 5, 34}, {4, 6, 248}};    //分配距離
	for (int i=0; i<7; i++)
	{
		graph.InsertEdge(edge[i][0], edge[i][1], edge[i][2]);
		graph.InsertEdge(edge[i][1], edge[i][0], edge[i][2]);
	}
	cout<<"二,新增邊後的圖(無向圖):=================================="<<endl;
	graph.PrintGraph();
	cout<<"圖中邊的數目(實際上是所示邊數的兩倍,因為是雙向的):"<<graph.GetNumEdges()<<endl;
	cout <<endl;
	system("color 0A");
	system("pause");
	return 0;
}




3,測試結果:

參考資源:

【1】http://www.cnblogs.com/rollenholt/archive/2012/04/09/2439055.html

【2】《維基百科》http://zh.wikipedia.org/wiki

【3】《演算法導論》