<C/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】《算法导论》

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值