图的问题

 

1.图的存储结构

  • 邻接矩阵

      使用二维数组来存储图的边的信息和权重,如下图所示的4个顶点的无向图:

    

    

      从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。

      如果换成有向图,则如图所示的五个顶点的有向图的邻接矩阵表示如下:

    

  • 邻接表

         邻接矩阵是一种不错的图存储结构,但是对于边数较少时,这种结构存在空间上的极大浪费,因此找到一种数组与链表相结合的存储方法称为邻接表。

     邻接表的处理方法是这样的:

    (1)图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过,数组可以较容易的读取顶点的信息,更加方便。

    (2)图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以,用单链表存储,无向图称为顶点vi的边表,有向图则称为顶点vi作为弧尾的出边表

      无向图的邻接表表示:

    

 

      从图中可以看出,顶点表的各个结点由data和firstedge两个域表示,data是数据域,存储顶点的信息,firstedge是指针域,指向边表的第一个结点,即此顶点的第一个邻接点。边表结点由adjvex和next两个域组成。adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标,next则存储指向边表中下一个结点的指针。

     有向图的邻接表表示:

  

2.图的遍历

  •  深度优先遍历(DFS)  

 堆栈的思想,对于不能访问后,一定要原路返回;

  基本实现思想(类似树的先序遍历):

(1)访问顶点v;

(2)从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;

(3)重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。

  伪代码表示:

(1)访问顶点v;visited[v]=1;//算法执行前visited[n]=0

(2)w=顶点v的第一个邻接点;

(3)while(w存在)  

           if(w未被访问)

                   从顶点w出发递归执行该算法; 
           w=顶点v的下一个邻接点;

 

  •  广度优先遍历(BFS)

 

队列的思想,入队,出队; 

基本实现思想(类似树的层序遍历): 

(1)顶点v入队列。

(2)当队列非空时则继续执行,否则算法结束。

(3)出队列取得队头顶点v;访问顶点v并标记顶点v已被访问。

(4)查找顶点v的第一个邻接顶点col。

(5)若v的邻接顶点col未被访问过的,则col入队列。

(6)继续查找顶点v的另一个新的邻接顶点col,转到步骤(5)。

        直到顶点v的所有未被访问过的邻接点处理完。转到步骤(2)。

(1)初始化队列Q;visited[n]=0;

(2)访问顶点v;visited[v]=1;顶点v入队列Q;

(3) while(队列Q非空)   

              v=队列Q的对头元素出队;

              w=顶点v的第一个邻接点;

             while(w存在) 

                     如果w未访问,则访问顶点w;

                     visited[w]=1;

                     顶点w入队列Q;

                     w=顶点v的下一个邻接点。

 

完整代码:

#include <iostream>
#include <queue>
#include <cstring> 
using namespace std;
const int N = 10010; // 图的顶点最大个数
int e[N][N]; // 储存图信息的邻接矩阵 
int book[N]; // 标记顶点是否被访问 

 // 对第 n 个顶点进行深度优先遍历 
void dfs(int n, int sum) {
    if(n != 0) { // 输出格式控制 
        cout << " ";
    }
    cout << n+1; // 输出顶点信息 
    for(int i = 0; i < sum; i++) { // 对当前所有的顶点进行讨论
        // 如果顶点 i 和顶点 n 之间存在边直接相连,并且顶点 i 未被访问 
        if(e[n][i] == 1 && book[i] == 0) {  
            book[i] = 1; // 标记这个顶点已经被访问 
            dfs(i, sum); // 对这个顶点继续进行深度优先遍历 
        }
    }
}

// 对图进行广度优先遍历 
void bfs(int n) {
    queue<int> que;
    book[0] = 1; // 标记第一个顶点已经被访问
    que.push(0);
    int s;
    while(!que.empty()) {
        s = que.front(); // 获取队头元素 
        que.pop(); // 队头元素出队
        if(s != 0) { // 输出格式控制
            cout << " ";
        }
        cout << s+1; // 输出顶点信息 
        for(int i = 0; i < n; i++) {
            // 如果顶点 i 和顶点 n 之间存在边直接相连,并且顶点 i 未被访问 
            if(e[s][i] == 1 && book[i] == 0) {
                book[i] = 1; // 标记这个顶点已经被访问 
                que.push(i); // 这个顶点入队尾 
            }
        } 
    }
}

int main() {
    int n, m; // 图的顶点个数和边的条数 
    cin >> n >> m;
    int x, y; // 边的开始顶点和结束顶点 
    for(int i = 0; i < m; i++) {
        cin >> x >> y;
        e[--x][--y] = e[y][x] = 1; // 因为是无向图,所以要双向储存 
    }

    cout << "深度优先遍历结果:" << endl;
    book[0] = 1; // 标记第一个顶点已经被访问
    dfs(0, n); // 从第一个顶点开始深度优先遍历 

    memset(book, 0, sizeof(book)); // 重置访问标记 
    cout << endl << "广度优先遍历结果:" << endl;
    bfs(n);

    return 0;
}

有关DFS与BFS应用模板可参考:https://blog.csdn.net/BillCYJ/article/details/78976932

参考链接:

https://blog.csdn.net/moshenglv/article/details/72954701

https://blog.csdn.net/hacker_zhidian/article/details/61260543

http://blog.51cto.com/ahalei/1387799

https://blog.csdn.net/qq_35644234/article/details/60870719

3.最短路径问题-迪杰斯特算法(Dijkstra)

     该算法采用了贪心的思想,每次都查找与该点距离最的点,也因为这样,它不能用来解决存在负权边的图。解决的问题大多是这样的:有一个无向图G(V,E),边E[i]的权值为W[i],找出V[0]到V[i]的最短路径。

算法步骤:

  • 引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。初始时,S只包含起点s;
  • 从U中选出”距离最短的顶点k”,并将顶点k加入到S中;同时,从U中移除顶点k。(也可以理解为按最短路径长度的递增次序依次把U中的顶点加入S中);
  • 以k为新考虑的中间点,更新U中各顶点到起点s的距离。例如:若从起点s到顶点u的距离(经过顶点k)比原来距离(不经过顶点k)短,则修改顶点u的距离值,修改后的距离值是顶点k的距离加上k到u的距离;
  • 重复上述两个步骤,直到所有顶点都包含在S中。

为什么不能解决负权边的图?

      当把一个节点选入集合S时,即意味着已经找到了从源点到这个点的最短路径,(在S中以后加入的新顶点不会影响之前顶点到源点的距离值),但若存在负权边,就与这个前提矛盾,可能会出现得出的距离加上负权后比已经得到S中的最短路径还短。

完整代码:

// 邻接矩阵
typedef struct _graph
{
    char vexs[MAX];       // 顶点集合
    int vexnum;           // 顶点数
    int edgnum;           // 边数
    int matrix[MAX][MAX]; // 邻接矩阵
}Graph, *PGraph;

// 边的结构体
typedef struct _EdgeData
{
    char start; // 边的起点
    char end;   // 边的终点
    int weight; // 边的权重
}EData;
/*
 * Dijkstra最短路径。
 * 即,统计图(G)中"顶点vs"到其它各个顶点的最短路径。
 *
 * 参数说明:
 *        G -- 图
 *       vs -- 起始顶点(start vertex)。即计算"顶点vs"到其它顶点的最短路径。
 *     prev -- 前驱顶点数组。也可理解为路径保存,prev[i]的值是"顶点vs"到"顶点i"的最短路径所经历的全部顶点中,位于"顶点i"之前的那个顶点。
 *     dist -- 长度数组。即,dist[i]是"顶点vs"到"顶点i"的最短路径的长度。
 */
void dijkstra(Graph G, int vs, int prev[], int dist[])
{
    int i,j,k;
    int min;
    int tmp;
    int flag[MAX];      // flag[i]=1表示"顶点vs"到"顶点i"的最短路径已成功获取。

    // 初始化
    for (i = 0; i < G.vexnum; i++)
    {
        flag[i] = 0;              // 顶点i的最短路径还没获取到。
        prev[i] = 0;              // 顶点i的前驱顶点为0。
        dist[i] = G.matrix[vs][i];// 顶点i的最短路径为"顶点vs"到"顶点i"的权。
    }

    // 对"顶点vs"自身进行初始化
    flag[vs] = 1;
    dist[vs] = 0;

    // 遍历G.vexnum-1次;每次找出一个顶点的最短路径。
    for (i = 1; i < G.vexnum; i++)
    {
        // 寻找当前最小的路径;
        // 即,在未获取最短路径的顶点中,找到离vs最近的顶点(k)。
        min = INF;
        for (j = 0; j < G.vexnum; j++)
        {
            if (flag[j]==0 && dist[j]<min)
            {
                min = dist[j];
                k = j;
            }
        }
        // 标记"顶点k"为已经获取到最短路径
        flag[k] = 1;

        // 修正当前最短路径和前驱顶点
        // 即,当已知"顶点k的最短路径"之后,更新"未获取最短路径的顶点的最短路径和前驱顶点"。
        for (j = 0; j < G.vexnum; j++)
        {
            tmp = (G.matrix[k][j]==INF ? INF : (min + G.matrix[k][j])); // 防止溢出
            if (flag[j] == 0 && (tmp  < dist[j]) )       //判断d(s,k)+d(k,j)与d(s,j)的大小,如果小就更新
            {
                dist[j] = tmp;
                prev[j] = k;
            }
        }
    }

    // 打印dijkstra最短路径的结果
    printf("dijkstra(%c): \n", G.vexs[vs]);
    for (i = 0; i < G.vexnum; i++)
        printf("  shortest(%c, %c)=%d\n", G.vexs[vs], G.vexs[i], dist[i]);
}

详细过程可参考:

https://blog.csdn.net/heroacool/article/details/51014824

https://blog.csdn.net/mu399/article/details/50903876

4.最小生成树问题

  • 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
  • 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
  • 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

           è¿éåå¾çæè¿°

1)克鲁斯卡算法(Kruskal

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。 
1. 把图中的所有边按代价从小到大排序; 
2. 把图中的n个顶点看成独立的n棵树组成的森林; 
3. 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。 
4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

2)普里姆算法(Prim)

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

  1. 图的所有顶点集合为VV;初始令集合u={s},v=V−uu={s},v=V−u;
  2. 在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
  3. 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。

由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息,:

struct
{
  char vertexData   //表示u中顶点信息
  UINT lowestcost   //最小代价
}closedge[vexCounts]

è¿éåå¾çæè¿°

  转载链接:勿在浮沙筑高台http://blog.csdn.net/luoshixian099/article/details/51908175

                                             https://blog.csdn.net/qq_27256783/article/details/78640063

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值