邻接多重表存储无向图以及有关操作

数据结构是编程里面最重要的一门基础课之一,所以学多少遍都不可以嫌多,算法的知识当然是融在其中,多练习,多思考,基础打好了,其他的东西学起来也就so easy了。

邻接多重表,是对用邻接表存储无向图的一种压缩存储,当然也是链式存储,邻接多重表的相关概念,可以百度、谷歌、或者看有关书籍。大部分书都没有详细介绍这个结构的应用(至少我目前还没看到有书上有写),只是说 这个结构在对无向图的边进行操作的时候会比较方便,确实有一点吧,在插入和删除边的时候,虽然不用想邻接表那样去找两条,但是也是需要进行一些判判断。下面是代码,邻接多重表存储的无向图的一些相关操作:

文件"graph.h"

#include <iostream>
#include <string>
#include <queue>
#include <stack>
using namespace std;

bool visited[100]; //顶点是否被访问标志

//邻接多重表的存储
//边结点
struct EBox
{
	int mark;//标志域,指示该边是否被访问过(0:没有 1:有)
	int ivex,jvex;//该边关联的两个顶点的位置
	EBox *ilink,*jlink;//分别指向关联这两个顶点的下一条边
};

//顶点结点
struct VexBox
{
	string data;  //顶点名称
	EBox *firstedge;//指向第一条关联该结点的边
};

class AMLGraph
{
private:
	VexBox *adjmulist; //顶点数组指针
	int vexnum; //定点数目
	int arcnum; //边数目
	int maxnum; //顶点数最大值
public:
	AMLGraph(int num=20)
	{
		adjmulist=new VexBox[num];
		maxnum=num;
	}
	
	~AMLGraph()
	{
		delete[]adjmulist;
	}

	//定位顶点在顶点数组中的位置
	int Locate_Vex(string v)
	{
		for(int i=0;i<vexnum;i++)
			if(adjmulist[i].data == v)
				return i;
		return -1;
	}

	void CreateUDG_AML()
	{
		//邻接多重表,存储无向图
		string v1,v2;
		int i,j,k;
		cout<<"输入定点数目和弧的数目:";
		cin>>vexnum>>arcnum;

		while(vexnum>maxnum)
		{
			cout<<"顶点数目太多,请重新输入顶点数和边数:";
			cin>>vexnum>>arcnum;
		}

		cout<<"输入每个顶点的名称:";
		for(i=0;i<vexnum;i++)
		{
			cin>>adjmulist[i].data;
			adjmulist[i].firstedge=NULL;
		}

		for(k=0;k<arcnum;k++)
		{
			cout<<"输入边的两个顶点:";
			cin>>v1>>v2;
			while(Search_Arc(v1,v2))
			{
				cout<<"该边已存在,本图不支持存在平行边"<<endl;
				cout<<"重新输入边的两个顶点:";
				cin>>v1>>v2;
			}

			i=Locate_Vex(v1);
			j=Locate_Vex(v2);

			while(i == -1 || j == -1)
			{
				cout<<"两个顶点中有不符合要求的,请重新输入:";
				cin>>v1>>v2;
				i=Locate_Vex(v1);
				j=Locate_Vex(v2);
			}

			//采用头插入方式,代码较好写,无向图顺序并不重要,所以没关系
			EBox *p=new EBox;
			p->ivex=i;
			p->jvex=j;
			p->ilink=adjmulist[i].firstedge;
			p->jlink=adjmulist[j].firstedge;
			adjmulist[i].firstedge=adjmulist[j].firstedge=p;
		}

		cout<<"无向图构造完成"<<endl;
	}

	bool Search_Arc(string v1,string v2)
	{
		//搜索对应的边是否存在
		int i,j;
		EBox *p;
		i=Locate_Vex(v1);
		j=Locate_Vex(v2);

		if(i == -1 || j == -1)
		{
			cout<<"顶点错误,改边不存在"<<endl;
			return false;
		}

		p=adjmulist[i].firstedge;
		while(p)
		{
			if(p->ivex == i && p->jvex == j)
				return true;
			else if(p->jvex == i && p->ivex == j)
				return true;
			else if(p->ivex == i)
				p=p->ilink;
			else if(p->jvex == i)
				p=p->jlink;
		}
		return false;
	}

	void Find_Neighbour(string v)
	{
		//输出顶点v的邻接顶点
		int i=Locate_Vex(v);
		if(i == -1)
		{
			cout<<"该顶点不存在图中"<<endl;
			return ;
		}
		
		EBox *p=adjmulist[i].firstedge;
		if(p)
		{
			cout<<"顶点 "<<v<<" 的邻接顶点为:";
			while(p)
			{
				if(p->ivex == i)
				{
					cout<<adjmulist[p->jvex].data<<"  ";
					p=p->ilink;
				}
				else
				{
					//p->jvex == i
					cout<<adjmulist[p->ivex].data<<"  ";
					p=p->jlink;
				}
			}
		}
		else
			cout<<"该顶点无相邻顶点"<<endl;
	}

	bool Insert_Arc(string v1,string v2)
	{
		//插入新边,不插入平行边
		if(Search_Arc(v1,v2))
		{
			cout<<"该边已存在图中,不重复插入"<<endl;
			return false;
		}

		int i,j;
		i=Locate_Vex(v1);
		j=Locate_Vex(v2);

		if(i == -1 || j == -1)
		{
			cout<<"两个顶点中,又不符合要求的,插入失败"<<endl;
			return false;
		}
		
		EBox *p=new EBox;
		p->ivex=i;
		p->jvex=j;
		p->ilink=adjmulist[i].firstedge;
		p->jlink=adjmulist[j].firstedge;
		adjmulist[i].firstedge=adjmulist[j].firstedge=p;

		//记得边数要加1
		arcnum++;
		return true;
	}

	bool Delete_Arc(string v1,string v2)
	{
		//删除顶点v1,v2之间的边
		if(Search_Arc(v1,v2))
		{
			int i,j;
			i=Locate_Vex(v1);
			j=Locate_Vex(v2);
	
			if(i == -1 || j == -1)
			{
				cout<<"输入的顶点中,有不在图中的,所以删除失败"<<endl;
				return false;
			}

			//四个指针,pre是指向待删除边的前一条边,rear是指向待删除的边
			//i,j是代表两个端点
			EBox *ipre,*irear,*jpre,*jrear;

			//初始化四个指针
			ipre=irear=adjmulist[i].firstedge;
			jpre=jrear=adjmulist[j].firstedge;

			//下面先判断第一条边是不是就是要删除的边
			if(ipre->ivex == i && ipre->jvex == j)
			{
				adjmulist[i].firstedge=ipre->ilink;
				adjmulist[j].firstedge=ipre->jlink;
				delete ipre;
				arcnum--;
				return true;
			}
			else if(ipre->jvex == i && ipre->ivex == j)
			{
				adjmulist[i].firstedge=ipre->jlink;
				adjmulist[j].firstedge=ipre->ilink;
				delete ipre;
				arcnum--;
				return true;
			}
			else
			{
				//待删除的边不是第一条
				//先让irear指向待删除的那条边
				while(irear)
				{
					if(irear->ivex == i && irear->jvex != j)
					{
						ipre=irear;
						irear=irear->ilink;
					}
					else if(irear->jvex == i && irear->ivex != j)
					{
						ipre=irear;
						irear=irear->jlink;
					}
					else if(irear->ivex == i && irear->jvex == j)
						break;
					else if(irear->jvex == i && irear->ivex == j)
						break;
				}

				if(!irear)
					return false;

				//下面让jrear指向待删除边
				if(irear->ivex == i)
				{
					//所以irear->jvex == j
					while(jrear)
					{
						if(jrear->ivex == i && jrear->jvex == j)
							break;
						else if(jrear->jvex == j)
						{
							jpre=jrear;
							jrear=jrear->jlink;
						}
						else if(jrear->ivex == j)
						{
							jpre=jrear;
							jrear=jrear->ilink;
						}
					}
					if(!jrear)
						return false;
				}
				else
				{
					//irear->ivex == j
					while(jrear)
					{
						if(jrear->ivex == j && jrear->jvex == i)
							break;
						else if(jrear->ivex == j)
						{
							jpre=jrear;
							jrear=jrear->ilink;
						}
						else if(jrear->jvex == j)
						{
							jpre=jrear;
							jrear=jrear->jlink;
						}
					}
					if(!jrear)
						return false;
				}

				//下面是开始删除,待删除边不是第一条边的情况
				if(irear->ivex == i && irear->jvex == j)
				{
					//删除时要判断每个顶点关联的前一条边的情况
					if(ipre->ivex == i)
						ipre->ilink=irear->ilink;
					else
						ipre->jlink=irear->ilink;

					if(jpre->ivex == j)
						jpre->ilink=jrear->jlink;
					else
						jpre->jlink=jrear->jlink;

					delete irear;
					arcnum--;
					return true;
				}
				else
				{
					//irear->jvex == i && irear->ivex == j
					//删除时要判断每个顶点关联的前一条边的情况
					if(ipre->ivex == i)
						ipre->ilink=irear->jlink;
					else
						ipre->jlink=irear->jlink;

					if(jpre->ivex == j)
						jpre->ilink=jrear->ilink;
					else
						jpre->jlink=jrear->ilink;

					delete irear;
					arcnum--;
					return true;
				}
			}
		}
		else
			cout<<"该边不存在"<<endl;
		return false;
	}

	//深度优先遍历
	void DFS_Traverse()
	{
		for(int i=0;i<vexnum;i++)
			visited[i]=false;
		for(i=0;i<vexnum;i++)
			if(!visited[i])
				DFS(i);
			cout<<endl;
	}

	void DFS(int v)
	{
		visited[v]=true;
		cout<<adjmulist[v].data<<"  ";
		EBox *p=adjmulist[v].firstedge;
		while(p)
		{
			if(p->ivex == v)
			{
				if(!visited[p->jvex])
					DFS(p->jvex);
				p=p->ilink;
			}
			else
			{
				if(!visited[p->ivex])
					DFS(p->ivex);
				p=p->jlink;
			}
		}
	}

	//广度优先遍历
	void BFS_Traverse()
	{
		for(int i=0;i<vexnum;i++)
			visited[i]=false;
		for(i=0;i<vexnum;i++)
			if(!visited[i])
				BFS(i);
		cout<<endl;
	}

	void BFS(int v)
	{
		visited[v]=true;
		cout<<adjmulist[v].data<<"  ";
		EBox *p;
		int pos;
		queue<int> qu;
		qu.push(v);
		while(!qu.empty())
		{
			pos=qu.front();
			qu.pop();
			p=adjmulist[pos].firstedge;
			while(p)
			{
				if(p->ivex == pos)
				{
					if(!visited[p->jvex])
					{
						visited[p->jvex]=true;
						cout<<adjmulist[p->jvex].data<<"  ";
						qu.push(p->jvex);
					}
					p=p->ilink;
				}
				else
				{
					if(!visited[p->ivex])
					{
						visited[p->ivex]=true;
						cout<<adjmulist[p->ivex].data<<"  ";
						qu.push(p->ivex);
					}
					p=p->jlink;
				}
			}
		}
	}

	void Mark_Unvisited()
	{
		//置边的访问标志为未访问
		int i;
		EBox *p;
		for(i=0;i<vexnum;i++)
		{
			p=adjmulist[i].firstedge;
			while(p)
			{
				p->mark=0;
				if(p->ivex == i)
					p=p->ilink;
				else
					p=p->jlink;
			}
		}
	}

	void Display()
	{
		//输出邻接多重表
		int i;
		EBox *p;
		Mark_Unvisited();
		cout<<"顶点为:";
		for(i=0;i<vexnum;i++)
			cout<<adjmulist[i].data<<"  ";
		cout<<endl;

		cout<<"总共有"<<arcnum<<"条边"<<endl;
		for(i=0;i<vexnum;i++)
		{
			p=adjmulist[i].firstedge;
			while(p)
			{
				if(p->ivex == i)
				{
					if(!p->mark)
					{
						cout<<adjmulist[i].data<<"--"<<adjmulist[p->jvex].data<<endl;
						p->mark=1;
					}
					p=p->ilink;
				}
				else
				{
					if(!p->mark)
					{
						cout<<adjmulist[p->jvex].data<<"--"<<adjmulist[p->ivex].data<<endl;
						p->mark=1;
					}
					p=p->jlink;
				}
			}
		}
	}
};

文件"main.cpp"

#include "graph.h"
#include <iostream>
#include <string>
using namespace std;

int main()
{
	AMLGraph G;
	G.CreateUDG_AML();

	string v,v1,v2;
	cout<<"输入顶点名称,将输出其相邻顶点:";
	cin>>v;
	G.Find_Neighbour(v);
	cout<<endl;

	cout<<"邻接多重表结构:"<<endl;
	G.Display();

	cout<<"深度优先遍历序列:";
	G.DFS_Traverse();

	cout<<"广度优先遍历序列:";
	G.BFS_Traverse();

	cout<<"输入你要插入的新的边的两个顶点的名称:";
	cin>>v1>>v2;
	if(G.Insert_Arc(v1,v2))
	{
		cout<<"插入成功"<<endl;
		G.Display();
		
		cout<<"插入后深度优先遍历序列为:";
		G.DFS_Traverse();
	
		cout<<"插入后广度优先遍历序列为:";
		G.BFS_Traverse();
	}

	cout<<"输入你要删除的边的另个顶点的名称:";
	cin>>v1>>v2;
	if(G.Delete_Arc(v1,v2))
	{
		cout<<"删除成功"<<endl;
		G.Display();

		cout<<"删除后深度优先遍历序列为:";
		G.DFS_Traverse();
		
		cout<<"删除后广度优先遍历序列为:";
		G.BFS_Traverse();
	}
	
	return 0;
}

测试结果:

输入定点数目和弧的数目:6 8
输入每个顶点的名称:v1 v2 v3 v4 v5 v6
输入边的两个顶点:v1 v2
输入边的两个顶点:v1 v3
输入边的两个顶点:v1 v5
输入边的两个顶点:v2 v5
输入边的两个顶点:v3 v4
输入边的两个顶点:v3 v1
该边已存在,本图不支持存在平行边
重新输入边的两个顶点:v2 v1
该边已存在,本图不支持存在平行边
重新输入边的两个顶点:v3 v6
输入边的两个顶点:v4 v2
输入边的两个顶点:v5 v4
无向图构造完成
输入顶点名称,将输出其相邻顶点:v5
顶点 v5 的邻接顶点为:v4  v2  v1
邻接多重表结构:
顶点为:v1  v2  v3  v4  v5  v6
总共有8条边
v1--v5
v1--v3
v1--v2
v2--v4
v2--v5
v3--v6
v3--v4
v4--v5
深度优先遍历序列:v1  v5  v4  v2  v3  v6
广度优先遍历序列:v1  v5  v3  v2  v4  v6
输入你要插入的新的边的两个顶点的名称:v5 v6
插入成功
顶点为:v1  v2  v3  v4  v5  v6
总共有9条边
v1--v5
v1--v3
v1--v2
v2--v4
v2--v5
v3--v6
v3--v4
v4--v5
v5--v6
插入后深度优先遍历序列为:v1  v5  v6  v3  v4  v2
插入后广度优先遍历序列为:v1  v5  v3  v2  v6  v4
输入你要删除的边的另个顶点的名称:v1 v2
删除成功
顶点为:v1  v2  v3  v4  v5  v6
总共有8条边
v1--v5
v1--v3
v2--v4
v2--v5
v3--v6
v3--v4
v4--v5
v5--v6
删除后深度优先遍历序列为:v1  v5  v6  v3  v4  v2
删除后广度优先遍历序列为:v1  v5  v3  v6  v4  v2
Press any key to continue
初始生成的无向图如下所示:


经过插入删除后生成的图就不画出来了 大家一看就知道了

写完代码 完善和debug的过程很艰辛 有时很恶心 但是自己觉得还满意 还不错后,就会觉得很爽啦 所以多自己动手写 是很好的锻炼

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值