图的学习
一、图的存储及BFS、DFS算法
1.1、邻接矩阵法
特点:
- 无向图的邻接矩阵一定是一个对称矩阵(并且唯一)。因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素。
- 对于无向图,邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数正好是顶点i的度TD(vi)。
- 对于有向图,邻接矩阵的第i行非零元素(或非∞元素)的个数正好是顶点i的出度OD(vi);第i列非零元素(或非∞元素)正好是顶点i的入度ID(vi)。
- 用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连。但是,要确定图中有多少边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大。
- 稠密图适合使用邻接矩阵的存储表示
- 设图G的邻接矩阵为A,An的元素An[i][j]等于由顶点i到顶点j的长度为n的路径的数目。
- 邻接矩阵实现BFS和DFS算法的空间复杂度均为O(|V|),时间复杂度均为O(|V|2)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MaxVertexNum 100 //顶点数目的最大值
#define Max 0x7FFFFFFF //int最大数
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
typedef struct{
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
int vexnum,arcnum;//图的当前顶点数和弧数
}MGragh;
//链表
typedef struct LinkNode{
int val;//数据域
struct LinkNode *next;//指针域
}LinkNode,*LinkedList;
//链式存储队列
typedef struct LinkedQueue{
LinkedList front,rear;
}LinkedQueue;
//初始化队列
void Init_LinkedQueue(LinkedQueue &q){
q.front=q.rear=(LinkedList)malloc(sizeof(LinkNode));//建立队头和队尾
q.front->next=NULL;
}
//判队空
bool is_Empty(LinkedQueue &q){
if(q.front==q.rear){
return true;
}
return false;
}
//入队
void EnQueue(LinkedQueue &q,int x){
LinkedList newNode=(LinkedList)malloc(sizeof(LinkNode));//新结点
newNode->val=x;
newNode->next=NULL;
//插入新结点
q.rear->next=newNode;
q.rear=newNode;
return;
}
//出队
bool DeQueue(LinkedQueue &q,int &x){
if(is_Empty(q)){
return false;
}
//获取被删结点
LinkedList p=q.front->next;
x=p->val;
//删除结点
q.front->next=p->next;
if(q.rear==p){
q.rear=q.front;
}
free(p);
return true;
}
//找出图G的x顶点的第一个邻接点
//若有则返回顶点号。若没有返回-1
int FirstNeighbor(MGragh G,int x){
if(x<0||x>=G.vexnum){//判断图是否存在以及查找顶点是否在表其中,不在的话直接返回-1
return -1;
}
for(int i=0;i<G.vexnum;i++){
if(G.Edge[x][i]==1){
return i;//查找到第一个邻接点
}
}
return -1;
}
//找出图G的x顶点的y的下一个邻接点
//若有则返回顶点号。若没有返回-1
int NextNeighbor(MGragh G,int x,int y){
if(x<0||x>=G.vexnum||y<0||y>=G.vexnum){//判断图是否存在以及查找顶点是否在表其中,不在的话直接返回-1
return -1;
}
for(int i=y+1;i<G.vexnum;i++){
if(G.Edge[x][i]==1){
return i;//查找y的下一个邻接点
}
}
return -1;
}
//深度优先遍历算法
void DFS(MGragh G,int v,bool* visited){
visited[v]=1;//将当前顶点标记为已访问
printf("%c->",G.Vex[v]);//打印该结点
for(int w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w]){
DFS(G,w,visited);
}
}
}
//对图进行深度优先遍历
void DFSTraverse(MGragh G,bool* visited){
for(int v=0;v<G.vexnum;v++){//从第一个结点开始遍历整个顶点表
if(!visited[v]){
DFS(G,v,visited);
}
}
printf("\n");
}
//广度优先遍历算法
void BFS(MGragh G,int v,bool* visited){
LinkedQueue q;
Init_LinkedQueue(q);//初始化队列
EnQueue(q,v);//初始顶点入队列
visited[v]=1;
printf("%c->",G.Vex[v]);//打印该结点
while(!is_Empty(q)){
DeQueue(q,v);//出队列
for(int w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w]){
EnQueue(q,w);
visited[w]=1;
printf("%c->",G.Vex[w]);//打印该结点
}
}
}
}
void BFSTraverse(MGragh G,bool* visited){
for(int v=0;v<G.vexnum;v++){//从第一个结点开始遍历整个顶点表
if(!visited[v]){
BFS(G,v,visited);
}
}
printf("\n");
}
int main(){
//测试图
MGragh mGragh={"ABCDEFGH",
{{0,1,0,0,1,0,0,0},
{1,0,0,0,0,1,0,0},
{0,0,0,1,0,1,1,0},
{0,0,1,0,0,0,1,1},
{1,0,0,0,0,0,0,0},
{0,1,1,0,0,0,1,0},
{0,0,1,1,0,1,0,1},
{0,0,0,1,0,0,1,0}},
8,
10};
bool visited[mGragh.vexnum];//定义访问顶点记录数组
memset(visited,0,sizeof(visited));
//DFS深度遍历算法
DFSTraverse(mGragh,visited);
memset(visited,0,sizeof(visited));
//BFS广度遍历算法
BFSTraverse(mGragh,visited);
//A->B->F->C->D->G->H->E->
//A->B->E->F->C->G->D->H->
}
1.2、邻接表法
特点:
- 若G为无向图,则所需的存储空间为O(|V|+2|E|);若G为有向图,则所需的存储空间为O(|V|+|E|)。前者倍数2是由于无向图中,每条边在邻接表中出现了两次。
- 对于稀疏图,采用邻接表表示将极大地节省存储空间。
- 在邻接表中,给定一顶点,能很容易地找出它所有邻边,因为只需要读取它的邻接表。在邻接表中,相同的操作则需要扫描一行,花费的时间为O(n)。但是,若要确定给定的两个顶点之间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接表中则需要在相应结点对应的边表中查找另一结点,效率极低。
- 在有向图的邻接表表示中,求一个给定顶点的度只需计算其邻接表中的结点个数;但求其顶点的入度则需要遍历全部的邻接表。因此,也有人采用逆邻接表的存储方式来加快求解给定顶点的入度。当然,这实际上与邻接表的存储方式是类似的。
- 图的邻接表表示并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType;//顶点类型
typedef struct ArcNode{//边表结点
int adjvex;//该弧所指向的顶点的为止
struct ArcNode *next;//指向下一条弧的指针
//InfoType info; //网的边权值
}ArcNode;
typedef struct VNode{//顶点表结点
VertexType data;//顶点信息
ArcNode *first;//指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct{
AdjList vertices;//邻接表
int vexnum,arcnum;//图的顶点数和弧数
}ALGraph;//ALGraph是以邻接表存储的图类型
//链表
typedef struct LinkNode{
int val;//数据域
struct LinkNode *next;//指针域
}LinkNode,*LinkedList;
//链式存储队列
typedef struct LinkedQueue{
LinkedList front,rear;
}LinkedQueue;
//初始化队列
void Init_LinkedQueue(LinkedQueue &q){
q.front=q.rear=(LinkedList)malloc(sizeof(LinkNode));//建立队头和队尾
q.front->next=NULL;
}
//判队空
bool is_Empty(LinkedQueue &q){
if(q.front==q.rear){
return true;
}
return false;
}
//入队
void EnQueue(LinkedQueue &q,int x){
LinkedList newNode=(LinkedList)malloc(sizeof(LinkNode));//新结点
newNode->val=x;
newNode->next=NULL;
//插入新结点
q.rear->next=newNode;
q.rear=newNode;
return;
}
//出队
bool DeQueue(LinkedQueue &q,int &x){
if(is_Empty(q)){
return false;
}
//获取被删结点
LinkedList p=q.front->next;
x=p->val;
//删除结点
q.front->next=p->next;
if(q.rear==p){
q.rear=q.front;
}
free(p);
return true;
}
//找出图G的x顶点的第一个邻接点
//若有则返回顶点号。若没有返回-1
int FirstNeighbor(ALGraph G,int x){
if(x<0||x>=G.vexnum){//判断图是否存在以及查找顶点是否在表其中,不在的话直接返回-1
return -1;
}
ArcNode* arc=G.vertices[x].first;//获取该弧
return arc==NULL?-1:arc->adjvex;
}
//找出图G的x顶点的y的下一个邻接点
//若有则返回顶点号。若没有返回-1
int NextNeighbor(ALGraph G,int x,int y){
if(x<0||x>=G.vexnum||y<0||y>=G.vexnum){//判断图是否存在以及查找顶点是否在表其中,不在的话直接返回-1
return -1;
}
ArcNode* arc=G.vertices[x].first;//获取该弧
while(arc!=NULL&&arc->adjvex!=y){
arc=arc->next;
}
if(arc==NULL){
return -1;
}else{
return arc->next==NULL?-1:arc->next->adjvex;
}
}
//深度优先遍历算法
void DFS(ALGraph G,int v,bool* visited){
visited[v]=1;//将当前顶点标记为已访问
printf("%c->",G.vertices[v].data);//打印该结点
for(int w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w]){
DFS(G,w,visited);
}
}
}
//对图进行深度优先遍历
void DFSTraverse(ALGraph G,bool* visited){
for(int v=0;v<G.vexnum;v++){//从第一个结点开始遍历整个顶点表
if(!visited[v]){
DFS(G,v,visited);
}
}
printf("\n");
}
//广度优先遍历算法
void BFS(ALGraph G,int v,bool* visited){
LinkedQueue q;
Init_LinkedQueue(q);//初始化队列
EnQueue(q,v);//初始顶点入队列
visited[v]=1;
printf("%c->",G.vertices[v].data);//打印该结点
while(!is_Empty(q)){
DeQueue(q,v);//出队列
for(int w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
if(!visited[w]){
EnQueue(q,w);
visited[w]=1;
printf("%c->",G.vertices[w].data);//打印该结点
}
}
}
}
void BFSTraverse(ALGraph G,bool* visited){
for(int v=0;v<G.vexnum;v++){//从第一个结点开始遍历整个顶点表
if(!visited[v]){
BFS(G,v,visited);
}
}
printf("\n");
}
int main(){
//初始化邻接表图
ALGraph g;
g.vexnum=8;
g.arcnum=10;
g.vertices[0].data='A';//结点A
ArcNode* a1=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[0].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[0].first->adjvex=1;
a1->adjvex=4;
a1->next=NULL;
g.vertices[0].first->next=a1;
g.vertices[1].data='B';//结点B
ArcNode* b1=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[1].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[1].first->adjvex=0;
b1->adjvex=5;
b1->next=NULL;
g.vertices[1].first->next=b1;
g.vertices[2].data='C';//结点C
ArcNode* c1=(ArcNode*)malloc(sizeof(ArcNode));
ArcNode* c2=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[2].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[2].first->adjvex=3;
c1->adjvex=5;
c2->adjvex=6;
c1->next=c2;
c2->next=NULL;
g.vertices[2].first->next=c1;
g.vertices[3].data='D';//结点D
ArcNode* d1=(ArcNode*)malloc(sizeof(ArcNode));
ArcNode* d2=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[3].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[3].first->adjvex=2;
d1->adjvex=6;
d2->adjvex=7;
d1->next=d2;
d2->next=NULL;
g.vertices[3].first->next=d1;
g.vertices[4].data='E';//结点E
g.vertices[4].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[4].first->adjvex=0;
g.vertices[4].first->next=NULL;
g.vertices[5].data='F';//结点F
ArcNode* f1=(ArcNode*)malloc(sizeof(ArcNode));
ArcNode* f2=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[5].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[5].first->adjvex=1;
f1->adjvex=2;
f2->adjvex=6;
f1->next=f2;
f2->next=NULL;
g.vertices[5].first->next=f1;
g.vertices[6].data='G';//结点G
ArcNode* g1=(ArcNode*)malloc(sizeof(ArcNode));
ArcNode* g2=(ArcNode*)malloc(sizeof(ArcNode));
ArcNode* g3=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[6].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[6].first->adjvex=2;
g1->adjvex=3;
g2->adjvex=5;
g3->adjvex=7;
g1->next=g2;
g2->next=g3;
g3->next=NULL;
g.vertices[6].first->next=g1;
g.vertices[7].data='H';//结点H
ArcNode* h1=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[7].first=(ArcNode*)malloc(sizeof(ArcNode));
g.vertices[7].first->adjvex=3;
h1->adjvex=6;
h1->next=NULL;
g.vertices[7].first->next=h1;
bool visited[g.vexnum];//定义访问顶点记录数组
memset(visited,0,sizeof(visited));
//DFS深度遍历算法
DFSTraverse(g,visited);
memset(visited,0,sizeof(visited));
//BFS广度遍历算法
BFSTraverse(g,visited);
//A->B->F->C->D->G->H->E->
//A->B->E->F->C->G->D->H->
}
1.3、十字链表法
十字链表是有向图的一种存储结构。
弧节点:tailvex(记录弧尾在图中的位置)、headvex(记录弧头在图中的位置)、hlink(指向弧头相同下一条弧)、tlink(指向弧尾相同的下一条弧)、info(指向该弧的相关信息)
顶点结点:data(存储顶点相关的数据信息)、firstin(指向该顶点为弧头的第一个弧结点)、firstout(指向该顶点为弧尾的第一个弧结点)
#include<stdio.h>
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode{//弧结点
int tailvex;//弧尾
int headvex;//弧头
ArcNode hlink*;//指向弧头相同的下一条弧
ArcNode tlink*;//指向弧尾相同的下一条弧
int info;//弧的相关信息
}ArcNode;
typedef struct VertexNode{//顶点
int data;// 存储顶点相关的数据信息
ArcNode firstin*;//指向该顶点为弧头的第一个弧结点
ArcNode firstout*;//指向该顶点为弧尾的第一个弧结点
}VertexNode,CrossList[MaxVertexNum];
typedef struct{
CrossList crossList;//十字链表
int vexnum,arcnum;//图的顶点数和弧数
}ALGraph;//ALGraph是以十字链表存储的图类型
1.4、邻接多重表法
邻接多重表是无向图的另一种存储结构。
弧节点:mark(标志域,可以标记该条边是否被搜索过);ivex(记录弧尾在图中的位置)、ilink(指向下一条依附于顶点ivex的边)、jvex(记录弧头在图中的位置)、jlink(指向下一条依附于点jvex的边)、info(为指向和边的各种信息的指针域)
顶点结点:data(存储顶点相关的数据信息)、firstedge(指示第一条依附于该顶点的边)。
#include<stdio.h>
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode{//弧结点
bool mark;//弧尾
int ivex;//弧头
ArcNode ilink*;//指向弧头相同的下一条弧
int jvex;//弧尾
ArcNode jlink*;//指向弧尾相同的下一条弧
int info;//弧的相关信息
}ArcNode;
typedef struct VertexNode{//顶点
int data;// 存储顶点相关的数据信息
ArcNode firstedge*;//指向该顶点为弧头的第一个弧结点
}VertexNode,MultiList[MaxVertexNum];
typedef struct{
MultiList multiList;//邻接多重表
int vexnum,arcnum;//图的顶点数和弧数
}ALGraph;//ALGraph是以邻接多重表存储的图类型