图的创建和遍历

  • 图的定义: 由顶点的有穷非空集合和顶点之间边的集合组成的数据类型
  • 图的表示:G(V,E),G表示一个图,V是图G的顶点集合,E为图G的边的集合
  • 图的逻辑结构:多对多

  • 图的存储结构:邻接矩阵 邻接表 十字链表 邻接多重表

  • 图的一些无聊术语:
    1. 顶点i与j之间的边无方向,则称此边为无向边(Edge),无向边构成的图成为无向图,无序偶表示(i,j)
    2. 若i到j有方向,则叫有向边,也成为弧(Arc),i叫弧尾,j叫弧头,有序偶表示:<i,j>
    3. 任意两顶点都存在边的图称为(有向|无向)完全图
    4. 边少的图成为稀疏图,反之稠密图
    5. 带权的图称为网
    6. 以顶点v为头的弧的数目称为v的入度,对应的叫出度
    7. 路径:从顶点a到顶点b的一个序列叫路径
    8. 路径长度:路径上的边或者弧的数目
    9. 连通:从顶点i到顶点j有路径,则称i和j是连通的
    10. 连通图:图中任何两个顶点都是连通的图

  • 图的创建一般用邻接表或者邻接矩阵,下面依次给出图的邻接矩阵和邻接表的创建及两种遍历
  • 图的遍历方式有两种:深度优先遍历(DFS) 广度优先遍历(BFS)

邻接表创建并遍历图代码(粘贴即可运行):

#include <iostream>
#include <stdlib.h>
using namespace std;
#define maxSize 100
int visit[maxSize];
typedef struct EdgeNode{//边结点(其实就是尾顶点) 
    int adjvex;//当前顶点下标 
    int weight;//边的权值 
    struct EdgeNode *next;//指向下一个边结点的指针 
}EdgeNode;

typedef struct VNode{//顶点结点 
    int data;//数据域 
    EdgeNode *firstEdg;//指向与之相邻接的边结点 
}VNode;

typedef struct AGraph{//图结点 
    VNode adjList[maxSize];//顶点数组 
    int numVNode,numEdge;//顶点数和边数 
}AGraph;

void createGraph(AGraph &G){//创建无向图 (因为要真正改变G,所以要传引用&) 
    int numVNode,numEdge,i,j,k;
    EdgeNode *s,*p;
    printf("请输入顶点个数和边数\n");
    scanf("%d %d",&numVNode,&numEdge);
    G.numEdge=numEdge;
    G.numVNode=numVNode; 
    printf("请输入顶点\n");
    for(i=0;i<numVNode;i++){//输入顶点 
        scanf("%d",&G.adjList[i].data);
        G.adjList[i].firstEdg=NULL;
    }

    printf("请输入边(vi,vj)的顶点的下标,格式为:i j\n");
    for(k=0;k<numEdge;k++){

        scanf("%d %d",&i,&j);
        s=(EdgeNode *)malloc(sizeof(EdgeNode));
        s->adjvex=j;
        s->next=G.adjList[i].firstEdg;
        G.adjList[i].firstEdg=s;
        p=(EdgeNode *)malloc(sizeof(EdgeNode));
        p->adjvex=i;
        p->next=G.adjList[j].firstEdg;
        G.adjList[j].firstEdg=p;
    } 
}
void dfs(AGraph G,int i){//深度优先遍历顶点i(类似二叉树的前序遍历) 
    printf("%d ",G.adjList[i].data);//先访问顶点 
    visit[i]=1;//然后标记被访问顶点 
    EdgeNode *p;
    p=G.adjList[i].firstEdg; 
    while(p!=NULL){//接着遍历被访问顶点的边表 
        if(visit[p->adjvex]!=1){//若顶点未访问 
            dfs(G,p->adjvex);//则递归调用 
        }
        p=p->next;
    }
} 

void dfsTravering(AGraph G){//dfs方法中只能遍历连通图的所有顶点,而此方法则能遍历有独立顶点的图的所有顶点 
    for(int i=0;i<G.numVNode;i++){
        visit[i]=0;//标记数组初始化 
    }
    printf("深度优先遍历的结果为:\n");
    for(int i=0;i<G.numVNode;i++){//遍历所有顶点 
        if(visit[i]!=1){//未访问的顶点 
            dfs(G,i);//从下标为0的开始访问 
        } 
    }
    printf("\n");
}

void bfs(AGraph G,int v){//广度优先遍历下标为v的顶点及和其相邻接的顶点(类似二叉树的广度遍历) 
    int que[maxSize];//注:队列存放的都是顶点下标 
    int front,rear;front=rear;//初始化队头队尾指针 
    int i,j;
    EdgeNode *p;//边结点 
    printf("%d ",G.adjList[v].data);//先访问结点,再标记,接着入队 
    visit[v]=1;
    rear=(rear+1)%maxSize;//注:循环队列进出队都是先移指针再改数据 
    que[rear]=v;//顶点下标v入队 
    while(rear!=front){//循环条件:队不为空 
        front=(front+1)%maxSize;
        i=que[front];//出队 
        p=G.adjList[i].firstEdg;//出队顶点指向的边结点 
        while(p!=NULL){//遍历出队顶点的边表 
            if(visit[p->adjvex]!=1){//顶点未曾访问过 
                printf("%d ",G.adjList[p->adjvex].data);//访问顶点 
                visit[p->adjvex]=1;//标记 
                rear=(rear+1)%maxSize;//入队 
                que[rear]=p->adjvex;
            }
            p=p->next;//边结点往下走 
        }
    }
}

void bfsTravering(AGraph G){//广度优先遍历(当图不是连通图时需要这个方法) 
    for(int i=0;i<G.numVNode;i++){
        visit[i]=0;//初始化标记数组 
    }
    printf("广度优先遍历结果:\n");
    for(int i=0;i<G.numVNode;i++){
        if(visit[i]!=1){//不为1说明顶点未曾访问 
            bfs(G,i);
        }
    }
    printf("\n");
}
int main(int argc, char** argv) {
    AGraph G;
    createGraph(G);
    dfsTravering(G); 
    bfsTravering(G);
    return 0;
}
/*
示例输入:
顶点数:10 边数:13
顶点:0 1 2 3 4 5 6 7 8 9
边下标:
0 2
0 1
1 4
1 3
2 5
2 3
3 4
4 7
4 6
5 7
6 9
7 8
8 9
*/

邻接矩阵创建并遍历图代码(粘贴即可运行):

#include <iostream>
#define maxSize 100
#define infinity 65535
using namespace std;
bool visit[100]={false};
typedef struct {//图结点 
    int vertext[maxSize];//顶点数组 
    int edge[maxSize][maxSize]; //邻接矩阵(matrix) 
    int n,e;//图的顶点数和边数 
}MGraph; 

void createGraph(MGraph &G){//用邻接矩阵创建无向图 
    int i,j,k,weight;
    printf("请输入顶点数和边数\n");
    scanf("%d %d",&G.n,&G.e);
    printf("请输入顶点:\n");
    for(i=0;i<G.n;i++){
        scanf("%d",&G.vertext[i]);//输入顶点 
    } 
    for(i=0;i<G.n;i++){
        for(j=0;j<G.n;j++){//初始化数组 
            G.edge[i][j]=infinity;
        }
    } 
    printf("请输入边的顶点下标i j及边的权重w\n");
    for(k=0;k<G.e;k++){
        scanf("%d %d %d",&i,&j,&weight);
        G.edge[i][j]=weight;
        G.edge[j][i]=G.edge[i][j];//无向图的邻接矩阵是对称的 
    }
}

void dfs(MGraph G,int v){//深度优先遍历算法 (类似前序遍历) 
    int i;
    printf("%d ",G.vertext[v]);//先访问 
    visit[v]=true;//再标记 
    for(i=0;i<G.n;i++){//最后遍历所有没访问过且与下标为v的顶点相邻的顶点,递归dfs 
        if(!visit[i]&&G.edge[v][i]!=infinity){
            dfs(G,i);
        }
    } 
}

void dfsTravering(MGraph G){//若不是连通图,则需要一个个检查顶点是否遍历过(连通图不需要,直接调用dfs方法即可) 
    for(int i=0;i<G.n;i++){
        visit[i]=false;
    }
    for(int i=0;i<G.n;i++){
        if(!visit[i]){
            dfs(G,i);
        }
    }
}

void bfs(MGraph G,int v){//广度优先搜索遍历 
    int i,j;
    int que[maxSize];
    int front,rear;
    front=rear=0;
    printf("%d ",G.vertext[v]);
    visit[v]=true;
    rear=(rear+1)%maxSize;
    que[rear]=v;
    while(front!=rear){
        front=(front+1)%maxSize;
        i=que[front];
        for(j=0;j<G.n;j++){
            if(!visit[j]&&G.edge[i][j]!=infinity){//没有被访问且顶点之间相邻接
                printf("%d ",G.vertext[j]); 
                visit[j]=true;
                rear=(rear+1)%maxSize;
                que[rear]=j; 
            }
        }
    } 
}
int main(int argc, char** argv) {
    MGraph G;
    createGraph(G);
    dfs(G,8);
    dfsTravering(G);
    bfs(G,5);
    return 0;
}
/*
实例输入:
顶点数:10,边数:13
顶点:0 1 2 3 4 5 6 7 8 9
边顶点下标及权值:
0 2 4
0 1 3
1 4 6
1 3 5
2 5 7
2 3 8
3 4 3
4 7 4
4 6 9
5 7 6
6 9 2
7 8 5
8 9 3
*/

总结

深度优先遍历4部曲(dfs):

  1. 先访问给定下标为v的顶点
  2. 再标记已访问顶点下标
  3. 遍历已访问顶点的边表
  4. 边表的顶点下标未曾被标记,则递归调用dfs(G,v)

广度优先遍历6部曲(bfs) :

  1. 先访问给定顶点下标v的顶点
  2. 再标记下标为v的顶点
  3. 接着将下标为v的顶点入队
  4. 然后遍历下标为v的顶点的边表
  5. 随后将边表中未曾标记的顶点依次入队
  6. 一个一个出队,重复执行以上步骤
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值