PS:什么是哈夫曼樹?
給定n個權值作為n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度達到最小,稱這樣的二叉樹為最優二叉樹,也稱為哈夫曼樹(Huffman Tree)。哈夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近。
計算規則:
假設一組權值,一個權值是一個結點,12 34 2 5 7 ,在這其中找出兩個最小的權值,然后組成一個新的權值,再次找出最小的權值結點。如圖:


問題:
1:如果程序找出兩個最小的權值,把兩個權值最小的相加結果再次添加到數組中呢。
2:完成第一步后,又怎么讓他們成為一個二叉樹呢。
3:這個二叉樹的結構體怎么定義呢,這組帶有權值的結點又以什么方式存在呢。
思路:
對於這種陸續找出兩個最小權值的算法可以利用排序的方式,從小到大排序,那么最左邊的就是最小的,這樣一來最小的權值可以挑選出來了,接下來再利用特定的結構體(都有左孩子和右孩子還有存放權值的data域)讓每一個權值結點存在一個數組中。這樣子不斷的操作數組,從數組中的5個元素到只有1個元素為止,此時的這一個元素就是二叉樹的跟。然后再利用遍歷方式打印這個二叉樹即可。
代碼實現:
結構體定義
一個二叉樹的結構體,一個數組的結構體。可以看出數組的結構體內部是包含一個二叉樹結點的結構體的。
/**
* Created by 劉志通 on 2018/11/22.
* @describe 哈夫曼樹的簡介
* 編程思想:
* 1:方式簡介:
* 利用數組(二叉樹結構體類型),來存放初始權值(首次認為權值就是一個樹跟,左右孩子分別是NULL),在數組初始化的之后排序,然后拿出index=0,1,更新
* 權值根,
* 2:所用知識:
* 數組,鏈表存儲,二叉樹結構體。
*/
#include "stdlib.h"
#include "stdio.h"
/**
* @describe 二叉樹結構體
* 結構形式:Lchild data Rchild
* */
typedef struct twoTree {
int data;
struct twoTree *Lchild, *Rchild;
} twoTree, *twoTreeL;
typedef struct {
twoTreeL data[100];
int length;
} arrayMy;
初始化數組
arrayMy InitList(arrayMy &L) {
// 初始化表,一個空表。
L.length = 0;
return L;
}
/**
* 冒泡排序
* */
void swap(int *weigth,int n){
for (int i = 0; i < n; i++){
for (int j = 0; j+1<n-i; j++){
if (weigth[j] > weigth[j+1]){
int temp;
temp = weigth[j];
weigth[j] = weigth[j+1];
weigth[j+1] = temp;
}
}
}
}
數組的特定操作
對於初始化數組可以看出是把每一個元素寫成了二叉樹結構的一個結點添加到了一維數組中,初始狀態左右孩子都是NULL,data是權值,還有兩個方法是插入和刪除,之所以有這兩個方法是因為,要對數組進行操作的時候需要對數組元素更新,找出前兩個最小元素后,要立馬刪除這兩個元素,然后把這兩個元素相加得到的結果添加到數組中,添加時要有序添加,否則還要排序。這里面有注釋,不懂也可以留言。
int ListDelete(arrayMy &L, int pos) {
if (pos > L.length) {
printf("刪除角標不合法");
return -1;
}
printf("\n刪除%d\n",L.data[pos]->data);
int i;
//在插入的同時,i要保證在pos以及pos后方,入1,2,3,4,5當在第3個插入時,須把原有的第三個數據以及以后數據后移一位,空出第三個位置。
for (i = pos; i <= L.length; i++) {
L.data[i - 1] = L.data[i];
}
L.length--;
return 0;//返回0表示成功;
}
int ListInsert(arrayMy &L, twoTreeL data) {
printf("插入%d\n",data->data);
int pos = 0;
for (int i = 0; i < L.length; i++) {
if (data->data >= L.data[i]->data) {
pos = i;
printf("pos---%d\n",pos);
break;
}
}
int i;
//在插入的同時,i要保證在pos以及pos后方,入1,2,3,4,5當在第3個插入時,須把原有的第三個數據以及以后數據后移一位,空出第三個位置。
for (i = L.length; i > pos; i--) {
L.data[i] = L.data[i - 1];
}
L.data[pos] = data;
L.length++;
return 0;
}
/**
* 數組初始化
* */
void arrayInit(int *weigth, int n, arrayMy &arr) {
int i;
for (i = 0; i < n; i++) {
twoTreeL treeL = (twoTreeL) malloc(sizeof(twoTree));
treeL->Lchild = NULL;
treeL->Rchild = NULL;
treeL->data = weigth[i];
arr.data[i] = treeL;
arr.length++;
// arr->lenght++;
}
printf("初始化成功");
}
哈夫曼算法
這是最重要的一點,首先拿到前兩個權值結點,相加計算得到結果賦值給新建結點,然后刪除數組中的前兩個結點,插入新建結點,然后遞歸重復此操作。
/**
* @describe 哈夫曼算法
* */
arrayMy haFuManSF(arrayMy &arr) {
printf("\n個數%d", arr.length);
if (arr.length >= 2) {
twoTreeL tA = arr.data[0];//拿到第一個結點data數,(權值最小)。
twoTreeL tB = arr.data[1];//拿到第二個結點data數,(權值第二小)。
twoTreeL tc = (twoTreeL) malloc(sizeof(twoTree));//創建新的結點
tc->data = tA->data + tB->data;//相加結果加入到新建的data域。
tc->Lchild = tA;//讓A稱為左子樹
tc->Rchild = tB;
ListDelete(arr, 0);//刪掉數組的前兩個結點元素。
ListDelete(arr, 0);
// printf(" geshu %d ",arr.length);
printf("還剩 ");
for(int i=0;i<arr.length;i++){
printf("%d ", arr.data[i]->data);
}
ListInsert(arr, tc);//插入新建結點元素。
printf("\n插入后 ");
for(int i=0;i<arr.length;i++){
printf("%d ", arr.data[i]->data);
}
haFuManSF(arr);//然后遞歸,重復上面操作。
} else {
return arr;
}
// printf(" geshu %d ",arr.length);
return arr;
}
遞歸遍歷
//先中后序遍歷二叉樹
void diGuiBianLi(twoTreeL tL, int xl) {
if (tL == NULL) {
return;
} else {
if (xl == 1) {
//先序遍歷
printf("%d ", tL->data);
diGuiBianLi(tL->Lchild, xl);
diGuiBianLi(tL->Rchild, xl);
} else if (xl == 2) {
//中序遍歷
diGuiBianLi(tL->Lchild, xl);
printf("%d ", tL->data);
diGuiBianLi(tL->Rchild, xl);
} else if (xl == 3) {
//后序遍歷
diGuiBianLi(tL->Lchild, xl);
diGuiBianLi(tL->Rchild, xl);
printf("%d ", tL->data);
}
}
}
調試:main
int main(void) {
//新建權值數組
int weigth[5] = {3,1 , 12, 4, 14};
//排序(升序)
swap(weigth,5);
//創建數組(存放權值和個數)
arrayMy arr;
arr = InitList(arr);
arrayInit(weigth, 5, arr);
//檢測數組是否排序好
for (int i = 0; i < 5; i++) {
printf("%d ", arr.data[i]->data);
}
//哈夫曼編程思想
arr = haFuManSF(arr);
twoTreeL treeL=arr.data[0];
printf("\n");
//先中后遍歷
printf("\n先序遍歷: ");
diGuiBianLi(treeL,1);
printf("\n中序遍歷:");
diGuiBianLi(treeL,2);
printf("\n后序遍歷:");
diGuiBianLi(treeL,3);
return 0;
}
完。
