筆記6- 二叉樹之 Haffman樹、AVL樹、紅黑樹

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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 目錄 0.樹0.1 一般樹的定義0.2 二叉樹的定義 1.查找樹ADT 2.查找樹的實現2.1 二叉查找樹2.2 ...
    王偵閱讀 7,305評論 0 3
  • 目錄 1、什么是樹 2、相關術語 3、二叉樹 3.1、二叉樹的類型 3.2、二叉樹的性質 3.3、二叉樹的結構 3...
    我哈啊哈啊哈閱讀 2,576評論 0 10
  • 一些概念 數據結構就是研究數據的邏輯結構和物理結構以及它們之間相互關系,并對這種結構定義相應的運算,而且確保經過這...
    Winterfell_Z閱讀 5,891評論 0 13
  • 樹的概述 樹是一種非常常用的數據結構,樹與前面介紹的線性表,棧,隊列等線性結構不同,樹是一種非線性結構 1.樹的定...
    Jack921閱讀 4,475評論 1 31
  • 突然想說一說需求,我將需求分成了強需求和弱需求,強需求可以強到早上起床做的第一件事情就是拿起手機上某APP,反之弱...
    六水君閱讀 1,923評論 0 2