图结构系列—基于邻接表的图实现

上一文中介绍了基于邻接矩阵的图实现,介绍了一些邻接矩阵的一些表示优势。下面来说一下基于邻接表的描述无向图的具体实现,在实现前有一些结点类型说明一下:

首先是边信息类型:

#pragma once
#include<iostream>
using namespace std;

template<class T, class E>
class EdgeNode
{
public:
	int dest; //该边的对称顶点在NodeTable中的索引值
	E weight; //边的权重值(虚拟类型)
	EdgeNode<T,E> *link; //指向下一条边的指针
public:
	EdgeNode()
	{
		link = NULL;
	}
	EdgeNode(int de, E wei, EdgeNode<T,E> *lk = NULL) //默认参数 根据权值与索引构造出一个边
	{
		dest = de;
		weight = wei;
		link = lk;
	}
	~EdgeNode()
	{

	}
	bool operator!= (EdgeNode<T,E> &E) //判断两条边不相等
	{
		return ((dest != E.dest) ? true : false);
	}
};


然后是顶点类:

#pragma once
#include<iostream>
using namespace std;
#include"EdgeNode.h"

template<class T, class E>
class VertexNode
{
public:
	T data; //顶点数据类型
	EdgeNode<T,E> *first_Adj; //该顶点所关联的边链表的首指针
public:
	VertexNode()
	{
		first_Adj = NULL;
	}
	VertexNode(T da, EdgeNode<T,E> *firadj = NULL)
	{
		data = da;
		first_Adj = firadj;
	}
	~VertexNode()
	{

	}

};

下面是图的实现代码:

#pragma once
#include"VertexNode.h"
#include<iostream>
#include<fstream>
using namespace std;
const int DefaultVertexNum = 30; //默认顶点最大数目 常量

template<class T, class E> //因为顶点是一个类型 边上的权值又是一个类型 所以声明了两个类型的模板
class GraphAdjList
{
protected:
	int maxVerticseNum; //最大顶点数目
	int numVertices; //当前顶点数目
	int numEdges; //当前边数目
	VertexNode<T,E> *nodeTable; //顶点顺序表
	const T nullVertex; //顶点类型的NULL 用于找不到指定顶点的时候的返回值 在GraphAdjList类中是常量
	const E nullEdge; //边类型的NULL 用于找不到指定边的时候返回的值
	static const int s_NULLidx = -1; //用来标示无法找到指定正确索引,返回-1 此写法是在C++2003标准出现的 在以后的编译器中均识别

	int GetVertexPos(const T &targetValue); //内部提供服务,根据传进来的参数返回该顶点所在的索引,如果失败则返回-1
public:
	GraphAdjList(T nullver, E nulled, int vertexsize = DefaultVertexNum); //配置一个图
	~GraphAdjList(); //析构
	void MakeEmpty(); //销毁一个基于邻接表的图

	T GetVertexValue(int vertex_idx); //获取指定索引的顶点的值 没有则返回nullVertex
	T GetFirstNeighbor(const T &target_vertex); //获取以vertex为顶点的第一个邻接顶点
	T GetNextNeighbor(const T &vertex, const T &adj_vertex); //获vertex的邻接顶点adj_vertex的下一个邻接顶点
	E GetEdgeWeight(const T &begvtx, const T &endvtx); //获取目标顶点begvtx,endvtx之间的权重值
	
	bool InsertVertex(const T &target_vertex); //插入一个顶点 返回布尔值
	bool InsertEdge(const T &begvtx, const T &endvtx, const E &weight); //在顶点begvtex,endvtx之间插入一条边(插入权重值) 返回布尔值
	bool RemoveVertex(const T &target_vertex); //删除指定顶点,返回布尔值
	bool RemoveEdge(const T &begvtx, const T &endvtx); //删除指定顶点之间的边,返回布尔值

	void print(); //无参标准输出
	void print(ostream &output); //输出这个图的信息
	template<class T, class E> friend ifstream& operator>>(ifstream &fin, GraphAdjList<T,E> &G); //文件输入流重载
	template<class T, class E> friend ofstream& operator<<(ofstream &fout, GraphAdjList<T,E> &G); //文件输出流重载
	template<class T, class E> friend istream& operator>>(istream &input, GraphAdjList<T,E> &G); //标准输入流重载
	template<class T, class E> friend ostream& operator<<(ostream &output, GraphAdjList<T,E> &G); //标准输出流重载
};

/*----------------------私有函数域-----------------------*/

template<class T, class E>
int GraphAdjList<T,E>::GetVertexPos(const T &targetValue)
{
	for(int idx = 0; idx < numVertices; idx++)
	{
		if(nodeTable[idx].data == targetValue) //遍历找目标顶点 如果是自定义类型的顶点 则须重载自定义类型的==运算符
			return idx;
	}

	return s_NULLidx;
}

/*-----------------------对外接口-------------------------*/

template<class T, class E>
GraphAdjList<T,E>::GraphAdjList(T nullver, E nulled, int vertexsize = DefaultVertexNum):nullVertex(nullver),nullEdge(nulled)
{
	//nullVertex = nullver; //初始化类中的常量 只能用参数初始化列表的形式做
	//nullEdge = nulled;
	maxVerticseNum = vertexsize;
	numVertices = 0;
	numEdges = 0;

	nodeTable = new VertexNode<T,E>[maxVerticseNum]; //开辟顶点类空间 所有的顶点类的边指针域均为NULL
	
	if(nodeTable == NULL)
	{
		cerr<<"内存分配错误"<<endl;
		exit(1);
	}
}

template<class T, class E>
GraphAdjList<T,E>::~GraphAdjList()
{
	//类模板的虚函数好像必须要有的,每次都是这个地方会错一下
	MakeEmpty();
}

template<class T, class E>
void GraphAdjList<T,E>::MakeEmpty()
{
	EdgeNode<T,E> *first_temp;
	for(int idx = 0; idx < numVertices; idx++)
	{
		while(nodeTable[idx].first_Adj != NULL) //删除边链表
		{
			first_temp = nodeTable[idx].first_Adj;
			nodeTable[idx].first_Adj = first_temp->link;
			delete first_temp;
		}
	}

	delete []nodeTable; //删除顶点数组空间 一定要加[] 否则就是删除空间的第一个元素空间 后果是内存"黑洞"
}

template<class T, class E>
T GraphAdjList<T,E>::GetVertexValue(int vertex_idx)
{
	if((0 <= vertex_idx) && (vertex_idx <= numVertices - 1)) //参数是索引值,而不是序数
	{
		return nodeTable[vertex_idx].data; //自定义类型的顶点类要重载=(赋值)运算符
	}
	else
	{
		return nullVertex;
	}
}

template<class T, class E>
T GraphAdjList<T,E>::GetFirstNeighbor(const T &target_vertex)
{
	int idx = GetVertexPos(target_vertex); //找到参数顶点的索引

	if(idx != s_NULLidx) //该顶点确实存在于图中
	{
		if(nodeTable[idx].first_Adj != NULL) //该顶点有边连接于其他的顶点
		{
			EdgeNode<T,E> *temp_link = nodeTable[idx].first_Adj;
			int target_idx = temp_link->dest; //找到目标索引

			return nodeTable[target_idx].data; //返回这个邻接顶点值
		}
	}

	return nullVertex; //其他情况都是不符合的 返回空
}

template<class T, class E>
T GraphAdjList<T,E>::GetNextNeighbor(const T &vertex, const T &adj_vertex)
{
	int idx = GetVertexPos(vertex); //先转换为索引
	int adj_idx = GetVertexPos(adj_vertex);

	if(idx != s_NULLidx) //该顶点存在图中
	{
		if(nodeTable[idx].first_Adj != NULL) //该顶点有边链表
		{
			EdgeNode<T,E> *temp_link = nodeTable[idx].first_Adj;
			while((temp_link != NULL) && (temp_link->dest != adj_idx))
			{
				temp_link = temp_link->link; //在边链表中找到adj_vertex
			}

			if((temp_link != NULL) && (temp_link->link != NULL)) //在adj_vertex于其下一个均存在的情况下
			{
				int target_idx = temp_link->link->dest; //获取目标值 索引
				return nodeTable[target_idx].data;
			}
		}
	}

	return nullVertex; //其他情况均不正确
}

template<class T, class E>
E GraphAdjList<T,E>::GetEdgeWeight(const T &begvtx, const T &endvtx)
{
	int begidx = GetVertexPos(begvtx); //获取索引
	int endidx = GetVertexPos(endvtx);

	if((begidx != s_NULLidx) && (endidx != s_NULLidx)) //这两个顶点均存在图中
	{
		EdgeNode<T,E> *temp_link = nodeTable[begidx].first_Adj;
		while((temp_link != NULL) && (temp_link->dest != endidx)) //遍历边链表 中间没有中断 并且找到了endvtx
		{
			temp_link = temp_link->link;
		}
		if(temp_link != NULL) //temp_link指向的是endvtx所在的边结点
		{
			return temp_link->weight; //返回权值
		}
	}

	return nullEdge; //返回空边结点
}

template<class T, class E>
bool GraphAdjList<T,E>::InsertVertex(const T &target_vertex)
{
	if(numVertices == maxVerticseNum)
	{
		return false; //图满
	}

	nodeTable[numVertices].data = target_vertex; //插入动态数组最后面
	numVertices++; //顶点数目+1

	return true;
}

template<class T, class E>
bool GraphAdjList<T,E>::InsertEdge(const T &begvtx, const T &endvtx, const E &weight)
{
	int begidx = GetVertexPos(begvtx); //获取相应顶点的索引
	int endidx = GetVertexPos(endvtx);

	if((begidx != s_NULLidx) && (endidx != s_NULLidx)) //两顶点都存在图中
	{
		//此为无向图 首先为begvtx建立边关系
		if(nodeTable[begidx].first_Adj != NULL) //起始顶点有边链表
		{
			EdgeNode<T,E> *temp_link = nodeTable[begidx].first_Adj;
			while((temp_link->link != NULL) && (temp_link->dest != endidx)) //当前边的后面还有边并且当前的边是没有出现过的
			{
				temp_link  = temp_link->link;
			}
			//跳出循环有三种可能
			//1:单方面由当前边的后面没有边了
			//2:单方面由当前边已存在
			//3:恰巧两个均起作用
			if((temp_link->link == NULL) && (temp_link->dest != endidx)) //后面没有变了并且当前边是没有出现过的
			{
				temp_link->link = new EdgeNode<T,E>(endidx, weight); //根据边结点信息开辟一个新的边结点放在边链表的后面
			}
			else
			{
				return false; //begvtx--->endvtx的边无法插入 直接返回 下面不执行
			}
		}
		//当起始顶点没有边链表的时候
		else
		{
			nodeTable[begidx].first_Adj = new EdgeNode<T,E>(endidx, weight); //插在边链表的首位置
		}

		//能执行下来说明可以插入begvtx--->endvtx的边 也就可以插入endvtx--->begvtx 所以不用做判断
		//下面为endvtx建立边关系 采用另一种简便的考虑情况少的方法
		EdgeNode<T,E> *newEdgeNode = new EdgeNode<T,E>(begidx, weight);
		newEdgeNode->link = nodeTable[endidx].first_Adj;
		nodeTable[endidx].first_Adj = newEdgeNode;

		//增加边数目
		numEdges++;
		return true;
	}

	return false; //其余情况均错
}

template<class T, class E>
bool GraphAdjList<T,E>::RemoveVertex(const T &target_vertex)
{
	int target_idx = GetVertexPos(target_vertex);
	if((target_idx < 0) || (target_idx >= numVertices) || (numVertices == 1))
	{
		return false; //要删除的顶点不存在 或是 最后剩下一个顶点不允许删除
	}

	EdgeNode<T,E> *beg_dellink = NULL; //指针务必初始化 哪怕是为NULL都行
	EdgeNode<T,E> *end_dellink = NULL;
	EdgeNode<T,E> *end_templink = NULL;
	int end_idx;

	while(nodeTable[target_idx].first_Adj != NULL) //外层循环要删除这个顶点的边链表
	{
		beg_dellink = nodeTable[target_idx].first_Adj;
		end_idx = beg_dellink->dest; //找到边链表中的终边索引
		end_dellink = nodeTable[end_idx].first_Adj;
		end_templink = NULL;

		while((end_dellink != NULL) && (end_dellink->dest != target_idx)) //找对称存储的边
		{
			end_templink = end_dellink;
			end_dellink = end_dellink->link;
		}
		if(end_dellink != NULL)
		{
			if(end_templink == NULL) //对称边上只有这一个待删除的顶点
			{
				nodeTable[end_idx].first_Adj = NULL; //将对称顶点链表置为NULL
			}
			else
			{
				end_templink->link = end_dellink->link; //删除链表中的一个元素
			}
			delete end_dellink;
		}

		nodeTable[target_idx].first_Adj = beg_dellink->link; //回到主循环里,待删除的边链表往下遍历
		delete beg_dellink;
		numEdges--; //每做一次就要删除一条边
	}//end of while


	EdgeNode<T,E> *beg_link = NULL; //这里是为了将顶点动态数组中的最后一个顶点转移到已删除的这个顶点位置
	EdgeNode<T,E> *end_link = NULL;
	numVertices--; //首先定点数目-1
	nodeTable[target_idx].data = nodeTable[numVertices].data; //顶点信息填补
	//下面是:循环遍历最后顶点的边链表 找与他连接的边 然后找到对称的顶点 把这个顶点中存放的边信息索引更新为这个target_idx
	beg_link = nodeTable[target_idx].first_Adj = nodeTable[numVertices].first_Adj;
	while(beg_link != NULL)
	{
		end_link = nodeTable[beg_link->dest].first_Adj;
		while(end_link != NULL)
		{
			if(end_link->dest == numVertices)
			{
				end_link->dest = target_idx;
				break;
			}
			else
			{
				end_link = end_link->link;
			}
		}//小while循环结束

		beg_link = beg_link->link;
	}//大while循环结束
}

template<class T, class E>
bool GraphAdjList<T,E>::RemoveEdge(const T &begvtx, const T &endvtx)
{
	int begidx = GetVertexPos(begvtx); //获取两个顶点的索引值
	int endidx = GetVertexPos(endvtx);
	if((begidx != s_NULLidx) && (endidx != s_NULLidx)) //两个顶点均存在
	{
		//先删除begvtx--->endvtx的边
	    EdgeNode<T,E> *beg_link = nodeTable[begidx].first_Adj;
	    EdgeNode<T,E> *temp_link = NULL;
	    while((beg_link != NULL)&& (beg_link->dest != endidx)) //在为遇到NULL 未遇到目标边的情况下 继续循环
	    {
		    temp_link = beg_link;
		    beg_link = beg_link->link;
	    }
	    if(beg_link !=NULL) //找到目标边
    	{
	    	if(temp_link == NULL) //begvtx顶点只有这么一条边
		    {
		    	nodeTable[begidx].first_Adj = NULL;
	    	}
	    	else
		    {
		    	temp_link->link = beg_link->link;
		    }
		    delete beg_link;
	    }
		else //未找到目标边
		{
			return false;
		}

	    //因为是无向边所以现在删除endvtx--->begvtx的边
    	EdgeNode<T,E> *end_link = nodeTable[endidx].first_Adj;
	    temp_link = NULL;
		while(end_link->dest != begidx) //能执行到这里 说明这个对称边也是存在的 所以不用加!=NULL的条件
		{
			temp_link = end_link;
			end_link = end_link->link;
		}
		if(temp_link == NULL)
		{
			nodeTable[endidx].first_Adj = NULL;
		}
		else
		{
			temp_link->link = end_link->link;
		}
		delete end_link;

		return true;
	}
	else //匹配最外层的if 至少有一个顶点不存在图中
	{
		return false;
	}
}

template<class T, class E>
void GraphAdjList<T,E>::print()
{
	int vertices_Num = numVertices;
	int edges_Num = numEdges;

	cout<<"顶点数目: "<<vertices_Num<<endl;
	cout<<"边数目: "<<edges_Num<<endl;

	EdgeNode<T,E> *first_temp = NULL;
	T begvtx,endvtx;
	E weight;
	int endidx;
	int edgeCount = 0;
	for(int idx = 0; idx < numVertices; idx++) //顶点遍历
	{
		if(nodeTable[idx].first_Adj != NULL) //当前顶点的边链表不为空
		{
			first_temp = nodeTable[idx].first_Adj;
			begvtx = GetVertexValue(idx); //获取该顶点的信息
			while(first_temp != NULL) //遍历顶点边链表
			{
				endidx = first_temp->dest;
				if(endidx > idx) //因为是无向图 所以只需输出一半的边信息
				{
					endvtx = GetVertexValue(endidx);
					weight = GetEdgeWeight(begvtx,endvtx);
					cout<<"<"<<begvtx<<", "<<endvtx<<">  权值:"<<weight<<endl;

					edgeCount++; //在这里做一个增加效率的判断 如果一旦边输出完 不管后面还有多少重复的边 都立即返回
					if(edgeCount == numEdges)
					{
						return;
					}
				}

				first_temp = first_temp->link; //顶点边链表指针后移
			}//end of while
		}// end of if
	}//end of for
}

template<class T, class E>
void GraphAdjList<T,E>::print(ostream &output)
{
	int vertices_Num = numVertices;
	int edges_Num = numEdges;

	output<<"顶点数目: "<<vertices_Num<<endl;
	output<<"边数目: "<<edges_Num<<endl;

	EdgeNode<T,E> *first_temp = NULL;
	T begvtx,endvtx;
	E weight;
	int endidx;
	int edgeCount = 0;
	for(int idx = 0; idx < numVertices; idx++)
	{
		if(nodeTable[idx].first_Adj != NULL)
		{
			first_temp = nodeTable[idx].first_Adj;
			begvtx = GetVertexValue(idx);
			while(first_temp != NULL)
			{
				endidx = first_temp->dest;
				if(endidx > idx)
				{
					endvtx = GetVertexValue(endidx);
					weight = GetEdgeWeight(begvtx,endvtx);
					output<<"<"<<begvtx<<", "<<endvtx<<">  权值:"<<weight<<endl;

					edgeCount++;
					if(edgeCount == numEdges)
					{
						return;
					}
				}

				first_temp = first_temp->link;
			}//end of while
		}// end of if
	}//end of for
}


template<class T, class E>
ifstream& operator>>(ifstream &fin, GraphAdjList<T,E> &G)
{
	int initializeVerticesNum; //从文件中读取 初始化定点数目
	fin>>initializeVerticesNum;

	T value;
	for(int idx = 0; idx < initializeVerticesNum; idx++)
	{
		fin>>value;
		G.InsertVertex(value); //以此插入顶点信息
	}

	int initializeEdgesNum; //配置边信息
	fin>>initializeEdgesNum;
	if(initializeEdgesNum > 0)
	{
		T begvtx, endvtx;
	    E weight;
	    int begidx, endidx;
	    for(int idx = 0; idx < initializeEdgesNum; idx++)
	    {
		    fin>>begvtx>>endvtx>>weight;

		    begidx = G.GetVertexPos(begvtx); //在这里做判断 文件读取进来的边的两个顶点信息是否均存在图中
		    endidx = G.GetVertexPos(endvtx);
		    if((begidx != G.s_NULLidx) && (endidx != G.s_NULLidx))
		    {
			    G.InsertEdge(begvtx, endvtx, weight);
		    }
	    }
	}

	return fin;
}

template<class T, class E>
ostream& operator<<(ostream &output, GraphAdjList<T,E> &G)
{
	G.print(output);

	return output;
}

同样也是用到了两个虚拟类型数据。在这里要说明一下的是,为什么要强调描述的是无向图,在这个图中用到了一个VertexNode<T,E>  *nodeTable。如果是要描述有向图的话,就需要用到两个这种变量了,因为有向图中需要区别某一个顶点的出表(从此顶点出发,能走的边),入表(所有的顶点能到达该顶点的表)。关于有向图的邻接表表示后序贴出。

下面是主函数:

#include<iostream>
#include<fstream>
#include"GraphAdjList.h"
using namespace std;
void main()
{
	GraphAdjList<char,int> gh('#',-1);
	ifstream in("GRAPH.txt");
	in>>gh;
	cout<<gh<<endl;
	gh.RemoveVertex('A');
	cout<<gh<<endl;
} 


这次没有自定义类型,用的都是基本内置类型。
截图如下:




  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C语言中,可以使用结构体和指针来实现的存储结构邻接表邻接表的一种常用存储结构,由表头结点和表结点两部分组成。表头结点存储的各个顶点,而表结点则用单向链表存储每个顶点的相邻顶点,从而表示的边。 下面是一个C语言实现邻接表的示例代码: ```c // 定义邻接表的结点结构 struct Node { int data; // 存储相邻顶点的数据 struct Node* next; // 指向下一个结点的指针 }; // 定义邻接表的表头结点结构 struct Head { int vertex; // 存储顶点的信息 int flag; // 判断该顶点是否被访问过的标志 struct Node* head_ele; // 指向链表的指针,存储相邻顶点的信息 }; // 定义结构体 struct Map { int vex; // 存储的顶点数 int edge; // 存储的边数 int tag; // 存储的其他信息 struct Head* head; // 指向表头结点数组的指针 }; // 初始化邻接表 struct Map* initMap(int vex, int edge, int tag) { struct Map* map = (struct Map*)malloc(sizeof(struct Map)); map->vex = vex; map->edge = edge; map->tag = tag; map->head = (struct Head*)malloc(vex * sizeof(struct Head)); for (int i = 0; i < vex; i++) { map->head[i].vertex = i; // 初始化表头结点的顶点信息 map->head[i].flag = 0; // 初始化访问标志 map->head[i].head_ele = NULL; // 初始化链表为空 } return map; } // 添加边到邻接表 void addEdge(struct Map* map, int start, int end) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = end; newNode->next = map->head[start].head_ele; map->head[start].head_ele = newNode; } // 打印邻接表 void printMap(struct Map* map) { for (int i = 0; i < map->vex; i++) { printf("顶点 %d 的相邻顶点:", map->head[i].vertex); struct Node* node = map->head[i].head_ele; while (node != NULL) { printf("%d ", node->data); node = node->next; } printf("\n"); } } // 主函数示例 int main() { struct Map* map = initMap(5, 7, 0); // 创建一个包含5个顶点和7条边的 addEdge(map, 0, 1); // 添加边 0->1 addEdge(map, 0, 2); // 添加边 0->2 addEdge(map, 1, 3); // 添加边 1->3 addEdge(map, 1, 4); // 添加边 1->4 addEdge(map, 2, 3); // 添加边 2->3 addEdge(map, 2, 4); // 添加边 2->4 addEdge(map, 3, 4); // 添加边 3->4 printMap(map); // 打印邻接表 return 0; } ``` 上述代码中,使用了两个结构体`Node`和`Head`来分别表示邻接表的结点和表头结点。通过调用`initMap`函数可以初始化一个,并通过`addEdge`函数来添加边到邻接表中。最后,使用`printMap`函数可以打印出邻接表的内容。这样就实现了C语言邻接表存储结构

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值