Haffman樹
1.概念和構造:
我們來看一個案例:
image.png
image.png
重點理解一下路徑長度和帶權的路徑長度的概念:(權重就是結點到結點之間的數字,代表重復了多少次)
image.png
下面我們來看一下Haffman樹的構造:
image.png
image.png
image.png
Haffman樹編碼:
image.png
image.png
2.附上Haffman樹的代碼實現:
package com.xx.tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Stack;
public class HaffmanTree {
public static void main(String[] arg0) {
HaffmanTree haffmanTree = new HaffmanTree();
ArrayList<TreeNode> list = new ArrayList<TreeNode>();
list.add(new TreeNode("good", 50));
TreeNode node = new TreeNode("nice", 10);
list.add(node);
list.add(new TreeNode("greate", 20));
list.add(new TreeNode("hello", 110));
list.add(new TreeNode("hi", 200));
haffmanTree.showHaffman(haffmanTree.createHaffmanTree(list));
haffmanTree.getCode(node);
}
TreeNode root;
// 創建哈夫曼樹
public TreeNode createHaffmanTree(ArrayList<TreeNode> list) {
// 排序
while (list.size() > 1) {
// 可以根據輸入的數組不同用不同的方法進行排序
Collections.sort(list);
TreeNode left = list.get(list.size() - 1);
TreeNode right = list.get(list.size() - 2);
TreeNode parent = new TreeNode("tree", left.weight + right.weight);
parent.leftChild = left;
parent.rightChild = right;
left.parent = parent;
right.parent = parent;
list.remove(right);
list.remove(left);
list.add(parent);
}
root = list.get(0);
return root;
}
// 從上到小 從左往右依次顯示
public void showHaffman(TreeNode root) {
LinkedList<TreeNode> list = new LinkedList<TreeNode>();
list.offer(root);
while (!list.isEmpty()) {
// 先進先出
TreeNode node = list.pop();
System.out.println(node.data);
if (node.leftChild != null) {
list.offer(node.leftChild);
}
if (node.rightChild != null) {
list.offer(node.rightChild);
}
}
}
// 獲取編碼
public void getCode(TreeNode node) {
TreeNode tNode = node;
Stack<String> stack = new Stack<String>();
while (tNode != null && tNode.parent != null) {
if (tNode.parent.leftChild == tNode) {
// node是左節點
stack.push("0");
} else if (tNode.parent.rightChild == tNode) {
// node是右節點
stack.push("1");
}
tNode = tNode.parent;
}
System.out.println();
while (!stack.isEmpty()) {
System.out.print(stack.pop());
}
}
public static class TreeNode<T> implements Comparable<TreeNode<T>> {
T data;
int weight;// 權重
TreeNode leftChild;
TreeNode rightChild;
TreeNode parent;
public TreeNode(T data, int weight) {
this.data = data;
this.weight = weight;
leftChild = null;
rightChild = null;
parent = null;
}
// 倒序 從大到小
public int compareTo(TreeNode<T> o) {
// TODO Auto-generated method stub
if (this.weight > o.weight) {
return -1;
} else if (this.weight < o.weight) {
return 1;
}
return 0;
}
}
}
AVL樹(平衡二叉樹)
概念:
1.平衡二叉樹,是一種二叉排序樹,其中每一個節點的左子樹和右子樹的高度差最多等于1.
2.平衡因子:二叉樹上節點的左子樹深度減去右子樹深度的值稱為平衡因子BF(Balance Factor),上面的概念又可以說成是 每個節點的平衡因子<=1的二叉排序樹。
3.最小不平衡子樹:距離插入結點最近的,且平衡因子的絕對值大于1的節點為根的子樹,我們成為最小不平衡子樹。
構建平衡二叉樹:
image.png
image.png
image.png
image.png
image.png
image.png
右旋:
image.png
插入規則:
1.左平衡操作:結點t的不平衡是因為左子樹過深
image.png
2.右平衡操作:結點t的不平衡是因為右子樹過深
image.png
實現代碼:
package com.xx.tree;
import java.util.LinkedList;
/**
* AVL樹(二叉平衡樹)
*
*/
public class AVLTree<E extends Comparable<E>> {
Node<E> root;
int size = 0;
private static final int LH = 1;
private static final int RH = -1;
private static final int EH = 0;
/**
* 左旋轉
* @param x
*/
public void left_rotate(Node<E> x) {
if (x != null) {
Node<E> y = x.right;// 先取到Y結點
// 1。把貝塔作為X的右孩子
x.right = y.left;
if (y.left != null) {
y.left.parent = x;
}
// 2。把Y移到原來X的位置
y.parent = x.parent;
if (x.parent == null) {
root = y;
} else {
if (x.parent.left == x) {
x.parent.left = y;
} else if (x.parent.right == x) {
x.parent.right = y;
}
}
// 3。X作為Y的左孩子
y.left = x;
x.parent = y;
}
}
/**
* 右旋轉
* @param y
*/
public void right_rotate(Node<E> y) {
if (y != null) {
Node<E> yl = y.left;
// step1
y.left = yl.right;
if (yl.right != null) {
yl.right.parent = y;
}
// step2
yl.parent = y.parent;
if (y.parent == null) {
root = yl;
} else {
if (y.parent.left == y) {
y.parent.left = yl;
} else if (y.parent.right == y) {
y.parent.right = yl;
}
}
// step3
yl.right = y;
y.parent = yl;
}
}
/**
* 右平衡操作,即結點t的不平衡是因為右子樹過深
*/
public void rightBalance(Node<E> t) {
Node<E> tr = t.right;
switch (tr.balance) {
case RH:// 新的結點插入到t的右孩子的右子樹中
left_rotate(t);
t.balance = EH;
tr.balance = EH;
break;
case LH:// 新的結點插入到t的右孩子的左子樹中
Node<E> trl = tr.left;
switch (trl.balance) {
case RH:
t.balance = LH;
tr.balance = EH;
trl.balance = EH;
break;
case LH:
t.balance = EH;
tr.balance = RH;
trl.balance = EH;
break;
case EH:
tr.balance = EH;
trl.balance = EH;
t.balance = EH;
break;
}
right_rotate(t.right);
left_rotate(t);
break;
}
}
/**
* 左平衡操作,即結點t的不平衡是因為左子樹過深
*/
public void leftBalance(Node<E> t) {
Node<E> tl = t.left;
switch (tl.balance) {
case LH:
right_rotate(t);
tl.balance = EH;
t.balance = EH;
break;
case RH:
Node<E> tlr = tl.right;
switch (tlr.balance) {
case LH:
t.balance = RH;
tl.balance = EH;
tlr.balance = EH;
break;
case RH:
t.balance = EH;
tl.balance = LH;
tlr.balance = EH;
break;
case EH:
t.balance = EH;
tl.balance = EH;
tlr.balance = EH;
break;
default:
break;
}
left_rotate(t.left);
right_rotate(t);
break;
}
}
//插入
public boolean insertElement(E element) {
Node<E> t = root;
if (t == null) {
root = new Node<E>(element, null);
size = 1;
root.balance = 0;
return true;
} else {
// 開始找到要插入的位置
int cmp = 0;
Node<E> parent;
Comparable<? super E> e = (Comparable<? super E>) element;
do {
parent = t;
cmp = e.compareTo(t.elemet);
if (cmp < 0) {
t = t.left;
} else if (cmp > 0) {
t = t.right;
} else {
return false;
}
} while (t != null);
// 開始插入數據
Node<E> child = new Node<E>(element, parent);
if (cmp < 0) {
parent.left = child;
} else {
parent.right = child;
}
// 節點已經放到了樹上
// 檢查平衡,回溯查找
while (parent != null) {
cmp = e.compareTo(parent.elemet);
if (cmp < 0) {
parent.balance++;
} else {
parent.balance--;
}
if (parent.balance == 0) {// 如果插入后還是平衡樹,不用調整
break;
}
if (Math.abs(parent.balance) == 2) {
// 出現了平衡的問題,需要修正
fixAfterInsertion(parent);
break;
} else {
parent = parent.parent;
}
}
}
size++;
return true;
}
public void showAVL(Node root) {
LinkedList<Node> list = new LinkedList<Node>();
list.offer(root);// 隊列放入
while (!list.isEmpty()) {
Node node = list.pop();// 隊列的取出
System.out.println(node.elemet);
if (node.left != null) {
list.offer(node.left);
}
if (node.right != null) {
list.offer(node.right);
}
}
}
private void fixAfterInsertion(Node<E> parent) {
if (parent.balance == 2) {
leftBalance(parent);
}
if (parent.balance == -2) {
rightBalance(parent);
}
}
public static class Node<E extends Comparable<E>> {
E elemet;
int balance = 0;// 平衡因子
Node<E> left;
Node<E> right;
Node<E> parent;
public Node(E elem, Node<E> pare) {
this.elemet = elem;
this.parent = pare;
}
public E getElemet() {
return elemet;
}
public void setElemet(E elemet) {
this.elemet = elemet;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public Node<E> getLeft() {
return left;
}
public void setLeft(Node<E> left) {
this.left = left;
}
public Node<E> getRight() {
return right;
}
public void setRight(Node<E> right) {
this.right = right;
}
public Node<E> getParent() {
return parent;
}
public void setParent(Node<E> parent) {
this.parent = parent;
}
}
public static void main(String[] arg0){
Integer[] nums={5,8,2,0,1,-2};
AVLTree<Integer> tree=new AVLTree<Integer>();
for(int i=0;i<nums.length;i++){
tree.insertElement(nums[i]);
}
tree.showAVL((Node) tree.root);
}
}
紅黑樹
上次我們講到AVL樹。AVL樹的性能(添加,刪除)還是比較大的,為了解決這些操作上的性能的問題,我們會用到紅黑樹。(查詢的效率和AVL是差不多的)
紅黑樹是對平衡樹的改進,任意一個節點,他的左右子樹的層次最多不超過一倍。
紅黑樹定義
image.png
插入節點
插入節點:先按照二叉排序樹的方式插入一個節點,再查找最小不平衡子樹,以最小不平衡子樹進行下面的操作
1. 插入的是根節點
直接將節點涂黑
2. 插入的節點的父節點是黑色
不違背任何性質,不用調整
3. 插入節點的父節點是紅色
3.1 父節點是祖父節點的左孩子
3.1.1 情況1:祖父節點的另一個子節點(叔叔節點)是紅色
將當前節點的父節點和叔叔節點涂黑,祖父節點涂紅,把當前節點指向祖父節點,從新的當前節點開始算法
3.1.2 情況2:叔叔節點是黑色
3.1.2.1 當前節點是其父節點的右孩子
當前節點的父節點做為新的當前節點,以新當前節點為支點左旋。
3.1.2.2 當前節點是其父節點的左孩子
父節點變為黑色,祖父節點變紅色,再祖父節點為支點進行右旋
3.2 父節點是祖父節點的右孩子
參考3.1 將左全部變成右即可
刪除節點
刪除節點:先進行二叉排序樹的刪除操作,然后已替換節點作為當前節點進行后面的平衡操作
1.當前節點是紅色
直接把當前節點染成黑色,結束。
2.當前節點是黑色
2.1 被刪除節點是父節點的左孩子
2.1.1 當前節點是根節點
什么都不做
2.1.2 當前節點x的兄弟節點是紅色(此時父節點和兄弟節點的子節點分為黑)
把父節點染成紅色,兄弟節點染成黑色,對父節點進行左旋,重新設置x的兄弟節點
2.1.3 當前節點x 的兄弟節點是黑色
2.1.3.1 兄弟節點的兩個孩子都是黑色
將x的兄弟節點設為紅色,設置x的父節點為新的x節點
2.1.3.2 兄弟的右孩子是黑色,左孩子是紅色
將x兄弟節點的左孩子設為黑色,將x兄弟節點設置紅色,將x的兄弟節點右旋,右旋后,重新設置x的兄弟節點。
2.1.3.3 兄弟節點的右孩子是紅色
把兄弟節點染成當前節點父節點顏色,把當前節點父節點染成黑色,兄弟節點右孩子染成黑色,再以當前節點的父節點為支點進行左旋,算法結算
2.2 被刪除節點是父節點的右孩子
參照2.1 把左設置為右
紅黑樹的應用:
Hashtable
TreeSet
TreeMap
計算機科學中的樹
image.png