数据结构-树

1. 顺序存储结构二叉树

顺序存储结构的二叉树:用一组连续的存储单元来存放二叉树中的结点

public class ArrayBinaryTree {
    int[] container;

    public ArrayTree(int[] container) {
        this.container = container;
    }

    // i的父节点
    public int parent(int i) {
        return (i - 1) / 2;
    }

    // i的左孩子
    public int left(int i) {
        return 2 * i + 1;
    }

    // i的右孩子
    public int right(int i) {
        return 2 * i + 2;
    }

    // 先序遍历
    public void preorder(int index) {
        if (container == null || container.length == 0)
            return;
        System.out.println(container[index]);
        if (left(index) < container.length)
            preorder(left(index));
        if (right(index) < container.length)
            preorder(right(index));
    }

    // 中序遍历
    public void inorder(int index) {
        if (container == null || container.length == 0)
            return;
        if (left(index) < container.length)
            inorder(left(index));
        System.out.println(container[index]);
        if (right(index) < container.length)
            inorder(right(index));
    }

    // 后序遍历
    public void postorder(int index) {
        if (container == null || container.length == 0)
            return;
        if (left(index) < container.length)
            postorder(left(index));
        if (right(index) < container.length)
            postorder(right(index));
        System.out.println(container[index]);
    }
}

2. 链式存储结构二叉树

链式存储结构的二叉树:用链表来存放二叉树中的结点

从递归执行过程的角度看,先序、中序和后序遍历是完全相同的

由二叉树的先序序列和中序序列,或后序序列和中序序列,或层序序列和中序序列,可以唯一地确定一颗二叉树;先序或后序可以确定根节点,在中序中可以根据根节点来确定其左右子树

import java.util.LinkedList;

public class LinkedBinaryTree<E> {
    BiTNode<E> root;

    static class BiTNode<E> {
        E data;
        BiTNode<E> left;
        BiTNode<E> right;

        public BiTNode(E data, BiTNode<E> left, BiTNode<E> right) {
            this.data = data;
            this.left = left;
            this.right = right;
        }
    }

    // 递归先序遍历
    public void preOrder(BiTNode<E> root) {
        if (root != null) {
            System.out.println(root.data);
            preOrder(root.left);
            preOrder(root.right);
        }
    }

    // 递归中序遍历
    public void inOrder(BiTNode<E> root) {
        if (root != null) {
            inOrder(root.left);
            System.out.println(root.data);
            inOrder(root.right);
        }
    }

    // 递归后序遍历
    public void postOrder(BiTNode<E> root) {
        if (root != null) {
            postOrder(root.left);
            postOrder(root.right);
            System.out.println(root.data);
        }
    }

    // 借助栈,非递归先序遍历
    // 先序遍历和中序遍历思想类似,只需把访问结点的操作放在入栈之前
    public void preOrderByStack(BiTNode<E> root) {
        // 初始化一个栈
        LinkedList<BiTNode<E>> stack = new LinkedList<>();
        // current指向当前遍历的结点
        BiTNode<E> current = root;
        // 当current不为空,或栈不为空时循环
        while (current != null || !stack.isEmpty()) {
            if (current != null) { // 一路向左
                System.out.println(current.data);
                stack.push(current);
                // 向左子树走
                current = current.left;
            } else { // 出栈,并转向出栈结点的右子树
                current = stack.pop();
                // 向右子树走
                current = current.right;
            }
        }
    }

    // 借助栈,非递归中序遍历
    public void inOrderByStack(BiTNode<E> root) {
        // 初始化一个栈
        LinkedList<BiTNode<E>> stack = new LinkedList<>();
        // current指向当前遍历的结点
        BiTNode<E> current = root;
        // 当current不为空,或栈不为空时循环
        while (current != null || !stack.isEmpty()) {
            if (current != null) { // 一路向左
                stack.push(current);
                // 向左子树走
                current = current.left;
            } else { // 出栈,并转向出栈结点的右子树
                current = stack.pop();
                System.out.println(current.data);
                // 向右子树走
                current = current.right;
            }
        }
    }

    /**
     * 借助栈,非递归后序遍历:
     * 从根节点开始,将其入栈,然后沿其左子树一直往下搜索,直到搜索到没有左孩子的结点,
     * 但是此时还不能出栈访问,因为如果其有右子树,还需按相同的规则对右子树进行处理。
     * 若栈顶元素想要出栈被访问,则其右子树需为空或刚被访问完
     */
    public void postOrderByStack(BiTNode<E> root) {
        // 初始化一个栈
        LinkedList<BiTNode<E>> stack = new LinkedList<>();
        // current指向当前遍历的结点
        BiTNode<E> current = root;
        // visited指向最近访问过的结点,用以分清返回时是从哪个子树返回的
        BiTNode<E> visited = null;
        while (current != null || !stack.isEmpty()) {
            if (current != null) { // 走到最左边
                stack.push(current);
                current = current.left;
            } else { // 向右
                // 读取栈顶元素
                current = stack.peek();
                // 若有右子树,且未被访问过
                if (current.right != null && current.right != visited) {
                    // 转向右子树
                    current = current.right;
                    stack.push(current);
                    // 再走向右子树的左边
                    current = current.left;
                } else { // 没有右子树,或右子树已经被访问了
                    // 将栈顶元素出栈,并访问
                    current = stack.pop();
                    System.out.println(current.data);
                    // 记录最近访问过的结点
                    visited = current;
                    // 右子树已访问(此时左子树早已访问完),则下一轮该从栈顶取出中间元素来访问,
                    // 因此置为空,不用再先走到最左边
                    current = null;
                }
            }
        }
    }

    /**
     * 借助队列,层次遍历:
     * 初始化将二叉树根节点入队
     * 每次出队一个结点并访问,直到队列为空
     * 若它有左子树,则将左子树根节点入队;
     * 若它有右子树,则将右子树根节点入队。
     */
    public void levelOrder(BiTNode<E> root) {
        // 初始化一个队列
        LinkedList<BiTNode<E>> queue = new LinkedList<>();
        // current指向当前遍历的结点
        BiTNode<E> current = root;
        // 将根节点入队
        queue.addLast(current);
        // 队列不为空则循环
        while (!queue.isEmpty()) {
            // 队头结点出队,并访问
            current = queue.removeFirst();
            System.out.println(current.data);
            // 若有左子树,则将左子树根节点入队
            if (current.left != null)
                queue.addLast(current.left);
            // 若有右子树,则将右子树根节点入队
            if (current.right != null)
                queue.addLast(current.right);
        }
    }

    // 根据二叉树的先序序列和中序序列建立其二叉链表
    // preFirst和preLast为先序的第一个和最后一个结点的下标
    // inFirst和inLast为中序的第一个和最后一个结点的下标
    // 初始时:preFirst = inFirst = 0; preLast = inLast = length - 1;
    public BiTNode<E> createByPreAndIn(E[] pre, E[] in, int preFirst, int preLast, int inFirst, int inLast) {
        // 建立根节点
        BiTNode<E> root = new BiTNode<>(pre[preFirst], null, null);
        // 寻找根节点在中序序列中的位置
        int i = inFirst;
        while (!in[i].equals(root.data))
            i++;
        // 左子树长度
        int lLen = i - inFirst;
        // 右子树长度
        int rLen = inLast - i;
        // 递归建立左子树
        if (lLen != 0)
            root.left = createByPreAndIn(pre, in, preFirst + 1, preFirst + lLen, inFirst, inFirst + lLen - 1);
        else
            root.left = null;
        // 递归建立右子树
        if (rLen != 0)
            root.right = createByPreAndIn(pre, in, preLast - rLen + 1, preLast, inLast - rLen + 1, inLast);
        else
            root.right = null;
        return root;
    }
}

3. 线索二叉树

对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域。二叉树的线索化是将二叉树中的空指针改为指向前驱或后继的线索。而前驱或后继信息只有在遍历时才能得到,因此线索化的实质就是遍历一次二叉树。

当线索化二叉树后,节点的属性left和right有如下情况:

  1. left指向的是左子树,也可能是指向的前驱节点
  2. right指向的是右子树,也可能是指向后继节点
public class ThreadedBinaryTree<E> {
    BiTNode<E> root;
    BiTNode<E> prev = null;

    static class BiTNode<E> {
        E data;
        BiTNode<E> left;
        BiTNode<E> right;
        // lTag为0则指向左子树,为1则指向前驱节点
        int lTag;
        // rTag为0则指向右子树,为1则指向后继节点
        int rTag;

        public BiTNode(E data, BiTNode<E> left, BiTNode<E> right) {
            this.data = data;
            this.left = left;
            this.right = right;
        }
    }

    /**
     * 通过中序遍历,对二叉树递归地线索化:
     * root指向正在访问的结点
     * prev指向刚刚访问过的结点,即prev指向root的前驱
     * 在中序遍历过程中:
     * 若root的左子树为空,则将root的左指针指向prev(即建立前驱线索),
     * 若prev的右子树为空,则将prev的右指针指向root(即建立后继线索)
     */
    private void inThread(BiTNode<E> root) {
        if (root != null) {
            // 递归,线索化左子树
            inThread(root.left);
            // 建立前驱线索
            if (root.left == null) {
                root.left = prev;
                root.lTag = 1;
            }
            // 建立前驱结点的后继线索
            if (prev != null && prev.right == null) {
                prev.right = root;
                prev.rTag = 1;
            }
            // prev若作为方法参数在递归中传递,则不能写prev = root,因为会引发在递归调用里修改引用无效的问题
            // 标记当前结点为刚刚访问过的结点
            prev = root;
            // 递归,线索化右子树
            inThread(root.right);
        }
    }

    // 建立中序线索二叉树
    public void createInThread(BiTNode<E> root) {
        if (root != null) {
            // 将二叉树线索化
            inThread(root);
            // 处理遍历的最后一个结点的后继
            prev.right = null;
            prev.rTag = 1;
        }
    }

    // 在中序线索二叉树中,获取中序序列中的第一个结点
    private BiTNode<E> firstInNode(BiTNode<E> root) {
        while (root.lTag == 0)
            root = root.left;
        return root;
    }

    // 在中序线索二叉树中,获取中序序列中node结点的后继
    private BiTNode<E> nextInNode(BiTNode<E> node) {
        if (node.rTag == 0)
            // 返回后继
            return firstInNode(node.right);
        else
            // rTag == 1,直接返回后继
            return node.right;
    }

    // 利用firstInNode和nextInNode方法,实现中序线索二叉树的中序遍历
    public void inOrder(BiTNode<E> root) {
        BiTNode<E> current = firstInNode(root);
        while (current != null) {
            System.out.println(current.data);
            // 将current设为其在中序序列中的后继
            current = nextInNode(current);
        }
    }
}

4. 哈夫曼树

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度(WPL)最小,称这样的二叉树为最优二叉树,也称为哈夫曼树

若规定根节点层数为1,则从根结点到第L层结点路径长度为L-1。

结点的带权路径长度为:从根节点到该节点之间的路径长度与该节点的权的乘积。

树的带权路径长度为:所有叶子结点的带权路径长度之和。

给定n个权值分别为w1,w2,…,wn的结点,构造哈夫曼树的过程如下:

  1. 将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F
  2. 构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和
  3. 从F中删除刚才选出的两棵树,同时将新得到的树加入F中
  4. 重复步骤2和3,直至F中只剩下一棵树为止

哈夫曼树的特点:

  1. 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大
  2. 构造过程中共新建了n-1个结点(双分支结点),因此哈夫曼树中的结点总数为2n-1
  3. 每次构造都选择2棵树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点

前缀编码:是指对字符集进行编码时,要求字符集中任一字符的编码都不是其它字符的编码的前缀。

哈夫曼编码是可变字长编码(VLC)的一种,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码。

由哈夫曼树构造哈夫曼编码:将每个出现的字符当作一个独立的结点,其权值为它出现的频度,构造出对应的哈夫曼树。可以将字符的编码解释为从根至该字符的路径上边标记的序列,其中边标记为0表示"转向左孩子”,标记为1表示“转向右孩子”。

import java.util.*;

/**
 * 创建哈夫曼树相关方法:getNodeList、createHuffmanTree
 * 获取哈夫曼编码相关方法:generateHuffmanCode、getHuffmanCode
 * 使用哈夫曼编码表编码(压缩)相关方法:encode
 * 使用哈夫曼编码表解码(解压)相关方法:decode、byteToBinaryString
 */
public class HuffmanTree {
    private static BiTNode root;
    private static Map<Byte, String> huffmanCode = new HashMap<>();

    private static class BiTNode {
        Byte data;
        int weights;
        BiTNode left;
        BiTNode right;

        public BiTNode(Byte data, int weights, BiTNode left, BiTNode right) {
            this.data = data;
            this.weights = weights;
            this.left = left;
            this.right = right;
        }
    }

    // 传递字节数组,统计各个字符出现次数作为权值,进而获取节点集合
    private static LinkedList<BiTNode> getNodeList(byte[] bytes) {
        // 节点集合
        LinkedList<BiTNode> nodes = new LinkedList<>();
        // 存储各个字符出现次数
        HashMap<Byte, Integer> map = new HashMap<>();
        // 统计每一个字符出现的次数
        for (byte b : bytes) {
            if (map.containsKey(b))
                map.put(b, map.get(b) + 1);
            else
                map.put(b, 1);
        }
        for (Map.Entry<Byte, Integer> entry : map.entrySet()) {
            nodes.add(new BiTNode(entry.getKey(), entry.getValue(), null, null));
        }
        return nodes;
    }

    // 给定权集,构造哈夫曼树
    private static BiTNode createHuffmanTree(LinkedList<BiTNode> forest) {
        while (forest.size() > 1) {
            // 按权值从小到大排序
            Collections.sort(forest, (o1, o2) -> o1.weights - o2.weights);
            // 选取两棵根结点权值最小的树
            BiTNode left = forest.removeFirst();
            BiTNode right = forest.removeFirst();
            // 构造一个新树,权值置为左、右子树上根结点的权值之和
            BiTNode parent = new BiTNode(null, left.weights + right.weights, left, right);
            // 将新得到的树加入森林中
            forest.add(parent);
        }
        // 森林中仅剩下的一棵树就是哈夫曼树
        return forest.removeFirst();
    }

    // 传递哈夫曼树,生成哈夫曼编码
    private static void generateHuffmanCode(BiTNode node, String code, StringBuilder stringBuilder) {
        // node不为空才处理
        if (node != null) {
            StringBuilder sb = new StringBuilder(stringBuilder);
            sb.append(code);
            if (node.data == null) { // 若是非叶子结点
                generateHuffmanCode(node.left, "0", sb);
                generateHuffmanCode(node.right, "1", sb);
            } else { // 若是叶子结点
                huffmanCode.put(node.data, sb.toString());
            }
        }
    }

    // 便于调用generateHuffmanCode方法
    public static Map<Byte, String> getHuffmanCode(byte[] bytes) {
        huffmanCode.clear();
        LinkedList<BiTNode> nodeList = getNodeList(bytes);
        root = createHuffmanTree(nodeList);
        if (root != null) {
            generateHuffmanCode(root.left, "0", new StringBuilder());
            generateHuffmanCode(root.right, "1", new StringBuilder());
        }
        return huffmanCode;
    }

    // 传递原始数据的字节数组和对应的哈夫曼编码表,进行编码(压缩)
    public static byte[] encode(byte[] source, Map<Byte, String> huffmanCode) {
        // 将bytes转换为对应的哈弗曼编码字符串
        StringBuilder code = new StringBuilder();
        for (byte b : source) {
            code.append(huffmanCode.get(b));
        }
        // 将code二进制字符串转换为byte数组
        // int length = (code.length() + 7) / 8;
        int length = (int) Math.ceil(code.length() / 8.0);
        // 编码后的byte数组
        byte[] encode = new byte[length];
        // encode的索引
        int index = 0;
        for (int i = 0; i < code.length(); i += 8) {
            String strByte;
            if (i + 8 > code.length()) // 如果不够8位
                strByte = code.substring(i);
            else
                strByte = code.substring(i, i + 8);
            // 将strByte转成一个byte,存入到encode中
            encode[index++] = (byte) Integer.parseInt(strByte, 2);
        }
        return encode;
    }

    // 传递压缩后的数据和对应的哈夫曼编码表,进行解码(解压)
    public static byte[] decode(byte[] encode, Map<Byte, String> huffmanCode) {
        // bytes对应的哈弗曼编码字符串
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < encode.length; i++) {
            boolean or = (i != encode.length - 1);
            code.append(byteToBinaryString(or, encode[i]));
        }
        // 将哈夫曼编码表的键和值交换
        Map<String, Byte> table = new HashMap<>();
        for (Map.Entry<Byte, String> entry : huffmanCode.entrySet()) {
            table.put(entry.getValue(), entry.getKey());
        }
        // 存放byte的集合
        List<Byte> source = new ArrayList<>();
        int index = 0;
        while (index < code.length()) {
            // 偏移量
            int offset = 1;
            // 是否添加
            boolean add = false;
            while (add == false) {
                String key = code.substring(index, index + offset);
                if (table.containsKey(key)) { // 匹配到了
                    add = true;
                    source.add(table.get(key));
                } else // 没有匹配到
                    offset++;
            }
            index += offset;
        }
        // 将集合转为数组
        byte[] result = new byte[source.size()];
        for (int i = 0; i < source.size(); i++) {
            result[i] = source.get(i);
        }
        return result;
    }

    public static String byteToBinaryString(boolean or, int b) {
        if (or)
            // 按位或,给其最高位补1
            b |= 256;
        // 获取b对应的二进制补码
        String binaryString = Integer.toBinaryString(b);
        if (or)
            return binaryString.substring(binaryString.length() - 8);
        return binaryString;
    }
}

// 测试
class HuffmanTreeClient {
    public static void main(String[] args) {
        String data = "I love you";
        byte[] source = data.getBytes();
        Map<Byte, String> huffmanCode = HuffmanTree.getHuffmanCode(source);
        byte[] encode = HuffmanTree.encode(source, huffmanCode);
        byte[] decode = HuffmanTree.decode(encode, huffmanCode);
        System.out.println(new String(source));
        System.out.println(new String(decode));
        System.out.println(new String(source).equals(new String(decode)));
        System.out.println((source.length - encode.length) / (source.length + 0.0));
    }
}

5. 二叉搜索树

二叉搜索树(Binary Search Tree),又称二叉排序树(Binary Sort Tree)。

二叉搜索树要么是一棵空树,要么是具有下列性质的二叉树:

  1. 若左子树不空,则左子树上所有结点的值均不大于它的根结点的值
  2. 若右子树不空,则右子树上所有结点的值均不小于它的根结点的值
  3. 左、右子树也分别为二叉搜索树

根据二叉搜索树的定义,左子树结点值<=根结点值<=右子树结点值,因此对二叉搜索树进行中序遍历,可以得到一个递增的有序序列。

public class BinarySearchTree {
    BiTNode root;

    static class BiTNode {
        int data;
        BiTNode parent;
        BiTNode left;
        BiTNode right;

        public BiTNode(int data, BiTNode parent, BiTNode left, BiTNode right) {
            this.data = data;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }
    }

    // 递归中序遍历
    public void inOrder(BiTNode root) {
        if (root != null) {
            inOrder(root.left);
            System.out.print(root.data + " ");
            inOrder(root.right);
        }
    }

    // 将结点插入到二叉搜索树
    public void insert(BiTNode node) {
        BiTNode current = null;
        BiTNode leaf = root;
        // 寻找在哪一个叶子结点下插入
        while (leaf != null) {
            // 记录当前结点
            current = leaf;
            if (node.data < leaf.data)
                leaf = leaf.left;
            else
                leaf = leaf.right;
        }
        // leaf为null,则current为叶子结点,将node插入到current下
        node.parent = current;
        if (current == null) // 若树为空
            root = node;
        else if (node.data < current.data)
            // node比这个叶子结点小,则挂左边
            current.left = node;
        else
            // node比这个叶子结点大,则挂右边
            current.right = node;
    }

    public void create(int[] sequence) {
        for (int i = 0; i < sequence.length; i++) {
            insert(new BiTNode(sequence[i], null, null, null));
        }
    }

    // 递归,查找给定关键字的结点
    public BiTNode find(BiTNode node, int key) {
        if (node == null || key == node.data)
            return node;
        if (key < node.data)
            return find(node.left, key);
        else
            return find(node.right, key);
    }

    // 非递归,查找给定关键字的结点
    public BiTNode search(int key) {
        BiTNode node = root;
        while (node != null && key != node.data) {
            if (key < node.data)
                node = node.left;
            else
                node = node.right;
        }
        return node;
    }

    // 查找某一子树的最小结点
    public BiTNode minimum(BiTNode tree) {
        BiTNode node = tree;
        // 最小结点在二叉搜索树的最左边
        if (node != null) {
            while (node.left != null)
                node = node.left;
        }
        return node;
    }

    // 查找某一子树的最大结点
    public BiTNode maximum(BiTNode tree) {
        BiTNode node = tree;
        // 最大结点在二叉搜索树的最右边
        if (node != null) {
            while (node.right != null)
                node = node.right;
        }
        return node;
    }

    // 查找某一结点的中序后继结点
    public BiTNode successor(BiTNode tree) {
        BiTNode node = tree;
        // 若其右子树不为空,则其后继为右子树中最小的结点
        if (node.right != null)
            return minimum(node.right);
        BiTNode parent = node.parent;
        // 从node开始沿树向上找,直到某一结点是其父节点的左子树
        while (parent != null && node == parent.right) {
            node = parent;
            parent = node.parent;
        }
        // parent为空,则为树根
        // node不是其父节点的右子树,则为其父节点的左子树
        return parent;
    }

    // 查找某一结点的中序前驱结点
    public BiTNode predecessor(BiTNode tree) {
        BiTNode node = tree;
        // 若其左子树不为空,则其前驱为左子树中最大的结点
        if (node.left != null)
            return maximum(node.left);
        BiTNode parent = node.parent;
        // 从node开始沿树向上找,直到某一结点是其父节点的右子树
        while (parent != null && node == parent.left) {
            node = parent;
            parent = node.parent;
        }
        // parent为空,则为树根
        // node不是其父节点的左子树,则为其父节点的右子树
        return parent;
    }

    // 用replacement子树替换target子树,子树替换而非结点替换;便于删除时移动子树
    private void transplant(BiTNode target, BiTNode replacement) {
        if (target.parent == null)
            // 需要被替换的是根节点
            root = replacement;
        else if (target == target.parent.left)
            // 若target是其父节点的左孩子,则让replacement成为其父节点的左孩子
            target.parent.left = replacement;
        else
            // 若target是其父节点的右孩子,则让replacement成为其父节点的右孩子
            target.parent.right = replacement;
        if (replacement != null)
            // 若replacement非空,则其父节点为target的父节点
            replacement.parent = target.parent;
    }

    /**
     * 删除有三种基本情况:
     * 1. target没有孩子结点:直接删除,用null替换
     * 2. target只有一个孩子:用这个孩子来替换target
     * 3. target有两个孩子:在其右子树中找最小的结点min,这个min没有左孩子
     * 3.1 若min不是其右孩子,则先用min的右孩子替换min,再用min替换target
     * 3.2 若min是其右孩子,则用min替换target
     */
    public void delete(BiTNode target) {
        if (target.left == null)
            // 没有左孩子,则用右孩子来替换target
            transplant(target, target.right);
        else if (target.right == null)
            // 没有右孩子,则用左孩子来替换target
            transplant(target, target.left);
            // 若没有孩子结点,则经过以上两步,其已被null替换
        else {
            // 在其右子树中找最小的结点min
            BiTNode min = minimum(target.right);
            // 若min不是其右孩子,则先用min的右孩子替换min
            if (min.parent != target) {
                transplant(min, min.right);
                // 下面两步是将target的右子树挂到min的右边
                min.right = target.right;
                min.right.parent = min;
            }
            // 用min替换target
            transplant(target, min);
            // 下面两步是将target的左子树挂到min的左边
            min.left = target.left;
            min.left.parent = min;
        }
    }
}

6. AVL树

平衡树(Balance Tree):它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度差的绝对值不超过1。常见的符合平衡树的有:红黑树、AVL树(二叉平衡搜索树)、B树(多路平衡搜索树)等。

定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡树结点的平衡因子的值只可能是-1、0、1。

红黑树和AVL树都是从二叉搜索树进化而来的平衡树。

平衡树类型平衡度调整频率适用场景
AVL树查询多
红黑树增删多

LL平衡旋转(右单旋转):由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。

  • LL平衡旋转如下图:将B的右孩子挂到A的左孩子,然后将A挂到B的右孩子

RR平衡旋转(左单旋转):由于在结点A的右孩子®的右子树®上插入了新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。

  • RR平衡旋转如下图:将B的左孩子挂到A的右孩子,然后将A挂到B的左孩子

LR平衡旋转(先左后右双旋转):由于在A的左孩子(L)的右子树®上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。

  • LR平衡旋转如下图:将C的左孩子挂到B的右孩子,C的右孩子挂到A的左孩子,然后将B挂到C的左孩子,A挂到C的右孩子

RL平衡旋转(先右后左双旋转):由于在A的右孩子®的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先右旋转后左旋转。

  • RL平衡旋转:将C的左孩子挂到A的右孩子,C的右孩子挂到B的左孩子,然后将A挂到C的左孩子,B挂到C的右孩子

weixin073智慧旅游平台开发微信小程序+ssm后端毕业源码案例设计 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值