数据结构之有向图的操作
图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
线性表中我们把数据元素叫做元素,树中将数据元素叫结点,在图中数据元素称之为顶点(Vertxt)。
线性表中可以没有数据元素,称为空表。树中可以没有结点,叫做空树。那么对于图呢?在图结构中,不允许没有顶点。在定义中,若V是顶点的集合,则强调了顶点集合V有穷非空。
线性表中,相邻的数据元素之间具有线性关系,树结构中,相邻两层的结点具有层次关系,在图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示。边集可以是空的。
邻接表
图的邻接表存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。如词条概念图所示,表结点存放的是邻接顶点在数组中的索引。对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。
邻接表是图的一种最主要存储结构,用来描述图上的每一个点。对图的每个顶点建立一个容器(n个顶点建立n个容器),第i个容器中的结点包含顶点Vi的所有邻接顶点。实际上我们常用的邻接矩阵就是一种未离散化每个点的边集的邻接表。
该图采用邻接表存储
1.存储结构定义为
typedef struct ArcNode{
int adjvex;//该弧所指向的顶点的位置
struct ArcNode *nextarc;//指向下一条弧的指针
int *info;//该弧相关的信息指针
}ArcNode;
typedef struct VNode{
int data;//顶点信息
ArcNode *firstarc;//指向第一条依附该顶点的弧指针
}VNode,AdjList[Max_num];
typedef struct{
AdjList vertices;
int vexnum,arcnum;//图当前顶点数和边数
int kind;//图的种类标志
}ALGraph;
2.构造图的邻接表
int CreateUDG(ALGraph &G){
printf("请输入总结点数和总边数:") ;
scanf("%d %d",&G.vexnum,&G.arcnum);//输入总顶点数,总边数
printf("输入各顶点的值:");
for(int i = 1;i <=G.vexnum;i++)
{//输入各顶点,构造表头结点表
scanf("%d",&G.vertices[i].data);//输入顶点值
G.vertices[i].firstarc=NULL;//初始化表头结点的指针域
}//for
int v[2*G.arcnum]={0};
printf("输入弧尾和弧头:\n");
for(int i=0;i<2*G.arcnum;i++){
scanf("%d",&v[i]);//保存弧头和弧尾
}
for(int k=1,m=0;k<=G.arcnum;k++,m+=2){
int i=LocateVex(G,v[m]);
int j=LocateVex(G,v[m+1]); //确定顶点在G.vertices中的序号
ArcNode *p1=(ArcNode*)malloc(sizeof(ArcNode)); //生成一个新的边结点*p1
p1->adjvex=j; //邻接点序号为j
//头插法将新结点*p1插入顶点vi的边表头部
p1->nextarc=G.vertices[i].firstarc;
G.vertices[i].firstarc=p1;
}//for
printf("创建成功!");
flag=1;
return OK;
}//CreateUDG
3.打印邻接表
int ShowALGraph(ALGraph G){//打印领接表
if(flag!=1){
printf("请先见图!");
return -1;
}
printf("该图的领接表为:\n");
for(int i=1;i<=G.vexnum;i++){
ArcNode*p=G.vertices[i].firstarc;
if(p)printf("[%d]->",G.vertices[i].data);
else printf("[%d]…NULL",G.vertices[i].data);
while(p){
ArcNode*q=p->nextarc;
if(q!=NULL)printf("%d ->",p->adjvex);
else printf("%d",p->adjvex);
p=p->nextarc;
}
printf("\n");
}
return OK;
}
4.深度优先遍历图
void DFSTraverse(ALGraph G,int u){//对图进行深度优先遍历
for(int i=1;i<=G.vexnum;i++){
visited[i]=0;//访问标志数组赋初值,false表示未访问
}
if(!visited[u])DFS(G,u);//指定选择一个未访问过的顶点开始进行访问
for(int i=1;i<=G.vexnum;++i){
if(!visited[i])DFS(G,i);//若图非联通,确保所有顶点能够全部遍历
}
}
void DFS(ALGraph G,int v){//从第v个顶点开始对图进行深度优先遍历
visited[v]=1;
printf("%d",G.vertices[v].data);
printf(">>");
for(int w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w)){
if(!visited[w])DFS(G,w);
}
}
5.广度优先遍历图
void BFSTraverse(ALGraph G,int u){//从第u个顶点开始对图进行广度优先遍历
queue<int>q;
for(int i=1;i<=G.vexnum;i++){
visited[i]=0;//访问标志数组赋初值,false表示未访问
}
for(int v=u,c=0;v<=G.vexnum;v++,c++){//从第u个开始进行遍历
if(!visited[v]){
visited[v]=1;
printf("%d",G.vertices[v].data);
printf(">>");
q.push(v);
while(!q.empty()){
int k=q.front();
q.pop();//将对头元素去出并保留顶点信息
for(int w=FirstAdjVex(G,k);w>=0;w=NextAdjVex(G,k,w)){
if(!visited[w]){
visited[w]=1;
printf("%d",G.vertices[w].data);
printf(">>");
q.push(w);
}
}
}
}
if(v==G.vexnum)v=0;
if(c==G.vexnum)break;//防止图不连通,将顶点遗漏
}
}
6.深度遍历中的三个定位算法
int LocateVex(ALGraph G,int v){//定位点在顶点表的位置
for(int i=1;i<=G.vexnum;i++){
if(v==G.vertices[i].data)return i;
}
return OK;
}
int FirstAdjVex(ALGraph G,int v){//返回v的第一个领接顶点,若没有则返回空
if(G.vertices[v].firstarc)return G.vertices[v].firstarc->adjvex;
return -1;
}
int NextAdjVex(ALGraph G,int v,int w){//返回v的下一个领接顶点(相对于w的)若w是最后一个,则返回空
ArcNode *p;
p=G.vertices[v].firstarc;
while(p){
if(p->adjvex==w)break;
p=p->nextarc;
}
if(p->adjvex!=w||!p->nextarc)return -1;
return p->nextarc->adjvex;
}
全部算法为
#include <iostream>
#include<stdio.h>
#include<queue>
#define OK 1
#define ERROR 0
#include<malloc.h>
using namespace std;
#define Max_num 100
typedef struct ArcNode{
int adjvex;//该弧所指向的顶点的位置
struct ArcNode *nextarc;//指向下一条弧的指针
int *info;//该弧相关的信息指针
}ArcNode;
typedef struct VNode{
int data;//顶点信息
ArcNode *firstarc;//指向第一条依附该顶点的弧指针
}VNode,AdjList[Max_num];
typedef struct{
AdjList vertices;
int vexnum,arcnum;//图当前顶点数和边数
int kind;//图的种类标志
}ALGraph;
int visited[Max_num];//访问标志数组
int CreateUDG(ALGraph &G);//用领接表建立一个图
int LocateVex(ALGraph G,int v);//定位点在顶点表的位置
int ShowALGraph(ALGraph G);//打印领接表
void DFSTraverse(ALGraph G,int u);//从第u个顶点开始对图进行深度优先遍历
void DFS(ALGraph G,int v);//从第v个顶点开始对图进行深度优先遍历
void BFSTraverse(ALGraph G,int u);//从第u个顶点开始对图进行广度优先遍历
int FirstAdjVex(ALGraph G,int v);//返回v的第一关领接顶点,若没有则返回空
int NextAdjVex(ALGraph G,int v,int w);//返回v的下一个领接顶点(相对于w的)若w是最后一个,则返回空
int OperateMenu();//建立菜单,提示操作代码
int flag=0;
int main()
{
ALGraph G;
char ch;
int i;
printf("是否要进行操作?(继续输入Y或者y,否则结束)\n");
scanf("%c",&ch);
while(ch=='Y'||ch=='y'){
switch(OperateMenu()){
case 1:
CreateUDG(G);
break;
case 2:
ShowALGraph(G);
break;
case 3:
if(flag!=1){
printf("请先建图!");
break;
}
printf("输入需要查找的顶点:");
scanf("%d",&i);
printf("该顶点所处位置为%d",LocateVex(G,i));
break;
case 4:
if(flag!=1){
printf("请先建图!");
break;
}
printf("输入遍历起点:");
scanf("%d",&i);
printf("遍历结果为");
DFSTraverse(G,i);
break;
case 5:
if(flag!=1){
printf("请先建图!");
break;
}
printf("输入顶点位置:");
scanf("%d",&i);
printf("下一个是%d",FirstAdjVex(G,i));
break;
case 6:
if(flag!=1){
printf("请先建图!");
break;
}
printf("输入顶点位置和已访问的位置:");
int w;
scanf("%d %d",&i,&w);
printf("另一个为%d",NextAdjVex(G,i,w));
break;
case 7:
if(flag!=1){
printf("请先建图!");
break;
}
printf("输入遍历起点:");
scanf("%d",&i);
printf("遍历结果为");
BFSTraverse(G,i);
break;
}
printf("\n是否还要继续操作?(继续输入Y或者y,否则结束)\n");
ch=getchar();
scanf("%c",&ch);
}
}
int CreateUDG(ALGraph &G){
printf("请输入总结点数和总边数:") ;
scanf("%d %d",&G.vexnum,&G.arcnum);//输入总顶点数,总边数
printf("输入各顶点的值:");
for(int i = 1;i <=G.vexnum;i++)
{//输入各顶点,构造表头结点表
scanf("%d",&G.vertices[i].data);//输入顶点值
G.vertices[i].firstarc=NULL;//初始化表头结点的指针域
}//for
int v[2*G.arcnum]={0};
printf("输入弧尾和弧头:\n");
for(int i=0;i<2*G.arcnum;i++){
scanf("%d",&v[i]);//保存弧头和弧尾
}
for(int k=1,m=0;k<=G.arcnum;k++,m+=2){
int i=LocateVex(G,v[m]);
int j=LocateVex(G,v[m+1]); //确定顶点在G.vertices中的序号
ArcNode *p1=(ArcNode*)malloc(sizeof(ArcNode)); //生成一个新的边结点*p1
p1->adjvex=j; //邻接点序号为j
//头插法将新结点*p1插入顶点vi的边表头部
p1->nextarc=G.vertices[i].firstarc;
G.vertices[i].firstarc=p1;
}//for
printf("创建成功!");
flag=1;
return OK;
}//CreateUDG
int LocateVex(ALGraph G,int v){//定位点在顶点表的位置
for(int i=1;i<=G.vexnum;i++){
if(v==G.vertices[i].data)return i;
}
return OK;
}
int ShowALGraph(ALGraph G){//打印领接表
if(flag!=1){
printf("请先见图!");
return -1;
}
printf("该图的领接表为:\n");
for(int i=1;i<=G.vexnum;i++){
ArcNode*p=G.vertices[i].firstarc;
if(p)printf("[%d]->",G.vertices[i].data);
else printf("[%d]…NULL",G.vertices[i].data);
while(p){
ArcNode*q=p->nextarc;
if(q!=NULL)printf("%d ->",p->adjvex);
else printf("%d",p->adjvex);
p=p->nextarc;
}
printf("\n");
}
return OK;
}
void DFSTraverse(ALGraph G,int u){//对图进行深度优先遍历
for(int i=1;i<=G.vexnum;i++){
visited[i]=0;//访问标志数组赋初值,false表示未访问
}
if(!visited[u])DFS(G,u);//指定选择一个未访问过的顶点开始进行访问
for(int i=1;i<=G.vexnum;++i){
if(!visited[i])DFS(G,i);//若图非联通,确保所有顶点能够全部遍历
}
}
void DFS(ALGraph G,int v){//从第v个顶点开始对图进行深度优先遍历
visited[v]=1;
printf("%d",G.vertices[v].data);
printf(">>");
for(int w=FirstAdjVex(G,v);w>=0;w=NextAdjVex(G,v,w)){
if(!visited[w])DFS(G,w);
}
}
void BFSTraverse(ALGraph G,int u){//从第u个顶点开始对图进行广度优先遍历
queue<int>q;
for(int i=1;i<=G.vexnum;i++){
visited[i]=0;//访问标志数组赋初值,false表示未访问
}
for(int v=u,c=0;v<=G.vexnum;v++,c++){//从第u个开始进行遍历
if(!visited[v]){
visited[v]=1;
printf("%d",G.vertices[v].data);
printf(">>");
q.push(v);
while(!q.empty()){
int k=q.front();
q.pop();//将对头元素去出并保留顶点信息
for(int w=FirstAdjVex(G,k);w>=0;w=NextAdjVex(G,k,w)){
if(!visited[w]){
visited[w]=1;
printf("%d",G.vertices[w].data);
printf(">>");
q.push(w);
}
}
}
}
if(v==G.vexnum)v=0;
if(c==G.vexnum)break;//防止图不连通,将顶点遗漏
}
}
int FirstAdjVex(ALGraph G,int v){//返回v的第一个领接顶点,若没有则返回空
if(G.vertices[v].firstarc)return G.vertices[v].firstarc->adjvex;
return -1;
}
int NextAdjVex(ALGraph G,int v,int w){//返回v的下一个领接顶点(相对于w的)若w是最后一个,则返回空
ArcNode *p;
p=G.vertices[v].firstarc;
while(p){
if(p->adjvex==w)break;
p=p->nextarc;
}
if(p->adjvex!=w||!p->nextarc)return -1;
return p->nextarc->adjvex;
}
int OperateMenu(){//建立菜单,提示操作代码
int num;
printf("-------------------------------------------------------------------------------------\n");
printf("1:建立图的领接表!\t2:打印图的领接表!\t\t3:返回顶点在顶点表中的位置!\n4:深度优先遍历图!\t5:输出顶点的第一个领接点!\t6:输出顶点除w外的其他领接点 !\n7:广度优先遍历图!\n");//提供菜单选项
printf("-------------------------------------------------------------------------------------\n");
scanf("%d",&num);
for(int i=0;;i++){
if(num<1||num>7){printf("选择错误!");return -1;}
else if(num==1){return num;break;}
else if(num==2){return num;break;}
else if(num==3){return num;break;}
else if(num==4){return num;break;}
else if(num==5){return num;break;}
else if(num==6){return num;break;}
else if(num==7){return num;break;}
}
}