普里姆算法(Prim)


概覽

普里姆算法(Prim算法),圖論中的一種算法,可在加權連通圖(帶權圖)里搜索最小生成樹。即此算法搜索到的邊(Edge)子集所構成的樹中,不但包括了連通圖里的所有頂點(Vertex)且其所有邊的權值之和最小。(注:N個頂點的圖中,其最小生成樹的邊為N-1條,且各邊之和最小。樹的每一個節點(除根節點)有且只有一個前驅,所以,只有N-1條邊。) 

該算法於1930年由捷克數學家沃伊捷赫·亞爾尼克(Vojtěch Jarník)發現;並在1957年由美國計算機科學家羅伯特·普里姆(Robert C. Prim)獨立發現;1959年,艾茲格·迪科斯徹再次發現了該算法。因此,在某些場合,普里姆算法又被稱為DJP算法、亞爾尼克算法或普里姆-亞爾尼克算法。 

定義

 假設G=(V, {E})是連通網,TE是N上最小生成樹中邊(Edge)的集合。V是圖G的頂點的集合,E是圖G的邊的集合。算法從U={u0} (u0∈V),TE={}開始。重復執行下述操作:

  • 在所有u∈U,v∈V-U的邊(u, v)∈E中找一條代價(權值)最小的邊(u0, v0)並入集合TE。
  • 同時v0並入U
  • 直至U=V為止。此時TE中必有n-1條邊,則T=(V, {TE})為N的最小生成樹。

由算法代碼中的循環嵌套可得知此算法的時間復雜度為O(n2)。

過程簡述

輸入:帶權連通圖(網)G,其頂點的集合為V,邊的集合為E。

初始:U={u},u為從V中任意選取頂點,作為起始點;TE={}。

操作:重復以下操作,直到U=V,即兩個集合相等。

  • 在集合E中選取權值最小的邊(u, v),u∈U,v∈V且v∉U。(如果存在多條滿足前述條件,即權值相同的邊,則可任意選取其中之一。)
  • 將v並入U,將(u, v)邊加入TE。

輸出:用集合U和TE來描述所得到的最小生成樹

如何實現

如上面的這個圖G=(V, {E}),其中V={v0, v1, v2, v3, v4, v5, v6, v7, v8},E= {(v0, v1), (v0, v5), (v1, v6), (v5, v6), (v1, v8), (v1, v2), (v2, v8), (v6, v7), (v3, v6), (v4, v5), (v4, v7), (v3, v7), (v3, v4), (v3,v8), (v2, v3)}

用鄰接矩陣來表示該圖G,得上圖右邊的一個鄰接矩陣。

此帶權連通圖G有n=9個頂點,其最小生成樹則必有n-1=8條邊。(注意:圖G的最小生成樹是一棵樹,且圖G中的每個頂點都在這棵樹里,故必含有n個頂點;而除樹根節點,每個節點有且只有一個前驅,所以圖G的最小生成樹有且只有n-1條邊。若邊數大於n-1,則必有樹中某個頂點與另一個頂點存在第二條邊,從而不能構成樹。樹中節點是一對多關系而不是多對多關系。)

①輸入:帶權連通圖G=(V, {E}),求圖G的最小生成樹。

②初始:U={u},取圖G中的v0作為u,用數組adjVex=int[9]來表示U(最終U要等於V),adjVex數組記錄的是U中頂點的下標。U是最小生成樹T的各邊的起始頂點的集合。

adjVex初始值為[0, 0, 0, 0, 0, 0, 0, 0, 0],表示從頂點v0開始去尋找權值最小的邊。

用數組lowCost=int[9] 表示adjVex中各點到集合V中頂點構成的邊的權值。lowCost數組中元素的索引即是頂點V的下標。解釋:adjVex[3]==0,表示v0,adjVex[5]==0,表示v0。lowCost[3]==∞且adjVex[3]==0,表示(v0, v3)邊不存在;lowCost[5]==11且adjVex[5]==0,表示(v0, v5)邊的權值為11。

 如:鄰接矩陣中的v0行,v0頂點與各頂點構成的邊及其權值用下面這的方式表示:

示例一 

 

索引:index  [0,  1, 2, 3, 4,  5, 6, 7, 8]

權值:lowCost[0, 10, ∞, , ∞, 11, ∞, ∞, ∞]

下標:adjVex [0,  0, 0, 0, 0,  0, 0, 0, 0]

(v0, v0, 0), (v0, v3, ), (v0, v5, 11) 

0表示以該頂點為終點的邊已經並入圖G的最小生成樹的邊集合——TE集合,不需要再比較(搜索)。

表示以該頂點為終點的邊不存在。

③操作:

1.上面示例一中,最小的權值為10,此時lowCost中下標k=1,相應地adjVex[k]即adjVex[1]==0,記錄下此時的邊為(v0, v1)。

2.將adjVex[k]即adjVex[1]設為1,表示將頂點v1放入圖G的最小生成樹的頂點集合U中。

3.將lowCost[k]即lowCost[1]設為0,表示以v1為終止點的邊已搜索。

4.然后,將焦點轉向頂點v1,看看從v1開始的邊有哪些是權值小於從之前的頂點v0開始的邊的。此時k==1。則有以下過程:

索引:index  [ 0,  1, 2, 3, 4,  5, 6, 7, 8]

權值:lowCost[ 0,  0, ∞, ∞, ∞, 11, ∞, ∞, ∞]

下標:adjVex [ 0,  1, 0, 0, 0,  0, 0, 0, 0]

頂點:vex[1] [10,  0,18, ∞, ∞,  ∞,16, ∞,12]

 

由於lowCost[0]和lowCost[1]為0,所以從lowCost[2]開始比較權值。vex[1][2]==18 < lowCost[2],意思是(v0, v2)==∞不存在這條邊,(v1, v2)==18,存在權為18的邊(v1, v2),類似的還有vex[1][6]==16 < lowCost[6]和vex[1][8]==12 < lowCost[8]。

把k==1賦值給

adjVex[2]、adjVex[6]和adjVex[8]。

把權值18、16和12賦值給

lowCost[2]、lowCost[6]和lowCost[8]。

 

更新后的權值數組和鄰接頂點數組如下:

索引:index  [ 0,1,  2, 3, 4,  5,  6, 7,  8]

權值:lowCost[ 0, 0, 18, ∞, ∞, 11, 16, ∞, 12]

下標:adjVex [ 0, 1,  1, 0, 0,  0,  1, 0,  1]

 

故下次循環從頂點v1為起點去搜索lowCost中權值最小的邊。如此往復循環,直到圖G中的每一個頂點都被遍歷到。(鄰接矩陣的每一行都被遍歷到)

④輸出:

 

演示過程

 Loop 1

lowCost: [ 0, 10, ∞, ∞, ∞, 11, ∞, ∞, ∞ ]

 adjVex: [ 0,  0, 0, 0, 0,  0, 0, 0, 0 ]

lowCost: [ 0, 0, 18, ∞, ∞, 11, 16, ∞, 12 ]

 adjVex: [ 0, 1,  1, 0, 0,  0,  1, 0,  1 ]

 

Loop 2

lowCost: [ 0, 0, 18, ∞, ∞, 11, 16, ∞, 12 ]

 adjVex: [ 0, 1,  1, 0, 0,  0,  1, 0,  1 ]

lowCost: [ 0, 0, 18, ∞, 26, 0, 16, ∞, 12 ]

 adjVex: [ 0, 1,  5, 0,  5, 0, 1, 0, 1 ]

 

Loop 3

lowCost: [ 0, 0, 18, ∞, 26, 0, 16, ∞, 12 ]

 adjVex: [ 0, 1,  5, 0,  5, 0,  1, 0,  1 ]

lowCost: [ 0, 0, 8, 21, 26, 0, 16, ∞, 0 ]

 adjVex: [ 0, 1, 8,  8,  5, 0,  1, 0, 1 ]

 

Loop 4

lowCost: [ 0, 0, 8, 21, 26, 0, 16, ∞, 0 ]

 adjVex: [ 0, 1, 8,  8,  5, 0,  1, 0, 1 ]

lowCost: [ 0, 0, 0, 21, 26, 0, 16, ∞, 0 ]

 adjVex: [ 0, 1, 8,  8,  2, 0,  1, 0, 1 ]

 

Loop 5

lowCost: [ 0, 0, 0, 21, 26, 0, 16, ∞, 0 ]

 adjVex: [ 0, 1, 8,  8,  2, 0,  1, 0, 1 ]

lowCost: [ 0, 0, 0, 21, 26, 0, 0, 19, 0 ]

 adjVex: [ 0, 1, 8,  8,  2, 6, 1,  6, 1 ]

 

Loop 6

lowCost: [ 0, 0, 0, 21, 26, 0, 0, 19, 0 ]

 adjVex: [ 0, 1, 8,  8,  2, 6, 1,  6, 1 ]

lowCost: [ 0, 0, 0, 16, 7, 0, 0, 0, 0 ]

 adjVex: [ 0, 1, 8,  7, 7, 6, 7, 6, 1 ]

 

Loop 7

lowCost: [ 0, 0, 0, 16, 7, 0, 0, 0, 0 ]

 adjVex: [ 0, 1, 8, 7,  7, 6, 7, 6, 1 ]

lowCost: [ 0, 0, 0, 16, 0, 0, 0, 0, 0 ]

 adjVex: [ 0, 1, 8,  7, 7, 6, 7, 4, 1 ]

 

Loop 8

lowCost: [ 0, 0, 0, 16, 0, 0, 0, 0, 0 ]

 adjVex: [ 0, 1, 8,  7, 7, 6, 7, 4, 1 ]

lowCost: [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ]

 adjVex: [ 0, 1, 8, 7, 7, 6, 7, 4, 3 ]

 

運行結果

(0, 1)

(0, 5)

(1, 8)

(8, 2)

(1, 6)

(6, 7)

(7, 4)

(7, 3)

 

算法代碼

  

using System;

 

namespace Prim

{

    class Program

    {

        static void Main(string[] args)

        {

            int numberOfVertexes = 9,

                infinity = int.MaxValue;

 

            int[][] graph = new int[][] {

                new int[]{010infinityinfinityinfinity11infinityinfinityinfinity },

                new int[]{ 10018infinityinfinityinfinity16infinity12 },

                new int[]{ infinity18022infinityinfinityinfinityinfinity8 },

                new int[]{ infinityinfinity22020infinity241621 },

                new int[]{ infinityinfinityinfinity20026infinity7infinity },

                new int[]{ 11infinityinfinityinfinity26017infinityinfinity },

                new int[]{ infinity16infinity24infinity17019infinity },

                new int[]{ infinityinfinityinfinity167infinity190infinity },

                new int[]{ infinity12821infinityinfinityinfinityinfinity0 },

            };

 

            Prim(graphnumberOfVertexes);

            //PrimSimplified(graph, numberOfVertexes);

        }

 

        static void Prim(int[][] graphint numberOfVertexes)

        {

            bool debug = true;

 

            int[] adjVex = new int[numberOfVertexes],  // 鄰接頂點數組:搜索邊的最小權值過程中各邊的起點坐標

                lowCost = new int[numberOfVertexes];   // 各邊權值數組:搜索邊的最小權值過程中各邊的權值,數組下標為邊的終點。

                        

            for (int i = 0i < numberOfVertexesi++) // 從圖G的下標為0的頂點開始搜索。(也是圖G的最小生成樹的頂點集合)。

            {

                adjVex[0] = 0;

            }

// 初始從下標為0的頂點開始到下標為i的頂點的邊的權值去搜索。找lowCost中權值最小的下標i

            for (int i = 0i < numberOfVertexesi++)

            {

                lowCost[i] = graph[0][i];

            }

 

            int k = 0;                                 // 初始假定權值最小的邊的終點的下標為k

 

            for (int i = 1i < numberOfVertexesi++)

            {

                if (debug)

                {

                    Console.WriteLine($"Loop {i}");

                    Console.Write("lowCost: ");

                    PrintArray(lowCost);

                    Console.Write(" adjVex: ");

                    PrintArray(adjVex);

                    Console.WriteLine();

                }

 

                int minimumWeight = int.MaxValue;      // 搜索過程中發現到的最小的權值。初始設置為最大的整數值以示兩點間無邊。

 

                for (int j = 1j < numberOfVertexesj++)

                {

// lowCost0表示該點已經搜索過了。lowCost[j] < minimumWeight即發現目前最小權值。

                    if (lowCost[j] != 0 && lowCost[j] < minimumWeight

                    {

                        minimumWeight = lowCost[j];    // 發現目前最小權值。

                        k = j;                         // 目前最小權值的邊的終點下標。

                    }

                }

 

                if (!debug)

                {

                    Console.WriteLine($"({adjVex[k]}, {k})");  // 輸出邊

                }

 

// 此時找到的k值即是權值最小的邊的終點。將V[k]放入集合U。(這步可省略,因lowCost[j]已被標為無需搜索了)。

                adjVex[i] = k;

// 0表示該點已經搜索過了,已不需要再被搜索了。

                lowCost[k] = 0;

                // 轉到以V[k]為開始頂點的邊,去與前面u為起始頂點到V[i]為終止頂點的邊的權值去比較。

                for (int j = 1j < numberOfVertexesj++)

                {

// lowCost0表示該點已經搜索過了。graph[k][j] < lowCost[j]即發現更小權值。

                    if (lowCost[j] != 0 && graph[k][j] < lowCost[j])

                    {

                        lowCost[j] = graph[k][j];  // 更新權值;索引j即終點下標。

                        adjVex[j] = k;             // 下次尋找權值小的邊時,從k為下標的頂點為起點。

                    }

                }

 

                if (debug)

                {

                    Console.Write("lowCost: ");

                    PrintArray(lowCost);

                    Console.Write(" adjVex: ");

                    PrintArray(adjVex);

                    Console.WriteLine();

                }

            }

        }

 

        static void PrimSimplified(int[][] graphint numberOfVertexes)

        {

            int[] adjVex = new int[numberOfVertexes],  // 鄰接頂點數組:搜索邊的最小權值過程中各邊的起點坐標

                lowCost = new int[numberOfVertexes];   // 各邊權值數組:搜索邊的最小權值過程中各邊的權值,數組下標為邊的終點。

 

            for (int i = 0i < numberOfVertexesi++)

            {

                adjVex[i] = 0;                         // 從圖G的下標為0的頂點開始搜索。(也是圖G的最小生成樹的頂點集合)。

                lowCost[i] = graph[0][i];              // 從下標為0的頂點開始到下標為i的頂點結束構成的邊去搜索。找最小權值。

            }

 

            int k = 0;                                 // 初始假定權值最小的邊的終點的下標為k

 

            for (int i = 1i < numberOfVertexesi++)

            {

                int minimumWeight = int.MaxValue;      // 搜索過程中發現到的最小的權值。初始設置為最大的整數值以示兩點間無邊。

 

                for (int j = 1j < numberOfVertexesj++)

                {

// lowCost0表示該點已經搜索過了。lowCost[j] < minimumWeight即發現目前最小權值。

                    if (lowCost[j] != 0 && lowCost[j] < minimumWeight)         

                    {

                        minimumWeight = lowCost[j];    // 發現目前最小權值。

                        k = j;                         // 目前最小權值的邊的終點下標。

                    }

                }

 

                Console.WriteLine($"({adjVex[k]}, {k})");  // 輸出邊

 

                lowCost[k] = 0;                        // 0表示該點已經搜索過了,已不需要再被搜索了。

// 轉到以V[k]為開始頂點的邊,去與前面u為起始頂點到V[i]為終止頂點的邊的權值去比較。

                for (int j = 1j < numberOfVertexesj++)                     

                {

// lowCost0表示該點已經搜索過了。graph[k][j] < lowCost[j]即發現更小權值。

                    if (lowCost[j] != 0 && graph[k][j] < lowCost[j])           

                    {

                        lowCost[j] = graph[k][j];      // 更新權值;索引j即終點下標。

                        adjVex[j] = k;                 // 下次尋找權值小的邊時,從k為下標的頂點為起點。

                    }

                }

            }

        }

 

        static void PrintArray(int[] array)

        {

            Console.Write("[ ");            

            for (int i = 0i < array.Length - 1i++) // 輸出數組的前面n-1

            {

                Console.Write($"{ToInfinity(array[i])}, ");

            }            

            if (array.Length > 0)                      // 輸出數組的最后1

            {

                int n = array.Length - 1;

                Console.Write($"{ToInfinity(array[n])}");

            }

            Console.WriteLine(" ]");

        }

 

        static string ToInfinity(int i) => i == int.MaxValue ? "∞" : i.ToString();

    }

}

 

參考資料:

《大話數據結構》 - 程傑 著 - 清華大學出版社 第247頁


免責聲明!

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



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