图的十字链表存储结构是将图的邻接表和逆邻接表组合起来的一个新的存储结构。因为邻接表只能方便计算出图中顶点的出度,而在计算顶点的入度问题时就不太方便了,必须遍历所有的顶点才能知道图中某个顶点的入度。而逆邻接表刚好和邻接表相反,所以组合他们就能够方便计算出图中任意顶点的入度和出度问题。
那么,十字链表很复杂吗?其实假如你的链表学到很好的话,那么可以说图的十字链表存储结构的实现是不难理解的。现在我们就来开始分析十字链表这一数据结构的逻辑结构和物理结构。
不管是十字链表还是邻接矩阵的存储结构,都是表征的一个图的顶点和边。这里我们就将图中的顶点和边都视为一个结点,分别称为顶点结点和弧结点,我们只要设计这两个结点的时候充分表征出图的性质那就大功告成了。
设计数据结构如下面的代码:
#define MaxVex 20
typedef int EdgeType ; // 定义弧信息的类型
typedef char VertexType ; // 定义顶点的类型
typedef struct EdgeNode // 弧结点的定义
{
int tailvex ; // 弧尾结点的下标
int headvex ; // 弧头结点的下标
struct EdgeNode * headlink ; // 指向弧头相同的下一条弧的链域
struct EdgeNode * taillink ; // 指向弧尾相同的下一条弧的链域
EdgeType info ; // 弧中所含的信息
} EdgeNode ;
typedef struct VertexNode // 顶点结点的定义
{
int index ; // 下标值
VertexType data ; // 顶点内容
EdgeNode * firstout ; // 顶点的第一条出弧
EdgeNode * firstin ; // 顶点的第一条入弧
} VertexNode ;
typedef struct OLGraph // 十字链表存储结构的有向图定义
{
VertexNode vertices[MaxVex] ;
int vexnum ;
int arcnum ;
} OLGraph ;
可以看到一个弧结点,有5个域:
tailvex:表示该弧的弧尾对应的顶点的下标
headvex:表示该弧的弧头对应的顶点的下标
headlink:表示指向弧头相同的下一条弧的的指针域
taillink:表示指向弧尾相同的下一条弧的指针域
一个顶点结点有4个域:
index:表示搞顶点的下标值
data:表示顶点的内容
firstout:这是一个EdgeNode类型的指针,即弧结点指针,它指向从该顶点出发的第一条弧,根据这个指针域,当然就可以找到从该顶点出发的所有的弧了。
firstin:与以上相同,只是它是指向弧头为该顶点的第一条弧,根据这个指针域可以找到所有虎弧头指向它的所有弧。
通过以上的描述,创建一个以十字链表为存储结构的图是很简单了,就像创建链表一样,根据结点之间的逻辑结构控制好指针的指向即可。
创建图的代码:
void CreateGraph( OLGraph * G )
{
// 输入弧的数目与顶点的数目
G->vexnum= 6 ;
G->arcnum = 7 ;
// 输入顶点的信息
printf("请输入顶点信息:") ;
for( int i = 0 ; i < G->vexnum ; i++ )
{
scanf("%c" , &G->vertices[i].data ) ;
G->vertices[i].index = i ;
G->vertices[i].firstin = NULL ;
G->vertices[i].firstout = NULL ;
}
// 输入弧的信息
printf("请输入边的信息:") ;
for( int j = 0 ; j < G->arcnum ; j++ )
{
EdgeNode * edge = (EdgeNode*)malloc(sizeof(EdgeNode)) ;
edge->taillink = NULL ;
edge->headlink = NULL ;
scanf("%d %d",&edge->tailvex , &edge->headvex ) ;
// 将该弧插入到当前顶点的出弧中
EdgeNode * p = G->vertices[edge->tailvex].firstout ;
if( p == NULL ) // 第一次插入边结点
{
G->vertices[edge->tailvex].firstout = edge ;
}
else
{
while( p != NULL )
{
if( p->taillink == NULL )
{
break ;
}
p = p->taillink ;
}
p->taillink = edge ;
}
// 将该弧插入到当前顶点的入弧中
EdgeNode * q = G->vertices[edge->headvex].firstin ;
if( q == NULL )
{
G->vertices[edge->headvex].firstin = edge ;
}
else
{
while( q->headlink != NULL )
{
if( q->headlink == NULL )
{
break ;
}
q = q->headlink ;
}
q->headlink = edge ;
}
}
}
在代码中,首先的操作是输入顶点的信息。然后是输入弧的信息,这里是有向无权图,所以输入的弧就只有弧两端顶点的下标值了,即一个二元组,前面的一个对应弧尾tailvex,后面的一个对应弧头headvex。在创建弧的过程中,需要动态生成一个EdgeNode类型的弧,然后将它的成员分别赋值即可。弧头相同的弧将都插入到顶点结点的指针域firstout的后面;而弧尾相同的弧将插入到顶点结点的指针域firstin的后面。
存储结构为十字链表的图深入优先遍历的递归实现:
// 深度优先遍历 递归实现
void DFS_Recursion( OLGraph * G , int v )
{
if( !visited[v] )
{
printf("%c " , G->vertices[v].data ) ;
visited[v] = true ;
}
EdgeNode * edge = G->vertices[v].firstout ;
while( edge != NULL )
{
if( !visited[edge->headvex] )
{
DFS_Recursion( G , edge->headvex ) ;
visited[edge->headvex] = true ;
}
edge = edge->taillink ;
}
}
// 针对非连通图
void DFS_Recursion_Traverse( OLGraph * G )
{
for( int i = 0 ; i < G->vexnum ; i++ )
{
if( !visited[i] )
{
DFS_Recursion( G , i ) ;
}
}
}
其中全局变量bool visited[MaxVex]标示是否顶点已被访问。
深入优先遍历的思想是:从图中某个顶点出发,访问此顶点,然后依次从v的未被访问的邻接点出发深度优先遍历,直至图中所有和v有路径相通的顶点都被访问到;若此时图中还有未被访问到的顶点,则选择图中一个未被访问的顶点作为起始顶点,重复上述过程,直至图中的所有顶点都已经被访问完。
深度优先遍历的实现步骤:
1.选择一个顶点入栈
2.若栈非空,出栈并访问出栈元素,标示为已访问。
3.将出栈顶点的邻接顶点(要求未被访问过)全部入栈。
4.重复第2步骤直至没有可以访问到的顶点。
5.重复第一步
这可看出深度优先遍历是一个递归的过程,对每一个顶点进行深度优先遍历,操作都是DFS_Recursion(),只是起始点不同而已。那么可以用栈来模拟整个递归的过程。代码将在最后的代码中给出。
存储结构为十字链表的图的宽度优先遍历的实现:
// 存储结构为十字链表的图的宽度优先遍历 void BFS( OLGraph * G , int v , bool *visited ) { int i = 0 ; Queue Q ; InitQueue( &Q ) ; // 初始化队列 EnQueue( &Q , v ) ; while( !is_queue_empty( &Q ) ) { DeQueue( &Q , &i ) ; if( !visited[i] ) { printf("%c " , G->vertices[i].data ) ; visited[i] = true ; } EdgeNode * edge = G->vertices[i].firstout ; while( edge != NULL ) { if( !visited[edge->headvex] ) { EnQueue( &Q , edge->headvex ) ; } edge = edge->taillink ; } } } void BFS_Traverse( OLGraph * G ) { bool visited[MaxVex] ; for( int i = 0 ; i < G->vexnum ; i++ ) { visited[i] = false ; // 初始化,表示都未被访问 } for( i = 0 ; i < G->vexnum ; i++ ) { BFS( G , i , visited ) ; visited[i] = true ; } }
图的宽度优先遍历用得到了队列,就是因为队列有“先进先出”的特性,与图的深度优先遍历用到栈的“先进后出”的道理是一样的。
宽度优先遍历的思想是:从图中某个顶点v出发,在访问了v之后依次访问v的各个未被访问过的邻接顶点,然后分别从这些邻接点出发依次访问他们的邻接点,并使“先被访问的顶点的邻接点”先于后被访问的顶点的临接点“被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若图中尚有顶点未被访问,另选图中一个未被访问过的邻接点作为起始点,重复以上的步骤。
实现步骤如下:
1.选择一个未被访问的顶点入队。
2.当队列非空,出队并访问,标记为已被访问。
3.将出队顶点的邻接顶点都入队。
4.重复2步骤直至没有可以访问的顶点。
5.重复第1步骤。
所有的代码如下:
//////////////////////////////////////////
//
// 十字链表存储结构的图的实现
//
////////////////////////////////////////////
////////////////
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define MaxVex 20
typedef int EdgeType ; // 定义弧信息的类型
typedef char VertexType ; // 定义顶点的类型
typedef struct EdgeNode // 弧结点的定义
{
int tailvex ; // 弧尾结点的下标
int headvex ; // 弧头结点的下标
struct EdgeNode * headlink ; // 指向弧头相同的下一条弧的链域
struct EdgeNode * taillink ; // 指向弧尾相同的下一条弧的链域
EdgeType info ; // 弧中所含的信息
} EdgeNode ;
typedef struct VertexNode // 顶点结点的定义
{
int index ; // 下标值
VertexType data ; // 顶点内容
EdgeNode * firstout ; // 顶点的第一条出弧
EdgeNode * firstin ; // 顶点的第一条入弧
} VertexNode ;
typedef struct OLGraph // 十字链表存储结构的有向图定义
{
VertexNode vertices[MaxVex] ;
int vexnum ;
int arcnum ;
} OLGraph ;
typedef struct Queue // 循环队列的定义
{
int data[MaxVex] ;
int front ;
int rear ;
} Queue ;
typedef struct Stack // 栈的定义
{
int data[MaxVex] ;
int top ;
int base ;
} Stack ;
void InitStack( Stack * S ) // 初始化栈
{
S->top = 0 ;
S->base = 0 ;
}
bool is_stack_empty( Stack * S )
{
if( S->base == S->top )
{
return true ;
}
return false ;
}
bool is_stack_full( Stack * S )
{
if( S->top - S->base + 1 == MaxVex )
{
return true ;
}
return false ;
}
void Push( Stack * S , int elem )
{
if( !is_stack_full( S ) )
{
S->data[S->top++] = elem ;
}
}
void Pop( Stack * S , int *elem )
{
if( !is_stack_empty( S ) )
{
*elem = S->data[--S->top] ;
}
}
void InitQueue( Queue * Q )
{
Q->front = 0 ;
Q->rear = 0 ;
}
bool is_queue_empty( Queue * Q )
{
if( Q->front == Q->rear )
{
return true ;
}
return false ;
}
bool is_queue_full( Queue * Q )
{
if( ( Q->rear + 1 ) % MaxVex == Q->front )
{
return true ;
}
return false ;
}
void EnQueue( Queue * Q , int elem )
{
if( !is_queue_full( Q ) )
{
Q->data[Q->rear] = elem ;
Q->rear = ( Q->rear + 1 ) % MaxVex ;
}
}
void DeQueue( Queue * Q , int * elem )
{
if( !is_queue_empty( Q ) )
{
*elem = Q->data[Q->front] ;
Q->front = ( Q->front + 1 ) % MaxVex ;
}
}
bool visited[MaxVex] ; // 针对于图的深度遍历的递归算法,需要用到全局变量俩标示该顶点是否被访问过
void CreateGraph( OLGraph * G )
{
// 输入弧的数目与顶点的数目
G->vexnum= 6 ;
G->arcnum = 7 ;
// 输入顶点的信息
printf("请输入顶点信息:") ;
for( int i = 0 ; i < G->vexnum ; i++ )
{
scanf("%c" , &G->vertices[i].data ) ;
G->vertices[i].index = i ;
G->vertices[i].firstin = NULL ;
G->vertices[i].firstout = NULL ;
}
// 输入弧的信息
printf("请输入边的信息:") ;
for( int j = 0 ; j < G->arcnum ; j++ )
{
EdgeNode * edge = (EdgeNode*)malloc(sizeof(EdgeNode)) ;
edge->taillink = NULL ;
edge->headlink = NULL ;
scanf("%d %d",&edge->tailvex , &edge->headvex ) ;
// 将该弧插入到当前顶点的出弧中
EdgeNode * p = G->vertices[edge->tailvex].firstout ;
if( p == NULL ) // 第一次插入边结点
{
G->vertices[edge->tailvex].firstout = edge ;
}
else
{
while( p != NULL )
{
if( p->taillink == NULL )
{
break ;
}
p = p->taillink ;
}
p->taillink = edge ;
}
// 将该弧插入到当前顶点的入弧中
EdgeNode * q = G->vertices[edge->headvex].firstin ;
if( q == NULL )
{
G->vertices[edge->headvex].firstin = edge ;
}
else
{
while( q->headlink != NULL )
{
if( q->headlink == NULL )
{
break ;
}
q = q->headlink ;
}
q->headlink = edge ;
}
}
}
void InitVisit( OLGraph * G , bool * visited )
{
for( int i = 0 ; i < G->vexnum ; i++ )
{
visited[i] = false ;
}
}
// 深度优先遍历 递归实现
void DFS_Recursion( OLGraph * G , int v )
{
if( !visited[v] )
{
printf("%c " , G->vertices[v].data ) ;
visited[v] = true ;
}
EdgeNode * edge = G->vertices[v].firstout ;
while( edge != NULL )
{
if( !visited[edge->headvex] )
{
DFS_Recursion( G , edge->headvex ) ;
visited[edge->headvex] = true ;
}
edge = edge->taillink ;
}
}
// 针对非连通图
void DFS_Recursion_Traverse( OLGraph * G )
{
for( int i = 0 ; i < G->vexnum ; i++ )
{
if( !visited[i] )
{
DFS_Recursion( G , i ) ;
}
}
}
//深度优先遍历的非递归算法
void DFS( OLGraph * G , int v , bool * visited )
{
int i =0 ;
Stack S ;
InitStack( &S ) ; // 初始化栈
Push( &S , v ) ;
while( !is_stack_empty( &S ) )
{
Pop( &S , &i ) ;
if( !visited[i] )
{
printf("%c " , G->vertices[i].data ) ;
visited[i] = true ;
}
EdgeNode * edge = G->vertices[i].firstout ;
while( edge != NULL )
{
if( !visited[edge->headvex] )
{
Push( &S , edge->headvex ) ;
}
edge = edge->taillink ;
}
}
}
// 非递归遍历的图的深度优先遍历
void DFS_Traverse( OLGraph * G )
{
bool visited[MaxVex] ; // 标示是否已经被访问
for( int i = 0 ; i < G->vexnum ; i++ )
{
visited[i] = false ; // 初始化,标示都未被访问
}
for( i = 0 ; i < G->vexnum ; i++ )
{
if( !visited[i] )
{
DFS( G , i , visited ) ;
visited[i] = true ;
}
}
}
// 存储结构为十字链表的图的宽度优先遍历
void BFS( OLGraph * G , int v , bool *visited )
{
int i = 0 ;
Queue Q ;
InitQueue( &Q ) ; // 初始化队列
EnQueue( &Q , v ) ;
while( !is_queue_empty( &Q ) )
{
DeQueue( &Q , &i ) ;
if( !visited[i] )
{
printf("%c " , G->vertices[i].data ) ;
visited[i] = true ;
}
EdgeNode * edge = G->vertices[i].firstout ;
while( edge != NULL )
{
if( !visited[edge->headvex] )
{
EnQueue( &Q , edge->headvex ) ;
}
edge = edge->taillink ;
}
}
}
void BFS_Traverse( OLGraph * G )
{
bool visited[MaxVex] ;
for( int i = 0 ; i < G->vexnum ; i++ )
{
visited[i] = false ; // 初始化,表示都未被访问
}
for( i = 0 ; i < G->vexnum ; i++ )
{
BFS( G , i , visited ) ;
visited[i] = true ;
}
}
int main()
{
OLGraph G ;
CreateGraph( &G ) ;
DFS_Recursion_Traverse( &G ) ;
printf("\n") ;
DFS_Traverse( &G ) ;
printf("\n") ;
BFS_Traverse( &G ) ;
printf( "\n" ) ;
return 0 ;
}