哈夫曼树

数据结构
Published

August 23, 2018

其实早就应该写完这个哈夫曼树,只不过最近有点没有心情学习。对于哈夫曼树的构造,我总结了以下几步:

  1. 取出元素构建子树
  2. 子树入队
  3. 构建新子树
  4. 将队列中的子树插入新子树,并入队
  5. 循环至最后一个子树
  6. 将队列中所有元素插入最后子树

程序

main.cpp

#include "hafuman.cpp"
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <queue>
using namespace std;

/* 运算符重载  */
struct mycmp {
    bool operator()(int a, int b) { //通过传入不同类型来定义不同类型优先级
        return a > b;               //最小值优先
    }
};

int TestNum[] = {5, 4, 3, 2, 1};

int main(int argc, char const *argv[]) {
    /* 最小堆用于存放数据 */
    priority_queue<int, vector<int>, mycmp> MyHeap;
    /* 队列于存放临时节点 */
    queue<HAFUNode_t<int>> TempQueue;
    /* 哈夫曼树根节点 */
    HAFUNode_t<int> HaFuTree = nullptr;
    /* 创建临时变量 */
    int tright, tleft;
    /* 赋值 */
    for (int i = 0; i < 5; i++) {
        MyHeap.push(TestNum[i]);
    }

    printf("测试哈夫曼\n");
    while (!MyHeap.empty()) {               /* 优先队列非空 */
        tleft = MyHeap.top(), MyHeap.pop(); /* 输出左值并释放 */
        if (MyHeap.empty()) {               /* 判断是否为空 */
            break;
        }
        tright = MyHeap.top(), MyHeap.pop(); /* 输出右值并释放 */
        /* 构造子树 */
        HaFuTree = CreatSubTree(tleft, tright, tleft + tright);
        if (MyHeap.empty()) { /* 若为最后一个节点 */
            break;
        } else {
            AutoJoin(HaFuTree, TempQueue);
        }
        /* 再加入元素 */
        MyHeap.push(tleft + tright);
    }
    /* 将队列中所有的子树合并成一颗 */
    while (!TempQueue.empty()) {
        FinalJoin(HaFuTree, TempQueue);
    }

    DrawTree(HaFuTree);
    return 0;
}

hafuman.cpp

#include "hafuman.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

/**
 * @brief  构造一个子树
 * @param[in]  类型T:左节点 右节点
 * @param[out]
 * @return
 **/
template <typename T> HAFUNode_t<T> CreatSubTree(T Left, T Right, T Root) {
    /* set the root  */
    HAFUTree<T> *pH = (HAFUNode_t<T>)malloc(sizeof(struct HAFUTree<T>));
    pH->data = Root;

    /* set the left child */
    pH->left = (HAFUNode_t<T>)malloc(sizeof(struct HAFUTree<T>));
    pH->left->data = Left;
    pH->left->left = nullptr;
    pH->left->right = nullptr;

    /* set the right child */
    pH->right = (HAFUNode_t<T>)malloc(sizeof(struct HAFUTree<T>));
    pH->right->data = Right;
    pH->right->left = nullptr;
    pH->right->right = nullptr;
    return pH;
}

/**
 * @brief 将一个根挂载在另一根的左侧
 **/
template <typename T>
static HAFUNode_t<T> LeftJoin(HAFUNode_t<T> LEFT, HAFUNode_t<T> ROOT) {
    free(ROOT->left);
    ROOT->left = LEFT;
    return ROOT;
}
/**
 * @brief 将一个根挂载在另一根的右侧
 **/
template <typename T>
static HAFUNode_t<T> RightJoin(HAFUNode_t<T> LEFT, HAFUNode_t<T> ROOT) {
    free(ROOT->right);
    ROOT->right = LEFT;
    return ROOT;
}

/**
 * @brief 将队中的子树插入新的子树,成功后保存至队列
 **/
template <typename T>
void AutoJoin(HAFUNode_t<T> NEW, std::queue<HAFUNode_t<T>> &Q) {
    HAFUNode_t<T> temp = nullptr;
    if (Q.empty()) {
        Q.push(NEW);
    } else { /* Queue not empty then start insert */
        // printf("NEW->data:%d Q.front()->left->data:%d\n", NEW->data,
        //        Q.front()->left->data);
        if (Q.front()->data == NEW->left->data) { /* left join */
            temp = LeftJoin(Q.front(), NEW);
            Q.pop();      /* pop the old  */
            Q.push(temp); /* push the new root */
        } else if (Q.front()->data == NEW->right->data) {
            temp = RightJoin(Q.front(), NEW);
            Q.pop();
            Q.push(temp);
        } else { /* can't insert */
            Q.push(NEW);
        }
    }
}
/**
 * @brief 最终插入,将队列中的元素插入到根中
 **/
template <typename T>
void FinalJoin(HAFUNode_t<T> ROOT, std::queue<HAFUNode_t<T>> &Q) {
    //        Q.front()->left->data);
    if (Q.front()->data == ROOT->left->data) { /* left join */
        LeftJoin(Q.front(), ROOT);
        Q.pop(); /* pop the old  */
    } else if (Q.front()->data == ROOT->right->data) {
        RightJoin(Q.front(), ROOT);
        Q.pop();
    } else { /* can't insert */
        printf("insert error!\r\n");
    }
}

/**
 * description  输出二叉树的高度
 * @param[in]   BinTree_t
 * @retval      int
 **/
template <typename T> static int FindTreeHeight(HAFUNode_t<T> BT) {
    int rightlen, leftlen, maxlen;
    if (BT) {
        rightlen = FindTreeHeight(BT->right);
        leftlen = FindTreeHeight(BT->left);
        maxlen = leftlen > rightlen ? leftlen : rightlen;
        return maxlen + 1;
    } else {
        return 0;
    }
}

/**
 * @brief 绘出树
 **/
template <typename T> void DrawTree(HAFUNode_t<T> BT) {
    int height = FindTreeHeight(BT);
    int cnt = 0;

    /* 开始层序遍历二叉树并且绘制图形 */
    std::queue<HAFUNode_t<T>> CurLevelqueue;  /*记录当前层的元素  */
    std::queue<HAFUNode_t<T>> NextLevelqueue; /*记录下一层的元素  */
    std::queue<HAFUNode_t<T>> CurTempqueue;   /*当前队列副本  */
    std::queue<HAFUNode_t<T>> NextTempqueue;  /*下一层队列副本  */
    HAFUNode_t<T> temp, lasttemp = NULL;
    int width = 0;
    CurLevelqueue.push(BT); //将头节点入队s
    /*现在修改了入队函数,空指针也可以入队
    所以在出队的时候就需要进行判断  */
    for (int i = 0; i < height; i++) {
        cnt = (int)pow(2, i);           //当前行的个数21
        width = pow(2, height - i + 1); //设置宽度为2^(height-i+1)
        CurTempqueue = CurLevelqueue;   /* 临时记录 */
        while (cnt--) {
            temp = CurLevelqueue.front();
            if (temp != NULL) {
                printf("%*d%*c", width, temp->data, width, ' ');
                NextLevelqueue.push(temp->left);
                NextLevelqueue.push(temp->right); //将下一层子节点入队
            } else {
                printf("%*c%*c", width, ' ', width, ' ');
                NextLevelqueue.push(nullptr);
                NextLevelqueue.push(nullptr); //如果此层是空,那么再入两个空指针
            }
            CurLevelqueue.pop(); //将上一层节点出队
        }
        printf("\n");
        NextTempqueue = NextLevelqueue; /* 记录下一层元素 */
        /* 接下来打印层间符号 */
        if (i != height - 1) { /* 若不是最后一行 */
            /* 先记录下一行元素宽度间隔 */
            int nextwidth = (int)pow(2, height - i);
            /* 并且以他下一层元素的个数打印'-' */
            for (int k = 0; k < (int)pow(2, i + 1); k++) {
                /* 每隔一位去打印width个'-' */
                if ((k % 2) == 0) { /* 偶数位打印 空格+`-` */
                    if (NextTempqueue.front() != nullptr) {
                        printf("%*c", nextwidth, '-');
                        for (uint8_t z = 0; z < nextwidth; z++) {
                            printf("-");
                        }
                    } else {
                        printf("%*c", nextwidth, ' ');
                        for (uint8_t z = 0; z < nextwidth; z++) {
                            printf(" ");
                        }
                    }
                    NextTempqueue.pop();
                } else { /* 奇数位打印 空格+`-` */
                    if (NextTempqueue.front() != nullptr) {
                        for (uint8_t z = 0; z < nextwidth; z++) {
                            printf("-");
                        }
                        printf("%*c", nextwidth, ' ');
                    } else {
                        for (uint8_t z = 0; z < nextwidth; z++) {
                            printf(" ");
                        }
                        printf("%*c", nextwidth, ' ');
                    }
                    NextTempqueue.pop();
                }
            }
            printf("\n");
        }
        printf("\r");
        swap(CurLevelqueue, NextLevelqueue); /* 交换队列 */
    }
}

hafuman.h

#ifndef _HAFUMAN_H
#define _HAFUMAN_H

#include <queue>
/**
 * @brief the defination
 **/
template <typename T> struct HAFUTree;
/**
 * @brief 别名
 **/
template <typename T> using HAFUNode_t = struct HAFUTree<T> *;
/**
 * @brief 结构体模板类
 **/
template <typename T> struct HAFUTree {
    T data;
    HAFUNode_t<T> left;
    HAFUNode_t<T> right;
};


#endif

makefile

CC = clang++
CFLAGS = -g # debug
CLIBS = #-lpthread
 
INCLUDE = $(wildcard ./*.h) # INCLUDE = a.h b.h ... can't be defined like "INCLUDE = ./*.h"
SOURCES = $(wildcard ./*.cpp)
 
TARGET = hafutree
OBJECTS = $(patsubst %.cpp,%.o,$(SOURCES))
 
$(TARGET) : $(OBJECTS)
    $(CC) $(CFLAGS) $^ -o $@ $(CLIBS)

$(OBJECTS) : %.o : %.cpp
    $(CC) -c $(CFLAGS) $< -o $@
 
.PHONY : clean
clean:
    rm -rf *.o $(TARGET)  

编译运行

  hafuman make && ./hafutree
clang++ -c -g  hafuman.cpp -o hafuman.o
clang++ -c -g  main.cpp -o main.o
clang++ -g  hafuman.o main.o -o hafutree
测试哈夫曼
                              15
               ---------------------------------
               6                               9
       -----------------               -----------------
       3               3               4               5
   ---------
   1       2