哈夫曼編碼樹
首先要知道什么是編碼:
就像上圖一樣,左邊是編碼,右邊是字符,所以左邊到右邊的變換就是解碼,右邊到左邊的變換就是編碼。但這是有8位,所以只能表示128位字符,這對英語是夠用的了,但是對其他語言例如漢語,日語確實遠遠不夠用,那該怎么辦呢?此時就有了多字節編碼。
但是多字節編碼也是有漏洞的,就像是假設我要用多字節編碼,但是如果我只輸入字符A,那么編碼就是00000000 00000000 00000000 01000001,這其實是十分浪費空間的,所以就出現了Unicode。
Unicode(編碼字符對應表)
Unicode本身不是一種編碼,而是一個表,對於每個字符都是U+一個16進制數字來表示。這時國際公認的規則,所有由它誕生的編碼都是根據它的規則所指定的。例如假設這個字符“x”在這個表中已經規定是這個16進制數,那么進行編碼成其他二進制數時這個二進制數的大小一定是等於表中所對應的二進制數的大小的。
同時這個統一編碼下面分有8為字節編碼,16位字節的編碼,32位字節的編碼。
UTF-8(可變編碼)
UTF-8是一種變長編碼,就是這個編碼的長度可以8位8位的依次增長,同時,UTF-8可以根據Unicode編碼所對應的16進制數自動轉化為對應的二進制數。例如:
- 我要輸出字符B,因為字符B的位數本身就是小於8位的,所以他是可以直接用UTF-8進行編碼和解碼輸出。
- 我要輸出字符“牛”,容易知道字符“牛”是不能直接用UTF-8的八位二進制輸出的,所以我可以先將“牛”這個字符在Unicode表中查找到的16進制數找出來,假設這個16進制數所對應的范圍是000080-0007FF,那么當他轉化為字節流時,UTF-8就會自動變成16位二進制數輸出。
編碼的種類:
- 變長編碼:就是像UTF-8這種編碼長度可以改變的編碼方式。
- 定長編碼:就是像UTF-16這樣編碼長度已經固定了的編碼方式。
編碼的使用場景
信息的傳輸,因為在信道中,信息是以二進制的方式進行傳輸的,所以要將信息進行二進制編碼,對於不同形式的二進制組合方式,都代表一個不同的信息。
平均編碼長度
- 平均編碼長度的計算方式:平均編碼長度 = 每個字符的編碼長度*每個字符的出現概率。
- 意義:平均編碼長度越短,說明發送一段相同數據流,所發送的二進制流就越短,發送的效率就越高。
哈夫曼樹
哈夫曼樹的生成方式就是利用了平均編碼長度這個概念。生成步驟如下:
- 首先計算出每個字符的生成概率。
- 這顆樹每個葉結點都代表一個字符,同時從根節點和葉結點都代表一個二進制數0或者1.
- 每要讀取一個字符,都是按照左0右1的方式讀取出來。
假設有數據流中各個字符的出現概率:
a: 0.8,b: 0.05,0: 0.1,1: 0.05
那么最終形成的樹的形狀為:
那么通過這顆哈夫曼樹所得到的哈夫曼編碼就是
- a : 0
- 0 : 10
- b : 110
- 1 : 1110
可見出現的概率越大的字符所對應的二進制就越長。
代碼演示:
class Node {
public:
Node* lchild, * rchild;
char ch;
double p;
Node(char c, double p, Node* l, Node* r) :ch(c), p(p), lchild(l), rchild(r) {}
};
Node* root;
class tree {
private:
vector<Node*> arr;
public:
void insert(char c,double p) {
arr.push_back(new Node(c, p, NULL, NULL));
}
void pick_min(int n) {
for (int i = n - 1; i >= 0; i--) {
if (arr[n]->p > arr[i]->p) {
swap(arr[n], arr[i]);
}
}
return;
}
Node* make_tree() {
int len = arr.size();
for (int i = 1; i < len; i++) {
pick_min(len - i);
pick_min(len - i - 1);
arr[len - i - 1] = combine(arr[len - i], arr[len - i - 1]);
}
return arr[0];
}
Node* combine(Node* a, Node* b) {
Node* n = new Node('\0', a->p + b->p, a, b);
n->lchild = a;
n->rchild = b;
return n;
}
void output(Node* root,string str,int k = 0) {
if (k >= str.size()) {
str.push_back('0');
}
if (root->lchild == NULL && root->rchild == NULL) {
printf("%c: %s\n", root->ch, str.c_str());
return;
}
str[k] = '0';
output(root->lchild,str, k + 1);
str[k] = '1';
output(root->rchild,str, k + 1);
return;
}
};
int main() {
tree* t = new tree();
int sum = 0;
string str;
double temp[26];
srand((unsigned int)(time(NULL)));
for (int i = 0; i < 26; i++) {
temp[i] = rand() % 1000;
sum += temp[i];
}
for (int i = 0; i < 26; i++) {
printf("%c 的出現概率為 %lf\n", 'A' + i, temp[i] / sum);
t->insert('A' + i, temp[i] / sum);
}
cout << "結果為" << endl;
root = t->make_tree();
t->output(root, str);
return 0;
}