一、介绍
1.十字链表是有向图的另一种存储结构
2.十字链表是将有向图的邻接表和逆邻接表结合起来的一种数据结构
tips:为什么说十字链表是邻接表和逆邻接表结合起来的一种数据结构呢?
首先我们需要知道的是对于有向图的邻接表来说,要计算一个顶点的出度非常简单,只需要遍历以这个顶点为链表头的链表,计算其中的结点数再减去顶点自身就可以得到出度,但是如果要计算一个顶点的入度该怎么办呢?在有向图的邻接表中我们只能通过遍历整个邻接表,计算除了以自身为链表头的其他所有链表中,自己这个节点存在的次数,这比计算出度来说显然麻烦了许多,因此就引入了逆邻接表,也就是入弧的方式是跟邻接表反过来的,创建了一个逆邻接表之后我们就可以和邻接表中计算出度一样只需要遍历一次本节点作为链表头的链表就可以得到入度了。而接下来要介绍的十字链表,他可以很方便的计算入度和出度,只需要对弧类进行一些数据项的添加即可。
二、类
十字链表与邻接表一样由两个类构成,分别是弧类和顶点类
弧类:
class arc//弧类
{
public:
int tailvertex; //弧尾顶点的位置
int headvertex; //弧头顶点的位置
arc* taillink; //指向弧尾相同的下一条弧
arc* headlink; //指向弧头相同的下一条弧
int weight; //本弧上的权值
};
顶点类:
class vertex//顶点类
{
public:
int data; //顶点内含的数据
arc* firstheadarc; //指向以该顶点为弧头的第一条弧
arc* firsttailarc; //指向以该顶点为弧尾的第一条弧
};
除了这两个类之外我们还需要定义一个有向图类,并在类中利用弧类和顶点类构建一个图。
有向图类:
class digraph//有向图类
{
public:
vertex* listhead; //链表头
int vertexnum; //有向图中的顶点数量
int arcnum; //有向图中的弧数量
void createdigraph();
int gethead(int value);
void getdegree();
};
在有向图类中我们定义了三个成员函数,一个是用来创建有向图的createdigraph(),一个是用来获取用户输入数据对应的链表头(也就是顶点)的gethead(int value),另一个是用来计算用户输入顶点的度的getdegree();
void digraph::createdigraph()
{
cout<<"请输入顶点数目:"<<endl; //输入顶点的数量
cin>>vertexnum;
listhead=new vertex[vertexnum]; //生成多个链表头的类
for (int i = 0; i < vertexnum; i++) //循环输入链表头内含的数据
{
cout<<"请输入顶点"<<i<<"的数据:"<<endl;
cin>>listhead[i].data;
listhead[i].firstheadarc=NULL; //链表头指向的弧初始化为空
listhead[i].firsttailarc=NULL;
}
cout<<"请输入弧的数量:"<<endl; //输入弧的数量
int v1,v2;
cin>>arcnum;
for (int i = 0; i < arcnum; i++)
{
cout<<"请输入要相连的两个点的数据:分别为弧头和弧尾"<<endl;
cin>>v1>>v2; //输入两个相连的顶点
int h1=gethead(v1);
int h2=gethead(v2);
arc* newarc=new arc; //为弧指针分配内存
memset(newarc,0,sizeof(arc)); //将指针分配的内存的数据全部初始化为0
newarc->headvertex=h1; //将新建的一个弧的弧头顶点赋值为顶点h1
newarc->tailvertex=h2; //将新建的一个弧的弧尾顶点赋值为顶点h2
newarc->weight=0; //弧的权重暂时不需要
newarc->headlink=listhead[h1].firstheadarc;//把该弧指向弧头相同的下一条弧指向顶点h1的以顶点h1为弧头的第一条弧
listhead[h1].firstheadarc=newarc;//把以顶点h1为弧头的第一条弧指向该弧
newarc->taillink=listhead[h2].firsttailarc;//把该弧指向弧尾相同的下一条弧指向顶点h2的以顶点h2为弧头的第一条弧
listhead[h2].firsttailarc=newarc;
}
cout<<"构建完毕"<<endl;
}
获取链表头函数gethead:
int digraph::gethead(int value)
{
int j;
for (j = 0; j < vertexnum; j++)
{
if (listhead[j].data==value)
{
break;
}
}
return j;
}
创建有向图函数createdigraph:
这一步是最为关键的一步,具体的实现步骤为:
1.获取用户输入的顶点数量
2.利用获取到的顶点数量循环为各个顶点输入数据并且将顶点的两个参数初始化为空,分别为firstheadarc(顶点指向的第一条以该顶点为弧头的弧)和firsttailarc(顶点指向的第一条以该顶点为弧尾的弧),从而避免野指针问题
3.获取用户输入的弧数量
4.利用获取到的弧数量构建循环从而连接顶点
5.(重点)顶点连接循环:
5.1.让用户输入要连接的两个顶点的数据(分别为弧头和弧尾)
5.2.通过gethead函数得到用户输入的两个数据对应的链表头
5.3.创建一条新弧
5.4.将这条新弧的弧头弧尾分别指向对应的顶点(上面已经得到了对应的链表头)
5.5.插入弧(这一步有点难懂,因为参数有点多),不过总体来说就是新建一条弧,并且将其插入已经存在的弧和对应顶点之间,不过由于一条弧是由弧头和弧尾构成的,因此每插入一条弧就要更改一次两个顶点的firstheadarc和firsttailarc。
下面是本人画的两幅图用来帮助理解:
1.用户输入1,3 插入弧arc1
2.用户输入1,2 插入弧arc2
要想更好的理解建议在纸上自己写写画画,并且最好多读两次各个参数代表的意思不然容易搞混
下面上代码:
void digraph::createdigraph()
{
cout<<"请输入顶点数目:"<<endl; //输入顶点的数量
cin>>vertexnum;
listhead=new vertex[vertexnum]; //生成多个链表头的类
for (int i = 0; i < vertexnum; i++) //循环输入链表头内含的数据
{
cout<<"请输入顶点"<<i<<"的数据:"<<endl;
cin>>listhead[i].data;
listhead[i].firstheadarc=NULL; //链表头指向的弧初始化为空
listhead[i].firsttailarc=NULL;
}
cout<<"请输入弧的数量:"<<endl; //输入弧的数量
int v1,v2;
cin>>arcnum;
for (int i = 0; i < arcnum; i++)
{
cout<<"请输入要相连的两个点的数据:分别为弧头和弧尾"<<endl;
cin>>v1>>v2; //输入两个相连的顶点
int h1=gethead(v1);
int h2=gethead(v2);
arc* newarc=new arc; //为弧指针分配内存
memset(newarc,0,sizeof(arc)); //将指针分配的内存的数据全部初始化为0
newarc->headvertex=h1; //将新建的一个弧的弧头顶点赋值为顶点h1
newarc->tailvertex=h2; //将新建的一个弧的弧尾顶点赋值为顶点h2
newarc->weight=0; //弧的权重暂时不需要
newarc->headlink=listhead[h1].firstheadarc;//把该弧指向弧头相同的下一条弧指向顶点h1的以顶点h1为弧头的第一条弧
listhead[h1].firstheadarc=newarc;//把以顶点h1为弧头的第一条弧指向该弧
newarc->taillink=listhead[h2].firsttailarc;//把该弧指向弧尾相同的下一条弧指向顶点h2的以顶点h2为弧头的第一条弧
listhead[h2].firsttailarc=newarc;
}
cout<<"构建完毕"<<endl;
}
求度函数getdegree:
求度只要定义两个指针,一个是用来计算出度的outptr,另一个是用来计算入度的inptr,每当指针经过一个节点,相应的度就加一即可,下面上代码。
void digraph::getdegree()
{
int outdegree=0,indegree=0;//初始化入度和出度为0
cout<<"请输入要求度的顶点:"<<endl;//用户输入顶点
int temp;
cin>>temp;
int ver=gethead(temp);//利用gethead函数得到链表头
arc* outptr=listhead[ver].firsttailarc;//定义一个指针用来计算出度
while (outptr)//当指针不为空时表示节点存在
{
outdegree++;//出度加一
outptr=outptr->taillink;//指针指向下一个节点
}
cout<<"出度为:"<<outdegree<<endl;
arc* inptr=listhead[ver].firstheadarc;//定义一个指针用来计算入度
while (inptr)//当指针不为空时表示节点存在
{
indegree++;//入度加一
inptr=inptr->headlink; //指针指向下一个节点
}
cout<<"入度为:"<<indegree<<endl;
cout<<"度为:"<<outdegree+indegree<<endl;//度等于入度加出度
}
main函数:
int main()
{
digraph dg;
dg.createdigraph();
dg.getdegree();
return 0;
}
三、总结
十字链表是一个非常不错的东西,结合了邻接表和逆邻接表,主要归功于弧类内部相对较多的参数得以实现,牺牲了一定的空间换来的较快的入度和出度的求解。
这是我的第二篇csdn博客,以后在学习的时候也会继续写一下自己的理解,如果有帮助到各位的可以给我点点赞,另外如果有没写好的欢迎指正,在这里谢谢各位啦!