【算法總結】哈夫曼樹


在一棵樹中,從任意一個結點到達另一個結點的通路被稱為路徑,該路徑上所需經過的邊的個數被稱為該路徑的長度。若樹中結點帶有表示某種意義的權值,那么從根結點到達該節點的路徑長度再乘以該結點權值被稱為該結點的帶權路徑長度。樹所有的葉子結點的帶權路徑長度和為該樹的帶權路徑長度和。給定 n 個結點和它們的權值,以它們為葉子結點構造一棵帶權路徑和最小的二叉樹, 該二叉樹即為哈夫曼樹,同時也被稱為最優樹

給定結點的哈夫曼樹可能不唯一,所以關於哈夫曼樹的機試題往往需要求解的是其最小帶權路徑長度和。回顧一下我們所熟知的哈夫曼樹求法(原理很容易理解,就是把小的往下放)。

1.將所有結點放入集合 K。

2.若集合 K 中剩余結點大於 2 個,則取出其中權值最小的兩個結點,構造他們同時為某個新節點的左右兒子,該新節點是他們共同的雙親結點,設定它的權值為其兩個兒子結點的權值和。並將該父親結點放入集合 K。重復步驟 2 或 3。

3.若集合 K 中僅剩余一個結點,該結點即為構造出的哈夫曼樹數的根結點, 所有構造得到的中間結點(即哈夫曼樹上非葉子結點)的權值和即為該哈夫曼樹的帶權路徑和

為了方便快捷高效率的求得集合 K 中權值最小的兩個元素,我們需要使用堆數據結構。它可以以 O(logn)的復雜度取得 n 個元素中的最小元素。為了繞過對堆的實現我們使用標准模板庫中的相應的標准模板——優先隊列。

:有4 個結點 a, b, c, d,權值分別為 7, 5, 2, 4,試構造以此 4 個結點為葉子結點的二叉樹。

根據給定的n個權值{w1,w2,…,wn}構成二叉樹集合F={T1,T2,…,Tn},其中每棵二叉樹Ti中只有一個帶權為wi的根結點,其左右子樹為空.

在F中選取兩棵根結點權值最小的樹作為左右子樹構造一棵新的二叉樹,且置新的二叉樹的根結點的權值為左右子樹根結點的權值之和.

在F中刪除這兩棵樹,同時將新的二叉樹加入F中.

重復,直到F只含有一棵樹為止.(得到哈夫曼樹)

利用語句 

priority_queue<int> Q; 

建立一個保存元素為 int 的堆 Q,但是請特別注意這樣建立的堆其默認為大頂堆,即我們從堆頂取得的元素為整個堆中最大的元素。而在求哈夫曼樹中,我們恰恰需要取得堆中最小的元素,於是我們使用如下語句定義一個小頂堆: (關於這一點見補充)

priority_queue<int , vector<int> , greater<int> > Q; 

例 3.3 哈夫曼樹

AC代碼

#include<cstdio>
#include<queue>

using namespace std;

priority_queue<int, vector<int>, greater<int> >Q;//建立一個小頂堆

int main()
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        while (Q.empty() == false)Q.pop();//清空堆中元素
        for (int i = 1; i <= n; i++)//輸入n個葉子結點權值
        {
            int x;
            scanf("%d", &x);
            Q.push(x);//第一步,將n個葉子結點全放入堆中
        }
        int ans = 0;//保存答案
        while (Q.size() > 1)//當堆中元素大於1個
        {
            int a = Q.top();
            Q.pop();
            int b = Q.top();
            Q.pop();//第二步,取出堆中兩個最小的元素,設置為同一節點的左右耳子,且雙親節點的權值為他們的和
            ans += a + b;//父節點必為非葉子結點
            Q.push(a + b);//將雙親結點放回堆中
        }
        printf("%d\n", ans);//輸出答案
    }
    return 0;
}
#include<cstdio>
#include<queue>
#include<iostream>
#include<vector>
using namespace std;
priority_queue<int, vector<int>, greater<int> >q;

int main()
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        int x, ans;
        while (!q.empty())q.pop();
        for (int i = 0; i < n; i++)
        {
            scanf("%d", &x);
            q.push(x);
        }
        ans = 0;
        while (q.size()>1)
        {
            int a = q.top();
            q.pop();
            int b = q.top();
            q.pop();
            ans += a + b;
            q.push(a+b);
        }
        printf("%d\n", ans);
    }
    //system("pause");
    return 0;
}
二刷

補充:關於priority_queue

priority_queue 對於基本類型的使用方法相對簡單。他的模板聲明帶有三個參數,priority_queue<Type, Container, Functional>
Type 為數據類型, Container 為保存數據的容器,Functional 為元素比較方式。
Container 必須是用數組實現的容器,比如 vector, deque 但不能用 list.
STL里面容器默認用的是 vector. 比較方式默認用 operator< , 所以如果你把后面倆個參數缺省的話,優先隊列就是大頂堆,隊頭元素最大。
看例子

#include <iostream>
#include <queue>
using namespace std;
int main(){
    priority_queue<int,vector<int>,less<int> >q;//使用priority_queue<int> q1;一樣
    for(int i=0;i<10;i++) 
        q1.push(i);
    while(!q1.empty()){
        cout<<q1.top()<< endl;
        q1.pop();
    }
    return 0;
}

如果要用到小頂堆,則一般要把模板的三個參數都帶進去。
STL里面定義了一個仿函數 greater<>,對於基本類型可以用這個仿函數聲明小頂堆
例子:

#include <iostream>
#include <queue>
using namespace std;
int main(){
    priority_queue<int,vector<int>,greater<int> >q;
    for(int i=0;i<10;i++) 
        q.push(i);
    while(!q.empty()){
        cout<<q.top()<< endl;
        q.pop();
    }
    return 0;
}

 


免責聲明!

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



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