無中生有之突擊 ( ) 更多演算法 一)

2021-07-24 03:52:15 字數 3899 閱讀 5692

又開始了哈:

最小生成樹(1)

何為最小生成樹呢?我們有很多路可以選,可有有很多種方案,然而如何達到乙個最低值,而取消掉那些多餘的路呢?這就是它的作用所在!
演算法分析:

首先我們要讓n個頂點的圖連通,那麼至少需要n-1條邊。既然我們想讓邊的總長度之和最短,我們自然可以首先選擇最短的邊,直到選擇了n-1條邊為止,此時有n個圖的邊才得以連通。然後中間肯定會有重複連線的,比如1->2,2->3,1->3此時就會形成迴路,就不是樹了,所以,我們要保證:若兩邊已連通,且因為我們是從最小邊開始找起的,為了防止迴路,我們要跳過與其形成迴路的邊。回顧演算法:我們發現想要判斷兩個頂點是否已經連通,這一點我們可以使用dfs和bfs來解決。但這樣效率會很低,所以,我們可以利用上一張所學,將所有頂點放大品牌乙個並查集裡面,判斷兩個頂點是否連通,只需判斷兩個頂點是否在同乙個集合即可,這樣的時間複雜度僅為logn。這個演算法的名字叫做kruskal,音譯過來是克魯斯卡爾,我們總結一下該演算法:首先按照邊的權值進行從小到大排序,每次從剩餘的邊中選擇權值較小且邊的兩個頂點不在同乙個集合內的邊。

**如下:

#include

struct edge

;//為了方便排序這裡建立了乙個結構體來判斷邊的關係

struct edfe e[10];//陣列大小根據實際情況來設定,要比m的最大值大1

int n,m;

int f[7]=,sum=0;count=0;

//這是並查集需要用到的變數,f陣列的大小根據實際情況來設定,要比n的最大值大1

void quicksort(int left,int right)

//並查集尋找祖先的函式

int getf(int v)

}//並查集合並兩個子集合的函式

int merge(int v,int u)

return0;}

//從此處閱讀哦:)

int main()

if( count == n-1 )//直到選用了n-1條邊之後退出迴圈

break;

}printf("%d",sum);

getchar();getchar();

return

0;}

最終時間複雜度為o(mlogm)

最小生成樹(2)

上節說過,最小生成樹里,要用n-1條邊把n個頂點連線起來,那麼每個頂點都必須至少有一條邊與它相連,(要不然這個店就是乙個孤立的點了)。那我隨便選乙個頂點開始,反正最終每個頂點都是要選到的,看看這個頂點有哪些邊,在它的邊中找乙個最短的,並且使其相連,然後我們再找其他的頂點,離著它們越近越好,然後我們列舉他們這兩個頂點所有的邊,看看哪些邊可以連線到沒有被選中的頂點,並且邊越短越好,通過這條邊就可以把其他的乙個點連在了一起了,相信你已經看出來了眉目,不錯,其實就是繼續採用剛才的方法去檢視一條最短邊可以連線到沒有被選中的頂點,照此方法,一共重複操作n-1次,直到所有的頂點都選中,演算法結束這個方法就像乙個生成樹在慢慢長大一樣,從乙個頂點長到了n個頂點。

總結一下這個演算法,我們將圖中所有的頂點分為兩類;樹頂點,(已被選入生成樹的頂點)和非樹頂點(還未被選入生成樹的頂點)。首先選擇任意乙個頂點加入生成樹(你可以理解為是生成樹的根)。接下來找到一條邊新增到生成樹,這需要列舉每乙個數頂點到每乙個非樹頂點所有的邊,然後找到乙個最短邊加入到生成樹,照此方法重複操作n-1次,直到所有頂點都加入生成樹中。

z在實現這個演算法的時候,比較麻煩和費事的是,如何找到下乙個新增到生成樹的邊,但我們學習了迪傑斯特拉演算法,該演算法的思想是用乙個dis陣列記錄各個點到原點的距離,然後每次掃瞄陣列dis,從中選出離頂點最近的頂點,(假設這個頂點為j),看看該頂點的所有邊能否更新源點到各個頂點的距離,即如果dis[k]>dis[j]+e[j]k則更新dis[k]=dis[j]+e[j][k]。在這裡我們也可以使用類似的方法,但有一點小小的變化。用陣列dis來記錄生成樹到各個頂點的距離,也就是說現在記錄的最短距離,不是每個頂點到1號頂點的最短距離,而是每個頂點到任意乙個樹頂點(已被選入生成樹的頂點)的最短距離,即如果dis[k]>e[j]k則更新dis[k]=e[j][k]。因此在計算更新最短路徑的時候不再需要加上dis[j]了,因為我們的目標並不是非得靠近1號頂點,而是靠近生成樹就可以。也就是說只需要靠近「生成樹」中任意乙個樹頂點就可以,也就是說只需要靠近生成樹·中任意乙個樹頂點就行。

這種方法是不是很巧妙,我們再來整理一下這個演算法的流程

1、從任意乙個頂點開始構造生成樹,假設從1號頂點開始,首先將1號頂點加入生成樹中,然後用乙個一維陣列book來記錄那些頂點加入了生成樹。

2、用陣列dis記錄生成樹到各頂點的距離。最初生成樹只有1號頂點,有直連邊時,陣列dis中儲存的就是1號頂點到該頂點的邊的權值,沒有直連邊時就是無窮大,即初始化dis陣列。

3、從陣列dis中選出離生成樹最近的頂點,(假設這個頂點為j)加入到生成樹中(即在陣列dis中找到最小值)。再以j為中間點,更新生成樹到每乙個非樹頂點的距離(就是鬆弛啦)即如果dis[k]>e[j][k]則更新dis[k]=e[j][k]。

4、重複第3步,直到生成樹中有n個頂點為止。

#include

int main()

;//對book陣列進行了初始化

int inf=99999999;//用inf儲存乙個認為無窮大的值。

intcount=0,sum=0;//count用來記錄生成樹中頂點的個數,sum用來儲存路徑之和。

//讀入n和m,n表示頂點個數,m表示邊的條數。

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

//初始化

for(i=1;i<=n;i++)

for(j=1;j<=n;j++)

if(i==j) e[i][j]=0;

else e[i][j]=inf;

//開始讀入邊

for(i=1;i<=m;i++)

//初始化dis陣列,這裡是1號id那個i頂點到各個頂點的初始距離,因為當前的生成樹中只有乙個1號頂點。

for(i=1;i<=n;i++)

dis[i]=e[1][i];

//prim核心部分開始

//將1號頂點加入生成樹

book[1]=1;//這裡用book來標記乙個頂點是否已經加入了生成樹

count++;

while(count

for(i=1;i<=n;i++)

}printf("%d",sum);

getchar();getchar();

return

0;}

該演算法時間複雜度為o(n²)。如果借助堆,每次選邊的時間複雜度是o(longm),然後使用鄰接表儲存圖的話,整個演算法的時間複雜度會降低到o(mlogn)。那麼如何使用堆來優化呢?

陣列dis用來記錄生成樹到各個頂點的距離,陣列h是乙個最小堆,堆裡面儲存的是頂點編號。請注意這裡並不是按照頂點編號的大小來建立最小堆的,而是按照頂點在陣列dis中所對應的值來建立最小堆。此外還需要要乙個陣列pos來記錄每個頂點在最小堆中的位置。例如上圖中,左邊最小堆的圓圈中儲存的是頂點編號,圓圈右下角的數是該頂點(圓圈裡面的數)到生成樹的最短距離,即陣列dis中儲存的值,下面**來了。

#include

int dis[7],book[7]=;//book陣列用來記錄哪些項點已經放入生成樹

int h[7],pos[7],size;//h用來儲存堆,pos用來儲存每個頂點在堆中的位置,size為堆的大小

void swap(int

x,int

y)//向下調整函式

void siftdown (int i)//傳入乙個需要向下調整的結點編號

//如果發現最小的結點編號不是自己,說明子節點中有比父節點跟小的

if(t!=i)

else

flat=1;

}return;

}

無中生有之突擊NOIP(1) 排序

1 桶排序 定義 時間複雜度特別大,較為雞肋,在給定的小範圍內可以進行進行對照般的排序。理解,就是把範圍內的資料按照範圍乙個個放到由範圍標號的桶裡,然後按照每個桶裡的數量列印。實現如下 include include include int main for i 1000 i 0 i for j 1...

第0章 無中生有

每個學計算機的同學都想寫乙個作業系統,每個學儲存的同學都想開發一款磁碟陣列。每當看到 作業系統 四個字我就衝動,那是大腦皮層的強烈反應,無奈能力不濟,繞了一大圈都沒入門。今年5月剛好沒啥正事,所以打算重整旗鼓,寫乙個非常簡單的os,想用來敲開 作業系統 這個大門。這個系列的文章只記錄開發過程的點滴經...

Go語言列舉 無中生有

go語言中沒有列舉這種資料型別的,但是可以使用const配合iota模式來實現,如果要為列舉新增方法的話還涉及到型別的定義,以及為新建型別新增方法。所以在開始介紹實現go語言的列舉實現之前,我們先來看看型別別名 在go1.9版本新增的功能,主要用於 公升級 遷移中型別的相容性問題 和型別定義的區別。...