哈夫曼算法證明+哈夫曼編碼譯碼程序實現

哈夫曼算法證明

哈夫曼算法是一種貪心算法,我們考慮證明其最優子結構和貪心選擇性質:

  • 最優子結構:假設一個樹是哈夫曼樹,則以其任意節點為根節點的最大子樹也是哈夫曼樹。

    證明:子樹的根節點的值是其所有葉子節點出現權值之和,因此無論子樹是什么形式,對子樹上方的節點計算WPL2都沒有影響。

    根據哈夫曼樹的定義:WPL最小的二叉樹。如果子樹不是哈夫曼樹,其WPL1就不會是最小,那么整個樹的WPL=WPL1+WPL2就不會是最小,這與哈夫曼樹的定義相悖,因此子樹是哈夫曼樹。

  • 貪心選擇性質(哈夫曼算法):每次去掉權值最低的兩個節點作為葉子節點形成一顆二叉樹,并將其父親節點放入待選節點中。

    證明:對于哈夫曼樹我們總可以通過取出兩個節點作為葉子節點并將其父親節點重新作為葉子節點的方式構造,需要證明的是是否每次選擇權值最低的節點可以構造成功。

    當含有2個及以下的葉子節點的時候,顯然正確。

    假設當含有小于k個葉子節點的時候哈夫曼算法可以形成哈夫曼樹

    對于含有k個葉子節點形成的樹,設其中權重最小的葉子節點為a,其中深度最深的葉子節點為b,h表示節點的深度,w表示節點的權值。則:
    WPL1=∑+wa?ha+wb?hbWPL_1=\sum+w_a*h_a+w_b*h_b WPL1?=+wa??ha?+wb??hb?
    交換這兩個節點:
    WPL2=∑+wb?ha+wa?hbWPL_2=\sum+w_b*h_a+w_a*h_b WPL2?=+wb??ha?+wa??hb?

WPL1?WPL2=(wa?wb)?ha+(wb?wa)?hb=(hb?ha)?(wb?wa)WPL_1-WPL_2=(w_a-w_b)*h_a+(w_b-w_a)*h_b=(h_b-h_a)*(w_b-w_a) WPL1??WPL2?=(wa??wb?)?ha?+(wb??wa?)?hb?=(hb??ha?)?(wb??wa?)

hb?hah_b-h_ahb??ha?wb?waw_b-w_awb??wa?都為正(由a,b節點的性質),所以我們得到結論:對于任何一棵樹,將權值小的節點盡可能移動到較深層的節點會使整個樹的WPL比較小。

對于k個節點,我們首先取出兩個節點,將其合并成一個節點以后我們有k-1個節點,可以使用哈夫曼算法構造。

如果這兩個節點不是所有節點中權值最小的兩個,則我們總可以通過交換使得構造的樹的WPL減小,因此不是哈夫曼樹。只有兩個節點是所有節點中權值最小的兩個時我們無法再降低樹的WPL。因為我們總可以構造成功,所以選擇權值最小的節點構造的樹就是哈夫曼樹。證畢。

哈夫曼編碼譯碼程序

#pragma once
#include<iostream>
#include<fstream>
#include<vector>
#include<queue>
#include<cstdio>
#include<string>using namespace std;static const int MAXN = 1005;struct times
{double weight;//字符的權值int num;//字符的序號,同時也是他的ASCALL值times(int _num=0,double _weight=0) :num(_num),weight(_weight){}friend bool operator < (const times & a,const times & b){return a.weight > b.weight;}
}p,q;struct Chara:times
{int father;//父親的序號int lson, rson;//左右兒子的序號string code;Chara(int _num = 0, double _weight = 0, int _lson = 0, int _rson = 0, int _father = 0) :times(_num,_weight), lson(_lson), rson(_rson), father(_father){code = "";//沒有編碼}void operator = (const Chara& x){weight = x.weight; num = x.num; father = x.father; lson = x.lson; rson = x.rson;}
};struct txt
{string t;double weight;txt(string _t,double _weight):t(_t),weight(_weight){}
};class HuffmanCode
{
public:int n=0;//字符的個數int cur;//當前所在位置int root;//哈夫曼樹根節點Chara A[MAXN];//順序表保存哈夫曼樹priority_queue<times> T;//用來構建哈夫曼樹void CreatHuffmanCode(int x, string now){if (A[x].lson != 0){CreatHuffmanCode(A[x].lson, now + "0");}if (A[x].rson != 0){CreatHuffmanCode(A[x].rson, now + "1");}if (A[x].lson == 0 && A[x].rson == 0)//說明是字符{A[x].code = now;}}void _HuffmanCode(int _n,vector<txt>& input){string tmp;n = _n;for (int i = 0; i < n; i++){tmp = input[i].t;A[tmp[0]].num = tmp[0];A[tmp[0]].weight = input[i].weight;T.push(times(tmp[0], A[tmp[0]].weight));}cur = 500;//構建哈夫曼樹while (T.size() > 1){p = T.top(); T.pop(); q = T.top(); T.pop();A[cur] = Chara(cur, p.weight + q.weight, p.num, q.num, 0); A[p.num].father = A[q.num].father = cur;T.push(times(A[cur])); cur++;}T.pop(); root = cur-1;CreatHuffmanCode(root, "");}
};class Huffman
{int n;vector<txt> input;HuffmanCode x;
public:void _Huffman(){string t; double weight; n = 0;FILE* stream;freopen_s(&stream,"hfmTree.txt","r",stdin);while (1){cin >> t;if (t == "Esc") break;n++;cin >> weight;input.push_back(txt(t, weight));}freopen_s(&stream, "CON", "r", stdin);input.push_back(txt(" ", 10000.0));//給空格很大的權值n++;x._HuffmanCode(n, input);}Huffman(){string t; double weight; n = 0;cout << "請輸入字符集 \n[Delete撤銷輸入,Esc退出輸入]\n[直接輸入Default按照默認文件組成哈夫曼編碼]\n[空格已經編碼]"<< endl;while(1){cout << "請輸入字符:";cin >> t;if (n == 0 && t == "Default")//按照默認文件構造哈夫曼樹{_Huffman();return;}else if (t == "Delete"){input.erase(input.end()-1);//刪除最后一個輸入的字符n--;continue;}else if (t == "Esc"){break;}n++;cout << "請輸入" << t[0] << "的權重:";cin >> weight;input.push_back(txt(t, weight));}FILE* stream;freopen_s(&stream, "hfmTree.txt", "w", stdout);for (int i = 0; i < n; i++){cout << input[i].t << " " << input[i].weight << endl;}cout << "Esc" << endl;freopen_s(&stream, "CON", "w", stdout);input.push_back(txt(" ", 10000.0));//給空格很大的權值n++;x._HuffmanCode(n, input);}void HuffmanDisplay(){cout << "哈夫曼編碼:" << endl;for (int i = 0; i < n; i++){cout << input[i].t[0] << ":" << x.A[input[i].t[0]].code << endl;}}void GenerateCode()//壓縮文件{cout << "請輸入需要壓縮文件的路徑[輸入Default將打開默認文件ToBeTran.txt]" << endl;string in;cin >> in;if (in == "Default"){in = "ToBeTran.txt";}cout << "請輸入保存壓縮后文件的路徑[輸入Default將打開默認文件CodeFile.txt]" << endl;string out;cin >> out;if (out == "Default"){out = "CodeFile.txt";}ifstream infile(in, ios::in);ofstream outfile(out, ios::out);char c;bool flag = true;while ((c = infile.get()) != EOF){if (x.A[c].code == ""){flag = false;break;}outfile << x.A[c].code;}infile.close();outfile.close();if (!flag){cout << "壓縮失敗,文件中出現了字符集中未包含的字符" << endl;return;}//展示壓縮的結果:infile.open(out, ios::in);string tmp;infile >> tmp;infile.close();cout << "編碼后的文件為:" << endl;for (int i = 0; i < tmp.size(); i++){if (i && i % 50 == 0) cout << endl;cout << tmp[i];}cout << endl;//將結果放入文件中outfile.open("CodePrint.txt", ios::out);for (int i = 0; i < tmp.size(); i++){if (i % 50 == 0) outfile << endl;outfile << tmp[i];}outfile.close();}void Decode(){cout << "請輸入需要解碼文件的路徑[輸入Default將打開默認文件CodeFile.txt]" << endl;string in;cin >> in;if (in == "Default"){in = "CodeFile.txt";}cout << "請輸入保存壓縮后文件的路徑[輸入Default將打開默認文件TextFile.txt]" << endl;string out;cin >> out;if (out == "Default"){out = "TextFile.txt";}ifstream infile(in, ios::in);string ss;infile >> ss;//cout << "test:" << ss << endl;int i = 0;string sss;while (i < ss.size()){int t = x.root;while ((x.A[t].lson != 0 || x.A[t].rson != 0) && i < ss.size()){if (ss[i] == '0') t = x.A[t].lson;else t = x.A[t].rson;i++;}if (x.A[t].lson == 0 || x.A[t].rson == 0){sss = sss + (char)t;}}infile.close();cout << "解碼后為:" << endl;cout << sss << endl;ofstream outfile(out,ios::out);outfile << sss << endl;outfile.close();}
};

測試代碼

#include"Huffman.h"#include<iostream>using namespace std;int main()
{Huffman x;x.HuffmanDisplay();x.GenerateCode();x.Decode();}

測試結果

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383651.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383651.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383651.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python3小知識

對于迭代器對象&#xff0c;Python默認賦值是將引用賦值&#xff0c;即指向同一片內存空間。為了實現對內存空間的賦值&#xff0c;我們可以使用分片進行深復制。例如&#xff1a; 當定義元組的時候&#xff0c;我們一般使用小括號將元素包圍起來&#xff0c;也可以不使用括號…

匯編:實現日歷星期數查詢工具

編制一個簡單日歷查詢工具&#xff0c;輸入年、月、日&#xff0c;能夠判斷當日的星期數&#xff0c;并進行輸出&#xff0c;數據的輸入和結果的輸出要有必要的提示&#xff0c;且提示獨占一行。 查閱資料 ? 經過查閱資料&#xff0c;發現有兩個相關的算法可以解決這個問題&…

一個通用純C隊列的實現

https://blog.csdn.net/kxcfzyk/article/details/31728179 隊列并不是很復雜的數據結構&#xff0c;但是非常實用&#xff0c;這里實現一個隊列是因為在我的另一篇博客非常精簡的Linux線程池實現中要用到。 隊列API定義如下&#xff1a; //queue.h #ifndef QUEUE_H_INCLUDED…

Dijkstra算法介紹+正確性證明+性能分析

算法介紹 源點s,數組d[u]表示s到u的最短距離&#xff0c;空集S&#xff0c;點集Q初始化&#xff1a;將源點s從點集中去掉&#xff0c;加入S&#xff0c;d[s]0&#xff0c;?v∈Q,d[v]w[s][v]\forall v\in Q ,d[v]w[s][v]?v∈Q,d[v]w[s][v]將Q中d[v]最小的點去掉加入S,并對u∈…

Linux C 實現一個簡單的線程池

https://www.cnblogs.com/GyForever1004/p/9185240.html 線程池的定義 線程池是一種多線程處理形式&#xff0c;處理過程中將任務添加到隊列&#xff0c;然后在創建線程后自動啟動這些任務。線程池線程都是后臺線程。每個線程都使用默認的堆棧大小&#xff0c;以默認的優先級…

斐波那契數列求解+尾遞歸

1.普通遞歸 這里觀察f[4]的遞歸樹代替f[10]的遞歸樹&#xff08;后者比較大&#xff0c;畫不下&#xff09;。 使用遞歸求解的時候復雜度為T(n)T(n?1)T(n?2)T(n)T(n-1)T(n-2)T(n)T(n?1)T(n?2)&#xff0c;觀察遞歸樹&#xff0c;發現降速最快的是最右邊每次減2&#xff0c…

循環服務器,并發服務器模型以及I/O多路轉接模型

https://blog.csdn.net/xinianbuxiu/article/details/53455784 一、基于TCP/IP協議的基本循環服務器 tcp_server.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #incl…

c++繼承父類的子類,如何調用父類的同名函數?

https://blog.csdn.net/qq_26399665/article/details/52080215 子類調用父類的同名函數&#xff1a; 子類和父類返回值參數相同&#xff0c;函數名相同&#xff0c;有virtual關鍵字&#xff0c;則由對象的類型決定調用哪個函數。 子類和父類只要函數名相同&#xff0c;沒有vi…

LCS最長公共子串

問題介紹 LCS問題(longest common subsequence problem)指的是求解兩個字符串最長公共子序列問題。這里的子序列是可以不連續的。LCS問題廣泛地出現在計算生物學中&#xff08;DNA序列、系統生成樹等等&#xff09;。這里介紹如何解決LCS問題&#xff0c;以及算法的正確性證明…

將字符串中的空格用%20替換

如果不需要原地操作&#xff0c;則一遍遍歷&#xff0c;將非空串復制&#xff0c;遇到空格加上%20&#xff0c;如果需要原地操作&#xff0c;首先進行遍歷出空格的個數x,然后擴容2x,從后往前遍歷實現。如果非空格字符串比空格字符串多的多的時候而且字符串非常長的時候使用原地…

12步輕松搞定python裝飾器

http://python.jobbole.com/81683/ 呵呵&#xff01;作為一名教python的老師&#xff0c;我發現學生們基本上一開始很難搞定python的裝飾器&#xff0c;也許因為裝飾器確實很難懂。搞定裝飾器需要你了解一些函數式編程的概念&#xff0c;當然還有理解在python中定義和調用函數…

操作系統【六】虛擬內存

傳統存儲管理方式的不足 一次性&#xff1a;作業必須一次性全部裝入內存后才能開始運行。這會造成&#xff1a;當作也很大時不能全部裝入內存&#xff1b;當大量作業要求運行時&#xff0c;由于內存無法容納所有作業&#xff0c;因此只有少量作業能夠運行&#xff0c;導致多道…

python裝飾器詳解

https://blog.csdn.net/xiangxianghehe/article/details/77170585 你會Python嘛&#xff1f; 我會&#xff01; 那你給我講下Python裝飾器吧&#xff01; Python裝飾器啊&#xff1f;我沒用過哎 以上是我一個哥們面試時候發生的真實對白。 ———————————————-分…

SQL Server【一】簡介和基本概念和命令

數據結構和數據庫的區別 數據庫是應用軟件級別研究數據的存儲和操作&#xff08;主要針對磁盤上的數據&#xff09; 數據結構是在系統軟件級別研究數據的存儲和操作&#xff08;主要是針對內存中的數據&#xff09; 對硬盤數操作是數據庫的強項&#xff0c;是數據庫研究的核心…

SQL Server【二】單表查詢

查詢 計算列 select * from emp; -- *通配符&#xff0c;表示所有的字段 -- from emp 從emp表查詢select empno, ename from emp; select ename as "員工姓名", sal*12 as "年薪" from emp;-- as可以省略&#xff0c;用于設置字段名 -- 注意用雙引號將字…

SQL Server【三】連接查詢

將兩個表或者兩個以上的表以一定的連接條件連接起來&#xff0c;從中檢索出滿足條件的數據。 內連接 使用inner join&#xff0c;inner可以省略 -- 查詢員工的姓名和部門名稱 select "E".ename as "員工姓名", "D".dname as "部門名稱&q…

Linux下網絡socket編程——實現服務器(select)與多個客戶端通信

一、關于socket通信 服務器端工作流程&#xff1a; 調用 socket() 函數創建套接字 用 bind() 函數將創建的套接字與服務端IP地址綁定調用listen()函數監聽socket() 函數創建的套接字&#xff0c;等待客戶端連接 當客戶端請求到來之后調用 accept()函數接受連接請求&#xff0c…

SQL Server【四】

identity 主鍵自動增長&#xff0c;用戶不需要為identity修飾的主鍵賦值 create table student (std_id int primary key identity(10,5),--(10,5)可以省略&#xff0c;默認為(1,1)std_name nvarchar(200) not null ) select * from student insert into student values (張三…

TCP服務器/客戶端實例(C/C )

1.1、Linux下的TCP服務器&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h>void error_handling(char *mess…

pip代理解決pip下載失敗問題

在用pip下載各種庫的時候發現速度實在是太慢了&#xff0c;還會有各種奇奇怪怪的問題&#xff0c;動不動就玄學失敗。 在網上找來找去找到知乎上一位大佬的回答&#xff1a;傳送門&#xff0c;用了豆瓣的代理。哇咔咔&#xff0c;媽媽再也不用擔心我下載失敗了。 代理&#x…