• 数据结构与算法(六)——图


    数据结构与算法(六)——图
      iwehdio的博客园:https://www.cnblogs.com/iwehdio/
    Github:https://github.com/iwehdio/DSA_THU_DJH_asJava

    1、图的基本术语

    • 图 G = ( V ;E )。V 是顶点集,E 是连边集。用 n 来表示顶点的数量,用 e 来表示连边的数量。
    • 在图中,两个顶点之间具有对应关系则称为邻接,用两个顶点间的连边表示。而两个顶点与这条连边具有的关系成为关联。
    • 一般而言,不讨论自己与自己邻接的自环。
    • 如果邻接顶点 u 和 v 的次序无所谓则( u,v )为无向边。
    • 如果邻接顶点 u 和 v 的方向被规定了,则( u,v )为有向边。
    • 所有边均无方向的图,称为无向图。所有边均有方向的图,称为有向图。有向边与无向边混合的成为混合图。有向图可以表示无向图和混合图。
    • 路径是,一系列的顶点按照邻接关系构成的序列。如果一条路径中不含重复的节点,则称为简单路径。如果路径的起始节点都是一个节点,则称为环路。
    • 如果一个有向图中不包含任何环路,则称为有向无环图。
    • 欧拉环路:经过所有的有向边恰好一次的环路。哈密尔顿环路:经过所有的顶点恰好一次的环路。

    2、图的实现

    • 邻接矩阵:描述两个顶点之间的邻接关系。如果图中有 n 个顶点,对于有向图是 n 行 n 列的方阵,第 i 行第 j 列的元素,表征了第 i 个顶点与第 j 个顶点是否邻接。如果是带权图,只需将元素的值改为权值。

    • 关联矩阵:描述顶点与边之间的关联关系。如果图中有 n 个顶点、e 条边,则为 n 行 e 列的矩阵,第 i 行第 j 列的元素,表征了第 i 个顶点是否与第 j 条边关联。每一列中只有两个元素不为0。

    • 邻接表:描述每个顶点的邻接关系。如果图中有 n 个顶点,则为规模为 n 的向量。向量中的元素为列表,内容为该顶点所指向的邻接顶点。

    • 顶点对象的实现:

      typedef enum {UNDISCOVERED, DISCOVERED, VISITED} VStatus;	//顶点三种状态的枚举
      template <typename Tv>
      struct Vertex{			//顶点对象
          Tv data;			//数据
          int inDegree, outDegree;	//出入度(指出和指入的边数)
          VStatus status;				//状态
          int dTime, fTime;			//时间标签(被发现和访问完毕的时刻)
          int parent;					//父节点
          int priority;				//优先级
          Vertex(Tv const &d):		//构造新节点
              data(d), inDegree(0), outDegree(0), status(UNDISCOVERED),
          	dTime(-1), fTime(-1), parent(-1),
          	priority(INT_MAX) {}
      }
      
    • 边对象的实现:

      typedef enum {UNDETERMINED, TREE, CROSS, FORWARD, BACKWARD} EStatus;
      template <typename Te>		//边对象
      struct Edge{
          Te data;				//数据
          int weight;				//权值
          EStatus status;			//类型
          Edge(Te const &d, int w):		//构造新边
          	data(d), weight(w), status(UNDETERMINED) {}
      }
      
    • 邻接矩阵的实现:

      template <typename Tv, typename Te>
      class GraphMaxtrix : public Graph<Tv, Te>{
      private:
          Vector< Vertex<Tv> > V;			//顶点集
          Vector< Vector< Edge<Te>* > > E;	//边集,实际上就是邻接矩阵
      public:
          /* 相关操作接口 */
          GraphMaxtrix() { n = e = 0 ;}		//构造
          ~GraphMaxtrix(){				//析构
              for(int j=0; j<n; j++)
                  for(int k=0; k<n; k++)
                      delete E[j][k];		//清除内存
          }
      }
      
    • 顶点的静态操作:

      //对于顶点的属性的查找,可以直接return其属性值
      //枚举顶点的所有邻接顶点
      int nextNbr(int i, int j){		//若已经枚举到邻居 j,则逆向查找下一个邻接
          while((-1<j) && !exists(i, --j));	//逆向查找O(n)
          return j;
      }		//使用邻接图可到O(1+outDegree(i))
      int firstNbr(int i){		//查找第一个邻接
          return nextNbr(i, n);
      }
      
    • 边的操作:

      bool exists(int i, int j){			//判断边(i, j)是否存在
          return (0<=i)&&(i<n)&&(0<=j)&&(j<n)&&E[i][j]!=null;
      }	//如果边存在,则对于边的属性的查找,可以直接return其属性值
      //插入边
      void insert(Te const & edge, int w, int i, int j){
          if(exists(i, j)) return;		//判断是否以及存在
          E[i][j] = new Edge<Te>(edge, w);		//创建新边
          e++;							//更新边的数量
          V[i].outDegree++;				//分别更新边两个顶点的入度和出度
          V[j].inDegree++;
      }
      //删除边,前提以及确认边存在
      Te remove(int i, int j){
          Te eBak = edge(i, j);		//备份
          delete E[i][j];				//删除边
          E[i][j] = null;
          e--;
          V[i].outDegree--;				//分别更新边两个顶点的入度和出度
          V[j].inDegree--;
          return eBak;			//返回被删除的边的信息
      }
      
    • 顶点的动态操作:

      //顶点的插入,邻接表的规模发生变化
      int insert(Tv const & vertex){
          for(int j=0; j<n; j++) E[j].insert(null); n++;	//扩充邻接表中每个元素的规模
          E.insert(Vector< Edge<Te>* >(n, n, null));	//扩充邻接表的规模
          return V.insert(Vector<Tv>(vertex));	//扩充顶点列表的规模
      }
      //顶点的删除
      Tv remove(int i){
          for(int j=0; j<n; j++){
              if(exists(i, j)){
                  delete E[i][j];
                  V[j].inDegree--;		//删除出边
              }
          }
          E.remove(i); n--;			//删除第 i 行
          for(int j=0; j<n; j++){
              if(exists(j, i)){
                  delete E[j].remove(i);		//删除第 i 列
                   V[i].outDegree--;			//删除所有入边
              }		
          }
          Tv vBak = vertex(i);
          V.remove(i);				//删除顶点
          return vBak;
      }		//删除行和列的操作的区别?
      
    • 邻接矩阵表示法:

      • 判断两点之间是否存在联边、获取顶点的出入度数、添加删除边后更新度数都是 O(1) 的时间复杂度。
      • 空间复杂度总是 O(n^2) ,只与顶点数有关,与边数无关。对于平面图(不相邻的边不相交)的空间利用率很低。

    3、广度和深度优先搜索

    • 数据结构的简化分析:图 遍历 得到 树,树 遍历 得到 向量。

    • 广度优先搜索:对于访问始自顶点为 s 的图。

      1. 访问顶点 s 。

      2. 依次访问 s 所有尚未访问的邻接顶点。

      3. 依次访问上述顶点尚未访问的邻接顶点。

      4. 如此反复直至没有尚未访问的邻接顶点。

    • 广度优先搜索:得到了原图的一个极大无环图,即原图的支撑树(遍历了原图的所有顶点)。

    • 树的层次遍历是广度优先搜索的特例。

    • 实现:

      template <typename Tv, typename Te>		//顶点类型、边类型
      void Graph<Tv, Te>::BFS(int v, int &clock){		//v是初始顶点
          Queue<int> Q;					//队列
          status(v) = DISCOVERED;			//顶点置为已发现并入队
          Q.enqueue(v);
          while(!Q.empty()){
              int v = Q.dequeue();		//取出队首顶点
              dTime = ++clock;			//打上时间标签
              for(int u=fisrtNbr(v); -1<u; u=nextNbr(v, u)){
                  if(UNDISCOVERED == status(u)){		//如果u尚未被发现
                      status = DISCOVERED;	//将u置为已发现并入队
                      Q.enqueue(u);
                      status(v, u) = TREE;	//将边(v,u)置为树边,即支撑树中的边
                      parent(u) = v;
                  } else {
                      status(v, u) = CROSS;	//如果u已被发现或访问,则将边(v,u)置为跨边,不引入到支撑树中
                  }
              }
              status(v) = VISITED;			//当前节点的邻接节点访问完毕
          }
      }
      
    • 上述的广度优先算法,实际上是遍历了初始顶点所在的连通域,但一个图中可能有多个连通域。这时需要有多个初始顶点:

      template <typename Tv, typename Te>	
      void Graph<Tv, Te>::bfs(int s){		//s是初始顶点
          reset();					//初始化
          int clock = 0;
          int v = s;
          do{							//逐一检查所有顶点
              if(UNDISCOVERED == status(v)){		//如果遇到尚未发现的顶点,则启动一次BFS
                  BFS(v, clock);
              }
          } while(s!=(v=(++v%n)))		//从s开始遍历所有顶点
      }
      
    • 时间复杂度:理论上是 O(n^2) ,但是由于邻接矩阵中一个顶点的邻居在逻辑和物理上的连续性,实际效率可能达到 O( n+e ) 。如果使用邻接表可直接达到 O( n+e ) 。

    • 最短路径:在图中,定义两个点之间的距离为其之间最短路径的距离。广度优先算法中,每个顶点到初始顶点的路径,都是其所有路径中到初始顶点的最短路径。

    • 深度优先搜索:对于访问始自顶点为 s 的图。

      1. 访问顶点 s 。
      2. 若 s 有尚未被访问的邻接,则任选其一 u ,递归执行深度优先搜索。
      3. 否则,返回上一顶点。
    • 实现:

      template <typename Tv, typename Te>	
      void Graph<Tv, Te>::DFS(int v, int &clock){
          dTime(v) = ++clock;				//打上发现时间标签
          status(v) = DISCOVERED;			//发现当前顶点v
          for(int u=firstNbr(v); -1<u; u=nextNbr(v, u)){	//枚举v的每一个邻居
              switch(status(u)){
                  case UNDISCOVERED:		//u未被发现
                      status(v, u) = TREE;	//置为树边
                      parent(u) = v;
                      DFS(u, clock);			//递归
                      break;
                  case DISCOVERED:		//如果已被发现,在此处是后代指向祖先,标记为回边
                      status(v, u) = BACKWARD;
                      break;
                  default:		//u已访问完毕(仅对于有向图有这种情况),视时间标签规定的承袭关系分为前向边或跨边
                      status(v, u) = dTime(v)<dTime(u) ? FOREARD : CROSS;
                      break;
              }
          }	
          status = VISITED;			//v访问完毕
          fTime(v) = ++clock;			//打上完成时间标签
      }
      
    • 可达域:对于有向图,可能由于指向的原因,在一个连通域内也无法从一个顶点遍历整个连通域,即具有多个可达域。这时可以借鉴BFS中遍历整个顶点向量的方式。

    • 深度优先搜索的实例:无向图 视频的 P204,有向图 视频的P205。

    • 嵌套引理:对于给定的有向图和其任一DFS森林。

      • 定义顶点的活动期:active[u]=(dTime[u], fTime[u])

      • 如果 u 是 v 的后代,当且仅当 acive[u] ⊆ acitve[v]

      • 如果 u 是 v 的祖先,当且仅当 acive[v] ⊆ acitve[u]

      • 如果 u 与 v 无关,当且仅当 active[u] ∩ acive[v] = Ø


    参考:数据结构与算法(清华大学C++描述):https://www.bilibili.com/video/av49361421


    iwehdio的博客园:https://www.cnblogs.com/iwehdio/

  • 相关阅读:
    解决maven update project 后项目jdk变成1.5的问题
    applicationContext-common.xml]; nested exception is java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal
    SQL0668N 不允许对表"xxx"执行操作,原因码为 "1"
    maven下的jar项目打包的方法
    spring security 4.2后出现CouldnotverifytheprovidedCSRFtokenbecauseyoursessionwasnotfound
    maven-javadoc-plugin 出现错误Unsupported major.minor version 51.0
    通过 Spring Security配置 解决X-Frame-Options deny 造成的页面空白 iframe调用问题
    yaml模块
    centos 下 yum安装python3
    Python 之ConfigParser模块
  • 原文地址:https://www.cnblogs.com/iwehdio/p/12356299.html
Copyright © 2020-2023  润新知