圖 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都是遍歷一個連通分量。
最短路
分類
- 單源,某點到其他點
- 多源,任意兩點
單源
無權圖
只需要考慮各邊數,利用廣度優先遍歷即可。
有權圖
要求:沒有負值圈
用解決
- 建立空的最短路集合,將源點加入。將所有節點到源點的距離設爲正無窮。
- 更新與新加入點相鄰的節點到源點的距離
- 遍歷所有未加入最短路集合的節點,找到到源點距離最短的節點,加入最短路集合,回到2.直到沒有節點能再加入
- 判斷是否所有節點都加入,若沒有,則圖不連通
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次
-
的最短路徑。i節點經過節點到達j的最短路徑。節點是節點k之前
-
k=n-1時,即最短路徑
-
由 推 時,
- 當k在最短路上,則更新 = +
- 當k不在最短路上,不跟新 =
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