圖的存儲與遍歷C++實現


1、圖的存儲

  • 設點數為n,邊數為m

1.1、二維數組

  • 方法:使用一個二維數組 adj 來存邊,其中 adj[u][v] 為 1 表示存在 u到 v的邊,為 0 表示不存在。如果是帶邊權的圖,可以在 adj[u][v] 中存儲u到v的邊的邊權。
  • 復雜度:
    • 查詢是否存在某條邊:\(O(1)\)
    • 遍歷一個點的所有出邊:\(O(n)\)
    • 遍歷整張圖:\(O(n^2)\)
    • 空間復雜度:\(O(n^2)\)

1.2、鄰接表

  • 方法:使用一個支持動態增加元素的數據結構構成的數組,如 vector< int> adj[n + 1] 來存邊,其中 adj[u] 存儲的是點u的所有出邊的相關信息(終點、邊權等);

  • 復雜度:

    • 查詢是否存在u到v的邊:\(O(d^+(u))\)(如果事先進行了排序就可以使用二分查找做到\(O(log(d^+(u)))\) )。
    • 遍歷點u的所有出邊:\(O(d^+(u))\)
    • 遍歷整張圖:O(n+m)。
    • 空間復雜度:O(m)。

1.3、直接存邊

  • 方法:使用一個數組來存邊,數組中的每個元素都包含一條邊的起點與終點(帶邊權的圖還包含邊權)。(或者使用多個數組分別存起點,終點和邊權。)
    struct Edge{
    	int u,v;//邊的端點
    	int w;//權重
    }Edges[MAXN];
    
  • 復雜度:
    • 查詢是否存在某條邊:\(O(m)\)
    • 遍歷一個點的所有出邊:\(O(m)\)
    • 遍歷整張圖:\(O(nm)\)
    • 空間復雜度:\(O(m)\)
  • 由於直接存邊的遍歷效率低下,一般不用於遍歷圖。在Kruskal算法 中,由於需要將邊按邊權排序,需要直接存邊

1.4、鏈式前向星(本質是用數組模擬鏈表)

  • 方法:本質是用數組模擬鏈表,主要有兩個數組

    Edges[MAXN] 存儲邊的信息,包括兩個端點、權重、下一條邊在Edges中的索引;
    head[MAXN] head[i]為節點i的第一條出邊在Edges中的序號;
    在插入邊的時候維護一個tot變量記錄總計的邊的個數
    
  • 復雜度:

    • 查詢是否存在u到v的邊:\(O(d^+(u))\)(如果事先進行了排序就可以使用二分查找做到\(O(log(d^+(u)))\) )。
    • 遍歷點u的所有出邊:\(O(d^+(u))\)
    • 遍歷整張圖:O(n+m)。
    • 空間復雜度:O(m)。
  • 代碼板子:

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN = 1e6;
    struct Edge
    {//邊結構體
    	int u,v;//邊的端點;
    	int w;//權重
    	int nxt;//下一條邊在Edge中的索引
    }Edges[MAXN];
    int head[MAXN];//每個節點出邊
    int tot;//總的邊數,隨着邊的增加而增加
    
    void init(int n){
    	tot=0;
    	//初始化head數組
    	for(int i=0; i<n; i++)
    		head[i]=-1;
    	//memset(head,-1,sizeof(head));
    	//memset是以字節為單位,初始化內存塊
    	//字節單位的數組時,可以用memset把每個數組單元初始化成任何你想要的值
    	//因為一個int類型的變量占4個字節,而memset是將每一個字節初始化成1,
    	//所以一個int類型的變量被初始化成了0x01010101。而這個數是16843009
    	//memset初始化int只能初始化0和-1;  
    }
    
    void addEdge(int u,int v,int w){
    	Edges[tot].u=u;
    	Edges[tot].v=v;
    	Edges[tot].w=w;
    	Edges[tot].nxt=head[u];
    	head[u] = tot;
    	tot++;
    }
    

2、圖的遍歷

  • 基於上述的鏈式前向星實現
    void dfs(int u) {
      //v 可以是圖中的一個頂點
      //也可以是抽象的概念,如 dp 狀態等,這一點很難想
      vis[u] = 1; //標記該節點被訪問過
      for (int i = head[u]; i; i = Edges[i].nxt) {
        if (!vis[Edges[i].v]) {
        //task to do
        dfs(v);
        }
      }
    }
    
  • 每次都嘗試訪問同一層的節點。 如果同一層都訪問完了,再訪問下一層。這樣做BFS 算法找到的路徑是從起點開始的最短合法路徑。換言之,這條路所包含的邊數最小。在 BFS 結束時,每個節點都是通過從起點到該點的最短路徑訪問的。
  • 基於上述的鏈式前向星實現:
    void bfs(int u){
    	vector<int> d;//記錄到達各個節點的最近距離;
    	vector<int> p;//記錄最短路上的節點
    	vector<int> vis(MAXN,0);//0代表節點未被訪問過
    	queue<int> q;
    	q.push(u);
    	vis[u]=1;//標記訪問
    	d[u]=0;
    	p[u]=-1;
    	while(!q.empty()){
    		u=q.front();
    		q.pop();
    		for(int i=head[u]; i; i=Edges[i].nxt){
    		  int v = Edges[i].v;//到達的點
    		  if(!vis[v]){
    		      q.push(v);
    		      vis[v]=1;
    		      d[v]=d[u]+1;
    		      p[v]=u;//記錄前序節點
    		      //task to do
    		  }
    		}
    	}
    }
    

😄
❤️


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM