最小生成樹——Prim算法


 

最小生成樹是圖這一數據結構里最常討論的方面之一。

 

先用一下幾個概念回憶一下什么是最小生成樹:

        連通圖:任意兩個結點之間都有一個路徑相連

        生成樹(Spannirng Tree):連通圖的一個極小的連通子圖,它含有圖中全部n個頂點,但只有足以構成一棵樹的n-1條邊

        最小生成樹(Minimum Spannirng Tree):連通圖的最小代價的生成樹(各邊的權值之和最小)

 

最小生成樹性質(MST性質)

        設G=(V,E)是一個連通網絡,U是頂點集V的一個真子集。若(u,v)是G中一條“一個端點在U中(例如:u∈U),另一個端點不在U中的邊(例如:v∈V-U),且(u,v)具有最小權值,則一定存在G的一棵最小生成樹包括此邊(u,v)。

        證明http://fdcwqmst.blog.163.com/blog/static/164061455201010392833100/

 

構造最小生成樹的兩種方法:Prim算法Kruskal算法。它們都利用了MST性質;都使用貪心策略,一次生成一條權值最小的“安全邊”。

 

下面看一下Prim算法的具體內容。

 

算法思想:

        1. 從圖中選取一個節點作為起始節點(也是樹的根節點),標記為已達;初始化所有未達節點到樹的距離為到根節點的距離;

        2. 從剩余未達節點中選取到樹距離最短的節點i,標記為已達;更新未達節點到樹的距離(如果節點到節點i的距離小於現距離,則更新);

        3. 重復步驟2直到所有n個節點均為已達。

 

上述過程應該是很清晰的,下面看一下Prim算法的C語言實現: 

 

#define N 10            // 定義最大節點數,實際有幾個是幾個
#define MAXDIST 100        // 最大距離,表示兩個節點間不可達, 為了輸入方便設置成100,實際可用INT_MAX

// 為了計算方便傳入的距離矩陣用指針數組的格式,n是節點數
int Prim(int (*map)[N], int n)
{
    int i, j;
    int minDist, minIndex, totalWeight;
    int *visited, *parent, *dist;    // 分別保存節點的已達標志,父節點,到樹的距離

    // 申請空間並清零
    visited = (int *)malloc(n * sizeof(int));
    parent = (int *)malloc(n * sizeof(int));
    dist = (int *)malloc(n * sizeof(int));

    memset(visited, 0, n * sizeof(int));
    memset(parent, 0, n * sizeof(int));
    memset(dist, 0, n * sizeof(int));

    // 初始化,設置節點0為根節點
    visited[0] = 1;
    totalWeight = 0;

    // 初始化未達節點到樹的距離
    for (i = 1; i < n; ++i)
    {
        parent[i] = 0;
        dist[i] = map[0][i];
    }

    printf("\nEdge\tWeight\n");

    // n - 1次循環找出n - 1條邊
    for (i = 0; i < n - 1; ++i)
    {
        minDist = MAXDIST;
        minIndex = i;

        // 找出到樹距離最小的節點
        for (j = 1; j < n; ++j)
        {
            if (visited[j] == 0 && dist[j] < minDist)
            {
                minDist = dist[j];
                minIndex = j;
            }
        }

        if (minIndex == i)        // 所有節點到樹的距離都為MAXDIST,說明不是連通圖,返回
        {
            printf("This is not a connected graph!\n");
            return MAXDIST;
        }

        // 標記並輸出找到的節點和邊
        visited[minIndex] = 1;
        totalWeight += minDist;
        printf("%d-->%d\t%3d\n", parent[minIndex], minIndex, map[parent[minIndex]][minIndex]);

        // 更新剩余節點到樹的距離
        for (j = 1; j < n; ++j)
        {
            if (visited[j] == 0 && map[j][minIndex] < dist[j])
            {
                parent[j] = minIndex;
                dist[j] = map[j][minIndex];
            }
        }
    }

    printf("\nTotal Weight: %d\n\n", totalWeight);

    return totalWeight;
}
View Code

 

 

再寫一個測試函數驗證一下功能:

 1 int main()  2 {  3     int map[N][N];  4     int i, j;  5     int n, tmp;  6 
 7     printf("Num of nodes: ");  8     scanf("%d", &n);  9 
10     printf("Distance matrix (lower triangular) : "); 11     for (i = 1; i < n; ++i) 12  { 13         map[i][i] = 0; 14 
15         for (j = 0; j < i; ++j) 16  { 17             scanf("%d", &tmp); 18             map[i][j] = tmp; 19             map[j][i] = tmp; 20  } 21  } 22 
23  Prim(map, n); 24 
25     return 0; 26 }
View Code

 

測試圖的距離矩陣如下:

因為無向圖矩陣是對稱的,所以程序中設置只要輸入下三角數據即可,也就是下面這些:

也就是輸入的數據是這些: 4 2 5 3 4 1 100 3 100 6 100 100 2 2 4

 

下面是程序運行截圖:

 

算法效率 O(n^2):

        很明顯對於每個節點(初始節點除外),都要進行此次遍歷查找距離樹最近的節點 O(n),和一次更新操作O(n),所以總的時間復雜度為O(n^2)。

 

優化:

        上述過程中,冗余操作在於對已經標記為可達的節點,在每次遍歷查找和更新時,都還要訪問一次。優化的關鍵就在於如何避免這種操作。

 

        使用優先隊列(Priority Queue),或叫堆(Heap),將每次更新的節點加入到隊列中,而且保證隊列有一定的大小順序(每次調整的時間復雜度O(logn))。而查找具有最小距離的元素,只需要取隊列首個元素再調整隊列(O(logn))。所以取出n個節點,總的時間復雜度為O(nlogn)。

 

        很明顯這是空間換時間的策略,效果相當可觀。但是在數據量大的情況下,可能會因為內存不足而無法工作。

 


免責聲明!

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



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