第六章学习小结

重点:邻接矩阵、邻接表的存储结构即创建代码;BFS、DFS算法的方法代码;Prim、Kruskal算法的方法

一、定义
(1)图G由V和E组成,记为G=(V,E),其中V是顶点的有穷非空集合,E是V中顶点偶对的又穷集合,这些顶点偶对称为边。
(2)V(G)通常表示图G的顶点集合,E(G)通常表示图G的边集合。
(3)E(G)可以为空集,则图G只有顶点而没有边。
二、分类
(1)有向图:边集E(G)为有向边的集合,该图为有向图。<x,y>,x是有向边的始点,y是有向边的终点。
(2)无向图:边集E(G)为无向边的合集,该图为无向图。(x,y)是顶点x与顶点y相关联的一条边。
三、基本术语
(1)子图:假设有两个图G=(V,E)和G'=(V',E'),如果V'属于V,且E'属于E,则称G'为G的子图
(2)无向完全图和有向完全图:对于无向图,若具有n(n-1)/2条边,则成为无向完全图。对于有向图,若具有n(n-1)条弧,则称之为有向完全图
(3)稀疏图和稠密图:e<nlog2n的图称为稀疏图,反之称为稠密图
(4)权:每条边可以标上具有目中含义的数值,该数值称为该边上的权
(5)网:带权的图通常称为网
(6)邻接点:对于无向图G,如果图的边(v,v')属于E,则称顶点v和v'互为邻接点,即v和v'相邻接。边(v,v')依附于顶点v和v'或者说边(v,v')于顶点v和v'相关联
(7)度、入度和出度:顶点v的度是指和v相关联的边的数目,记为TD(v)。对于有向图,顶点v的度分为入度和出度。入度是以顶点v为头的弧的数目,记为ID(v);出度是以顶点v为尾的弧的数目
(8)路径:在无向图G中,从顶点v到顶点v'的路径是一个顶点序列,其中(vi,j-1,vi,j)属于E,1<=j<=m
(9)路径长度:一条路径上经过的边或弧的数目
(10)简单路径:顶点不重复出现的路径
(11)简单回路或简单环:除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路
(12)连通:在无向图G中,如果从顶点v到顶点v'有路径,则称v和v'是连通的
(13)连通图:图中任意两个顶点vi、vj属于V,vi和vj都是连通的,则称G是连通图
(14)连通分量:无向图中的极大连通子图
(15)强连通图:在有向图G中,如果对于每一对vi,vj属于V,vi不等于vj,从vi到vj和从vj到vi都存在路径,则称图G是强连通图
(16)强连通分量:有向图中的极大连通子图称作有向图的强连通分量
(17)连通图的生成树:一个极小连通子图,它含有图中全部顶点,但只有足以构成一棵树的n-1条边
(18)有向树:有一个顶点的入度为0,起浴顶点的入度均为1的有向图
(19)生成森林:有若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧
四、存储结构
(1)邻接矩阵

1.存储结构

#define MaxInt 32767 //表示极大值,即无穷 
#define MVNum 100 //最大顶点数 
typedef char VerTexType; //假设顶点的数据类型为字符型 
typedef int ArcType; //假设边的权值类型为整型 
typedef struct
{
    VerTexType vexs[MVNum]; //顶点表 
    ArcType arcs[MVNum][MVNum]; //邻接矩阵 
    int vexnum,arcnum; //图的当前点数和边数 
}AMGraph;
View Code

2.创建无向图

void CreateUDN(AMGraph &G) //因为要改变图的内容,所以要加&
{//采用邻接矩阵表示法,创建无向网G
    int i,j,k,v1,v2,w;
    
    cin >> G.vexnum >> G.arcnum; //总顶点数,总边数 
    
    for(i = 0;i < G.vexnum;i++)    cin >> G.vexs[i]; //依次输入点的信息 
    
    for(i = 0;i < G.vexnum;i++)
    {//初始化 
        for(j = 0;j < G.vexnum;j++)
            G.arcs[i][j] = MaxInt;
    }
    
    for(k = 0;k < G.arcnum;k++)
    {//构造 
        cin >> v1 >> v2 >> w; //输入始点、终点和权值 
        i = LocateVex(G,v1); //返回始点在G中的位置 
        j = LocateVex(G,v2); //返回终点在G中的位置 
        
        G.arcs[i][j] = w; //边<v1,v2>的权值置为w
        G.arcs[j][i] = w; //因为是无向图所以对称边的权值也置为w 
    }
}
View Code

3.优点:便于判断两个顶点之间是否有边;便于计算咯咯咯顶点的度

4.缺点:不便于增加和删除顶点;不便于统计边的数目(时间复杂度为O(n^2));空间复杂度高(空间复杂度为O(n));如果为稀疏图浪费的空间较多(无向图沿对角线对称)

(2)邻接表

1.存储结构

#define MVNum 100 //最大顶点数 
typedef struct ArcNode 
{//边结点 
    int adjvex; //改变所指向的始点的位置 
    struct ArcNode *nextarc; //指向下一条边的指针 
    OtherInfo info; //和边相关的信息(eg.权值) 
}ArcNode;
typedef struct VNode
{//顶点信息 
    VerTexType data;
    ArcNode *firstarc; //指向第一条依附该顶点的边的指针 
}VNode,AdjList[MVNum];
typedef struct
{//邻接表 
    AdjList vertices;
    int vexnum,arcnum; //图的当前的顶点数和边数 
}ALGraph;
View Code

 2.创建无向图

void CreateUDG(ALGraph &G)
{
    int i,j,k,v1,v2;
    ArcNode *p1,*p2;
    
    cin >> G.vexnum >> G.arcnum; //输入总顶点数,总边数 
    
    for(i = 0;i < G.vexnum;i++)
    {//构造结点表 
        cin >> G.vertices[i].data; //输入顶点值 
        G.vertices[i].firstarc = NULL; //初始化 
    }
    
    for(k = 0;k < G.arcnum;k++)
    {//构造临界边表 
        cin >> v1 >> v2; //输入两个顶点 
        i = LocateVex(G,v1); //确定v1在G中位置 
        j = LocateVex(G,v2); //确定v2在G中位置 
        
        p1 = new ArcNode; //生成新结点 
        p1->adjvex = j; //邻接点序号为j 
        p1->nextarc = G.vertices[i].firstarc;
        G.vertices[i].firstarc = p1; //将新结点插入顶点v1的边表头部 
        
        p2 = new ArcNode; //生成另一个对称的新的边界的点 
        p2->adjvex = i; //邻接点序号为i 
        p2->nextarc = G.vertices[j].firstarc;
        G.vertices[j].data = p2; //将新结点插入顶点vj的边表头部 
    } 
} 
View Code

3.优点:便于增加和删除顶点;便于统计边的数目(时间复杂度O(n+e));空间效率高(空间复杂度O(n+e))

4.缺点:不便于判断顶点之间是否有边;不便于计算有向图各个顶点的度
(3)十字链表
(4)邻接多重表
五、遍历


(1)深度优先搜索(DFS)
1.方法
a.从图中某个顶点v出发,访问v
b.找出刚访问过的顶点的第一个未被访问的邻接点,访问该顶点。以该定点为新顶点,重复此步骤,直至刚访问的顶点没有未被访问的邻接点为止
c.返回前一个访问过的且仍有为被访问的邻接点的顶点,找出该顶点的下一个未被访问的邻接点,访问该顶点
d.重复步骤b、c,直至涂重所有顶点都有被访问过,搜索结束
2.遍历连通图

bool visited[MVNum];

void DFS(Graph G,int v)
{//从第v个顶点出发递归地深度优先遍历图G 
    cout << v; //访问第v个顶点 
    visited[v] = true; //置访问标志数组相应分量值为true 
    for(w = FirstAdjVex(G,v);w >= 0;w = NextAdjVex(G,v,w)) //一次检查v地所有邻接点w,FirstAdjVex(G,v)表是v的第一个邻接点 
                                                           //NextAdjVex(G,v,w)表示v相对w的下一个邻接点,w>=0表示存在邻接点 
        if(!visited[w])    DFS(G,w); //对v的尚未访问的邻接顶点w递归调用DFS 
 } 
View Code

3.遍历非连通图

void DFSTraverse(Graph G)
{//对非连通图G做深度优先遍历 
    for(v = 0;v < G.vexnum;v++)    visited[v] = false; //访问标志数组初始化 
    
    for(v = 0;v < G.vexnum;v++) //循环调用 
        if(!visited[v])    DFS(G,v); //对为访问的顶点调用DFS 
} 
View Code

4.邻接矩阵表示图

void DFS_AM(AMGraph G,int v)
{//G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历 
    cout << v; //访问第v个顶点 
    visited[v] = true; //置访问标志数组相应分量为true 
    
    for(w = 0;w < G.vexnum;v++) //依次检查邻接矩阵v所在的行 
        if((G.arcs[v][w] != 0)&&(!visited[w]) //G.arcs[v][w] != 0表示w是v的邻接点,如果w未访问,则递归调用DFS 
            DFS(G,w);
    }
 } 
View Code

5.邻接表表示图

void DFS_AL(Graph G,int v)
{//G为邻接表类型,从第v个顶点出发深度优先搜索 
    cout << v; //访问第v个顶点 
    visited[v] = true; //置访问标志数组相应分量值为true 
    
    ArcNode *p;
    p = G.vertices[v].firstarc; //p指向v的便链表的第一个边结点 
    
    while(p)
    {
        w = p->adjvex; //表示w是v的邻接点 
        if(!visited[w])    DFS(G,w); //如果w未被访问,则递归调用DFS 
        p = p->nextarc;
    } 
} 
View Code

(2)广度优先搜索(BFS)

1.方法

a.从图中某个顶点v出发,访问v

b.依次访问v的各个未曾访问过的邻接点

c.分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。(使用队列)

d.重复步骤c,直至图中所有已被访问的顶点的邻接点都被访问到

2.遍历连通图

#include<queue>
void BFS(Graph G,int v)
{//按广度优先非递归遍历连通图 
    queue<int>Q; //队列 
    
    cout << v; //访问第v个顶点 
    visited[v] = true; //置访问标志数组相应分量值未true 
    
    Q.push(v); //v入队
    
    while(!Q.empty()) //队列非空 
    {
        u = Q.front();//获得队头元素给k
        Q.pop();//队头元素出队
        
        for(w = FirstAdjVex(G,u);w >= 0;w = NextAdjVex(G,u,w))
        //依次检查u的所有邻接点w
        //FirstAdjVex(G,u)表示u的仪狄格邻接点
        //NextAdjVex(G,u,w)表示u相对于w的下一个邻接点,w>=0表示存在邻接点 
            if(!visited[w]) //w为u的尚未访问的邻接顶点 
            {
                cout << w; //访问w 
                visited[w] = true; //置访问标志数组相应分量值为true 
                Q.push(v); //v入队
            }
    }
 } 
View Code

六、最短路径

(1)迪杰斯特拉算法(Dijkstra)

1.方法

对于网N = (V,E),将N中的顶点分成两组

第一组S:已求出的最短路径的终点集合(初始时值包含源点v0)

第二组V-S:尚未求出的最短路径的顶点集合(初始时为V-{v0})

按各顶点与v0减最短路径长度递增的次序,逐个将集合V-S中的顶点加入到集合S中去。在这个过程中,总保持从v0到集合S中各顶点的路径长度始终不大与到集合V-S中各个顶点的路径长度

2.代码

void ShortestPath_DIJ(AMGraph G, int v0)
{//使用迪杰斯特拉算法求有向网G中的V0顶点到其余顶点的最短路径
    int n = G.vexnum; //n为顶点数
    for (int v = 0; v < n; v++) //n个顶点依次初始化
    {
        S[v] = false; //S初始化为空集
        D[v] = G.arcs[v0][v]; //将v0到各个终点的最短路径长度初始化为边上的权值
        if (D[v] < MAX)
            Path[v] = v0; //如果v0和v之间有边,则将v的前驱初始化为v0
        else
            Path[v] = -1; //如果v0和v之间无边,则将v的前驱初始化为-1
    }
    S[v0] = true; //将v0加入s
    D[v0] = 0; //源点到源点的权值为0
    //---------初始化结束,开始主循环,每次求得v0到某个顶点的最短路径,将v加到S数组
    for (int i = 1; i < n; i++) //依次对其余n-1个顶点进行计算
    {
        int    min = MAX;
        int v = v0;
        for (int w = 0; w < n; w++)
        {
            if (!S[w] && D[w] < min)
            {//选择一条当前最短路径,终点为v
                v = w;
                min = D[w];
            }
            S[v] = true; //将v加到s集合中
            for (int w = 0; w < n; w++)
            {//更新从v0出发到集合V-S上所有顶点的最短路径长度
                if (!S[w] && (D[v] + G.arcs[v][w] < D[w]))
                {
                    D[w] = D[v] + G.arcs[v][w]; //更新D[w]
                    Path[w] = v; //更改w的前驱为v
                }
            }
        }
    }
}
View Code

七、最小生成树

(1)普里姆算法(Prim)

1.方法

a.U = {u0}(u0∈V),TE = {}

b.在所有u∈U,v∈V-U的边(u,v)∈E中找一条权值最小的边(u0,v0)并入集合TE,同时v0并入U

c.重复步骤b,直至U = V为止

2.辅助数组,记录从顶点集U到V-U的权值最小的边

struct
{
    VerTexType adjvex; //最小边在U中的那个顶点 
    ArcType lowcost; //最小边上的权值 
}closedge[MVNum];
View Code

3.代码

void MiniSpanTree_Prim(AMGraph G,VerTexType u)
{//从顶点u出发构造G的最小生成树T,输出T的各条边 
    int k = LocateVex(G,u); //k为顶点u的下标 
    
    for(int i = 0;i < G.vexnum;i++) //对V-U的每一个顶点vj,初始化closedge[j] 
        if(i != k)    closedge[i] = {u,G.arcs[k][i]}; // 

    closedge[k].lowcost = 0;

    for (int i = 1; i < G.vexnum; i++)
    {//选择其余n-1个顶点,生成n-1条边 

        k = Min(closedge); //求出T的下一个结点:第k个顶点,closedge[k]中存有当前最小边
        u0 = closedge[k].adjvex; //u0为最小边的一个顶点,u0∈U 
        v0 = G.vexs[k]; //v0为最小边的另一个顶点,v0∈V-U 

        cout << u0 << v0; //输出当前的最小边(u0,v0) 

        for (int j = 0 ;j < G.vexnum; j++)
        {
            if (G.arcs[k][j] < closedge[j].lowcost) //新顶点并入U后重新选择最小边 
                closedge[j] = {G.vexs[k],G.arcs[k][j]};
        }
    }
}
View Code

(2)克鲁斯卡尔算法(Kruskal)

1.方法

假设连通网N = (V,E),将N中的边按权值从小到大的顺序排列

a.初始状态为只有n个顶点而五辩的非连通图T=(V,{}),图中每个顶点字成一个连通分量

b.在E中选择权值最小的边,若该边衣服的顶点落在T中不同的连通分量上(即不形成回路),则将赐匾加入到T中,否则社区赐匾而选择吓一跳权值最小的边

c.重复步骤b,直至T中所有顶点都在统一连通分量上为止

2.辅助数组

a.结构体数组Edge:存储边的信息,包括边的两个顶点信息和权值

struct
{
    VerTexType Head; //边的始点
    VerTexType Tail; //边的终点
    ArcType lowcost; //边上的权值
}Edge[arcnum];
View Code

b.标识各个顶点所属的连通分量

int Vexset[MVNum]; //初始时Vexset[i]= i,表示各顶点自成一个连通分量

3.代码

(太长了 来不及打)

 

补充:.和->的区别

typedef struct
{
……
}*p;
指向p中内容应用->
声明结构体指针,手动开辟空间
typedef struct
{
……
}Node;
指向Node中内容应用.
声明结构体

posted @ 2020-06-14 23:18  繁华声  阅读(265)  评论(0编辑  收藏  举报