zoukankan      html  css  js  c++  java
  • 图的基本概念与常用算法总结

    一.思维导图

    二.重要概念笔记

    1.图的定义

    图:
    图G由顶点集V和边集E组成,记为G=(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的关系(边)的集合。
    (线性表可以是空表,树可以是空树,图不可以是空图,图可以没有边,但是至少要有一个顶点。)

    有向图:
    若E是有向边(简称弧)的有限集合时,则G为有向图。弧是顶点的有序对,记为<v,w>,其中 v,w 是顶点,v 是弧尾,w 是弧头。称为从顶点v到顶点w的弧。

    无向图:
    若E是无向边(简称边)的有限集合时,则G为无向图。边是顶点的无序对,记为 (v,w) 或(w,v) ,且有 (v,w) =(w,v) 。其中 v,w 是顶点。

    简单图:
    简单图满足以下两条内容:
    1)不存在重复边
    2)不存在顶点到自身的边
    (上述有向图与无向图均为简单图。)

    完全图:
    无向图中任意两点之间都存在边,称为无向完全图;
    有向图中任意两点之间都存在方向向反的两条弧,称为有向完全图;

    2.部分术语解释

    顶点的度、入度和出度:
    顶点的度为以该顶点为一个端点的边的数目。
    对于无向图,顶点的边数为度,度数之和是顶点边数的两倍。
    对于有向图,入度是以顶点为终点,出度相反。有向图的全部顶点入度之和等于出度之和且等于边数。顶点的度等于入度与出度之和。
    (入度与出度是针对有向图来说的。)

    路径、路径长度和回路:
    两顶点之间的路径指顶点之间经过的顶点序列,经过路径上边的数目称为路径长度。若有n个顶点,且边数大于n-1,此图一定有环。

    简单路不重复出现的路径称为简单路径。
    除第一个顶点和最后一个顶点外,其余顶点不重复出现的回路称为简单回路。

    距离:
    若两顶点存在路径,其中最短路径长度为距离。

    有向树:
    有一个顶点的入度为0,其余顶点的入度均为1的有向图称作有向树。

    3.图的储存

    1) 邻接矩阵:对于较小或者中等规模的图的构造较为适用。因为须要V*V大小的空间。

    2) 边的数组:使用一个简单的自己定义edge类,还有两个变量,分别代表边的两个端点编号,实现简单,可是在求每一个点的邻接点的时候实现较为困难。

    3) 邻接表数组:较为经常使用,使用一个以顶点为索引的数组。数组每一个元素都是和该顶点相邻的顶点列表。这样的数组占空间相对于邻接矩阵少了非常多。而且能非常好的找到某个给定点的全部邻接点。

    4.图的遍历

    深度优先遍历:
    从图中某顶点V0出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到;
    若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点;
    重复上述过程,直至图中所有顶点都被访问到为止。
    (时间复杂度O(n+e))

    广度优先遍历:
    从图中某个顶点V0出发,并在访问此顶点后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路 径相通的顶点都被访问到;
    若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点;
    重复上述过程,直至图中所有顶点都被访问到为止。
    (时间复杂度与深度优先遍历相同)

    5.各类算法

    Prim算法:
    首先以一个结点作为最小生成树的初始结点,然后以迭代的方式找出与最小生成树中各结点权重最小边,并加入到最小生成树中。加入之后如果产生回路则跳过这条边,选择下一个结点。当所有结点都加入到最小生成树中之后,就找出了连通图中的最小生成树了。
    (时间复杂度O(n^2),适用于稠密图)

    Kruskal算法:
    Kruskal算法与Prim算法的不同之处在于,Kruskal在找最小生成树结点之前,需要对所有权重边做从小到大排序。将排序好的权重边依次加入到最小生成树中,如果加入时产生回路就跳过这条边,加入下一条边。当所有结点都加入到最小生成树中之后,就找出了最小生成树。
    (时间复杂度O(eloge),适用于稀疏图)

    Dijkstra算法:
    通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)。
    此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)。
    初始时,S中只有起点s;U中是除s之外的顶点,并且U中顶点的路径是"起点s到该顶点的路径"。然后,从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 然后,再从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径。 ... 重复该操作,直到遍历完所有顶点。
    (时间复杂度:依赖于用来实现优先队列的数据结构以及用来表示输入图本身的数据结构)

    Floyd算法:
    比较经过顶点的权值,如果经过的顶点路径比原两点间的路径更短,将当前两点间的权值设为更小的一个。
    (时间复杂度为O(n3))

    6.拓扑排序

    对一个有向无环图(DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
    由AOV网构造拓扑序列的拓扑排序算法主要是循环执行以下两步,直到不存在入度为0的顶点为止。
    (1) 选择一个入度为0的顶点并输出之;
    (2) 从网中删除此顶点及所有出边。

    三.疑难问题及解决方案

    刚开始对题目的理解出了一定的问题,对边与边的关系感到有点混乱,后来通过观察网上他人的代码与一定的思考后,理解到此题属于最短路径问题,可以使用迪杰斯特拉解决,不过要多一个条件:当距离相等的情况下,要判断怎样花的钱更少。

    代码实现如下:

    #include<iostream>
    using namespace std;
    #define MAX 500
    int main() {
    	int cost[MAX], dist[MAX] = {MAX} , c[MAX][MAX], a[MAX][MAX];
        int num, arc, start, destination;
        int collected[MAX] = {0};
    	int path[MAX] = {-1};
        cin >> num >> arc >> start >> destination;
    	for(int i = 0; i < num; i++){   //初始化
    		for(int j =0; j < num; j++){
    			a[i][j] = c[i][j] = MAX;
    		}
    	}
        for(int i = 0; i < arc; i++){   //输入
            int aa, b, tempa, tempb;
            cin >> aa >> b >> tempa >> tempb;
            a[aa][b] = a[b][aa] = tempa;
            c[aa][b] = c[b][aa] = tempb;
        }
        for(int i = 0; i < num; i++){   //初始化
            if(i != start){
                dist[i] = a[start][i];
                cost[i] = c[start][i];
                if(dis[i] < MAX){
                    path[i] = start;
                }
            }
        }
        collected[start] = 1;
        while(1){
            int min = MAX, ptr;
            for(int i = 0; i < num; i++){   //找临边中最小的
                if(!collected[i] && dist[i] < min){
                    min = dist[i];
                    ptr = i;
                }
            }
            if(min == MAX){                 //无法更新,退出
                break;
            }
            collected[ptr] = 1;
            for(int i = 0; i < num; i++) {  //如果收录ptr使start到i的距离变短,则start到i的最短路一定经过ptr
                if(!collected[i] && dist[ptr] + a[ptr][i] < dist[i] ){
                    dist[i] = dist[ptr] + a[ptr][i];
                    path[i] = ptr;
                    cost[i] = cost[ptr] + c[ptr][i]; // ?
                }
                else if(!collected[i] && dist[ptr] + a[ptr][i] == dist[i] && cost[ptr] + c[ptr][i] < cost[i]){
                    cost[i] = cost[ptr] + c[ptr][i];
                    path[i] = ptr;
                }
            }
        }
        cout << dist[destination] <<  " "  << cost[destination];
        return 0;
        
    }
    
  • 相关阅读:
    LUA学习
    python中的面向对象学习以及类的多态
    python中的面向对象学习之继承实例讲解
    python中的面向对象学习以及类的继承和继承顺序
    python中的面向对象学习以及类的封装(这篇文章初学者一定要好好看)
    python中的re模块
    python中configparser模块的使用
    python中装饰器
    python中简单的递归(断点报错的小福利)
    python中函数与函数式编程(二)
  • 原文地址:https://www.cnblogs.com/qijing-cy/p/12906892.html
Copyright © 2011-2022 走看看