日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

NOIP復(fù)賽復(fù)習(xí)(十)圖論算法鞏固與提高

 長(zhǎng)沙7喜 2018-04-16

一、圖的存儲(chǔ)

 

1、鄰接矩陣

 

假設(shè)有n個(gè)節(jié)點(diǎn),建立一個(gè)n×n的矩陣,第i號(hào)節(jié)點(diǎn)能到達(dá)第j號(hào)節(jié)點(diǎn)就將[i][j]標(biāo)記為1(有權(quán)值標(biāo)記為權(quán)值), 

樣例如下圖:

 

/*無(wú)向圖,無(wú)權(quán)值*/

int a[MAXN][MAXN];//鄰接矩陣

int x,y;//兩座城市

for(int i=1;i<>

{

    for(intj=1;j<>

    {

       scanf('%d%d',&x,&y);//能到達(dá),互相標(biāo)記為1

        a[x][y]=1;

        a[y][x]=1;

    }

}

/*無(wú)向圖,有權(quán)值*/

int a[MAXN][MAXN];//鄰接矩陣

int x,y,w;//兩座城市,路徑長(zhǎng)度

for(int i=1;i<>

{

    for(intj=1;j<>

    {

       scanf('%d%d%d',&x,&y,&w);//能到達(dá),互相標(biāo)記為權(quán)值w

        a[x][y]=w;

        a[y][x]=w;

    }

}

/*有向圖,無(wú)權(quán)值*/

int a[MAXN][MAXN];//鄰接矩陣

int x,y;//兩座城市

for(int i=1;i<>

{

    for(intj=1;j<>

    {

       scanf('%d%d',&x,&y);//能到達(dá),僅僅是xy標(biāo)記為1

        a[x][y]=1;

    }

}

/*有向圖,有權(quán)值*/

int a[MAXN][MAXN];//鄰接矩陣

int x,y,w;//兩座城市,路徑長(zhǎng)度

for(int i=1;i<>

{

    for(intj=1;j<>

    {

       scanf('%d%d%d',&x,&y,&w);//能到達(dá),僅僅是xy標(biāo)記為權(quán)值w

        a[x][y]=w;

    }

}

 

鄰接矩陣很方便,但是在n過(guò)大或者為稀疏圖時(shí),就會(huì)很損耗時(shí)空,不建議使用!

 

2.鄰接表

 

鄰接表是一個(gè)二維容器,第一維描述某個(gè)點(diǎn),第二維描述這個(gè)點(diǎn)所對(duì)應(yīng)的邊集們。 

鄰接表由表頭point,鏈點(diǎn)構(gòu)成,如下圖是一個(gè)簡(jiǎn)單無(wú)向圖構(gòu)成的鄰接表:

 

我們可以用指針來(lái)創(chuàng)建鏈表,當(dāng)然,這是很復(fù)雜也很麻煩的事情,下面來(lái)介紹一種用數(shù)組模擬鏈表的方法:

 

//有向圖鄰接表存儲(chǔ)

const int N=1005;

const int M=10050;

int point[N]={0};//i節(jié)點(diǎn)所對(duì)應(yīng)鏈表起始位置(表頭)

int to[M]={0};

int next[M]={0};//i節(jié)點(diǎn)下一個(gè)所指的節(jié)點(diǎn)

int cc=0;//計(jì)數(shù)器(表示第幾條邊)

void AddEdge(int x,int y)//節(jié)點(diǎn)xy

{

    cc++;

    to[cc]=y;

    next[cc]=point[x];

    point[x]=cc;

}

void find(int x)

{

    int now=point[x];

    while(now)

    {

       printf('%d\n',to[now]);

        now=next[now];

    }

}

int main()

{

   

}

如果要加強(qiáng)記憶的話可以用我所給的例子模擬一下point[],to[],next[],然后再調(diào)用函數(shù)find(x)來(lái)輸出x這個(gè)節(jié)點(diǎn)能到的點(diǎn),大概就能YY到數(shù)組是怎么存儲(chǔ)鄰接表的了。 

還是不理解的話,推一個(gè)blog,這里面說(shuō)的和我這里給出的思路很相似:http://developer.51cto.com/art/201404/435072.htm

 

二、樹(shù)的遍歷

 

1.BFS

運(yùn)用隊(duì)列,一開(kāi)始隊(duì)列中有一個(gè)點(diǎn),將一個(gè)點(diǎn)出隊(duì),將它的子結(jié)點(diǎn)全都入隊(duì)。 

算法會(huì)在遍歷完一棵樹(shù)中每一層的每個(gè)結(jié)點(diǎn)之后,才會(huì)轉(zhuǎn)到下一層繼續(xù),在這一基礎(chǔ)上,隊(duì)列將會(huì)對(duì)算法起到很大的幫助:


//廣度優(yōu)先搜索

void BreadthFirstSearch(BitNode *root)

{

    queuenodeQueue;

   nodeQueue.push(root);//將根節(jié)點(diǎn)壓入隊(duì)列

    while(!nodeQueue.empty())//隊(duì)列不為空,繼續(xù)壓入隊(duì)列

    {

        BitNode *node =nodeQueue.front();

        nodeQueue.pop();//彈出根節(jié)點(diǎn)

        if(node->left)//左兒子不為空

        {

           nodeQueue.push(node->left);//壓入隊(duì)列

        }

        if(node->right)//右兒子不為空

        {

           nodeQueue.push(node->right);//壓入隊(duì)列

        }

    }

}


2.DFS

運(yùn)用棧,遞歸到一個(gè)點(diǎn)時(shí),依次遞歸它的子結(jié)點(diǎn)。 

還可以利用堆棧的先進(jìn)后出的特點(diǎn),現(xiàn)將右子樹(shù)壓棧,再將左子樹(shù)壓棧,這樣左子樹(shù)就位于棧頂,可以保證結(jié)點(diǎn)的左子樹(shù)先與右子樹(shù)被遍歷:

 

//深度優(yōu)先搜索

//利用棧,現(xiàn)將右子樹(shù)壓棧再將左子樹(shù)壓棧

void DepthFirstSearch(BitNode *root)

{

    stacknodeStack;

   nodeStack.push(root);//將根節(jié)點(diǎn)壓棧

    while(!nodeStack.empty())//棧不為空,繼續(xù)壓棧

    {

        BitNode *node =nodeStack.top();//引用棧頂

        cout data < '="">

        nodeStack.pop();//彈出根節(jié)點(diǎn)

        if(node->right)//優(yōu)先遍歷右子樹(shù)

        {

           nodeStack.push(node->right);

        }

        if (node->left)

        {

           nodeStack.push(node->left);

        }

    }

}


三、無(wú)根樹(shù)變成有根樹(shù)

 

選擇一個(gè)點(diǎn)作為根結(jié)點(diǎn),開(kāi)始遍歷。 

遍歷到一個(gè)點(diǎn)時(shí),枚舉每一條連接它和另一個(gè)點(diǎn)的邊。若另一個(gè)點(diǎn)不是它的父結(jié)點(diǎn),那就是它的子結(jié)點(diǎn)。遞歸到子結(jié)點(diǎn)。 

我們可以更加形象的比喻為:抓住一個(gè)點(diǎn),把它拎起來(lái)構(gòu)成一棵新的樹(shù)。

 

四、并查集

 

這是我學(xué)OI這么久以來(lái)覺(jué)得性?xún)r(jià)比最高的算法(簡(jiǎn)單又實(shí)用?。。。?/span>,用來(lái)處理不相交合并和查詢(xún)問(wèn)題。 

給大家推個(gè)超超超超級(jí)易懂的blog,保證一看就懂,這里我就不再詳解了:http://blog.csdn.net/dellaserss/article/details/7724401

 

五、最小生成樹(shù)

 

1.Prim算法(適用于稠密圖) 

算法描述: 

1).輸入:一個(gè)加權(quán)連通圖,其中頂點(diǎn)集合為V,邊集合為E;

2).初始化:Vnew = {x},其中x為集合V中的任一節(jié)點(diǎn)(起始點(diǎn)),Enew = {},為空;

3).重復(fù)下列操作,直到Vnew = V

a.在集合E中選取權(quán)值最小的邊,其中u為集合Vnew中的元素,而v不在Vnew集合當(dāng)中,并且vV(如果存在有多條滿足前述條件即具有相同權(quán)值的邊,則可任意選取其中之一);

b.v加入集合Vnew中,將邊加入集合Enew中;

4).輸出:使用集合VnewEnew來(lái)描述所得到的最小生成樹(shù)。

 

#include//普里姆算法

const int N=1050;

const int M=10050;

struct Edge//定義圖類(lèi)型結(jié)構(gòu)體,ab權(quán)值為c

{

    int a,b,c;

}edge[M];

int n,m;//n個(gè)點(diǎn),m條邊

bool black[N];//染黑這個(gè)點(diǎn),表示這個(gè)點(diǎn)已經(jīng)被選過(guò)了

int ans=0;//最小生成樹(shù)權(quán)值和

int main()

{

    int i,j,k;

   scanf('%d%d',&n,&m);

    for(i=1;i<>

   scanf('%d%d%d',&edge[i].a,&edge[i].b,&edge[i].c);

    black[1]=1;//把第一個(gè)點(diǎn)染黑

    for(k=1;k<>

    {

        intmind,minz=123456789;

       for(i=1;i<=m;i++)>開(kāi)始!

        {

            if(black[edge[i].a]!=black[edge[i].b]&&edge[i].c<>

            {

                mind=i;

               minz=edge[i].c;

            }

        }

        ans+=minz;

       black[edge[mind].a]=1;//染黑兩個(gè)節(jié)點(diǎn)

       black[edge[mind].b]=1;

    }

    printf('%d\n',ans);//輸出答案

    return 0;

}


2.kruskal算法(適用于稀疏圖)

 

算法描述: 

克魯斯卡爾算法從另一途徑求網(wǎng)的最小生成樹(shù)。 

假設(shè)連通網(wǎng)N=V,{E}),則令最小生成樹(shù)的初始狀態(tài)為只有n個(gè)頂點(diǎn)而無(wú)邊的非連通圖T=V,{}),圖中每個(gè)頂點(diǎn)自成一個(gè)連通分量。 

E中選擇代價(jià)最小的邊,若該邊依附的頂點(diǎn)落在T中不同的連通分量上,則將此邊加入到T中,否則舍去此邊而選擇下一條代價(jià)最小的邊。 

依次類(lèi)推,直至T中所有頂點(diǎn)都在同一連通分量上為止。

  

#include//克魯斯卡爾算法

#include

#include

using namespace std;

const int N=1050;

const int M=10050;

struct Edge//定義圖類(lèi)型結(jié)構(gòu)體

{

    int a,b,c;//ab的權(quán)值為c

}edge[M];

int fa[N];//父親數(shù)組

int n,m;//n個(gè)節(jié)點(diǎn),m條邊

int ans=0;//最小生成樹(shù)權(quán)值和

bool cmp(Edge x,Edge y)//比較權(quán)值大小

{

    return (x.c<>

}

int getf(int x)//尋找x的最原始祖先(并查集)

{

    if(fa[x]!=x)

    fa[x]=getf(fa[x]);

    return fa[x];//返回最原始祖先

}

int main()

{

    int i,j;

   scanf('%d%d',&n,&m);

    for(i=1;i<>

   scanf('%d%d%d',&edge[i].a,&edge[i].b,&edge[i].c);

   sort(edge+1,edge+m+1,cmp);//從小到大排序邊數(shù)組

    for(i=1;i<>

    fa[i]=i;//初始值,每個(gè)節(jié)點(diǎn)的父親就是自己

    for(i=1;i<>

    {

        int a=edge[i].a;

        int b=edge[i].b;

        a=getf(a);//尋找a的最原始祖先

        b=getf(b);//尋找b的最原始祖先

        if(a!=b)//如果兩個(gè)的最終祖先不相同(不會(huì)構(gòu)成回路)

        {

           ans+=edge[i].c;//加入

            fa[a]=b;//加入當(dāng)前父親的兒子們中(合并并查集)

        }

    }

   printf('%d\n',ans);

    return 0;

}


經(jīng)典例題:繁忙的都市(Luogu 2330

城市C是一個(gè)非常繁忙的大都市,城市中的道路十分的擁擠,于是市長(zhǎng)決定對(duì)其中的道路進(jìn)行改造。城市C的道路是這樣分布的:城市中有n個(gè)交叉路口,有些交叉路口之間有道路相連,兩個(gè)交叉路口之間最多有一條道路相連接。這些道路是雙向的,且把所有的交叉路口直接或間接的連接起來(lái)了。每條道路都有一個(gè)分值,分值越小表示這個(gè)道路越繁忙,越需要進(jìn)行改造。但是市政府的資金有限,市長(zhǎng)希望進(jìn)行改造的道路越少越好,于是他提出下面的要求:

1.改造的那些道路能夠把所有的交叉路口直接或間接的連通起來(lái)。 

2.在滿足要求1的情況下,改造的道路盡量少。 

3.在滿足要求1、2的情況下,改造的那些道路中分值最大的道路分值盡量小。 

任務(wù):作為市規(guī)劃局的你,應(yīng)當(dāng)作出最佳的決策,選擇那些道路應(yīng)當(dāng)被修建。 

這題是經(jīng)典的最小瓶頸生成樹(shù)問(wèn)題:只用邊權(quán)小于等于x的邊,看看能不能構(gòu)成最小生成樹(shù)。 

kruskal算法中,我們已經(jīng)對(duì)邊從小到大排過(guò)序了,所以只要用≤x的前若干條邊即可。

 

3.最小生成樹(shù)計(jì)數(shù)問(wèn)題

 

題目:現(xiàn)在給出了一個(gè)簡(jiǎn)單無(wú)向加權(quán)圖。你不滿足于求出這個(gè)圖的最小生成樹(shù),而希望知道這個(gè)圖中有多少個(gè)不同的最小生成樹(shù)。(如果兩顆最小生成樹(shù)中至少有一條邊不同,則這兩個(gè)最小生成樹(shù)就是不同的)。 

解法:按邊權(quán)排序,先選小的,相同邊權(quán)的暴力求出有幾種方案,將邊按照權(quán)值大小排序,將權(quán)值相同的邊分到一組,統(tǒng)計(jì)下每組分別用了多少條邊。然后對(duì)于每一組進(jìn)行dfs,判斷是否能夠用這一組中的其他邊達(dá)到相同的效果。最后把每一組的方案數(shù)相乘就是答案。 

換句話說(shuō):就是不同的最小生成樹(shù)方案,每種權(quán)值的邊的數(shù)量是確定的,每種權(quán)值的邊的作用是確定的排序以后先做一遍最小生成樹(shù),得出每種權(quán)值的邊使用的數(shù)量x然后對(duì)于每一種權(quán)值的邊搜索,得出每一種權(quán)值的邊選擇方案。

 

#include

#include

#define N 105

#define M 1005

#define MOD 31011

using namespace std;

struct node//定義圖類(lèi)型結(jié)構(gòu)體

{

    int a,b;//節(jié)點(diǎn)ab

    int zhi;//ab的權(quán)值

}xu[M];

int n,m;

int fa[N];

int lian[N];

int ans=1;

int cmp(struct node x,struct node y)//從小到大排序函數(shù)

{

    return(x.zhi<>

}

int getf(int x)

{

    if(fa[x]!=x)

        fa[x]=getf(fa[x]);

    return(fa[x]);

}

int getlian(int x)

{

    if(lian[x]==x)

        return x;

    return (getlian(lian[x]) );

}

int dfs(int now,int end,int last)

{

    if(now==end)

    {

        if(last==0)

            return 1;

        return 0;

    }

    intres=dfs(now+1,end,last);

    ints=getlian(xu[now].a);

    intt=getlian(xu[now].b);

    if(s!=t)

    {

        lian[s]=t;

       res+=dfs(now+1,end,last-1);

        lian[s]=s;

    }

    return res;

}

int main()

{

    int i,j,k;

    int s,t;

    int now;

    int sum=0;

   scanf('%d%d',&n,&m);

    for(i=1;i<=n;i++)>初始化,每個(gè)節(jié)點(diǎn)的父親就是自己

        fa[i]=i;

    for(i=1;i<>

       scanf('%d%d%d',&xu[i].a,&xu[i].b,&xu[i].zhi);

   sort(xu+1,xu+m+1,cmp);//從小到大排序邊數(shù)組

    for(i=1;i<>

    {

       for(j=1;j<>

            lian[j]=j;

        k=i;

       while(i<>

        {

           xu[i].a=getf(xu[i].a);

           xu[i].b=getf(xu[i].b);

            i++;

        }

        now=sum;

        for(j=k;j<>

        {

           s=getf(xu[j].a);

           t=getf(xu[j].b);

            if(s!=t)

            {

                sum++;

                fa[s]=t;

            }

        }

       ans*=dfs(k,i,sum-now);

        ans%=MOD;//防止溢出

    }

    if(sum!=n-1)

        ans=0;

   printf('%d\n',ans);

    return 0;

}


六、最短路徑

 

1.Floyd算法(插點(diǎn)法) 


通過(guò)一個(gè)圖的權(quán)值矩陣求出它的每?jī)牲c(diǎn)間的最短路徑(多源最短路)。 

算法描述: 

一個(gè)十分暴力又經(jīng)典的DP,假設(shè)ij的路徑有兩種狀態(tài): 

ij直接有路徑相連:

ij間接聯(lián)通,中間有k號(hào)節(jié)點(diǎn)聯(lián)通:

假設(shè)dis[i][j]表示從ij的最短路徑,對(duì)于存在的每個(gè)節(jié)點(diǎn)k,我們檢查一遍dis[i][k]+dis[k][j]。

//Floyd算法,時(shí)間復(fù)雜度:O(n^3) 

int dis[MAXN][MAXN];

for(k=1;k<=n;k++)>

{

    for(i=1;i<>

    {

        for(j=1;j<>

        {

            dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);//DP

        }

    }

} 

 

2.Dijkstra算法(無(wú)向圖,無(wú)負(fù)權(quán)邊)

 

算法描述: 

多源最短路! 

a. 初始時(shí),S只包含源點(diǎn),即S{v},v的距離為0。U包含除v外的其他頂點(diǎn),即:U={其余頂點(diǎn)},若vU中頂點(diǎn)u有邊,則正常有權(quán)值,若u不是v的出邊鄰接點(diǎn),則權(quán)值為 

b. U中選取一個(gè)距離v最小的頂點(diǎn)k,把k,加入S中(該選定的距離就是vk的最短路徑長(zhǎng)度)。 

c. k為新考慮的中間點(diǎn),修改U中各頂點(diǎn)的距離;若從源點(diǎn)v到頂點(diǎn)u的距離(經(jīng)過(guò)頂點(diǎn)k)比原來(lái)距離(不經(jīng)過(guò)頂點(diǎn)k)短,則修改頂點(diǎn)u的距離值,修改后的距離值的頂點(diǎn)k的距離加上邊上的權(quán)。 

d. 重復(fù)步驟bc直到所有頂點(diǎn)都包含在S中。 

還是舉個(gè)例子吧!如下圖!

我們假設(shè)1號(hào)節(jié)點(diǎn)為原點(diǎn)。 

第一輪,我們可以算出2,3,4,5,6號(hào)節(jié)點(diǎn)到原點(diǎn)1的距離為[7,9,∞,∞,14],表示無(wú)窮大(節(jié)點(diǎn)間無(wú)法直接連通),取其中最小的7,就確定了1->1的最短路徑為0,1->2的最短路徑為7,同時(shí)去最短路徑最小的2節(jié)點(diǎn)為下一輪的前驅(qū)節(jié)點(diǎn)。 

第二輪,取2節(jié)點(diǎn)為前驅(qū)節(jié)點(diǎn),按照前驅(qū)節(jié)點(diǎn)到原點(diǎn)的最短距離 + 新節(jié)點(diǎn)到前驅(qū)節(jié)點(diǎn)的距離來(lái)計(jì)算新的最短距離,可以得到3,4,5,6號(hào)節(jié)點(diǎn)到原點(diǎn)1的距離為[17,22,∞,∞](新節(jié)點(diǎn)必須經(jīng)過(guò)2號(hào)節(jié)點(diǎn)回到原點(diǎn)),這時(shí)候需要將新結(jié)果和上一輪計(jì)算的結(jié)果比較,3號(hào)節(jié)點(diǎn):17>9,最短路徑仍然為9;4號(hào)節(jié)點(diǎn):22<>,更新4號(hào)節(jié)點(diǎn)的最短路徑為22,;5號(hào)節(jié)點(diǎn):仍然不變?yōu)?/span>;6號(hào)節(jié)點(diǎn):14<>,更新6號(hào)節(jié)點(diǎn)的最短路徑為14。得到本輪的最短距離為[9,22,∞,14]1->3的最短路徑為9,同時(shí)取最短路徑最小的3節(jié)點(diǎn)為下一輪的前驅(qū)節(jié)點(diǎn)。 

第三輪:同理上,以3號(hào)節(jié)點(diǎn)為前驅(qū)節(jié)點(diǎn),可以得到4,5,6號(hào)節(jié)點(diǎn)到原點(diǎn)1的距離為[20,∞,11],根據(jù)最短路徑原則,和上一輪最短距離比較,刷新為[20,∞,11],1->3->6的最短路徑為11,同時(shí)取最短路徑最小的6節(jié)點(diǎn)為下一輪的前驅(qū)節(jié)點(diǎn)。 

第四輪:同理,得到4,5號(hào)節(jié)點(diǎn)最短距離為[20,20],這兩個(gè)值相等,運(yùn)算結(jié)束,到達(dá)這兩個(gè)點(diǎn)的最短距離都是20,如果這兩個(gè)值不相等,還要進(jìn)行第五輪運(yùn)算!

 

#include

#include

const int N=100500;

const int M=200500;

int point[N]={0},to[M]={0},next[M]={0},len[M]={0},cc=0;

int dis[N];//最短路長(zhǎng)度

bool ever[N];//當(dāng)前節(jié)點(diǎn)最短路有沒(méi)有確定

int n,m;

void AddEdge(int x,int y,int z)//添加新的邊和節(jié)點(diǎn):xy邊長(zhǎng)z

{

    cc++;

    next[cc]=point[x];

    point[x]=cc;

    to[cc]=y;

    len[cc]=z;//len記錄xy的邊長(zhǎng)

}

int main()

{

    int i,j,k;

   scanf('%d%d',&n,&m);

    for(i=1;i<>

    {

        int a,b,c;

       scanf('%d%d%d',&a,&b,&c);

        AddEdge(a,b,c);//無(wú)向圖,要加兩遍

        AddEdge(b,a,c);

    }

    memset(dis,0x3f,sizeofdis);//用極大值來(lái)初始化

    dis[1]=0;//1號(hào)節(jié)點(diǎn)到自己最短距離為0

    for(k=1;k<>

    {

        intminp,minz=123456789;

       for(i=1;i<>

        {

            if(!ever[i])

            {

               if(dis[i]<>

                {

                    minz=dis[i];

                   minp=i;

                }

            }

        }

        ever[minp]=1;

        intnow=point[minp];

        while(now)

        {

            inttox=to[now];

           if(dis[tox]>dis[minp]+len[now])

            dis[tox]=dis[minp]+len[now];

            now=next[now];

        }

    }

    for(i=1;i<>

       printf('%d\n',dis[i]);

    return 0;

}


3.SPFA算法(有負(fù)權(quán)邊,無(wú)負(fù)圈,能檢測(cè)負(fù)圈但不能輸出)

 

SPFADijkstra極為相似,只是加了個(gè)隊(duì)列優(yōu)化來(lái)檢測(cè)負(fù)圈和負(fù)權(quán)邊。 

算法描述: 

建立一個(gè)隊(duì)列,初始時(shí)隊(duì)列里只有起始點(diǎn),再建立一個(gè)表格記錄起始點(diǎn)到所有點(diǎn)的最短路徑(該表格的初始值要賦為極大值,該點(diǎn)到他本身的路徑賦為0)。然后執(zhí)行松弛操作,用隊(duì)列里有的點(diǎn)作為起始點(diǎn)去刷新到所有點(diǎn)的最短路,如果刷新成功且被刷新點(diǎn)不在隊(duì)列中則把該點(diǎn)加入到隊(duì)列最后。重復(fù)執(zhí)行直到隊(duì)列為空。 

判斷有無(wú)負(fù)環(huán):

如果某個(gè)點(diǎn)進(jìn)入隊(duì)列的次數(shù)超過(guò)N次則存在負(fù)環(huán)(SPFA無(wú)法處理帶負(fù)環(huán)的圖)

 

#include

#include

const int N=100500;

const int M=200500;

int point[N]={0},to[M]={0},next[M]={0},len[M]={0},cc=0;

int dis[N];//最短路長(zhǎng)度

int queue[N],top,tail;//雙向隊(duì)列queue,隊(duì)頭,隊(duì)尾

bool in[N];//記錄這個(gè)點(diǎn)在不在隊(duì)列中,1表示在,0表示不在

int n,m; //n個(gè)節(jié)點(diǎn),m條邊

void AddEdge(int x,int y,int z)//xy邊長(zhǎng)為z

{

    cc++;

    next[cc]=point[x];

    point[x]=cc;

    to[cc]=y;

    len[cc]=z;

}

int main()

{

    int i,j;

   scanf('%d%d',&n,&m);

    for(i=1;i<>

    {

        int a,b,c;

        scanf('%d%d%d',&a,&b,&c);

        AddEdge(a,b,c);//因?yàn)槭请p向隊(duì)列,左邊加一次,右邊加一次

        AddEdge(b,a,c);

    }

    memset(dis,0x3f,sizeofdis);//用極大值來(lái)初始化

    dis[1]=0;//1號(hào)節(jié)點(diǎn)到自己最短距離為0

   top=0;tail=1;queue[1]=1;in[1]=1;//初始化,只有原點(diǎn)加入

    while(top!=tail)

    {

        top++;

        top%=N;

        intnow=queue[top];

        in[now]=0;

        int ed=point[now];

        while(ed)

        {

            inttox=to[ed];

           if(dis[tox]>dis[now]+len[ed])

            {

               dis[tox]=dis[now]+len[ed];

                if(!in[tox])

                {

                   tail++;

                   tail%=N;

                   queue[tail]=tox;

                   in[tox]=1;

                }

            }

            ed=next[ed];

        }

    }

    for(i=1;i<>

        printf('%d\n',dis[i]);

    return 0;

}


4.BellmanFord算法(有負(fù)權(quán)邊,可能有負(fù)圈,能檢測(cè)負(fù)圈并輸出)

 

算法描述: 

1.初始化:將除源點(diǎn)外的所有頂點(diǎn)的最短距離估計(jì)值 d[all]=+∞, d[start]=0;

2.迭代求解:反復(fù)對(duì)邊集E中的每條邊進(jìn)行松弛操作,使得頂點(diǎn)集V中的每個(gè)頂點(diǎn)v的最短距離估計(jì)值逐步逼近其最短距離;(運(yùn)行|v|-1次)

3.檢驗(yàn)負(fù)權(quán)回路:判斷邊集E中的每一條邊的兩個(gè)端點(diǎn)是否收斂。如果存在未收斂的頂點(diǎn),則算法返回false,表明問(wèn)題無(wú)解;否則算法返回true,并且從源點(diǎn)可達(dá)的頂點(diǎn)v的最短距離保存在 d[v]中。

簡(jiǎn)單的說(shuō),如下圖所示:

 

松弛計(jì)算之前,點(diǎn)B的值是8,但是點(diǎn)A的值加上邊上的權(quán)重2,得到5,比點(diǎn)B的值(8)小,所以,點(diǎn)B的值減小為5。這個(gè)過(guò)程的意義是,找到了一條通向B點(diǎn)更短的路線,且該路線是先經(jīng)過(guò)點(diǎn)A,然后通過(guò)權(quán)重為2的邊,到達(dá)點(diǎn)B

如果出現(xiàn)了以下情況:

松弛操作后,變?yōu)?/span>7,7>6,這樣就不修改(Bellman Frod算法的高妙之處就在這),保留原來(lái)的最短路徑就OK,代碼實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單。

 

int n,m;//n個(gè)點(diǎn),m條邊

struct Edge//定義圖類(lèi)型結(jié)構(gòu)體

{

    int a,b,c;//ab長(zhǎng)度為c

}edge[];

int dis[];

memset(dis,0x3f,sizeof dis);

dis[1]=0;

for(int i=1;i<>

{

    for(intj=1;j<>

    {

       if(dis[edge[j].b]>dis[edge[j].a]+edge[j].c)

        {

            dis[edge[j].b]=dis[edge[j].a]+edge[j].c;       

        }

    }

}

 

七、拓?fù)渑判?/span>

 

對(duì)一個(gè)有向無(wú)環(huán)圖(Directed Acyclic Graph簡(jiǎn)稱(chēng)DAG)G進(jìn)行拓?fù)渑判?,是?/span>G中所有頂點(diǎn)排成一個(gè)線性序列,使得圖中任意一對(duì)頂點(diǎn)uv,若邊(u,v)E(G),則u在線性序列中出現(xiàn)在v之前。通常,這樣的線性序列稱(chēng)為滿足拓?fù)浯涡?/span>(Topological Order)的序列,簡(jiǎn)稱(chēng)拓?fù)湫蛄小?/span> 

打個(gè)比喻:我們要做好一盤(pán)菜名字叫做紅燒茄子,那么第一步得買(mǎi)茄子和配料,第二步就是要洗茄子,第三步就是要開(kāi)始倒油進(jìn)鍋里啊什么七七八八的,第四步,你不可能先洗茄子再買(mǎi)茄子和配料,這樣的一些事件必須是按照順序進(jìn)行的,這些依次進(jìn)行的事件就構(gòu)成了一個(gè)拓?fù)湫蛄小?/span>

 

算法描述: 

我們需要一個(gè)棧或者隊(duì)列,兩者都可以無(wú)所謂,只是找個(gè)容器把入度為0的元素維護(hù)起來(lái)而已。 

從有向圖中選擇一個(gè)入度為0(無(wú)前驅(qū))的頂點(diǎn),輸出它。 

從網(wǎng)中刪去該節(jié)點(diǎn),并且刪去從該節(jié)點(diǎn)出發(fā)的所有有向邊。 

重復(fù)以上兩步,直到剩余的網(wǎng)中不再存在沒(méi)有前驅(qū)的節(jié)點(diǎn)為止。 

具體操作過(guò)程如下: 

若棧非空,則在棧中彈出一個(gè)元素,然后枚舉這個(gè)點(diǎn)能到的每一個(gè)點(diǎn)將它的入度-1(刪去一條邊),如果入度=0,則壓入棧中。

如果沒(méi)有輸出所有的頂點(diǎn),則有向圖中一定存在環(huán)

 

//拓?fù)渑判颍瑫r(shí)間復(fù)雜度:O(n+m)

#include

#include

const int N=100500;

const int M=200500;

int point[N]={0},to[M]={0},next[M]={0},cc=0;

int xu[N]={0};//棧,初始值為空,xu[0]表示棧的大小

int in[N]={0};//入度,a可以到達(dá)b,in[b]++

int ans[N]={0};//ans[0]整個(gè)拓?fù)湫蛄械拇笮?/span>

int n,m;

void AddEdge(int x,int y)//鄰接表ab

{

    cc++;

    next[cc]=point[x];

    point[x]=cc;

    to[cc]=y;

}

int main()

{

    int i,j;

   scanf('%d%d',&n,&m);

    for(i=1;i<>

    {

        int a,b;

       scanf('%d%d',&a,&b);

        in[b]++;//統(tǒng)計(jì)每個(gè)節(jié)點(diǎn)的入度

        AddEdge(a,b);

    }

    for(i=1;i<>

    {

        if(in[i]==0)//這個(gè)節(jié)點(diǎn)入度為0,壓入棧

        xu[++xu[0]]=i;

    }

    while(xu[0])

    {

        int now=xu[xu[0]];//出棧

        xu[0]--;

        ans[++ans[0]]=now;

        int ed=point[now];

        while(ed)

        {

            inttox=to[ed];

            in[tox]--;

            if(!in[tox])

           xu[++xu[0]]=tox;

            ed=next[ed];//找下一個(gè)相鄰節(jié)點(diǎn)

        }

    }

    if(ans[0]有向圖中一定存在環(huán),無(wú)結(jié)果

    printf('nosolution');

    else

    {

       for(i=1;i<>

        printf('%d',ans[i]);

    }

    return 0;

}


八、聯(lián)通分量

 

強(qiáng)連通:有向圖中,從a能到b并且從b可以到a,那么ab強(qiáng)連通。 

強(qiáng)連通圖:有向圖中,任意一對(duì)點(diǎn)都滿足強(qiáng)連通,則這個(gè)圖被稱(chēng)為強(qiáng)連通圖。 

強(qiáng)聯(lián)通分量:有向圖中的極大強(qiáng)連通子圖,就是強(qiáng)連通分量。 

一般用Tarjan算法求有向圖強(qiáng)連通分量: 

推一個(gè)蠻容易理解的bloghttp://www.cnblogs.com/uncle-lu/p/5876729.html

 

九、歐拉路徑與哈密頓路徑

 

1.歐拉路徑:從某點(diǎn)出發(fā)一筆畫(huà)遍歷每一條邊形成的路徑。 

歐拉回路:在歐拉路徑的基礎(chǔ)上回到起點(diǎn)的路徑(從起點(diǎn)出發(fā)一筆畫(huà)遍歷每一條邊)。 

歐拉路徑存在: 

無(wú)向圖:當(dāng)且僅當(dāng)該圖所有頂點(diǎn)的度數(shù)為偶數(shù)或者除了兩個(gè)度數(shù)為奇數(shù)外其余的全是偶數(shù)。 

有向圖:當(dāng)且僅當(dāng)該圖所有頂點(diǎn)出度=入度或者一個(gè)頂點(diǎn)出度=入度+1,另一個(gè)頂點(diǎn)入度=出度+1,其他頂點(diǎn)出度=入度 

歐拉回路存在: 

無(wú)向圖:每個(gè)頂點(diǎn)的度數(shù)都是偶數(shù),則存在歐拉回路。 

有向圖:每個(gè)頂點(diǎn)的入度都等于出度,則存在歐拉回路。 

求歐拉路徑/歐拉回路算法常常用Fleury算法: 

再推一個(gè)蠻容易理解的bloghttp://www.cnblogs.com/Lyush/archive/2013/04/22/3036659.html

 

2.哈密頓路徑:每個(gè)點(diǎn)恰好經(jīng)過(guò)一次的路徑是哈密頓路徑。 

哈密頓回路:起點(diǎn)與終點(diǎn)之間有邊相連的哈密頓路徑是哈密頓回路。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多