從存圖到最短路演算法的圖論總結

来源:https://www.cnblogs.com/HNFOX/archive/2019/07/31/11272427.html
-Advertisement-
Play Games

INTRODUCTION: 圖論演算法在電腦科學中扮演著很重要的角色,它提供了對很多問題都有效的一種簡單而系統的建模方式。很多問題都可以轉化為圖論問題,然後用圖論的基本演算法加以解決。--百度百科 對於OI而言,圖是指由若幹給定的點及若幹條連接兩點的線(邊)所構成的圖形 藉助圖論知識,我們往往可以將一 ...


INTRODUCTION:

圖論演算法在電腦科學中扮演著很重要的角色,它提供了對很多問題都有效的一種簡單而系統的建模方式。很多問題都可以轉化為圖論問題,然後用圖論的基本演算法加以解決。--百度百科

對於OI而言,圖是指由若幹給定的點及若幹條連接兩點的線(邊)所構成的圖形

藉助圖論知識,我們往往可以將一些複雜的問題轉化到基礎的圖論演算法上,進而使用已有演算法解決全新問題

那麼想如果想要運用圖論,首先要從存圖開始

前排感謝教我圖論的周潤喵老師,syc學長,就序老師

可是我還是沒學會

一,存圖

 

對於一個圖而言,它可以根據便是否有反向分成兩類:有向圖與無向圖,

不過二者的存圖方式大同小異,以下以有向圖為例;

1,鄰接矩陣(Adjacency matrix)

鄰接矩陣作為一種簡潔實用的存圖方式,具有簡單可靠的優勢,一般不太會打錯,但是他的空間複雜度高達O(n^2),使得他的使用相當受局限

不過,在數據範圍比較小或者想要打暴力部分分的時候,鄰接矩陣還是具有相當大的優勢的。

(比如說鄰接矩陣+Floyd打暴力)

在鄰接矩陣中,我們用e[i][j]表示點i到點j的距離(也就是邊i->j的邊權)

 

 1     const int INF = 9999999999;//設一個較大的數為無窮大
 2     int n, m;//n為點數,m為邊數
 3     int e[5005][5005];//貌似開5005*5005就快MLE了...所以要謹慎一點
 4     for (int i = 1; i <= n; i++)
 5         for (int j = 1; j <= n; j++)
 6             if (i == j)
 7                 e[i][j] = 0;//我自己到我自己的距離當然是0
 8             else
 9                 e[i][j] = INF;//一開始還沒有邊,所以我到其他人的距離先設為無窮大
10     for (int i = 1; i <= m; i++)//讀入邊
11     {
12         int from, to, weight;//從哪來,到哪去,路多長
13         cin >> from >> to >> weight;
14         e[from][to] = weight;//無向圖存兩遍
15         e[to][from] = weight;//from到to的距離和to到from的距離是相等的
16     }

 個人認為鄰接矩陣是一種比較可靠的存圖方式,在數據較小的時候一般不會出錯,

不過在使用時一定要根據題目含義對有向圖,無向圖或重邊,自環,等特殊情況進行判斷,以免出錯。

2,鄰接表(Adjacency table)

觀察之前的鄰接矩陣,我們可以看出,當存在很多個點(假設有n個),但邊的數量(m)卻遠小於n2時,矩陣中很多的空間都沒有用到,存在著極大的空間浪費

這使得鄰接矩陣無法應付n>=10000(甚至更大)的情況,然而這種在OI里是很常見的,所以我們就要引入一種OI里最常見(總之我覺得挺常見的)

的存圖方式:鄰接表

首先,鄰接表本質上是一種鏈表,表中的每一個節點使用指針或模擬指針進行連接(其實是不連著的)

同時,鄰接表不同於鄰接矩陣,他是以邊為單位進行存儲的,所以他所占的空間完全由邊的數量決定,和點的數量沒什麼關係,

他無論在空間還是時間上都相當優秀,在OI中一般情況下不會出現連鄰接矩陣都存不下的圖(至少本蒟蒻沒見過)

 

 1 #include<iostream>
 2 using namespace std;
 3 struct edge
 4 {
 5     int from;
 6     int to;
 7     int next;//模擬指針
 8     int weight;
 9 }e[2000080];//看吧,他開很大都不會爆,不過要註意無向圖開兩倍
10 //畢竟一條無向邊其實是當作兩條有向邊存的
11 int head[50005];//head[i]表示點i所發出的第一條邊的數組下標
12 int tot;//邊的總數
13 int n, m;
14 void add(int from,int to,int weight)
15 {
16     tot++;
17     e[tot].from = from;
18     e[tot].to = to;
19     e[tot].weight = weight;
20     e[tot].next = head[from];
21     head[from] = tot;
22 }//加邊的模板
23 int main()
24 {
25     cin >> n >> m;
26     for (int i = 1; i <= m; i++)
27     {
28         int x, y, z;
29         cin >> x >> y >> z;
30         add(x, y, z);
31         add(y, x, z);//依然無向邊存兩次
32     }
33     for (int i = head[1]; i; i = e[i].next)
34         //遍歷該點上所有的邊,如果沒有下一條了(i=0),我就停
35         //如果還有下一條邊,我就繼續往後遍歷(i=e[i].next)
36         cout << e[i].to;
37     //貌似沒解釋清楚,感性理解一下?
38     return 0;
39 }

3,vector存圖

利用stl庫中提供的動態數組vector存圖,時空上的效率都和鄰接表差不多(據說開了OI優化會稍微快一點)

註意要開<vector>頭文件

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct edge
{
    int from;
    int to;
    int weight;
};
vector<edge> e[100086];//e[i][j]表示點i的第j條邊
//貌似比鄰接表稍微簡單一點
int n, m;
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        edge t;//定義一條新的的邊出來
        int x, y, z;
        cin >> x >> y >> z;
        t.from = x;
        t.to = y;
        t.weight;
        e[x].push_back(t);//把他塞進去
        t.from = y;
        t.to = x;
        e[y].push_back(t);//改一改,反向塞進去
    }
    for (int i = 0; i < e[1].size(); i++)
        //查詢很方便
        //不過註意vector是從0開始的
        cout << e[1][i].to;
    return 0;
}

存圖時的坑點:

  • 重邊:比較一下他和原本的那條邊那個權值更小,選更小的存

  • 自環:對於一般的題貌似可以直接不管他

  • 無向圖沒開兩倍:二話不說直接RE

二,最短路

常見的最短路演算法主要有三類:

Floyd,Dijkstra以及Bellman Ford

當然還有他們的優化,以及一些其他的演算法,不過貌似那些都有很多限制條件,只能在一些特定情況下只用

1,Floyd

這個演算法實在太著名了,因為他的核心代碼只有5行....

1     for (int k = 1; k <= n; k++)//枚舉中間點
2         for (int i = 1; i <= n; i++)//枚舉起點
3             for (int j = 1; j <= n; j++)//枚舉終點
4                 e[i][j] = min(e[i][j], e[i][k] + e[k][j]);//替換

大概就是說,從i點到j點是直接走比較近還是從i到k再從k到j這樣繞一圈比較近,如果我繞一圈近的話就更新一下路徑長度

完整代碼

 

 1 #include<iostream>
 2 #include<vector>
 3 #include<algorithm>
 4 using namespace std;
 5 int e[1000][1000];
 6 int main()
 7 {
 8     int n, m;
 9     cin >> n >> m;
10     for (int i = 1; i <= n; i++)
11         for (int j = 1; j <= m; j++)
12             if (i == j)
13                 e[i][j] = 0;
14             else
15                 e[i][j] = 9999999;
16     for (int i = 1; i <= m; i++)
17     {
18         int x, y, z;
19         cin >> x >> y >> z;
20         e[x][y] = z;
21         e[y][z] = z;
22     }
23     for (int k = 1; k <= n; k++)//枚舉中間點
24         for (int i = 1; i <= n; i++)//枚舉起點
25             for (int j = 1; j <= n; j++)//枚舉終點
26                 e[i][j] = min(e[i][j], e[i][k] + e[k][j]);//替換
27     return 0;
28 }

演算法優點:

  • 他求的是多源最短路,也就是說跑完一次Floyd,那麼圖中任意兩個點之間的最短路就都知道了,不像後兩種求的是單源最短路

  • 好打

演算法缺點:

  • 太慢了.....時間複雜度O(n3)

2,Dijkstra

 1 #include<iostream>
 2 #include<algorithm>
 3 using namespace std;
 4 const long inf = 20041001;
 5 int n;
 6 int m;
 7 int s;
 8 int tot;
 9 struct edge
10 {
11     long weight;
12     int to;
13     int next;
14 }e[500005];
15 struct node
16 {
17     int head;
18 }no[10086];
19 long long dis[10086];
20 bool book[10086];
21 void add(int from, int to, int weight)
22 {
23     tot++;
24     e[tot].to = to;
25     e[tot].weight = weight;
26     e[tot].next = no[from].head;
27     no[from].head = tot;
28 }
29 int main()
30 {
31     cin >> n >> m >> s;
32     book[s] = 1;
33     for (int i = 1; i <= n; i++)
34         dis[i] = inf;
35     dis[s] = 0;
36     for (int i = 1; i <= m; i++)
37     {
38         int x, y, w;
39         cin >> x >> y >> w;
40         if (x != y)
41             add(x, y, w);
42     }
43     for (int i = no[s].head; i; i = e[i].next)
44     {
45         if (e[i].weight < dis[e[i].to])
46             dis[e[i].to] = e[i].weight;
47     }
48     for (int i = 1; i < n; i++)
49     {
50         int u = 0;
51         int minn = inf;
52         for (int j = 1; j <= n; j++)
53         {
54             if (book[j] == 0 && dis[j] < minn)
55             {
56                 minn = dis[j];
57                 u = j;
58             }
59         }
60         book[u] = 1;
61         for (int i = no[u].head; i; i = e[i].next)
62         {
63             if (dis[e[i].to] > dis[u] + e[i].weight)
64                 dis[e[i].to] = dis[u] + e[i].weight;
65         }
66     }
67     for (int i = 1; i <= n; i++)
68         cout << dis[i] << " ";
69     return 0;
70 }

演算法優點

  • 快了不少,好歹達到了O(n2)的複雜度,用堆兒優化之後甚至可以達到O((n+m)logn),算是比較優秀了

演算法缺點

  • 求的是單源最短路,也就是說我每次求完都只能知道點s到各個點的最短距離,如果我要求每個點的,也就要跑n次,就很慢了

  • 關鍵是他處理不了負權邊,尤其是負權迴路

3,Bellman Ford

 1 #include<iostream>
 2 #include<string.h>
 3 #include<algorithm>
 4 #include<vector>
 5 #include<map>
 6 #include<bitset>
 7 #include<set>
 8 #include<string>
 9 #if !defined(_WIN32)
10 #include<bits/stdc++.h>
11 #endif // !defined(_WIN32)
12 #define ll long long
13 #define dd double
14 using namespace std;
15 int t;
16 int n, m, w;
17 int tot;
18 struct edge
19 {
20     int from;
21     int to;
22     int weight;
23 }e[100086];
24 int dis[5005];
25 void add(int x, int y, int z)
26 {
27     tot++;
28     e[tot].from = x;
29     e[tot].to = y;
30     e[tot].weight = z;
31 }
32 bool Bellman_Ford()
33 {
34     memset(dis, 0x3f3f3f3f, sizeof(dis));
35     dis[1] = 0;
36     for (int i = 1; i < n; i++)
37     {
38         for (int j = 1; j <= tot; j++)
39         {
40             if (dis[e[j].to] > dis[e[j].from] + e[j].weight)
41                 dis[e[j].to] = dis[e[j].from] + e[j].weight;
42         }
43     }
44     for (int i = 1; i <= tot; i++)
45         if (dis[e[i].to] > dis[e[i].from] + e[i].weight)
46             return 0;
47     return 1;
48 }
49 int main()
50 {
51     cin >> t;
52     while (t--)
53     {
54         tot = 0;
55         memset(e, 0, sizeof(e));
56         memset(dis, 0, sizeof(dis));
57         n = 0, m = 0, w = 0;
58         cin >> n >> m >> w;
59         for (int i = 1; i <= m; i++)
60         {
61             int x, y, z;
62             cin >> x >> y >> z;
63             add(x, y, z);
64             add(y, x, z);
65         }
66         for (int i = 1; i <= w; i++)
67         {
68             int x, y, z;
69             cin >> x >> y >> z;
70             add(x, y, 0 - z);
71         }
72         if (Bellman_Ford())
73             cout << "NO" << endl;
74         else
75             cout << "YES" << endl;
76     }
77     return 0;
78 }

演算法優點

  • 可以處理負權,如果有負權迴路,則可以把他判斷出來

演算法缺點

  • 很慢,時間複雜度O(nm),而且求的是單元最短路,一般只用來判斷是否有負權迴路,而且實際使用時往往要使用他的隊列優化,也就是SPFA

(今天剛註冊的博客,立刻就像寫一篇博客試試,然而實際寫起來才覺得沒那麼簡單,寫完後返回來以看就發現了很多缺陷,而且也不太清楚從何改起.......不過我相信寫博客同樣也是熟能生巧的,只要一直寫下去,想必將來也能寫出優秀的博客)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • CSS3動畫animation的學習筆記,包括animation的屬性及其值的設定,以及keyframes的值的設定方法 ...
  • 1、面向對象特點:封裝、繼承、多態。2、構造函數 = 構造器 + 原型對象;(1)父類function UserClass(name,age,word){ //構造器 constructor this.name=name; this.age =age; this.word =word; this.i ...
  • 前言 上一節我們說了介面隔離原則,就是讓介面的職責最小化。這樣對維護代碼簡單,調用方法也清晰。 這節我們來研究依賴倒置原則。這個原則我認為是特別特別重要的。在很多地方我們能看到。比如Dubbo中使用到的SPI等等。 基本介紹 什麼是依賴倒置原則? 我們可以將其分為兩點: 1) 高層模塊不應該依賴低層 ...
  • 官網:www.fhadmin.org 特別註意: Springboot 工作流 前後分離 + 跨域 版本 (許可權控制到菜單和按鈕) 後臺框架:springboot2.1.2+ activiti6.0.0+ mybaits+maven+介面 前端頁面:html +vue.js 形式 jquery aj ...
  • ​ 這篇文章我們來學習如何使用 Spring Boot 集成 Apache Shiro 。安全應該是互聯網公司的一道生命線,幾乎任何的公司都會涉及到這方面的需求。在 Java 領域一般有 Spring Security、 Apache Shiro 等安全框架,但是由於 Spring Security ...
  • RocketMQ中通過DefaultMQProducer創建Producer DefaultMQProducer定義如下: 其中defaultMQProducerImpl成員是Producer的具體實現,其餘的一些成員是對一些參數的設置:createTopicKey:是一個Topic值,在創建時使用 ...
  • 組件聲明 在類上聲明 @Component、@Configuration、@RestController、@Service、@Repository 等註解,表示這個類需要被註入IoC容器。 1、@Configuration 和 @Bean @Configuration 常用來和 @Bean 配合使用 ...
  • 引言 StringBuffer類的delete()方法和deleteCharAt()方法都是用來刪除StringBuffer字元串中的字元 區別 1.對於delete(int start,int end)這個方法一共有兩個參數是int類型的,代表從索引下標start刪除字元到索引下標end字元,但是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...