数据结构笔记-图

图 graph

定义

包含多对多的关系
包含

  • 一组顶点:V
  • 一组边 E
    • 边是顶点对
    • 不考虑自回路和重边(两点间有多条直接相连的边)
    • 可以没有边,但一定有点
      图主要进行以下操作
  • DFS
  • BFS
  • 最短路
  • 最小生成树

网络:带权的有向图和无向图的总称

图的表示

邻接矩阵

  • 无向图只利用了一半的空间
    • 可用数组代替,n个节点需要数组长度n(n+1)/2,数组下标对应(i,j)-> i*(i+1)/2+j 。转成三角矩阵后,元素排列为等差数列,第0行为1,第i-1行为i,共i行,i(i+1)/2 ,再加上第i行的j个
    • 对角为0,没有自身到自身的边
    • 方便计算度
    • 适合稠密图,特别是完全图,但对于稀疏图比较浪费
  • 邻接表
    • 节约空间,但一条边存了两次
    • 方便算度,计算链表长度即可
    • 不便检查两点间是否有边

图的遍历

DFS(depth first search) 深度优先搜索

void dfs(int g[][],int p){
visit[p] = 1;
   for(i=0;i<g.size;i++){
      if(g[p][i] ==1 &&!visit[j])
         dfs(g,j);
   }
}

void dfs(){
  int g[][],int p,visit[];
  stack s;
  s.push(p);
  visit[p] = 1;
  while(!s.empty()){
      for(i=0;i<size;i++)
        if(g[p][i] && !visit[i]){
            p = i;
            visit[p] = 1;
            s.push(p);
        }
     p = s.top();
     s.pop();
  }
}

BFS breadth first search 广度优先搜索

void BFS(){
  queue q;
  vector g;
  visit[0]= 1;     
  q.push(g[0]);
  while(!q.empty()){
      next = q.top();
      q.pop();
     for(i=0;i<next.son.size();i++){
         if(!visit[next.son[i]]){
           q.push(next.son[i]);
           vistit[next.son[i])]= 1;
         }
      }   
  }

}

图不连通

  • 连通:两个节点间存在一条路径,则两个节点连通
  • 路径:一系列节点的结合。路径长度是路径中边的数量,若路径中所有节点都不同,则是简单路径
  • 回路:起点等于终点的路径
  • 连通图:任意两个定点均连通
  • 连通分量:无向图的极大连通子图。再多一个定点就不连通了,包含子图中所有定点相连的所有边。
  • 强联通:有向图中,两个定点之间存在双向路径,则两个顶点是强联通的
  • 强联通图:任意两个顶点都是强联通的
  • 每次进行BFS,DFS都是遍历一个连通分量。

最短路

分类

  • 单源,某点到其他点
  • 多源,任意两点

单源

无权图

只需要考虑各边数,利用广度优先遍历即可。

有权图

要求:没有负值圈
D i j k s t r a 算 法 Dijkstra算法 Dijkstra解决

  1. 建立空的最短路集合,将源点加入。将所有节点到源点的距离设为正无穷。
  2. 更新与新加入点相邻的节点到源点的距离
  3. 遍历所有未加入最短路集合的节点,找到到源点距离最短的节点,加入最短路集合,回到2.直到没有节点能再加入
  4. 判断是否所有节点都加入,若没有,则图不连通
int nodes[][];
int visit[];
int dist[];
int pre[];
int s;
next = s;
visit[s] = 1;
dist[s] = 0;
while(1)[
  for(i=0;i<n;i++)
    if(nodes[next][i] &&! visit[i] && dist[i]>dist[next]+nodes[next][i]){
            dist[i] = dist[next]+nodes[next][i] ;
            pre[i] = next;
    }
    min = INT_MAX;
  for(i=0;i<nodes.size();i++){
     if(!visit[i] &&  dist[min] >dist[i]){
          min = i;
     }
  }  
  visit[min] = 1;
  next = min;
}

多源最短路

Floyd算法

  • 针对稠密图

  • 若稀疏,则单源最短路调用n次

  • D k [ i ] [ j ] = 路 径 i → l ≤ k → j D^k[i][j]=路径{i \rightarrow{l\leq k\rightarrow j}} Dk[i][j]=ilkj的最短路径。i节点经过节点 l l l到达j的最短路径。节点 l l l是节点k之前

  • D k [ i ] [ j ] D^k[i][j] Dk[i][j] k=n-1时,即最短路径

  • D k − 1 [ i ] [ j ] D^{k-1}[i][j] Dk1[i][j] D k [ i ] [ j ] D^k[i][j] Dk[i][j] 时,

    • 当k在最短路上,则更新 D k [ i ] [ j ] D^k[i][j] Dk[i][j] = D k [ i ] [ k ] D^k[i][k] Dk[i][k] + D k [ k ] [ j ] D^k[k][j] Dk[k][j]
    • 当k不在最短路上,不跟新 D k [ i ] [ j ] D^k[i][j] Dk[i][j] = D k − 1 [ i ] [ j ] D^{k-1}[i][j] Dk1[i][j]
int nodes[][];
for(i=0;i<N;i++){
   for(j=0;j<N;j++){
      path [i][j] = -1 ;// ij两点未连通
      // nodes 初始化为邻接矩阵,对角线为0
   }
}
for(k=0;k<N;k++){
  for(i=0;i<N;i++){
     for(j=0;j<N;j++){
        if(D[i][k]+D[k][j] <D[i][j]){
           D[i][j] = D[i][k]+D[k][j];
           path[i][j]] = k;
        }
     }
  }
}

最小生成树

最小生成树定义

最小生成树:

  • 是棵树
    • 无回路
    • n个顶点有n-1条边
  • 生成树
    • 包含全部顶点
    • 边都在图中
  • 边的权重和最小

想生成树添加任意一条边都会形成回路。
如果能生成最小生成树,则图一定连通,反之亦然
最小生成树的算法都是贪心算法

prim算法

小树长大,每次选一条到生成树最小的边

int edge[N][N];
int visit[N];
int dist[N];// 到最小生成树的距离,初始化为无穷大
dist[0] = 1; // 假设从0开始
next = 0;
while(1){
   // update weight
   for(i=0;i<N;i++){
      if(edge[next][i] && dist[i]  && dist[i] >edge[next][i]){// 两点相连,第i个点未加入,权重要更新
           dist[i] = edge[next][i];
      }
   }
   next = heap.pop();   
   if(next == -1)
       break;
}


kruskal算法

森林合并成树

int set[N];
vector<int> nodes;
void init(){
   for(i=0;i<N;i++){
       set[i] = -1;
  }
}
void union(int x, int y){
   int a,b;
   a = find(x);
   b = find(y);
   // 按秩归并
   if(a  != b){
       if(set[a]<set[b]){
          set[b] = a;
       }else{
         set[a] = b;
       }
   }
}

int find(int x){
  int a = x,t;
  while(set[x]>=0){
     x = set[x];
  }
  // 路径压缩
  while(a != x){
     t = set[a] ;
     set[a] = x;
     a = t;
  }
}
int del(){
   int r = nodes[0];
   int next = nodes[nodes.size()];
   nodes.erase(nodes.end());
   for(p=0;i*2<nodes.size();p=c){
       c = p*2;
       if(p*2+1 < nodes.size() && nodes[p*2] <nodes[p*2+1])
         c = p*2+1;
         if(nodes[p] <nodes[c]){
            nodes[p] = nodes[c] ;
         }else{
            break;
         }
   }
   ndoes[p] = next;
   returun r;
}
void kruskal(){
   init();
   
}

拓扑排序

拓扑序:若图中v到w有一条有向边,则v一定排在w之前,满足条件的都是拓扑序,获得拓扑序的过程是拓扑排序
若AOV网络存在合理的拓扑序,则必定是有向无环图DAG。存在环,则不可能。

int nodes[][];
queue<int> q ;
int count[];
for(i=0;i<N;i++){
   flag = 0;
   for(j=0;j<N;j++){
       if(nodes[i][j] == 1){
           flag = 1;
           count[j] ++;
       }
   }
   if(!flag)
      q.push_back(q);
}
while(c<=0){
   if(!q.empty()){
      next = q.top();
      q.pop();
   }else{
      printf(" 存在环“);
     break;
   }
    for(i=0;i<N;i++){
       nodes[next][j] = 0;
       count[j] --;
       if(count[j] == 0)
         q.push(j);
    }
}

关键路径是AOE网,AOE(activity on edge)

trick

最短路

  • 求最短路数量时。在加入节点后,对其周围节点更新时,若找到更短的路径进行更新,则到更新节点的最短路数量等于其前驱节点最短路数量;若两条最短路相等,则更新节点的最短路数量++
  • 若求边数最少的最短路或其他两种排序(先按距离排,再按其他条件排)即需要在更新时,当两条边相等时,需要按第二要求进行更新。

reference

浙江大学 数据结构mook 树https://www.icourse163.org/learn/ZJU-93001?tid=1003013004#/learn/announce

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值