數據結構筆記-圖

圖 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都是遍歷一個連通分量。

最短路

分類

  • 單源,某點到其他點
  • 多源,任意兩點

單源

無權圖

只需要考慮各邊數,利用廣度優先遍歷即可。

有權圖

要求:沒有負值圈
DijkstraDijkstra算法解決

  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次

  • Dk[i][j]=ilkjD^k[i][j]=路徑{i \rightarrow{l\leq k\rightarrow j}}的最短路徑。i節點經過節點ll到達j的最短路徑。節點ll是節點k之前

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

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

    • 當k在最短路上,則更新 Dk[i][j]D^k[i][j] = Dk[i][k]D^k[i][k] + Dk[k][j]D^k[k][j]
    • 當k不在最短路上,不跟新 Dk[i][j]D^k[i][j] = Dk1[i][j]D^{k-1}[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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章