哈夫曼樹


1. 概念

哈夫曼樹是一個特殊的二叉樹,它的特殊在於:

  • 葉子節點帶有權值:對葉子結點賦予的一個有意義的數值量
  • 二叉樹的帶權路徑長度:設二叉樹具有n個帶權值的葉子結點,從根結點到各個葉子結點的路徑長度與相應葉子結點權值的乘積之和。記為 WPL=Wklk,這里的WPL即帶權路徑長度(Weight Path Length)。
  • 權值越大的葉子結點越靠近根結點,而權值越小的葉子結點越遠離根結點
  • 只有度為0的葉子結點和度為2的分支節點,不存在度為1的結點
    在這里插入圖片描述
    綜上,總結哈夫曼樹的概念為:

哈夫曼樹:給定一組具有確定權值的葉子節點,帶權路徑長度最小的二叉樹

舉例:給定4個葉子結點,其權值分別為{2,3,4,7},可以構造出形狀不同的多個二叉樹。
在這里插入圖片描述
如上圖所示,可以根據給定的葉子結點構建出不同的二叉樹結構,按照計算以上三個二叉樹帶權路徑長度計算公式得出的WPL分別為32、41、30,根據哈夫曼樹定義可知,最右側WPL=30的這個二叉樹就是一個哈夫曼樹。

2. 基本思想

2.1 初始化

在這里插入圖片描述
由給定的n個權值 {w1,w2,…,wn} 構造n顆只有一個根結點的二叉樹,從而得到一個二叉樹集合F= {T1,T2,…,Tn}

2.2 選取與合並

在這里插入圖片描述
在集合F中選取根結點的權值最小的兩顆二叉樹分別作為左、右子樹構造一顆新的二叉樹,這顆二叉樹的根結點的權值為其左、右子樹根結點的權值之和

2.3 刪除與加入

在這里插入圖片描述
在集合F中刪除作為作為左、右子樹的兩顆二叉樹,並將新建立的二叉樹加入到集合F中

2.4 遞歸

在這里插入圖片描述
重復以上選取與合並、刪除與加入兩步,當集合F中只剩下一顆二叉樹時,這顆二叉樹便是哈夫曼樹

2.5 實例

我們按照上面的例子,初始化集合{2,3,4,7}來模擬下哈夫曼樹的推演過程,記錄如下:
在這里插入圖片描述

3. 數據結構

設置一個數組 huffTree 來存儲哈夫曼樹中各結點信息,數組元素的數據結構如下圖:
在這里插入圖片描述

  • weight 權值
  • leftChild 左子節點,存儲數組下標
  • rightChild 右子節點,存儲數組下標
  • parent 父節點,存儲數組下標

數組大小可以通過2n-1來計算獲得,為何是2n-1大小,可以參考二叉樹性質來計算。

根據上面的初始化、選取與合並、刪除與加入、遞歸的步驟方法推演,進行數組操作過程如下:
在這里插入圖片描述

4. 代碼實現

參考的資料中是采用朴素的數組進行存儲的,數組存儲的困難在於不利於動態擴容和調整,這里采用List集合進行存儲,實際上底層也是數組存儲,只是通過封裝好的容器進行調用即可。這里還是延用上面圖例中的權值數組 {2,3,4,7},除此之外,還可以使用 {2,3,5,7,5} 這種新構建結點等於已存在權值結點、多個權值結點權值相等各種特殊情況來進行驗證。

/** * 哈夫曼樹 * created by guanjian on 2020/12/30 15:43 */ public class HuffmanTree { //權值結點 private Integer[] weightArray; //哈夫曼樹結點總數 2*weights - 1 private int huffmanTreeNodeLength; //哈夫曼樹結點數組 private List<HuffmanTreeNode> huffmanTreeNodeList; //每次獲取最小權值數量 private final static int minNodeLength = 2; public HuffmanTree(Integer[] weightArray) { Assert.notEmpty(weightArray, "weightArray can not be empty"); this.weightArray = weightArray; } /** * 初始化哈夫曼樹結點數組 */ public void initHuffmanTreeNodeArray() { //哈夫曼樹結點總數 2*weights - 1 this.huffmanTreeNodeLength = 2 * this.weightArray.length - 1; this.huffmanTreeNodeList = Lists.newArrayListWithCapacity(huffmanTreeNodeLength); //初始化填充哈夫曼樹結點 IntStream.range(0, weightArray.length).forEach(i -> { //這里將權值灌入到node節點中 huffmanTreeNodeList.add( HuffmanTreeNode.Builder.aHuffmanTreeNode() .weight(weightArray[i]) .build() ); }); System.out.format("初始化完成 huffmanTreeNodeList=%s \n", Arrays.toString(huffmanTreeNodeList.toArray())); } /** * 查找最小權值方法 */ public List<HuffmanTreeNode> findMinNodes() { //按權值從小到大升序進行排序 List<HuffmanTreeNode> sortedList = huffmanTreeNodeList.stream() .filter(x -> !x.isSorted()) .sorted(Comparator.comparing(HuffmanTreeNode::getWeight)) .collect(Collectors.toList()); sortedList.forEach(x -> System.out.format("排序 %s 處理 %s \n", x.getWeight(), x.isSorted())); //取最小權值的兩個node List<HuffmanTreeNode> list = sortedList.subList(0, sortedList.size() >= minNodeLength ? minNodeLength : sortedList.size()); if (CollectionUtils.isEmpty(list)) return list; list.forEach(x -> { x.setSorted(true); }); return list; } /** * 構建哈夫曼樹 */ public void buildHuffmanTree() { for (; ; ) { /** * 選取權值最小的兩個結點,構造父節點 */ List<HuffmanTreeNode> minNodes = findMinNodes(); if (minNodes.size() != minNodeLength) break; //左子節點 HuffmanTreeNode leftChild = minNodes.get(0); //右子節點 HuffmanTreeNode rightChild = minNodes.get(1); //父結點 HuffmanTreeNode parent = HuffmanTreeNode.Builder.aHuffmanTreeNode() .weight(leftChild.getWeight() + rightChild.getWeight()) .leftChild(leftChild) .rightChild(rightChild) .build(); leftChild.setParent(parent); rightChild.setParent(parent); System.out.println("===================================="); System.out.format("獲取左子結點權值=%s\n", leftChild.getWeight()); System.out.format("獲取右子結點權值=%s\n", rightChild.getWeight()); System.out.format("獲取父結點權值=%s\n", parent.getWeight()); huffmanTreeNodeList.add(parent); } } /** * 計算當前二叉樹的WPL * weight(結點權值) * path(樹高度) = WPL(權值路徑) */ public void calculateWeightPathLength() { System.out.println("===================================="); int wpl = 0; for (HuffmanTreeNode node : huffmanTreeNodeList) { //跳過構建結點,只計算權值結點 if (null != node.leftChild || null != node.rightChild) continue; int path = 0; HuffmanTreeNode currentNode = node; while (null != currentNode.parent) { path++; currentNode = currentNode.parent; } System.out.format("權值:%s * 路徑長度:%s \n", node.getWeight(), path); wpl += node.getWeight() * path; } System.out.format("WPL=%s \n", wpl); } public Integer[] getWeightArray() { return weightArray; } public void setWeightArray(Integer[] weightArray) { this.weightArray = weightArray; } public int getHuffmanTreeNodeLength() { return huffmanTreeNodeLength; } public void setHuffmanTreeNodeLength(int huffmanTreeNodeLength) { this.huffmanTreeNodeLength = huffmanTreeNodeLength; } public List<HuffmanTreeNode> getHuffmanTreeNodeList() { return huffmanTreeNodeList; } public void setHuffmanTreeNodeList(List<HuffmanTreeNode> huffmanTreeNodeList) { this.huffmanTreeNodeList = huffmanTreeNodeList; } public static int getMinNodeLength() { return minNodeLength; } static class HuffmanTreeNode { /** * 父結點 */ private HuffmanTreeNode parent; /** * 左子結點 */ private HuffmanTreeNode leftChild; /** * 右子結點 */ private HuffmanTreeNode rightChild; /** * 權重值 */ private int weight; /** * 是否完成排序 */ private boolean sorted; public HuffmanTreeNode getParent() { return parent; } public void setParent(HuffmanTreeNode parent) { this.parent = parent; } public HuffmanTreeNode getLeftChild() { return leftChild; } public void setLeftChild(HuffmanTreeNode leftChild) { this.leftChild = leftChild; } public HuffmanTreeNode getRightChild() { return rightChild; } public void setRightChild(HuffmanTreeNode rightChild) { this.rightChild = rightChild; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public boolean isSorted() { return sorted; } public void setSorted(boolean sorted) { this.sorted = sorted; } public static class Builder { private HuffmanTreeNode parent; private HuffmanTreeNode leftChild; private HuffmanTreeNode rightChild; private int weight; private boolean sorted; private Builder() { } public static Builder aHuffmanTreeNode() { return new Builder(); } public Builder weight(int weight) { this.weight = weight; return this; } public Builder parent(HuffmanTreeNode parent) { this.parent = parent; return this; } public Builder rightChild(HuffmanTreeNode rightChild) { this.rightChild = rightChild; return this; } public Builder leftChild(HuffmanTreeNode leftChild) { this.leftChild = leftChild; return this; } public Builder sorted(boolean sorted) { this.sorted = sorted; return this; } public HuffmanTreeNode build() { HuffmanTreeNode huffmanTreeNode = new HuffmanTreeNode(); huffmanTreeNode.setLeftChild(this.leftChild); huffmanTreeNode.setRightChild(this.rightChild); huffmanTreeNode.setParent(this.parent); huffmanTreeNode.setWeight(this.weight); huffmanTreeNode.setSorted(this.sorted); return huffmanTreeNode; } } @Override public String toString() { return "HuffmanTreeNode{" + "parent=" + parent + ", leftChild=" + leftChild + ", rightChild=" + rightChild + ", weight=" + weight + ", sorted=" + sorted + '}'; } } public static void main(String[] args) { //權值結點 Integer[] weightArray = new Integer[]{2,3,4,7}; //聲明哈夫曼樹 HuffmanTree huffmanTree = new HuffmanTree(weightArray); //初始化哈夫曼樹 huffmanTree.initHuffmanTreeNodeArray(); //構建哈夫曼樹 huffmanTree.buildHuffmanTree(); //WPL huffmanTree.calculateWeightPathLength(); } } 【控制台輸出】 初始化完成 huffmanTreeNodeList=[HuffmanTreeNode{parent=null, leftChild=null, rightChild=null, weight=2, sorted=false}, HuffmanTreeNode{parent=null, leftChild=null, rightChild=null, weight=3, sorted=false}, HuffmanTreeNode{parent=null, leftChild=null, rightChild=null, weight=4, sorted=false}, HuffmanTreeNode{parent=null, leftChild=null, rightChild=null, weight=7, sorted=false}] 排序 2 處理 false 排序 3 處理 false 排序 4 處理 false 排序 7 處理 false ==================================== 獲取左子結點權值=2 獲取右子結點權值=3 獲取父結點權值=5 排序 4 處理 false 排序 5 處理 false 排序 7 處理 false ==================================== 獲取左子結點權值=4 獲取右子結點權值=5 獲取父結點權值=9 排序 7 處理 false 排序 9 處理 false ==================================== 獲取左子結點權值=7 獲取右子結點權值=9 獲取父結點權值=16 排序 16 處理 false ==================================== 權值:2 * 路徑長度:3 權值:3 * 路徑長度:3 權值:4 * 路徑長度:2 權值:7 * 路徑長度:1 WPL=30 

5. 參考

https://space.bilibili.com/26340287 懶貓老師數據結構講解

 

轉載:https://blog.csdn.net/u013161278/article/details/111942554?utm_medium=distribute.pc_category.none-task-blog-hot-5.nonecase&depth_1-utm_source=distribute.pc_category.none-task-blog-hot-5.nonecase

https://blog.csdn.net/u013161278/article/details/111942554?utm_medium=distribute.pc_category.none-task-blog-hot-5.nonecase&depth_1-utm_source=distribute.pc_category.none-task-blog-hot-5.nonecase


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM