今天把哈夫曼樹又實現了一遍。主要使用了c++的stl的priority_queue優先佇列實現哈夫曼樹的構建。優先佇列的底層其實是小頂堆(或大頂堆),哈夫曼樹的構建用到的是小頂堆啦,因為每次都是從優先佇列中彈出最小的兩個元素,也就是小頂堆頂的兩個元素。優先佇列的語法是這樣的:
#include #include using namespace std;
//以下兩個表達是等價的,都是數字越大的優先順序大
priority_queueq;
priority_queue, less> q;
//表示數字越小的優先順序越大
priority_queue, greater> q;
其中第二種寫法中的vector填寫的是來承載底層資料結構堆的容器,第三個引數less表示數字大的優先順序越大,而greater表示數字小的優先順序越大。
我個人對less的理解是:想象每次從優先佇列中取出優先順序最高的數(數字最大的數),排列成一排,比如5 4 3 2 1,它是降序排列,所以是less
那麼greater就是相反的,每次從優先佇列中取出優先順序最高的數(數字最小的數),排列成一排,比如1 2 3 4 5,它是公升序排列,所以是greater
如果資料是基本資料型別的話如int double char就可以像上面那樣定義優先佇列,如果不是基本資料型別而是結構體的話,可以像下面這樣來定義(當然基本資料型別也可以這麼用):
struct fruit
};priority_queueq;
可以看到,結構體fruit中增加了乙個友元函式,這個函式用來過載操作符「」,因為數學上來說只需要過載小於號也可以得到相同效果,即f1>f2等價於判斷f2f2.price」。
這裡有點不太好理解,它和sort中的cmp有點類似,但是效果好像是相反的 ,在sort的cmp中,如果是「return f1.price > f2.price」則**是從高到低排序,但是在優先佇列中卻是**低的優先順序高且位於隊首,我是這樣理解過載函式的:
「 bool operator < (fruit f1, fruit f2)」表示我想判斷f1的優先順序是否 < f2的優先順序
「return f1.price < f2.price」表示,若f1的**小於f2的**(f2的**高),則返回1,函式的返回結果是f1的優先順序 < f2的優先順序,也即f2的優先順序高,上面的猜測得證了。所以這個過載函式的作用就是**高的優先順序高。
那麼如果是「return f1.price > f2.price」,則表示,若f1的**大於f2的**(f2的**低),則返回1,函式的返回結果是f1的優先順序 < f2的優先順序,也即f2的優先順序高,所以這樣寫就是**低的優先順序高。
過載函式還有另一種寫法:另外單獨定義乙個結構體
struct cmp
};priority_queue, cmp> q;
這樣子寫和上面的效果也是一樣的。 如果結構體內的資料較為龐大,建議使用引用(加上const和&)來提高效率,因為它在比較的時候傳入的引數其實是資料的拷貝,在資料龐大的時候效率不高,如果是引用的話則不需要拷貝資料。
//兩種寫法等價
friend bool operator < (const fruit &f1, const fruit &f2)
bool operator () (const fruit &f1, const fruit &f2)
我個人在使用過載函式和優先佇列的時候,發現如果我想在優先佇列中儲存的是結構體指標,由於在型別結構體中定義過載函式時它的引數不能是指標型別,所以沒法用,但是如果另外單獨結構體定義過載函式的話就可以傳入指標型別的引數,像下面這樣:
//不可以傳指標,這種寫法是錯誤的
struct fruit
}priority_queueq;
//可以傳指標,這種寫法是正確的
struct fruit
struct cmp
}priority_queue, cmp> q;
說完了優先佇列,接下來就進入哈夫曼樹的正題吧。
構建哈夫曼樹:使用優先佇列priority_queue儲存原始結點(葉子結點)和中間節點(非葉子節點),從而每次從優先佇列中得到的兩個元素都是當前最小的,獲得最小的兩個元素後從優先佇列中刪除他們,將這兩個元素的頻率(或資料域,要看你是根據什麼進行排序了)相加,得到的結果作為新節點的頻率(或資料域),然後將這個新節點的左右孩子指標分別指向構成它的那兩個元素,然後把這個新節點壓入優先佇列。
哈弗曼編碼:構建哈夫曼樹之後,採用先序遍歷對左右分支進行01編碼,往左編碼0,往右編碼1,用乙個vector記錄中間產生的編碼串,如果當前遍歷到的節點是葉子結點,則把當前的編碼串賦給這個葉子結點,然後刪除編碼串最後乙個碼元。
下面的實現是以字元出現的頻率作為根據構建哈夫曼樹,然後對字元進行編碼
#include #include #include #include using namespace std;
int wpl = 0; //帶權路徑和
vectorpath; //記錄先序遍歷時產生的01編碼
struct node
//葉子結點建構函式
node(int y) :data(null), freq(y), lchild(null), rchild(null){} //非葉子節點建構函式
};//過載操作符,freq域小的結點優先順序高
struct cmp
};//構造哈夫曼樹
node* createhuffman(priority_queue, cmp> q, int n)
node* root = q.top(); //優先佇列中剩下的最後乙個結點即為建好的哈夫曼樹的根結點
return root;
}//先序遍歷對葉子結點進行編碼
void encodehuffman(node* root)
if (root->lchild != null)
if (root->rchild != null) }
void printhuffmancode(vectornodes)
cout << endl; }}
int main()
node* root = createhuffman(q, n); //構建哈夫曼樹
encodehuffman(root); //哈弗曼編碼
printhuffmancode(nodes); //輸出每個字元對應的哈弗曼編碼
cout << wpl << endl; //帶權路徑和
system("pause");
return 0;
}
輸入4個字元,a的頻率為4,b為3,c為2,d為1
輸出字元編碼,帶權路徑和
資料結構 哈夫曼樹 哈夫曼編碼
哈夫曼樹又稱最優樹 二叉樹 是一類帶權路徑最短的樹。構造這種樹的演算法最早是由哈夫曼 huffman 1952年提出,這種樹在資訊檢索中很有用。結點之間的路徑長度 從乙個結點到另乙個結點之間的分支數目。樹的路徑長度 從樹的根到樹中每乙個結點的路徑長度之和。結點的帶權路徑長度 從該結點到樹根之間的路徑...
哈夫曼編碼 哈夫曼樹 (資料結構)
哈夫曼編碼,又稱霍夫曼編碼,是一種編碼方式,哈夫曼編碼是可變字長編碼 vlc 的一種。huffman於1952年提出一種編碼方法,該方法完全依據字元出現概率來構造異字頭的平均長度最短的碼字,有時稱之為最佳編碼,一般就叫做huffman編碼 有時也稱為霍夫曼編碼 include include inc...
資料結構 哈夫曼樹與哈夫曼編碼
1 路徑 由乙個結點到另乙個結點之間的所有分支共同構成。2 路徑長度 結點之間的分支數目。3 樹的路徑長度 從樹的根結點到其他所有結點的路徑長度之和。4 權 賦予某一實體的值。在資料結構中,實體包括結點和邊,所以對應有結點權和邊權。5 結點的帶權路徑長度 結點與樹的根結點之間的路徑長度與結點權的乘積...