C++算法:圖論基礎


背景

C++ 有一個非常著名的算法,叫做 圖論 。

廢話不多說,了解這么多。潦草結束

洛谷博客也有這篇文章


相關知識

基礎模板涉及:

結構體

數組

for循環

進階算法涉及:(這篇文章不寫)

while循環

優先隊列(隊列)

vector


一、圖的基本概念

圖是一種數據結構,從嚴格意義上講,它的定義為:

圖論基礎

G在這里代表的是圖,而V是一個非空的有限集合,代表頂點(也就是結點),E代表邊的集合。

如下圖,下面這個就是圖

圖論基礎

圖的一些定義與概念

因為有很多,我一項一項列出來

1.有向與無向:

有向圖就是圖的邊有方向,按箭頭的方向從一點到另一點。

無向圖就是圖的邊沒有方向,雙向通行。

圖論基礎

2.度、出度、入度

度是無向圖中的,而出度和入度是有向圖中的

圖論基礎

上圖中每個結點的周圍都寫着它的度(或者是出度,入度)

無向圖的度就是這個點所連接的邊的數量

有向圖的出度就是從這個點出發的邊數,入度就是從任一點(除自己外)出發,連接自己的邊數就叫入度

這么說你可能很繞腦,你看圖就行了

3.邊權(權值)

邊權,你可以抽象的理解為走這條邊的費用(也可以理解為長度、代價等),也就是下圖

圖論基礎

比如有向圖,從1到2或者從2到1都要有1的代價,再比如無向圖里,從3到4要有4的代價,這就是邊權了。

4.連通

連通並不是兩個點之間有邊就算連通,只要是從A出發,不論怎么走,只要能走到B,那么就可以說A和B是連通的。

准確、科學的說只要能從U點出發,經過若干點后達到點V,那么就稱UV是連通的

圖論基礎

上圖中,1和2是連通的,1和3也是連通的,2和3同理

5.完全圖、稀疏圖、稠密圖

完全圖就是在有n個點,且有n(n−1)/2條邊的圖,叫做完全圖

而稀疏圖和稠密圖就是一些比較模糊的了,稠密圖就是邊的數量接近完全圖但不是完全圖的圖,稀疏圖是邊更少,比稠密圖的邊還要少的圖

圖論基礎

6.環(回路)

起點與終點相同的路徑,叫做環。每個圖有環,就叫環圖(也叫哈爾密頓圖),否則叫無環圖。

圖論基礎

7.強連通分量

在一個有向圖中,由n個點構成環的子圖叫做強連通分量,單個點(未算在其他子圖中的點)也算強連通分量。

圖論基礎

在上圖中,有2個強連通分量: 第1個是由1->3->5->4構成的子圖 第2個是由2構成的子圖


二、圖的存儲結構與建邊

存圖有很多種方法,這里介紹兩種


1.鄰接矩陣

鄰接矩陣是個 二維數組 ~~

它的調用格式是這樣的

g[i][j]//表示從i到j的邊的權值(也可以用來存是否存在這個信息)

我這里給出幾個圖康康吧

圖論基礎

這個圖,用鄰接矩陣存儲后是:(給出鄰接矩陣)

圖論基礎

你有沒有發現什么呢?

你會發現這個圖他是對稱的。。。

這就證明,他是個無向圖。

為什么呢?不因為什么,就因為他是無向圖,兩邊都可以通行

那么為什么要這樣存圖呢?

我先來說明一下啊

第 i行存儲的是結點 i所連接的邊的權值(或者是否連接這個點)

那么第i行的第j列不就是從i到j的那條邊了嗎?

你會發現,哇!原來這么簡單

當然,鄰接矩陣的好處之一就是方便調用邊

在有邊權的時候,要先初始化才可以使用

for(int i=1;i<=n;i++){
    for(int j=1;j<=n;j++){
        g[i][j]=0x7fffffff;
    }
}

當然了,你也可以寫這個

memset(g,0x7f,sizeof(g));

但是要注意寫cstring或者string.h頭文件

存圖該怎么存呢?

總不能一個一個去存吧,,,而且你還不知道數據

那么就要用到for了

for(int i=1;i<=m;i++){//m是邊的數量 
    int from,to,w;
    cin>>from>>to>>w;
    g[from][to]=w;
}

g[from][to]意思就是從from到to的那條邊,w就是權值,從from到to的邊的權值是w

當然,如果你要存的是這條邊是否存在,那么你初始化要寫0,然后有邊就寫1

當然!這只是有向圖

無向圖的話就證明兩邊都可以走對吧?那么如果寫一個雙向的箭頭不也是一樣嗎?所以說我們要這樣存圖

for(int i=1;i<=m;i++){//m是邊的數量 
    int from,to,w;
    cin>>from>>to>>w;
    g[from][to]=w;
    g[to][from]=w;
}

沒錯,就是存兩邊,只不過起點和終點反過來了而已(雖然只是起點終點反過來了,但是他們的意義完全不一樣)

總結:鄰接矩陣的優點是方便度的計算(讀入時就可以算)、方便判斷兩點是否有邊及其權值、大小是n*n、占用的單元只與頂點有關。缺點是尋找一個點的所有相連的邊需要1-n循環,而且內存過大容易爆


2.鄰接表

哇,,,終於碼到鄰接表了,累死我了啊啊啊

鄰接表非常像鏈表

圖論基礎

要用到結構體了。。。

首先定義一個名為Edge(邊)的結構體並定義一個Edge類型的edge數組

struct Edge{
    int nxt,to,w;
}edge[MAX];

nxt就是nxet的意思,就是下一條邊的指針。不寫next的原因是評測機可能會吞特殊字,所以這里寫了nxt

to是這條邊指向的結點

w就是權值了

MAX嘛,,,自己定義,但是要注意存題上給的二倍(比如1e5+9要變成2e5+9),因為可能是無向圖

然后建邊嘛。。。這個就比較麻煩了

首先需要定義一個變量和一個數組

int num_edge=0,head[MAX];

num_edge就是當前edge數組存到的位置

head[i]就代表結點i連接的最后一條邊

這么說你又雙叒叕可能會有點暈(其實我更暈),一會寫建邊給你講

void add_edge(int from,int to,int w){
    edge[++num_edge].nxt=head[from];
    edge[num_edge].to=to;
    edge[num_edge].w=w;
    head[from]=num_edge;
}

這個就是建邊的手寫數組

需要3個參數,我就不講了。。。。

首先函數的第一行,++num_edge就是先自增后存儲,這樣就不用額外寫一個num_edge++了(我這里是從1開始存的,要從0開始存的話,你num_edge初始值可以為-1)

也就是說,建一個新邊,首先邊的總數+1了(就是++num_edge),他的下一條邊的值就是from(也就是新邊的起始點)的最后一條邊的指針(這里是之前的最后一條邊的指針,它加進來之后就是最后一條邊就是他了)

然后第二、三步,建立新邊指向的結點和權值

第四步,就是第一步寫的“它加進來之后就是最后一條邊就是他了”,只不過要存他的指針而已,當然就是num_edge。就是from(新邊的起始點)的最后一條邊更新

然后就變成了這樣:

for(int i=1;i<=n;i++){
    int from,to,w;
    cin>>from>>to>>w;
    add_edge(from,to,w);
    add_edge(to,from,w);
}

 

這就是建邊的全過程了,累死我了

你可能會覺得,還是鄰接矩陣好用,鄰接表根本沒聽懂(???),還不好用,這么麻煩

但是到了后面寫Dijkstra等高級算法的時候,特別是學優化的時候,你不僅會習慣寫鄰接表,還會說真香。。。(我直呼內行!)

三、結束了,總結一下下

唉鴨累死我了先寫這些吧

做圖、碼字、寫代碼都累死我了,用了2個多小時才寫出來

所以各方面都體諒一下吧。。。

這里練習題我就不提供了,大家自學一下吧

回顧:

這次我們學了鄰接矩陣、鄰接表以及圖的概念,你都學會了嗎?

沒學會再去學一遍吧qaq

這次就到這里了886/qq

轉載請注明出處作者~


免責聲明!

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



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