鄰接表
感覺寫的很好啊!
轉載自:陣列模擬鄰接表 - AcWing
首先假設我們有n個點(n <= N),m條邊(m <= M)。
我們可以想一下對於任意一個結點u, 需要記錄鄰邊的哪些資訊。
這些資訊應該包括這條鄰邊的終點,權重,以及下一條鄰邊的編號。
注意這裡不需要記錄鄰邊的起點,因為我們使用的時候都是給出起點的。
所以我們可以定義一個struct來表示鄰邊:
struct Edge
{
int eid; // 該條邊的編號
int e; // 該條邊的終點
int w; // 該條邊的權重
int nxt; // 下一條鄰邊的編號
};
如果我們用上面的資料結構來記錄鄰邊的資訊,那麼我們只需要定義如下變數來表示鄰接表:
// 注意N和M的區別
int h[N];
Edge edges[M];
int eidx;
由於每條邊都記錄了下一條邊的編號,這樣我們只要把每個結點的第一條鄰邊的編號記錄在h陣列,我們就可以遍歷它的每一條鄰邊了。
如果我們把Edge裡的資訊分開存到不同陣列裡,那麼我們可以得到平時我們看到的變數定義:
// 注意N和M的區別
int h[N];
int e[M], w[M], nxt[M]; // 這三個陣列等價於之前的Edge edges[M],注意這些陣列的下標表示鄰邊的編號
int eidx;
這裡每個陣列的下標的含義不一樣。
關鍵的事情說三遍:h陣列的下標為結點的編號,e,w,nxt陣列的下標為邊的編號,eidx為邊的編號
關鍵的事情說三遍:h陣列的下標為結點的編號,e,w,nxt陣列的下標為邊的編號,eidx為邊的編號
關鍵的事情說三遍:h陣列的下標為結點的編號,e,w,nxt陣列的下標為邊的編號,eidx為邊的編號
如果理解了以上,下面就很好理解了。
有向圖的鄰接表儲存就是對於每個點 u 對應一個頭節點h[u],記錄第一條鄰邊的編號。
e, w, nxt陣列的編號和建圖的順序有關,對於某一個點u, 它的所有鄰邊的編號不一定是連續的。
nxt[eidx]=h[u]; h[u]=eidx; 這個操作就是把新建的邊插入表頭。(先把新建的邊的next指向現在隊頭的next,然後更新隊頭的next)
然後再eidx++, 給下一次建邊使用
下邊用圖模擬一下加入四條邊的過程
- 初始狀態
- 加完第一條邊(1,2,9)之後
- 加完第二條邊(2,4,1)之後
- 加完第三條邊(1,3,3)之後
這裡可以看到後加入的邊,反而在鄰接表的最前面
- 加完第四條邊(3,4,5)之後
最後是程式碼及註釋
const int N = 1010, M = 1010;
int h[N], e[M], w[M], nxt[M], eidx;
void add(int u, int v, int weight) // 新增有向邊 u->v, 權重為weight
{
e[eidx] = v; // 記錄邊的終點
w[eidx] = weight; // 記錄邊的權重
nxt[eidx] = h[u]; // 將下一條邊指向結點u此時的第一條邊
h[u] = eidx; // 將結點u的第一條邊的編號改為此時的eidx
eidx++; // 遞增邊的編號edix, 為將來使用
}
void iterate(int u) // 遍歷結點u的所有鄰邊
{
// 從u的第一條邊開始遍歷,直到eid==-1為止
for(int eid = h[u]; eid != -1; eid = nxt[eid])
{
int v = e[eid];
int weight = w[eid];
cout << u << "->" << v << ", weight: " << weight << endl;
}
}
int main()
{
int n, m;
cin >> n >> m;
memset(h, -1, sizeof h); // 初始化h陣列為-1
eidx = 0; // 初始化邊的編號為0
while(m--)
{
int u, v, weight;
cin >> u >> v >> weight;
add(u, v, weight);
}
for(int u = 1; u <= n; ++u)
iterate(u);
return 0;
}