樹結構的應用

一、堆排序

  詳情查看:排序算法

二、赫夫曼樹

  源碼: 構建赫夫曼樹

1,基本介紹

  • 給定n個權值作爲n個葉子結點,構造一棵二叉樹,若該樹的帶權路徑長度(wpl)達到最小,稱這樣的二叉樹爲最優二叉樹,也稱爲哈夫曼樹(Huffman Tree)。
  • 赫夫曼樹是帶權路徑長度最短的樹,權值較大的結點離根較近

  結點的路徑長度爲:層數 - 1;

  結點的帶權路徑長度爲:從根結點到該結點之間的路徑長度與該結點的權的乘積

  樹的帶權路徑長度WPL(weighted path length) :規定爲所有葉子結點的帶權路徑長度之和

2,構建思路:

1) 將集合從小到大進行排序 。其中每個數據都是一個節點 ,每個節點可以看成是一顆最簡單的二叉樹 
2) 取出根節點權值最小的兩顆二叉樹 
3) 組成一顆新的二叉樹, 該新的二叉樹的根節點的權值是前面兩顆二叉樹根節點權值的和
4) 再將這顆新的二叉樹,以根節點的權值大小 再次排序, 不斷重複 1-2-3 的步驟,直到數列中,所有的數 據都被處理,就得到一顆赫夫曼樹

3,代碼實現

/**
 * 構建赫夫曼樹
 */
public static Node createHuffman(int[] arr) {
    List<Node> list = new ArrayList<>(arr.length);
    for (int value : arr) {
        list.add(new Node(value));
    }

    while (list.size() > 1) {
        Collections.sort(list);
        //先排序,從小到大
        Node leftNode = list.get(0);
        Node rigthNode = list.get(1);
        Node parent = new Node(leftNode.no + rigthNode.no);
        parent.left = leftNode;
        parent.rigth = rigthNode;

        list.remove(leftNode);
        list.remove(rigthNode);
        list.add(parent);
    }
    return list.get(0);
}

static class Node implements Comparable<Node>{
    int no;
    Node left;
    Node rigth;

    public Node(int no) {
        this.no = no;
    }

    @Override
    public String toString() {
        return "Node[" +
                "no=" + no +
                ']';
    }

    @Override
    public int compareTo(Node node) {
        return this.no - node.no;
    }
}
View Code

三、赫夫曼編解碼

  源碼: 赫夫曼壓縮

1,基本介紹

  • 赫夫曼編碼也翻譯爲 哈夫曼編碼(Huffman Coding),又稱霍夫曼編碼,是一種編碼方式,屬於一種程序算法
  • 赫夫曼編碼是赫哈夫曼樹在電訊通信中的經典的應用之一。赫夫曼編碼廣泛地用於數據文件壓縮。其壓縮率通常在20%~90%之間
  • 赫夫曼碼是可變字長編碼(VLC)的一種。Huffman於1952年提出一種編碼方法,稱之爲最佳編碼

2,定長編碼與變長編碼

a)定長編碼

  • 通信領域中信息的處理方式:定長編碼,比如我需要發送如下字符串:
i like like like java do you like a java    // 共40個字符(包括空格)
  • 上述字符串對應的 ASCII 碼爲:
105 32 108 105 107 101 32 108 105 107 101 32 108 105 107 101 32 106 97 118 97 32 100 111 32 121 111 117 32 108 105 107 101 32 97 32 106 97 118 97 //對應Ascii碼
01101001 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101100 01101001 01101011 01100101 00100000 01101010 01100001 01110110 01100001 00100000 01100100 01101111 00100000 01111001 01101111 01110101 00100000 01101100 01101001 01101011 01100101 00100000 01100001 00100000 01101010 01100001 01110110 01100001 //對應的二進制
  • 按照二進制來傳遞信息,總的長度是 359 (包括空格)

b)變長編碼

  • 通信領域中信息的處理方式:變長編碼,比如我需要發送如下字符串:
i like like like java do you like a java    // 共40個字符(包括空格)
  • 統計上述字符串出現的各字符出現的次數
d:1 y:1 u:1 j:2  v:2  o:2  l:4  k:4  e:4 i:5  a:5   :9  // 各個字符對應的個數
  • 按照各個字符出現的次數進行編碼,原則是出現次數越多的,則編碼越小,比如 空格出現了9 次, 編碼爲0 ,其它依次類推
0=  ,  1=a, 10=i, 11=e, 100=k, 101=l, 110=o, 111=v, 1000=j, 1001=u, 1010=y, 1011=d
  • 按照上面給各個字符規定的編碼,則我們在傳輸數據時,編碼就是:
10010110100...  

  問題:字符編碼爲其他編碼的前綴,比如:100到底是按照1=a, 10=i, 100=k中的哪一個進行解碼?(赫夫曼編碼進行解決)

3,赫夫曼編碼原理

  • 通信領域中信息的處理方式:赫夫曼編碼

  • 字符的編碼都不能是其他字符編碼的前綴,符合此要求的編碼叫做前綴編碼, 即不能匹配到重複的編碼

  • 比如我們處理如下字符串

i like like like java do you like a java       // 共40個字符(包括空格)
  • 統計各字符
d:1 y:1 u:1 j:2  v:2  o:2  l:4  k:4  e:4 i:5  a:5   :9  // 各個字符對應的個數
  • 按照上面字符出現的次數構建一顆赫夫曼樹, 次數作爲權值,根據赫夫曼編碼表確定具體字符的編碼

       

  • 根據赫夫曼樹,給各個字符的編碼 :向左的路徑爲 0 ;向右的路徑爲1
o: 1000   u: 10010  d: 100110  y: 100111  i: 101
a: 110    k: 1110   e: 1111    j: 0000    v: 0001
l: 001     : 01
  • 按照上面的赫夫曼編碼,我們的"i like like like java do you like a java" 字符串對應的編碼爲 (注意這裏我們使用的無損壓縮)
1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
  • 編碼後長度爲 133 ,原來長度是 359 , 壓縮了 (359-133) / 359 = 62.9% ,此編碼滿足前綴編碼, 即字符的編碼都不能是其他字符編碼的前綴,不會造成匹配的多義性

4,赫夫曼編碼思路和實現

 

  • 統計字節數組中各個數據的權重
  • 將字節數組按照上面的權重值創建赫夫曼樹
  • 根據上面創建的赫夫曼樹獲得每個數值對應的可變長編碼值(往左走爲 0 ,往右走爲 1)
  • 以每個數值新的編碼重新對字符數組進行編碼,即可得到赫夫曼編碼後的字節數組
/**
 * 整合 : 將原始的數據轉換爲赫夫曼數組
 */
private static byte[] encodeToHuffmanBytes(byte[] contentBytes) {
    //第一步:構建node結點集合,將每個字符(byte)出現的次數統計並封裝到list集合中
    List<Node> nodes = getNodes(contentBytes);
    //第二步:構建赫夫曼樹
    Node root = creatHuffmanTree(nodes);
    //第三步:獲取赫夫曼樹中每個結點對應的字符(byte),出現對應的路徑。其中key爲字符(byte),value爲路徑(0,1組裝)
    Map<Byte, String> nodePath = getNodePath(root);
    //第四步:根據字符路徑集合,獲取赫夫曼編碼的字符串
    String huffmanCode = createHuffmanCode(contentBytes, nodePath);
    System.out.println(huffmanCode);
    //第五步:將赫夫曼編碼字符串,轉換爲壓縮後的赫夫曼數組
    return convertToHuffmanBytes(huffmanCode);
}

/**
 * 將赫夫曼編碼數據轉換爲壓縮之後的數組
 *
 * @param code 赫夫曼編碼的字符串
 * @return 赫夫曼壓縮數組
 */
private static byte[] convertToHuffmanBytes(String code) {
    //獲取轉換後數組長度
    int length = (code.length() + 7) / 8;
    byte[] bytes = new byte[length+1];
    for (int i = 0, index = 0; i < code.length(); i += 8, index++) {
        if (code.length() > i + 8) {
            //將2進制轉成10進制  並強轉爲byte
            bytes[index] = (byte) Integer.parseInt(code.substring(i, i + 8), 2);
        } else {
            //最後一個byte存儲  倒數第二個字節的位數(防止出現0110->6->110)  出現位數少1的情況 最後無法匹配而報錯
            String substring = code.substring(i);
            bytes[index] = (byte) Integer.parseInt(substring, 2);
            bytes[index + 1] = (byte) substring.length();
        }
    }
    return bytes;
}

/**
 * 獲取赫夫曼編碼的字符串
 */
private static String createHuffmanCode(byte[] contentBytes, Map<Byte, String> nodePath) {
    StringBuilder stringBuilder = new StringBuilder();
    for (byte key : contentBytes) {
        stringBuilder.append(nodePath.get(key));
    }
    return stringBuilder.toString();
}

/**
 * 根據根節點獲取對應的編碼集合
 */
private static Map<Byte, String> getNodePath(Node node) {
    return getNodePath(node, "", new StringBuilder());
}

static Map<Byte, String> huffmanCodeMap = new HashMap<>();

/**
 * 獲取對應的節點和對應的編碼
 *
 * @param node          需要獲取的節點
 * @param code          當前節點相對上一個節點的編碼
 * @param stringBuilder 父節點的路徑
 * @return map集合:key爲字符byte,value爲路徑
 */
private static Map<Byte, String> getNodePath(Node node, String code, StringBuilder stringBuilder) {
    StringBuilder sb = new StringBuilder(stringBuilder);
    sb.append(code);
    if (node != null) {
        //非葉子結點
        if (node.data == null) {
            //左子樹
            getNodePath(node.left, "0", sb);
            //右子數
            getNodePath(node.right, "1", sb);
        } else {
            //葉子結點
            huffmanCodeMap.put(node.data, sb.toString());
        }
    }
    return huffmanCodeMap;
}

/**
 * 獲取節點集合
 */
private static List<Node> getNodes(byte[] contentBytes) {
    //先統計bytes中每個字節的次數
    Map<Byte, Integer> map = new HashMap<>();
    for (byte key : contentBytes) {
        map.put(key, map.get(key) == null ? 1 : map.get(key) + 1);
    }

    List<Node> list = new ArrayList<>();
    for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
        list.add(new Node(entry.getKey(), entry.getValue()));
    }
    return list;
}

/**
 * 生成赫夫曼樹
 */
private static Node creatHuffmanTree(List<Node> nodes) {
    while (nodes.size() > 1) {
        Collections.sort(nodes);
        Node leftNode = nodes.get(0);
        Node rightNode = nodes.get(1);
        Node parent = new Node(null, leftNode.count + rightNode.count);
        parent.left = leftNode;
        parent.right = rightNode;

        nodes.remove(leftNode);
        nodes.remove(rightNode);
        nodes.add(parent);
    }

    return nodes.get(0);
}

public static void preOrder(Node root) {
    if (root == null) {
        System.out.println("當前二叉樹爲空,不能遍歷!");
        return;
    }
    root.preOrder();
}


/**
 * 結點
 */
static class Node implements Comparable<Node> {
    //字節 i l i ...
    Byte data;
    //出現的次數
    Integer count;
    //左子樹
    Node left;
    //右子數
    Node right;

    //前序遍歷
    public void preOrder() {
//            System.out.println((this.data == null?null:(char)Integer.parseInt(this.data.toString()))+"\t"+this.count);
        System.out.println(this);
        if (this.left != null) {
            this.left.preOrder();
        }
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    public Node(Byte data, Integer count) {
        this.data = data;
        this.count = count;
    }

    @Override
    public String toString() {
        return "Node[" +
                "data=" + data +
                ", count=" + count +
                ']';
    }

    @Override
    public int compareTo(Node o) {
        return this.count - o.count;
    }
}
View Code

5,赫夫曼解碼思路和實現

 

  • 先將壓縮後的字節數組轉換爲對應的赫夫曼編碼的10010..組成的字符串
    • 遍歷字節數組,將每個字節轉成二進制
    • 判斷當前二進制長度是否大於8,如果大於8則截取後8位追加到編碼字符串
    • 如果二進制長度小於8,則判斷是否爲最後一個字節,如果是則直接加入追加,如果不是則將高位補0
  • 再將赫夫曼編碼字符串轉成原始的字節數組
    • 將原始的map(key爲字節,value爲二進制),進行翻轉爲新的map(key爲二進制,value爲字節)
    • 遍歷赫夫曼編碼字符串的每個字符,如果能匹配到新的map中的key,則存儲其value爲字節
    • 組裝上一步中所有的字節爲數組,則爲原始字節數組返回
/**
 * 轉換成原始的字符串
 */
private static byte[] decodeToSourceStr(byte[] bytes, Map<Byte, String> huffmanMap) {
    //先將數組轉換爲對應的赫夫曼編碼的10010..組成的字符串
    String huffmancode = decodeBytesToHuffmancode(bytes);
    System.out.println(huffmancode);
    //將赫夫曼編碼轉換爲對應的字符串
    return decodeHuffmancodeToSourceStr(huffmancode, huffmanMap);
}

/**
 * 將赫夫曼編碼進行解碼爲原始字節數組
 *
 * @param huffmancode 赫夫曼編碼
 * @param huffmanMap  記錄原始字節與編碼的map集合
 * @return 原始字符串
 */
private static byte[] decodeHuffmancodeToSourceStr(String huffmancode, Map<Byte, String> huffmanMap) {
    //將map反轉。原來是32->010,98->001... 轉換爲001->98,010->32...
    Map<String, Byte> map = new HashMap<>();
    for (Map.Entry<Byte, String> entry : huffmanMap.entrySet()) {
        map.put(entry.getValue(), entry.getKey());
    }
    //原始字節集合
    List<Byte> byteList = new ArrayList<>();
    for (int i = 0; i < huffmancode.length(); ) {
        StringBuilder stringBuilder = new StringBuilder();
        //將huffman編碼組成的字符村1000100011...,進行遍歷。
        //當指針指向一定的值存儲在反轉紅藕的map集合中時,停止
        while (map.get(stringBuilder.toString()) == null) {
            if (i >= huffmancode.length()) {
                break;
            }
            stringBuilder.append(huffmancode.charAt(i));
            i++;
        }
        //獲取每個字符,並組裝成對應的字符串
        Byte aByte = map.get(stringBuilder.toString());
        byteList.add(aByte);
    }
    byte[] bytes = new byte[byteList.size()];
    for (int i = 0; i < byteList.size(); i++) {
        bytes[i] = byteList.get(i);
    }
    return bytes;
}

/**
 * 將壓縮後的數組轉換爲赫夫曼編碼
 *
 * @param bytes 壓縮後的字節數組
 * @return 赫夫曼編碼
 */
private static String decodeBytesToHuffmancode(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bytes.length; i++) {
        byte b = bytes[i];
        //將當前byte轉換爲二進制字符串
        String binaryString = Integer.toBinaryString(b);
        //判斷當前二進制是否長度大於8,如果大於8則截取
        if (binaryString.length() > 8) {
            sb.append(binaryString.substring(binaryString.length() - 8));
        } else {
            //判斷是否爲倒數第二個字節,如果爲倒數第二個字節  則其二進制的長度應該爲倒數第一個數的長度。
            if (i == bytes.length - 2) {
                byte last = bytes[i + 1];
                int length = binaryString.length();
                StringBuilder builder = new StringBuilder();
                //將少於倒數第一個數值長度 的二進制  前面用0 補齊
                for (int j = 0; j < last - length; j++) {
                    builder.append(0);
                }
                builder.append(binaryString);
                sb.append(builder);
                break;
            } else {
                //如果不是則補齊前面的0
                int length = binaryString.length();
                StringBuilder builder = new StringBuilder();
                for (int j = 0; j < 8 - length; j++) {
                    builder.append(0);
                }
                builder.append(binaryString);
                sb.append(builder);
            }
        }

    }
    return sb.toString();
}
View Code

6,赫夫曼壓縮文件

/**
 * 壓縮文件
 *
 * @param srcFile  原始文件路徑
 * @param destFile 壓縮後文件路徑
 */
private static void zipFile(String srcFile, String destFile) {
    InputStream is = null;
    OutputStream os = null;
    //對象輸出流
    ObjectOutputStream oos = null;
    try {
        is = new FileInputStream(srcFile);
        //創建原始字節數組
        byte[] bytes = new byte[is.available()];
        //讀取文件
        is.read(bytes);
        //壓縮
        byte[] zip = zip(bytes);

        os = new FileOutputStream(destFile);
        //關聯
        oos = new ObjectOutputStream(os);
        //將壓縮之後的數組 和 赫夫曼編碼對應關係 寫出
        oos.writeObject(zip);
        oos.writeObject(huffmanCodeMap);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            oos.close();
            os.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
View Code

7,赫夫曼解壓文件 

/**
 * 解壓文件
 *
 * @param srcFile   壓縮之後的文件
 * @param destFile  解壓後的文件
 */
private static void unzipFile(String srcFile, String destFile) {
    InputStream is = null;
    ObjectInputStream ois = null;
    OutputStream os = null;
    try {
        is = new FileInputStream(srcFile);
        ois = new ObjectInputStream(is);
        //讀取壓縮之後的 文件 和 對應的赫夫曼編碼
        byte[] zipBytes = (byte[]) ois.readObject();
        Map<Byte, String> huffmanMap = (Map<Byte, String>) ois.readObject();

        //解壓
        byte[] srcBytes = unzip(zipBytes, huffmanMap);

        //輸出
        os = new FileOutputStream(destFile);
        os.write(srcBytes);
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        try {
            os.close();
            ois.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
View Code

四、二叉排序樹

  源碼:二叉排序樹

1,介紹

  • 二叉排序樹BST(Binary Sort Tree):對於二叉排序樹的任何一個非葉子節點,要求左子節點的值比當前節點的值小右子節點的值比當前節點的值大
  • 特別說明:如果有相同的值,可以將該節點放在左子節點或右子節點
  • 二叉排序樹的中序遍歷爲有序數列

        

2,添加子節點

  a)思路

1)添加結點爲node。如果node.value<this.value,則需要操作左子樹
   如果this.left ==null 則this.left=node
   如果this.left != null 則遞歸調用this.left.add(node)  
2)如果node.value>=this.value,則需要操作右子樹
   如果this.right ==null 則this.right =node
   如果this.right != null 則遞歸調用this.right .add(node)  

  b)代碼實現

/**
 * 添加結點
 */
public void add(Node node) {
    if (node == null) {
        return;
    }
    //如果當前節點值大於添加的值
    if (this.value > node.value) {
        //如果左節點不爲空就向左遞,否則就賦值給左節點
        if (this.left != null) {
            this.left.add(node);
        } else {
            this.left = node;
        }
    } else {
        if (this.right != null) {
            this.right.add(node);
        } else {
            this.right = node;
        }
    }
}
View Code

3,刪除子節點

  a)思路

查找需要刪除的當前節點target和父節點parent
1)葉子結點直接刪除
2)有且僅有一個子節點(左或右)
    當前結點有左子樹,當前節點是父節點的左節點  parent.left = target.left
    當前結點有左子樹,當前節點是父節點的右節點  parent.right = target.left
    當前結點有右子樹,當前節點是父節點的左節點  parent.left = target.right
    當前結點有右子樹,當前節點是父節點的右節點  parent.right = target.right
3)有兩個子節點
    查找當前右子樹的最左邊結點使用臨時變量temp,賦值target.value = temp.value
    刪除temp

  b)代碼實現

public void del(int value) {
    Node target = root.search(value);
    if (target == null) {
        return;
    }

    Node parent = root.searchParent(value);
    //1)葉子結點
    if (target.left == null && target.right == null) {
        //當前父節點爲空,目標結點是葉子結點(目標結點是根節點)
        if (parent == null) {
            root = null;
            return;
        }
        if (parent.left == target) {
            parent.left = null;
        }
        if (parent.right == target) {
            parent.right = null;
        }

    }
    //2)存在兩個葉子結點
    else if (target.left != null && target.right != null) {
        //找到右子樹的最左結點,並刪除它
        Node endNode = findLeft(target.right);
        del(endNode.value);
        //如果parent爲null 則刪除結點爲根節點
        if (parent == null) {
            root = endNode;
        } else {
            if (parent.left == target) {
                parent.left = endNode;
            }
            if (parent.right == target) {
                parent.right = endNode;
            }
        }
        endNode.left = target.left;
        endNode.right = target.right;
    }
    //3)存在一個葉子結點
    else {
        if (target.left != null) {
            //當前刪除爲根節點,設置目標結點的左節點爲根節點
            if (parent == null) {
                root = target.left;
                return;
            }
            if (parent.left == target) {
                parent.left = target.left;
            } else {
                parent.right = target.left;
            }
        }
        if (target.right != null) {
            if (parent == null) {
                root = target.right;
                return;
            }
            if (parent.left == target) {
                parent.left = target.right;
            } else {
                parent.right = target.right;
            }
        }
    }
}

/**
 * 查詢目標結點的父節點
 */
public Node searchParent(int val) {
    if (this.value > val && this.left != null) {
        //左節點爲需要尋找的結點
        if (this.left.value == val) {
            return this;
        }
        return this.left.searchParent(val);
    }
    if (this.value < val && this.right != null) {
        if (this.right.value == val) {
            return this;
        }
        return this.right.searchParent(val);
    }
    return null;
}

/**
 * 查找當前結點
 */
public Node search(int val) {
    if (this.value == val) {
        return this;
    }
    //如果查找的值小於當前值 並且 左子節點不爲空 遞歸查找
    if (this.value > val && this.left != null) {
        return this.left.search(val);
    }
    if (this.value < val && this.right != null) {
        return this.right.search(val);
    }
    return null;
}
View Code

五、平衡二叉樹(AVL樹)

  源碼:平衡二叉樹

1,二叉排序樹的問題

  給你一個數列{1,2,3,4,5,6},要求創建一顆二叉排序樹(BST), 並分析問題所在

  • 左子樹全部爲空,從形式上看,更像一個單鏈表
  • 插入速度沒有影響
  • 查詢速度明顯降低(因爲需要依次比較),不能發揮BST 的優勢,因爲每次還需要比較左子,其查詢速度比單鏈表還慢

         

2,介紹

  • 平衡二叉樹也叫平衡二叉搜索樹(Self-balancing binary search tree)又被稱爲AVL樹可以保證查詢效率較高
  • 平衡二叉樹具有以下特點:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹
  • 平衡二叉樹的常用實現方法有紅黑樹AVL、替罪羊樹、Treap、伸展樹等。
  • 注意:平衡二叉樹一定是二叉排序樹

3,樹的高度

/**
 * 左子樹高度
 */
public int leftHeight() {
    return this.left == null ? 0 : this.left.height();
}
/**
 * 右子樹高度
 */
public int rightHeight() {
    return this.right == null ? 0 : this.right.height();
}
/**
 * 二叉樹的總高度
 */
public int height() {
    //取左子樹和右子樹的最大值爲總高度。左子樹遞歸查找,如果爲null則是0,不爲空則獲取原先高度+1
    return Math.max(this.left == null ? 0 : this.left.height(), this.right == null ? 0 : this.right.height()) + 1;
}

4,左旋轉

  a)思路

   

  b)代碼實現

/**
 * 左旋轉(以當前結點爲根節點)
 */
private void leftRotate() {
    //1)創建新節點值爲當前根節點的值
    Node newNode = new Node(value);
    //2)新結點的左結點爲根節點的左結點
    newNode.left = this.left;
    //3)新結點的右結點爲根節點的右結點的左結點
    newNode.right = this.right.left;
    //4)當前結點值爲當前結點的右結點值
    this.value = this.right.value;
    //5)當前結點的左結點指向新結點
    this.left = newNode;
    //6)當前結點的右結點指向原來右子樹的右結點
    this.right = this.right.right;
}

5,右旋轉

  a)思路

     

  b)代碼實現

/**
 * 右旋轉
 */
public void rightRotate() {
    //1)創建新結點值爲當前根節點的值
    Node newNode = new Node(value);
    //2)新結點的右子樹爲當前右子樹
    newNode.right = this.right;
    //3)新結點的左子樹爲當前根節點的左子樹的右子樹
    newNode.left = this.left.right;
    //4)當前結點值爲當前結點的左子樹的值
    this.value = this.left.value;
    //5)當前結點的右子樹爲新結點
    this.right = newNode;
    //6)當前結點的左子樹爲當前結點左子樹的左子樹
    this.left = this.left.left;
}

6,雙旋轉

  前面的兩個數列,進行單旋轉(即一次旋轉)就可以將非平衡二叉樹轉成平衡二叉樹,但是在某些情況下,單旋轉不能完成平衡二叉樹的轉換。
int[] arr = { 10, 11, 7, 6, 8, 9 };// 運行原來的代碼可以看到,並沒有轉成 AVL 樹。
int[] arr = {2,1,6,5,7,3};// 運行原來的代碼可以看到,並沒有轉成 AVL 樹。

   

解決思路

  • 當符合右旋轉的條件時
  • 如果它的左子樹的右子樹高度大於它的左子樹的高度
  • 先對當前這個結點的左節點進行左旋轉
  • 再對當前結點進行右旋轉的操作即可

代碼實現

/**
 * 添加結點
 */
public void add(Node node) {
    if (node == null) {
        return;
    }
    //如果當前節點值大於添加的值
    if (this.value > node.value) {
        //如果左節點不爲空就向左遞,否則就賦值給左節點
        if (this.left != null) {
            this.left.add(node);
        } else {
            this.left = node;
        }
    } else {
        if (this.right != null) {
            this.right.add(node);
        } else {
            this.right = node;
        }
    }

    if (rightHeight() - leftHeight() > 1) {
        if (right.leftHeight() > right.rightHeight()) {
            right.rightRotate();
        }
        leftRotate();
    }

    if (leftHeight() - rightHeight() > 1) {
        if (left.rightHeight() > left.leftHeight()) {
            left.leftRotate();
        }
        rightRotate();
    }
}
View Code

六、紅黑樹

  紅黑樹

 

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章