Java 数据结构总结

1. 链表

链表(LinkedList) 虽然是一种线性表,但是并不会按线性的顺序存储数据,使用的不是连续的内存空间来存储数据

链表的插入和删除操作的复杂度为 O(1) ,只需要知道目标位置元素的上一个元素即可。但是,在查找一个节点或者访问特定位置的节点的时候复杂度为 O(n)

使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但链表不会节省空间,相比于数组会占用更多的空间,因为链表中每个节点存放的还有指向其他节点的指针。除此之外,链表不具有数组随机读取的优点

1.1 单向链表

单向链表只有一个方向,结点只有一个后继指针 next 指向后面的节点。因此,链表这种数据结构通常在物理内存上是不连续的。我们习惯性地把第一个结点叫作头结点,链表通常有一个不保存任何值的 head 节点(头结点),通过头结点我们可以遍历整个链表。尾结点通常指向 null
在这里插入图片描述

1.1.1 定义节点类 Node

节点类:

@NoArgsConstructor
@AllArgsConstructor
public class Node<T> {
    T val;  // 节点值
    public Node next; // 指向后续节点
    Node(T val) {
        this.val = val;
    }
}

1.1.2 直接对节点类操作

1. 生成链表

public static void main(String[] args) {
        Node head = new Node();
        Node node1 = new Node<>("王五");
        Node node2 = new Node<>(1);

        Node node3 = new Node<>("李四");
        Node node4 = new Node<>(2);

        head.next = node1;
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
}

2. 遍历链表

queryAll(head);

public static void queryAll(Node node){
        Node currentNode = node;
        while (currentNode.next != null){
            currentNode = currentNode.next;
            System.out.println(currentNode.val);
        }
}

在这里插入图片描述
3. 直接添加

Node addNode = new Node("赵六");
head = add(head, addNode);

public static Node add(Node head, Node addNode){
        Node currentNode = head;

		// 找到链表的最后一个节点
        while (currentNode.next != null){
            currentNode  = currentNode.next;
        }
        // 让该节点的后一个节点为添加的节点,即将该节点添加到链表
        currentNode.next = addNode;
        return head;
}

在这里插入图片描述
4. 添加到指定位置

Node addIndexNode = new Node("11");
head = add(head, addIndexNode, 2);

public static Node add(Node head, Node addNode, int index) throws Exception {
        Node currentNode = head;

		// 找到指定位置的上一个节点
        for (int i = 0; i < index - 1; i++) {
            currentNode = currentNode.next != null ? currentNode.next : null;
            if (currentNode == null) {
                throw new Exception("位置不存在");
            }
        }

		// 让添加节点的下一个节点为该节点的下一个节点
        addNode.next = currentNode.next;
        // 再让该节点的下一个节点为添加节点,即在指定位置插入一个节点
        currentNode.next = addNode;
        return head;
}

在这里插入图片描述
5. 删除指定位置的元素

head = delete(head, 4);

public static Node delete(Node head, int index) throws Exception {
        Node currentNode = head;

		// 找到指定位置的上一个节点
        for (int i = 0; i < index - 1; i++) {
            currentNode = currentNode.next != null ? currentNode.next : null;
            if (currentNode == null) {
                throw  new Exception("位置不存在");
            }
        }
        // 该节点的下一个节点就是要删除的节点
        Node delNode = currentNode.next;
        // 让该节点的下一个节点为删除节点的下一个节点,指定位置的节点就被删除了
        currentNode.next = delNode.next;
        return head;
}

在这里插入图片描述
完整示例如下:

public static void main(String[] args) throws Exception {
        Node head = new Node();
        Node node1 = new Node<>("王五");
        Node node2 = new Node<>(1);

        Node node3 = new Node<>("李四");
        Node node4 = new Node<>(2);

        head.next = node1;
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;

        Node addNode = new Node("赵六");
        head = add(head, addNode);

        Node addIndexNode = new Node("11");
        head = add(head, addIndexNode, 2);

        head = delete(head, 4);
        queryAll(head);
}

public static Node delete(Node head, int index) throws Exception {
        Node currentNode = head;

        for (int i = 0; i < index - 1; i++) {
            currentNode = currentNode.next != null ? currentNode.next : null;
            if (currentNode == null) {
                throw  new Exception("位置不存在");
            }
        }
        Node delNode = currentNode.next;
        currentNode.next = delNode.next;
        return head;
}

public static Node add(Node head, Node addNode, int index) throws Exception {
        Node currentNode = head;

        for (int i = 0; i < index - 1; i++) {
            currentNode = currentNode.next != null ? currentNode.next : null;
            if (currentNode == null) {
                throw new Exception("位置不存在");
            }
        }

        addNode.next = currentNode.next;
        currentNode.next = addNode;
        return head;
}

public static Node add(Node head, Node addNode) {
        Node currentNode = head;

        while (currentNode.next != null) {
            currentNode = currentNode.next;
        }
        currentNode.next = addNode;
        return head;
}

public static void queryAll(Node node) {
        Node currentNode = node;
        while (currentNode.next != null) {
            currentNode = currentNode.next;
            System.out.println(currentNode.val);
        }
}

1.1.3 封装节点操作类

定义一个类,封装对节点进行操作的方法。实现 Iterable 接口,用来遍历

public class LinkNode<T> implements Iterable<T>{
    private Node headNode; // 头结点,链表的 headNode 是不可以动的
    private int length; // 结点个数

    public LinkNode(){
        headNode = new Node();
        length = 0;
    }

	// 清空
    public void clear(){
        headNode.next = null; // 头节点 next 为 null 就是空链表
        headNode.val = null;
        length = 0;
    }

	// 是否非空
    public boolean isEmpty(){
        return length == 0;
    }

	// 获取长度
    public int length(){
        return length;
    }

    // 读取链表第 i 位置的元素值并返回
    public T get(int index){
        if (index < 0 || index > length){
            System.out.println("不存在该位置");
        }

        // 头指针不可以移动,不然就无法再找到链表
        // 定义一个临时的Node也指向头指针,通过该指针来进行操作
        Node<T> currentNode = headNode;
        for (int i = 0; i < index; i++) {
            currentNode = currentNode.next;
        }
        return currentNode.val;
    }

    // 插入到链表末尾
    public void insert(T val){
        Node<T> addNode = new Node<>(val);

        Node currentNode = headNode;

        // 找到最后一个节点
        while (currentNode.next != null){
            currentNode = currentNode.next;
        }
        currentNode.next = addNode;
        length++;
    }

    // 插入到 i 位置
    public void insert(T val, int index){
        if (index < 0 || index > length){
            System.out.println("不存在该位置");
        }

        Node currentNode = headNode;
        for (int i = 0; i < index - 1; i++) {
            currentNode = currentNode.next;
        }
        Node<T> addNode = new Node<>(val);
        addNode.next = currentNode.next;
        currentNode.next = addNode;
        length++;
    }

    // 移除并返回 i 位置的元素值
    public T remove(int index){
        if (index < 0 || index > length){
            System.out.println("不存在该位置");
        }

        Node currentNode = headNode;
        for (int i = 0; i < index - 1; i++) {
            currentNode = currentNode.next;
        }
        // 要删除的节点
        Node<T> delCurrent = currentNode.next;

        currentNode.next = delCurrent.next;
        length --;
        return delCurrent.val;
    }

    // 查找元素在链表中第一次出现的位置,索引值
    public int indexOf(T value){
        Node currentNode = headNode;

        for (int i = 0; currentNode.next != null; i++) {
            currentNode = currentNode.next;
            if (currentNode.val.equals(value)){
                return i;
            }
        }
        return -1;
    }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            Node currentNode = headNode;

            @Override
            public boolean hasNext() {
                return currentNode.next != null;
            }

            @Override
            public T next() {
                currentNode = currentNode.next;
                return (T) currentNode.val;
            }
        };
    }
}

测试类:

public static void main(String[] args) {
        LinkNode linkNode = new LinkNode<>();
//        LinkNode<String> linkNode = new LinkNode<>(); // 限制泛型

        linkNode.insert("张三11");
        linkNode.insert(22);
        linkNode.insert("王五33");

        for (Object o : linkNode) {
            System.out.print(o + " ");
        }

        System.out.println();

        linkNode.insert("赵六", 2);
        for (Object o : linkNode) {
            System.out.print(o + " ");
        }
        System.out.println();

        System.out.println("删除的元素:" + linkNode.remove(3));
        for (Object o : linkNode) {
            System.out.print(o + " ");
        }
        System.out.println();

        System.out.println(linkNode.indexOf("赵六"));
}

在这里插入图片描述

1.2 双向链表

包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点
在这里插入图片描述

1.2.1 定义节点类

@AllArgsConstructor
@NoArgsConstructor
public class DoubleNode<T> {
    T val;
    DoubleNode prev;
    DoubleNode next;

    public DoubleNode(T val) {
        this.val = val;
    }
}

1.2.2 封装节点操作类

public class DoubleLinkNode<T> {
    private DoubleNode head;
    private DoubleNode last;
    private int length;

    public DoubleLinkNode(){
        length = 0;
    }

    // 遍历
    public void queryAll(){
        DoubleNode currentNode = head;

        while (currentNode != null){
            System.out.print(currentNode.val + " ");
            currentNode = currentNode.next;
        }
        System.out.println();
    }

    // 获取长度
    public int getLength(){
        return length;
    }

    // 头插法
    public void addFirst(T val){
        // 插入的节点
        DoubleNode addNode = new DoubleNode(val);

        // 加入链表为空,则插入的节点就是头节点和尾结点
        if (head == null){
            head = addNode;
            last = addNode;
            length ++;
        } else {
            // 让添加节点的下一个节点为头结点
            addNode.next = head;
            // 再让头结点的上一个为添加节点
            head.prev = addNode;
            // 再把头节点设为添加节点
            head = addNode;
            length ++;
        }
    }

    // 尾插法
    public void addLast(T val){
        DoubleNode addNode = new DoubleNode(val);

        if (head == null){
            head = addNode;
            last = addNode;
            length ++;
        } else {
            // 让尾结点的下一个节点为添加节点
            last.next = addNode;
            // 再让添加节点的上一个为尾结点
            addNode.prev = last;
            // 再把尾结点设为添加节点
            last = addNode;
            length ++;
        }
    }

    // 在任意位置插入
    public void addIndex(T val, int index) throws Exception {
        // 假如添加位置为头结点位置,就是头插法
        if (index <= 0){
            addFirst(val);
            return;
        }
        // 假如添加位置为尾结点的位置,就是尾插法
        if (index > length){
            addLast(val);
            return;
        }

        DoubleNode addNode = new DoubleNode(val);
        DoubleNode currentNode = head;
        // 找到指定位置的节点
        for (int i = 0; i < index - 1; i++) {
            currentNode = currentNode.next;
        }
        // 让添加节点的下一个节点为指定位置的节点
        addNode.next = currentNode;
        // 让指定位置节点的上一个节点的下一个节点为添加节点,即在指定位置插入添加节点
        currentNode.prev.next = addNode;
        // 再让添加节点的上一个节点为指定位置节点的上一个节点
        addNode.prev = currentNode.prev;
        // 再让指定位置的上一个节点为添加节点
        currentNode.prev = addNode;
        length ++;
    }
}

测试类:

public class DoubleLinkNodeTest {
    public static void main(String[] args) throws Exception {
        DoubleLinkNode<Object> doubleLinkNode = new DoubleLinkNode<>();
        doubleLinkNode.addFirst("张三"); // 张三
        doubleLinkNode.addFirst(1); // 1 张三
        doubleLinkNode.queryAll();

        doubleLinkNode.addLast("李四"); // 1 张三 李四
        doubleLinkNode.queryAll();

        doubleLinkNode.addIndex("王五", 3); // 1 张三 王五 李四

        doubleLinkNode.queryAll();
        System.out.println("长度:" + doubleLinkNode.getLength());
    }
}

在这里插入图片描述

1.3 循环列表与双向循环列表

只需把单链表与双向链表的尾结点的下一个节点指向头结点即可
在这里插入图片描述
在这里插入图片描述

1.4 应用场景与数组比较

1.4.1 应用场景

  • 如果需要支持随机访问的话,链表没办法做到
  • 如果需要存储的数据元素的个数不确定,并且需要经常添加和删除数据的话,使用链表比较合适
  • 如果需要存储的数据元素的个数确定,并且不需要经常添加和删除数据的话,使用数组比较合适

1.4.2 与数组比较

  • 数组支持随机访问,而链表不支持
  • 数组使用的是连续内存空间对 CPU 的缓存机制友好,链表则相反
  • 数组的大小固定,而链表则天然支持动态扩容。如果声明的数组过小,需要另外申请一个更大的内存空间存放数组元素,然后将原数组拷贝进去,这个操作是比较耗时的

2. 栈

栈 (stack)只允许在有序的线性数据集合的一端(称为栈顶 top)进行加入数据(push)和移除数据(pop)。因而按照 后进先出(LIFO, Last In First Out) 的原理运作。在栈中,push 和 pop 的操作都发生在栈顶

栈常用一维数组或链表来实现,用数组实现的栈叫作 数组栈(顺序栈),用链表实现的栈叫作 链式栈。其时间复杂度为:

  • 访问:O(n)最坏情况
  • 插入删除:O(1)顶端插入和删除元素
    在这里插入图片描述

2.1 数组栈

public class MyStack<T> {
    private T[] storage; // 存放栈中元素的数组
    private int capacity; // 栈的容量
    private int count; // 栈中元素数量
    private static final int GROW_FACTOR = 2; // 扩容倍数

    // 无参构造方法。默认容量为 8
    public MyStack(){
        capacity = 8;
        storage = (T[]) new Object[8];
        count = 0;
    }

    // 带初始容量的构造方法
    public MyStack(int initialCapacity){
        if (initialCapacity < 1) {
            throw new IllegalArgumentException("Capacity too small.");
        }
        capacity = initialCapacity;
        storage = (T[]) new Object[initialCapacity];
        count = 0;
    }

    // 入栈
    public void push(T val){
        if (count == capacity){
            expansion();
        }
        storage[count++] = val;
    }

    // 扩容,使用 Arrays.copyOf() 复制原数组并扩容
    public void expansion(){
        int newCapacity = capacity * GROW_FACTOR;
        storage = Arrays.copyOf(storage, newCapacity);
        capacity = newCapacity;
    }

    // 返回栈顶元素并出栈
    public T pop(){
        if (count == 0) {
            throw new IllegalArgumentException("Stack is empty.");
        }
        return storage[--count];
    }

    // 返回栈顶元素不出栈
    public T peek(){
        if (count == 0){
            throw new IllegalArgumentException("Stack is empty.");
        } else {
            return storage[count - 1];
        }
    }

    // 判断栈是否为空
    public boolean isEmpty(){
        return count == 0;
    }

    // 返回栈中元素的个数
    public int size(){
        return count;
    }
}

测试类:

public static void main(String[] args) {
        MyStack myStack = new MyStack(3);
        myStack.push(1);
        myStack.push("张三");
        myStack.push(2);
        myStack.push("李四");

        System.out.println(myStack.peek()); // 李四
        System.out.println(myStack.isEmpty()); // false
        System.out.println(myStack.size()); // 4
        for (int i = 0; i < 4; i++) {
            System.out.println(myStack.pop());
        }
}

在这里插入图片描述

2.2 链栈

与链表实现类似,入栈即使用头插法插入,出栈即把头结点指向下一个节点
定义节点类:

@NoArgsConstructor
@AllArgsConstructor
public class Node<T> {
    T val;  // 节点值
    public Node next; // 指向后续节点

    Node(T val) {
        this.val = val;
    }
}

操作类:

public class LinkStack<T> {
    private Node head;
    private int length;

    public LinkStack(){
        head = new Node();
        length = 0;
    }

    // 清空
    public void clear(){
        head.next = null;
        head.val = null;
        length = 0;
    }

    // 入栈,头插法
    public void push(T val){
        Node addNode = new Node<>(val);
        if (head.val == null){
            head = addNode;
        } else {
            addNode.next = head;
            head = addNode;
        }
        length ++;
    }

    // 出栈,将头结点指向下一个,并返回值
    public T pop(){
        T val = (T) head.val;
        head = head.next;
        length --;
        return val;
    }

    public int getLength(){
        return length;
    }

    public T peek(){
        return (T) head.val;
    }

    public void queryAll(){
        Node currentNode = head;

        while (currentNode != null){
            System.out.println(currentNode.val);
            currentNode = currentNode.next;
        }
    }
}

测试类:

public static void main(String[] args) {
        LinkStack<Object> linkStack = new LinkStack<>();
        linkStack.push("张三");
        linkStack.push(1);
        linkStack.push(2);

        linkStack.pop();
        System.out.println("栈顶元素:" + linkStack.peek());
        System.out.println("链表长度:" + linkStack.getLength());
        linkStack.queryAll();
}

在这里插入图片描述

2.3 应用场景

当要处理的数据只涉及在一端插入和删除数据,并且满足 后进先出(LIFO, Last In First Out) 的特性时,就可以使用栈这个数据结构

2.3.1 实现浏览器的回退和前进功能

只需要使用两个栈(Stack1 和 Stack2)就能实现这个功能。比如按顺序查看了 1,2,3,4 这四个页面,依次把 1,2,3,4 这四个页面压入 Stack1 中。当想回头看 2 这个页面的时候,点击回退按钮,依次把 4,3 这两个页面从 Stack1 弹出,然后压入 Stack2 中。假如又想回到页面 3,你点击前进按钮,将 3 页面从 Stack2 弹出,然后压入到 Stack1 中
在这里插入图片描述

2.3.2 检查符号是否成对出现

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断该字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。
比如 "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]" 、"([)]" 则不是。

首先将括号间的对应规则存放在 Map 中。然后将字符串转为数组进行遍历,假如是左符号,就直接压入栈中;右符号则将栈顶元素出栈进行比较。因为栈是后入先出,刚好可以与成对符号进行匹配。判断左右符号则由 Map 的 Key,Value 进行判断,Key 为右符号,Value 为左符号

public boolean isValid(String s){
    // 括号之间的对应规则
    HashMap<Character, Character> mappings = new HashMap<Character, Character>();
    mappings.put(')', '(');
    mappings.put('}', '{');
    mappings.put(']', '[');
    Stack<Character> stack = new Stack<Character>();
    char[] chars = s.toCharArray();
    for (int i = 0; i < chars.length; i++) {
        if (mappings.containsKey(chars[i])) {
            char topElement = stack.empty() ? '#' : stack.pop();
            if (topElement != mappings.get(chars[i])) {
                return false;
            }
        } else {
            stack.push(chars[i]);
        }
    }
    return stack.isEmpty();
}

2.3.3 反转字符串

先压栈再出栈即可

2.3.4 维护函数调用

最后一个被调用的函数必须先完成执行,符合栈的 后进先出(LIFO, Last In First Out) 特性

3. 队列

队列 是 先进先出( FIFO,First In, First Out) 的线性表。在具体应用中通常用链表或者数组来实现,用数组实现的队列叫作 顺序队列 ,用链表实现的队列叫作 链式队列 。队列只允许在后端(rear)进行插入操作也就是 入队 enqueue,在前端(front)进行删除操作也就是出队 dequeue

队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加
在这里插入图片描述

3.1 单队列

单队列就是常见的队列, 每次添加元素时,都是添加到队尾。单队列又分为 顺序队列(数组实现) 和 链式队列(链表实现)

顺序队列存在“假溢出”的问题也就是明明有位置却不能添加的情况
在这里插入图片描述
上图是一个顺序队列,将前两个元素 1,2 出队,并入队两个元素 7,8。当进行入队、出队操作的时候,front 和 rear 都会持续往后移动,当 rear 移动到最后的时候,无法再往队列中添加数据,即使数组中还有空余空间,这种现象就是 ”假溢出“ 。除了假溢出问题之外,如下图所示,当添加元素 8 的时候,rear 指针移动到数组之外(越界

为了避免当只有一个元素的时候,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向对头元素,rear 指针指向队列最后一个元素的下一个位置,这样当 front 等于 rear 时,此队列不是还剩一个元素,而是空队列

3.1.1 数组单队列

只能使用一次

public class ArrayQueue<T> {
    private T[] storage; // 存放队列元素的数组
    private int front; // 队列头
    private int rear; // 队列尾
    private int maxsize; // 队列最大容量

    // 无参构造,默认大小为8
    public ArrayQueue(){
        storage = (T[]) new Object[8];
        front = -1;
        rear = -1;
        maxsize = 8;
    }

    // 带初始大小的构造方法
    public ArrayQueue(int size){
        storage = (T[]) new Object[size];
        front = -1;
        rear = -1;
        maxsize = size;
    }

    // 判断是否队列已满
    public boolean isFull(){
        return rear == maxsize - 1;
    }

    // 判断队列是否为空
    public boolean isEmpty(){
        return front == rear;
    }

    // 入队
    public void enqueue(T val){
        if (isFull()){
            throw new ArrayIndexOutOfBoundsException("队列已满");
        }
        storage[++rear] = val;
    }

    // 遍历队列
    public void queryQueue(){
        if (isEmpty()){
            System.out.println("队列为空");
            return;
        }
        for (int i = 0; i < storage.length; i++) {
            if (storage[i] != null){
                System.out.print(storage[i] + " ");
            }
        }
        System.out.println();
    }

    // 出队
    public T dequeue(){
        if (isEmpty()){
            throw new ArrayIndexOutOfBoundsException("队列为空");
        }
        T result = storage[++front];
        storage[front] = null;
        return result;
    }

    // 获取队列头元素
    public T peek(){
        if (isEmpty()){
            throw new ArrayIndexOutOfBoundsException("队列为空");
        }
        return storage[front + 1];
    }
}

测试类:

public class Demo{
    public static void main(String[] args) {
        ArrayQueue<Object> arrayQueue = new ArrayQueue<>();
        arrayQueue.enqueue(1); // 1
        arrayQueue.enqueue("张三"); // 张三 1
        arrayQueue.enqueue(2); // 2 张三 1
        arrayQueue.enqueue("李四"); // 李四 2 张三 1

        System.out.println("队列是否已满:" + arrayQueue.isFull()); // false

        System.out.println("出队元素:" + arrayQueue.dequeue());
        System.out.println("出队元素:" + arrayQueue.dequeue());
        System.out.println("获取队头元素不出队:" + arrayQueue.peek());

        arrayQueue.queryQueue();

        System.out.println("出队元素:" + arrayQueue.dequeue());
        System.out.println("出队元素:" + arrayQueue.dequeue());
        System.out.println("队列是否为空:" + arrayQueue.isEmpty());
    }
}

在这里插入图片描述

3.1.2 链式单队列

  • 插入元素时:每次直接插到末尾即可(尾插法),新插入的元素即为新的尾结点(区别于栈的头插法)
  • 取出元素时:返回首结点指向的下一个元素即可。(先进先出),此时和弹栈一样

队列为尾插头取,栈为头插头取

public class LinkQueue<T> {
    private Node head;
    private Node last;
    private int length;

    public LinkQueue() {
        length = 0;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return length == 0;
    }

    // 入队
    public void enqueue(T val) {
        Node node = new Node(val);

        if (isEmpty()) {
            head = node;
            last = node;
        } else {
            last.next = node;
        }
        last = node;
        length++;
    }

    // 遍历队列
    public void queryQueue() {
        Node currentNode = head;
        while (currentNode != null) {
            System.out.print(currentNode.val + " ");
            currentNode = currentNode.next;
        }
        System.out.println();
    }

    // 出队
    public T dequeue() {
        if (isEmpty()) {
            return null;
        }
        T val = (T) head.val;
        head = head.next;
        length--;
        return val;
    }

    // 获取队列长度
    public int getLength() {
        return length;
    }

    // 获取队列头元素
    public T getHead() {
        if (isEmpty()) {
            return null;
        }
        return (T) head.val;
    }

    // 获取队列尾元素
    public T getLast() {
        if (isEmpty()) {
            return null;
        }
        return (T) last.val;
    }
}

测试类:

public class Demo05{
    public static void main(String[] args) {
        LinkQueue<Object> linkQueue = new LinkQueue<>();
        linkQueue.enqueue(1); // 1
        linkQueue.enqueue("张三"); // 张三 1
        linkQueue.enqueue(2); // 2 张三 1
        linkQueue.enqueue("李四"); // 李四 2 张三 1

        System.out.println("出队元素:" + linkQueue.dequeue()); // 1
        System.out.println("队头元素:" + linkQueue.getHead()); // 张三
        System.out.println("队尾元素:" + linkQueue.getLast()); // 李四
        System.out.println("出队元素:" + linkQueue.dequeue()); // 张三

        linkQueue.queryQueue();
        System.out.println("队列长度:" + linkQueue.getLength()); // 2
    }
}

在这里插入图片描述

3.2 循环队列

循环队列可以解决顺序队列的假溢出和越界问题。解决办法就是:从头开始,这样也就会形成头尾相接的循环,这也就是循环队列名字的由来。将 rear 指针指向数组下标为 0 的位置就不会有越界问题了。当再向队列中添加元素的时候, rear 向后移动
在这里插入图片描述
顺序队列中,front == rear 的时候队列为空,循环队列中则不一样,也可能为满,如上图所示。解决办法有两种:

  1. 可以设置一个标志变量 flag,当 front == rear 并且 flag=0 的时候队列为空,当 front == rear 并且 flag=1 的时候队列为满
  2. 队列为空的时候就是 front == rear ,队列满的时候,保证数组还有一个空闲的位置,rear 就指向这个空闲位置,如下图所示,那么现在判断队列是否为满的条件就是: (rear+1) % QueueSize= front
    在这里插入图片描述

3.2.1 数组循环队列

  1. front 变量的含义做一个调整:front 指向队列的第一个元素,也就是说 arr[front] 就是队列的第一个元素,front 的初始值=0
  2. rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置,因为希望空出一个位置来进行判断,rear 的初始值=0
  3. 当队列满时,条件是 (rear +1) % maxSize = front【满】
  4. 对队列为空的条件,rear == front 空
  5. 队列中有效的数据的个数 (rear + maxSize -front) % maxSize
public class CircularQueue<T> {
    private T[] storage; // 存放队列元素的数组
    private int front; // 队列头
    private int rear; // 队列尾
    private int maxsize; // 队列最大容量

    // 无参构造,默认大小为8
    public CircularQueue(){
        storage = (T[]) new Object[8];
        maxsize = 8;
        // front = 0; 指向第一个元素,默认是0可以不写
        // rear = 0;  指向最后一个元素的下一位,默认是0可以不写
    }

    // 带初始大小的构造方法
    public CircularQueue(int size){
        storage = (T[]) new Object[size];
        maxsize = size;
        // front = 0; 指向第一个元素,默认是0可以不写
        // rear = 0;  指向最后一个元素的下一位,默认是0可以不写
    }

    // 判断是否队列已满
    public boolean isFull(){
        return (rear + 1) % maxsize == front;
    }

    // 判断队列是否为空
    public boolean isEmpty(){
        return front == rear;
    }

    // 入队
    public void enqueue(T val){
        if (isFull()){
            throw new ArrayIndexOutOfBoundsException("队列已满");
        }
        storage[rear] = val;
        rear = (rear + 1) % maxsize;
    }

    // 求出当前有效数据
    public int getEffectiveSize(){
        return (rear + maxsize - front) % maxsize;
    }

    //遍历
    public void queryQueue() {
        if (isEmpty()) {
            System.out.println("队列为空");
            return;
        }
        for (int i = front; i < front + getEffectiveSize(); i++) {
            System.out.print(storage[i % maxsize] + " ");
        }
        System.out.println();
    }

    // 出队
    public T dequeue(){
        if (isEmpty()){
            throw new ArrayIndexOutOfBoundsException("队列为空");
        }
        T val = storage[front];
        storage[front] = null;
        front = (front + 1) % maxsize;
        return val;
    }

    // 取队首元素
    public T getFront(){
        if (isEmpty()){
            throw new ArrayIndexOutOfBoundsException("队列为空");
        }
        return storage[front];
    }

    // 取队尾元素
    public T getRear(){
        if (isEmpty()){
            throw new ArrayIndexOutOfBoundsException("队列为空");
        }
        return storage[(rear - 1 + maxsize) % maxsize];
    }
}

测试类:

public class Demo{
    public static void main(String[] args) {
        CircularQueue<Object> circularQueue = new CircularQueue<>(4);

        circularQueue.enqueue(1); // 1
        circularQueue.enqueue("张三"); // 张三 1
        circularQueue.enqueue("李四"); // 李四 张三 1

        System.out.println("出队元素:" + circularQueue.dequeue());
        System.out.println("队头元素:" + circularQueue.getFront());
        System.out.println("出队元素:" + circularQueue.dequeue());
        circularQueue.queryQueue();

        circularQueue.enqueue("王五"); // 王五 李四
        circularQueue.enqueue("赵六"); // 赵六 王五 李四
        circularQueue.queryQueue();

        System.out.println("队尾元素:" + circularQueue.getRear());
    }
}

在这里插入图片描述

3.2.2 链式循环队列

在链式单队列的基础上,让尾结点的 next 指向头结点即可

3.3 常见应用场景

  • 阻塞队列: 阻塞队列可以看成在队列基础上加了阻塞操作的队列。当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现 “生产者 - 消费者“ 模型
  • 线程池中的请求 / 任务队列当线程池中没有空闲线程时,新的任务请求线程资源时,将这些请求放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如 :FixedThreadPool 使用无界队列 LinkedBlockingQueue。但是有界队列就不一样了,当队列满的话后面再有任务/请求就会拒绝,在 Java 中的体现就是会抛出 java.util.concurrent.RejectedExecutionException 异常
  • Linux 内核进程队列(按优先级排队)
  • 播放器上的播放列表
  • 消息队列
  • 等等......

4. 树

树就是一种类似现实生活中的树的数据结构(倒置的树)。任何一颗非空树只有一个根节点。一棵树具有以下特点:

  1. 一棵树中的任意两个结点有且仅有唯一的一条路径连通
  2. 一棵树如果有 n 个结点,那么它一定恰好有 n-1 条边
  3. 一棵树不包含回路

在这里插入图片描述
树中的常用概念:

  • 节点 :树中的每个元素都可以统称为节点。
  • 根节点 :顶层节点或者说没有父节点的节点。上图中 A 节点就是根节点。
  • 父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点。上图中的 B 节点是 D 节点、E 节点的父节点。
  • 子节点 :一个节点含有的子树的根节点称为该节点的子节点。上图中 D 节点、E 节点是 B 节点的子节点。
  • 兄弟节点 :具有相同父节点的节点互称为兄弟节点。上图中 D 节点、E 节点的共同父节点是 B 节点,故 D 和 E 为兄弟节点。
  • 叶子节点 :没有子节点的节点。上图中的 D、F、H、I 都是叶子节点。
  • 节点的高度 :该节点到叶子节点的最长路径所包含的边数。
  • 节点的深度 :根节点到该节点的路径所包含的边数
  • 节点的层数 :节点的深度+1。
  • 树的高度 :根节点的高度

4.1 二叉树的分类

  • 二叉树(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构
  • 二叉树 的分支通常被称作 “左子树” 或 “右子树”。并且,二叉树 的分支具有左右次序,不能随意颠倒
  • 二叉树 的第 i 层至多拥有 2^(i-1) 个节点,深度为 k 的二叉树至多总共有 2^(k+1) - 1 个节点(满二叉树的情况),至少有 2^(k) 个节点

4.1.1 满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是 满二叉树。也就是说,如果一个二叉树的层数为 K,且结点总数是 (2^k) -1 ,则它就是 满二叉树。如下图所示:
在这里插入图片描述

4.1.2 完全二叉树

除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则这个二叉树就是 完全二叉树 。

即一棵树从根结点开始扩展,扩展完左子节点才能开始扩展右子节点,每扩展完一层,才能继续扩展下一层。如下图所示:
在这里插入图片描述
完全二叉树有一个很好的性质:父结点和子节点的序号有着对应关系

当根节点的值为 1 的情况下,若父结点的序号是 i,那么左子节点的序号就是 2i,右子节点的序号是 2i+1。这个性质使得完全二叉树利用数组存储时可以极大地节省空间,以及利用序号找到某个节点的父结点和子节点

4.1.3 二叉排序树

一棵空树,或者是具有下列性质的二叉树:

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

在这里插入图片描述
1. 查找 Find

  • 查找的值 x 从根节点开始
  • 如果 x 小于根节点的值,则在左子树中继续查找
  • 如果 x 大于根节点的值,则在右子树中继续查找
  • 如果x等于根节点的值则返回该节点
  • 查不到就返回 null

查找的效率决定于树的高度,最大元素在树的最右支的节点,最小元素在树的最左支的节点上
2. 插入 Insert

  • 插入的值从根节点开始查找
  • 如果 x 小于根节点的值,则在左子树中继续查找
  • 如果 x 大于根节点的值,则在右子树中继续查找
  • 如果该节点是叶节点,x 小于该节点值则插入左子节点,否则插入右节点

3. 删除 delete,删除节点分为3类

  • 无儿子节点的节点,也就是叶子结点(删除节点分为左叶子节点和右叶子节点
    找到要删除的节点及其父节点,然后判断删除节点是父节点的左节点还是右节点,再将对应的删除节点置为 null
    在这里插入图片描述
  • 只有一个儿子的节点(删除节点分为只有一个左子节点或只有一个右子节点,同时删除节点也分为左节点和右节点
    找到要删除的节点及其父节点,判断删除节点拥有左子节点还是右子节点,再判断删除节点是父节点的左节点还是右节点,再将父亲节点链接删除节点的子节点,相当于删除要删除的节点
    在这里插入图片描述
    在这里插入图片描述
  • 有两个儿子的节点(应考虑用哪个节点替换该节点的位置,并保证新的树也是个二叉排序树
    找到删除节点,再找到其右子树的最小值,即删除节点的右子节点的最左子节点,其为替换节点,同时找到替换节点的父节点,然后将删除节点与替换节点进行替换,再删掉替换节点,即置为 null。假如没有最左子节点,删除节点的右子节点就是替换节点
    在这里插入图片描述
    在这里插入图片描述

定义树节点:

@Data
public class TreeNode {
    public Integer data;
    public TreeNode leftChild;
    public TreeNode rightChild;

    public TreeNode(Integer data) {
        this.data = data;
    }
}

二叉排序树操作类:

public class BinarySortedTree {

    public TreeNode root;

    // 非递归构建二叉排序树
    public void insert(int val) {
        TreeNode addTreeNode = new TreeNode(val);

        if (root == null) {
            root = addTreeNode;
        } else {
            TreeNode currentTreeNode = root;
            TreeNode parentTreeNode;

            while (true) {
                parentTreeNode = currentTreeNode;

                if (addTreeNode.data > currentTreeNode.data) {
                    currentTreeNode = currentTreeNode.rightChild;

                    if (currentTreeNode == null) {
                        parentTreeNode.rightChild = addTreeNode;
                        return;
                    }
                } else {
                    currentTreeNode = currentTreeNode.leftChild;

                    if (currentTreeNode == null) {
                        parentTreeNode.leftChild = addTreeNode;
                        return;
                    }
                }
            }
        }
    }

    // 递归构建二叉排序树
    public TreeNode insert(TreeNode treeNode, int val) {
        TreeNode addTreeNode = new TreeNode(val);

        if (root == null) {
            return root = addTreeNode;
        }

        if (val > treeNode.data) {
            if (treeNode.rightChild == null) {
                treeNode.rightChild = addTreeNode;
                return root;
            }
            return insert(treeNode.rightChild, val);
        } else {
            if (treeNode.leftChild == null) {
                treeNode.leftChild = addTreeNode;
                return root;
            }
            return insert(treeNode.leftChild, val);
        }
    }

    // 先序遍历二叉排序树
    public void preOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }

        System.out.print(treeNode.data + " ");
        preOrder(treeNode.leftChild);
        preOrder(treeNode.rightChild);
    }

    // 中序遍历二叉排序树
    public void inOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }

        inOrder(treeNode.leftChild);
        System.out.print(treeNode.data + " ");
        inOrder(treeNode.rightChild);
    }

    // 后序遍历二叉排序树
    public void postOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }

        postOrder(treeNode.leftChild);
        postOrder(treeNode.rightChild);
        System.out.print(treeNode.data + " ");
    }

    // 层序遍历二叉排序树
    public void levelOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }
        LinkedList<TreeNode> linkedList = new LinkedList<>();
        linkedList.offer(treeNode);
        TreeNode currentTreeNode;

        while (!linkedList.isEmpty()) {
            currentTreeNode = linkedList.poll();
            System.out.print(currentTreeNode.data + " ");

            if (currentTreeNode.leftChild != null) {
                linkedList.offer(currentTreeNode.leftChild);
            }
            if (currentTreeNode.rightChild != null) {
                linkedList.offer(currentTreeNode.rightChild);
            }
        }
        System.out.println();
    }

    // 查找元素是否在二叉排序树中,如果在返回该节点,否则返回null
    public TreeNode find(int val) {
        TreeNode currentTreeNode = root;

        while (currentTreeNode != null) {
            if (currentTreeNode.data == val) {
                return currentTreeNode;
            } else if (currentTreeNode.data > val) {
                currentTreeNode = currentTreeNode.leftChild;
            } else {
                currentTreeNode = currentTreeNode.rightChild;
            }
        }
        return null;
    }

    // 查找二叉排序树中最小值
    public int findMin() {
        TreeNode deleteTreeNode = root;

        while (deleteTreeNode.leftChild != null) {
            deleteTreeNode = deleteTreeNode.leftChild;
        }
        return deleteTreeNode.data;
    }

    // 删除节点
    public void delete(int val) {
        TreeNode deleteTreeNode = root;
        TreeNode parentTreeNode = root;

        // 找到要删除的节点
        while (deleteTreeNode.data != val) {
            parentTreeNode = deleteTreeNode;

            if (deleteTreeNode.data > val) {
                deleteTreeNode = deleteTreeNode.leftChild;
            } else {
                deleteTreeNode = deleteTreeNode.rightChild;
            }
            if (deleteTreeNode == null) {
                break;
            }
        }

        if (deleteTreeNode != null) {
            if (deleteTreeNode.leftChild == null && deleteTreeNode.rightChild == null) { // 叶子节点
                if (deleteTreeNode == root) {
                    root = null;
                } else if (parentTreeNode.leftChild == deleteTreeNode) { // 左子节点
                    parentTreeNode.leftChild = null;
                } else { // 右子节点
                    parentTreeNode.rightChild = null;
                }
            } else if (deleteTreeNode.leftChild == null) { // 只有一个右子节点
                if (deleteTreeNode == root) {
                    root = deleteTreeNode.rightChild;
                } else if (parentTreeNode.leftChild == deleteTreeNode) { // 左子节点
                    parentTreeNode.leftChild = deleteTreeNode.rightChild;
                } else { // 右子节点
                    parentTreeNode.rightChild = deleteTreeNode.rightChild;
                }
            } else if (deleteTreeNode.rightChild == null) { // 只有一个左子节点
                if (deleteTreeNode == root) {
                    root = deleteTreeNode.leftChild;
                } else if (parentTreeNode.leftChild == deleteTreeNode) { // 左子节点
                    parentTreeNode.leftChild = deleteTreeNode.leftChild;
                } else { // 右子节点
                    parentTreeNode.rightChild = deleteTreeNode.leftChild;
                }
            } else { // 有两个子节点
                // 先定义一个 替换节点 保存要删除节点的右子树的最小值
                TreeNode replaceTreeNode = deleteTreeNode.rightChild;
                // 定义一个 替换节点的父节点 保存要删除节点的右子树的最小值的父节点
                TreeNode replaceParentTreeNode = deleteTreeNode;

                // 找到删除节点的右节点的最左子节点,即右子树的最小值
                while (replaceTreeNode.leftChild != null) {
                    // 将其赋值给 替换节点的父节点
                    replaceParentTreeNode = replaceTreeNode;
                    // 将其 左节点 赋值给替换节点
                    replaceTreeNode = replaceTreeNode.leftChild;
                }
                // 将替换节点的值赋值给要删除节点
                deleteTreeNode.data = replaceTreeNode.data;
                // 如果替换节点的父节点的左节点是替换节点,则将替换节点的右节点赋值给替换节点的父节点的左节点
                // 即有最左子节点的情况
                if (replaceParentTreeNode.leftChild == replaceTreeNode) {
                    replaceParentTreeNode.leftChild = replaceTreeNode.rightChild;
                } else {
                    // 如果替换节点的父节点的右节点是替换节点,则将替换节点的右节点赋值给替换节点的父节点的右节点
                    // 即没有左子节点的情况,删除节点的右子节点就是替换节点,同时其并没有左子节点,所以使用替换节点的右节点
                    replaceParentTreeNode.rightChild = replaceTreeNode.rightChild;
                }
            }
        } else {
            System.out.println("要删除的节点不存在!");
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) {
        BinarySortedTree binarySortedTree = new BinarySortedTree();

        // binarySortedTree.insert(binarySortedTree.root, 1);
        binarySortedTree.insert(5);
        binarySortedTree.insert(7);
        binarySortedTree.insert(2);
        binarySortedTree.insert(1);
        binarySortedTree.insert(3);
        binarySortedTree.insert(4);
        binarySortedTree.insert(6);
        binarySortedTree.insert(12);
        binarySortedTree.insert(10);
        binarySortedTree.insert(9);
        binarySortedTree.insert(11);
        binarySortedTree.insert(13);

        System.out.print("先序遍历:");
        binarySortedTree.preOrder(binarySortedTree.root); // 5 2 4 8
        System.out.println();
        System.out.print("中序遍历:");
        binarySortedTree.inOrder(binarySortedTree.root); // 2 4 5 8
        System.out.println();
        System.out.print("后序遍历:");
        binarySortedTree.postOrder(binarySortedTree.root); // 1 4 2 6 9 8 5
        System.out.println();
        System.out.print("层次遍历:");
        binarySortedTree.levelOrder(binarySortedTree.root); // 5 2 8 1 4 6 9

        System.out.println("查找值【3】对应的节点:" + binarySortedTree.find(3));

        binarySortedTree.delete(7);
        System.out.print("删除值【7】后的二叉排序树:");
        binarySortedTree.levelOrder(binarySortedTree.root);
    }
}

在这里插入图片描述

4.1.4 平衡二叉树

平衡二叉树 是一棵二叉排序树,且具有以下性质:

  1. 可以是一棵空树
  2. 如果不是空树,它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树(左右高度差小于等于 1)

平衡二叉树的常用实现方法有:红黑树、AVL 树、替罪羊树、加权平衡树、伸展树 等

如下这棵树已经退化为一个链表了,管它叫 斜树
在这里插入图片描述
二叉树相比于链表,由于父子节点以及兄弟节点之间往往具有某种特殊的关系,这种关系使得在树中对数据进行搜索和修改时,相对于链表更加快捷便利

但是,如果二叉树退化为一个链表了,那么那么树所具有的优秀性质就难以表现出来,效率也会大打折,为了避免这样的情况,希望每个父结点分给左儿子和分给右儿子的尽可能一样多,相差最多不超过一层,如下图所示:
在这里插入图片描述

4.2 二叉树的存储

二叉树的存储主要分为 链式存储 和 顺序存储 两种

4.2.1 顺序存储

顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的 data,不存储左右子节点的指针,子节点的索引通过数组下标完成
根结点的序号为 1,对于每个节点 Node,假设它存储在数组中下标为 i 的位置,那么它的左子节点就存储在 2i 的位置,它的右子节点存储在下标为 2i+1 的位置

一棵完全二叉树的数组顺序存储如下图所示:
在这里插入图片描述
如果存储的二叉树不是完全二叉树,在数组中就会出现空隙,导致内存利用率降低
在这里插入图片描述
简单实现

public class ArrayTree {
    private int[] storage;

    public ArrayTree(){
        storage = new int[8];
    }

    public ArrayTree(int length){
        storage = new int[length];
    }

    // 添加根节点
    public void addRoot(int val){
        storage[1] = val;
    }

    // 添加左子节点
    public void addLeft(int index, int val){
        if(index > storage.length || index < 0){
            throw new IndexOutOfBoundsException();
        }

        storage[2 * index] = val;
    }

    // 添加右子节点
    public void addRight(int index, int val){
        storage[ 2 * index + 1] = val;
    }

    // 获取一个节点的父节点
    public int getParent(int index){
        if(index > storage.length || index < 0){
            throw new IndexOutOfBoundsException();
        }
        return storage[index / 2];
    }

    // 遍历
    public void query(){
        for(int i = 1; i < storage.length; i++){
            System.out.print(storage[i] + " ");
        }
        System.out.println();
    }
}

测试类:

public class Demo05 {
    public static void main(String[] args) {
        ArrayTree arrayTree = new ArrayTree(10);
        arrayTree.addRoot(1);
        arrayTree.addLeft(1, 2);
        arrayTree.addRight(1, 3);

        arrayTree.addLeft(2, 4);
        arrayTree.addLeft(4, 5);
        arrayTree.query();

        System.out.println(arrayTree.getParent(8));
    }
}

在这里插入图片描述

4.2.2 链式存储

和链表类似,二叉树的链式存储依靠指针将各个节点串联起来,不需要连续的存储空间。每个节点包括三个属性:

  • 数据 data。data 不一定是单一的数据,根据不同情况,可以是多个具有不同类型的数据
  • 左节点指针 left
  • 右节点指针 right

在这里插入图片描述

4.3 二叉树的遍历

4.3.1 先序遍历

二叉树的先序遍历,就是先输出根结点,再遍历左子树,最后遍历右子树,遍历左子树和右子树的时候,同样遵循先序遍历的规则,也就是说,可以递归实现先序遍历
在这里插入图片描述

public void preOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }

        System.out.print(treeNode.data + " ");
        preOrder(treeNode.leftChild);
        preOrder(treeNode.rightChild);
    }

4.3.2 中序遍历

二叉树的中序遍历,就是先递归中序遍历左子树,再输出根结点的值,再递归中序遍历右子树,可以想象成一巴掌把树压扁,父结点被拍到了左子节点和右子节点的中间,如下图所示:
在这里插入图片描述
在这里插入图片描述

public void inOrder(TreeNode root){
	if(root == null){
		return;
	}
	inOrder(root.left);
	system.out.println(root.data);
	inOrder(root.right);
}

4.3.3 后序遍历

二叉树的后序遍历,就是先递归后序遍历左子树,再递归后序遍历右子树,最后输出根结点的值
在这里插入图片描述

public void postOrder(TreeNode root){
	if(root == null){
		return;
	}
	postOrder(root.left);
	postOrder(root.right);
	system.out.println(root.data);
}

4.3.4 层次遍历

即一层一层往下遍历。使用链表,先将根节点加到链表中,然后再将根节点的左节点和右节点加入到链表中,这里定义一个 Current 节点,用来将左右节点加入到链表里以及遍历链表,这里用 poll 方式来给 Current 节点赋值

public void levelOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }
        LinkedList<TreeNode> linkedList = new LinkedList<>();
        linkedList.offer(treeNode);
        TreeNode currentTreeNode;

        while (!linkedList.isEmpty()) {
            currentTreeNode = linkedList.poll();
            System.out.print(currentTreeNode.data + " ");

            if (currentTreeNode.leftChild != null) {
                linkedList.offer(currentTreeNode.leftChild);
            }
            if (currentTreeNode.rightChild != null) {
                linkedList.offer(currentTreeNode.rightChild);
            }
        }
        System.out.println();
    }

在这里插入图片描述

4.4 哈夫曼树(最优⼆叉树)

4.4.1 概述

  • 路径在一棵树中,一个结点到另一个结点之间的通路,称为路径。如下图中,从根结点到结点 a 之间的通路就是一条路径
  • 路径长度在一条路径中,每经过一个结点,路径长度都要加 1 。例如在一棵树中,规定根结点所在层数为1层,那么从根结点到第 i 层结点的路径长度为 i - 1 。下图中从根结点到结点 c 的路径长度为 3
  • 结点的权给每一个结点赋予一个新的数值,被称为这个结点的权。例如,下图 中结点 a 的权为 7,结点 b 的权为 5
  • 结点的带权路径长度:指的是从根结点到该结点之间的路径长度与该结点的权的乘积。例如,下图中结点 b 的带权路径长度为 2 * 5 = 10
  • 树的带权路径长度为树中所有叶子结点的带权路径长度之和。通常记作 “WPL” 。例如下图中所示的这颗树的带权路径长度为:
    WPL = 7 * 1 + 5 * 2 + 2 * 3 + 4 * 3
    在这里插入图片描述

当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为 “最优二叉树”,有时也叫 “赫夫曼树” 或者 “哈夫曼树”

在构建哈弗曼树时,要使树的带权路径长度最小,只需要遵循一个原则,那就是:权重越大的结点离树根越近。在上图中,因为结点 a 的权值最大,所以理应直接作为根结点的孩子结点。

构建方法:

  1. 在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和
  2. 在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推
  3. 重复 1 和 2 ,直到所以的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树
    在这里插入图片描述

4.4.2 实现

定义节点类:

public class TreeNode<T> {
    public T data;
    public int weight;
    public TreeNode<T> leftChild;
    public TreeNode<T> rightChild;

    public TreeNode(T data, int weight) {
        this.data = data;
        this.weight = weight;
    }

    public String toString(){
        return data + " " + weight;
    }
}

哈夫曼树构建:

public class HuffmanTree {
    // 构建Huffman树
    public <T> TreeNode<T> buildHuffmanTree(List<TreeNode> treeNodes) {
        while (treeNodes.size() > 1){
            sortTreeNode(treeNodes);

            // 左边比右边小
            TreeNode left = treeNodes.get(0);
            TreeNode right = treeNodes.get(1);

            // 生成一个新的节点,父结点权重为两个子结点之和
            TreeNode parent = new TreeNode(null, left.weight + right.weight);

            // 让子结点与父结点连接
            parent.leftChild = left;
            parent.rightChild = right;

            // 删除最小的
            treeNodes.remove(0);
            // 删除第二小的
            treeNodes.remove(0);
            // 把新的父结点加入到 list 中
            treeNodes.add(parent);
        }
        // 返回哈夫曼树的根节点
        return treeNodes.get(0);
    }

    // 对节点进行排序,从小到大
    public void sortTreeNode(List<TreeNode> treeNodes) {
        for (int i = 0; i < treeNodes.size(); i++){
            for (int j = 0; j < treeNodes.size() - 1 - i; j++){
                if (treeNodes.get(j).weight > treeNodes.get(j + 1).weight) {
                    TreeNode temp = treeNodes.get(j + 1);
                    treeNodes.set(j+1,treeNodes.get(j));
                    treeNodes.set(j,temp);
                }
            }
        }
    }

    // 打印哈夫曼树
    public void printTree(TreeNode root) {
        System.out.println("Node-" + root.toString());
        if(root.leftChild != null){
            System.out.print("left:");
            printTree(root.leftChild);
        }
        if(root.rightChild !=null){
            System.out.print("right:");
            printTree(root.rightChild);
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) {
        HuffmanTree huffmanTree = new HuffmanTree();

        List<TreeNode> treeNodes = new ArrayList<TreeNode>();
        //把节点加入至list中
        treeNodes.add(new TreeNode("张三", 10));
        treeNodes.add(new TreeNode(1, 15));
        treeNodes.add(new TreeNode("李四", 12));
        treeNodes.add(new TreeNode(2, 3));
        treeNodes.add(new TreeNode("王五", 4));
        treeNodes.add(new TreeNode(3, 13));
        treeNodes.add(new TreeNode("赵六", 1));

        // 进行哈夫曼树的构造
        TreeNode root = huffmanTree.buildHuffmanTree(treeNodes);
        // 打印哈夫曼树
        huffmanTree.printTree(root);
    }
}

在这里插入图片描述

4.5 AVL 树

4.5.1 概述

AVL 树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差的绝对值不超过1),一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡。不管是插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此 AVL 树适合用于插入与删除次数比较少,但查找多的情况

由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。如果应用场景中对插入删除不频繁,只是对查找要求较高,那么 AVL 还是较优于红黑树(AVL 树查找比红黑树快)

二叉平衡树的调整:调整原则根据插入的节点和失衡节点的位置上关系来划分

  1. LL旋转,让失衡节点的左子节点成为新节点,让新节点的右子节点成为失衡节点的左子节点,再让失衡节点成为新节点的右子节点
    在这里插入图片描述
    在这里插入图片描述
  2. RR旋转,让失衡节点的右子节点成为新节点,让新节点的左子节点成为失衡节点的右子节点,再让失衡节点成为新节点的左子节点
    在这里插入图片描述
    在这里插入图片描述
  3. LR旋转,对失衡节点的左子树做一次RR旋转,再对失衡节点做一次LL旋转
    在这里插入图片描述
    在这里插入图片描述
  4. RL旋转
    在这里插入图片描述
    在这里插入图片描述

4.5.2 实现

节点类

public class AVLTreeNode {
    Integer data; // 数据
    AVLTreeNode leftChild; // 左子节点
    AVLTreeNode rightChild; // 右子节点
    int height; // 记录该节点所在的高度

    public AVLTreeNode(int data) {
        this.data = data;
    }
}

AVL树实现类

public class AVLTree {
    public AVLTreeNode root;
    private int size;

    public AVLTree() {
        this.root = null;
        this.size = 0;
    }

    public int getSize(){
        return size;
    }

    // 获取节点的高度,让空节点高度为-1
    public int getHeight(AVLTreeNode avlTreeNode) {
        return avlTreeNode == null ? -1 : avlTreeNode.height;
    }

    // 先序遍历
    public void printTree(AVLTreeNode root) {
        System.out.print(root.data + " ");
        if(root.leftChild != null){
            printTree(root.leftChild);
        }
        if(root.rightChild != null){
            printTree(root.rightChild);
        }
    }

    // 插入节点
    public AVLTreeNode insert(AVLTreeNode avlTreeNode, int val) {
        if (avlTreeNode == null){
            avlTreeNode = new AVLTreeNode(val);
            size ++;
            return avlTreeNode;
        }

        if (val < avlTreeNode.data) {
            // 如果插入的值小于当前节点的值,则插入到左子树中
            avlTreeNode.leftChild = insert(avlTreeNode.leftChild, val);
            // 如果左子树与右子树的高度差大于1,则需要进行平衡调整,这里使用左子树的高度减右子树的高度,节点为空高度为-1
            if (getHeight(avlTreeNode.leftChild) - getHeight(avlTreeNode.rightChild) > 1) {
                // 如果插入的值小于当前节点(即失衡节点)的左子节点的值,即插入的节点在失衡节点的左子节点的左边,则进行LL型旋转
                if (val < avlTreeNode.leftChild.data) {
                    System.out.println("LL型旋转");
                    avlTreeNode = LLRotate(avlTreeNode);
                } else { // 如果插入的值不小于当前节点(即失衡节点)的左子节点的值,即插入的节点在失衡节点的左子节点的右边,则进行LR型旋转
                    System.out.println("LR型旋转");
                    avlTreeNode = LRRotate(avlTreeNode);
                }
            }
        } else { // 如果插入的值不小于当前节点的值,则插入到右子树中
            avlTreeNode.rightChild = insert(avlTreeNode.rightChild, val);
            // 平衡调整
            if (getHeight(avlTreeNode.rightChild) - getHeight(avlTreeNode.leftChild) > 1){
                // 如果插入的值小于等于当前节点(即失衡节点)的右子节点的值,即插入的节点在失衡节点的右子节点的左边,则进行RL型旋转
                if (val < avlTreeNode.rightChild.data){
                    System.out.println("RL型旋转");
                    avlTreeNode = RLRotate(avlTreeNode);
                } else { // 如果插入的值大于当前节点(即失衡节点)的右子节点的值,即插入的节点在失衡节点的右子节点的右边,则进行RR型旋转
                    System.out.println("RR型旋转");
                    avlTreeNode = RRRotate(avlTreeNode);
                }
            }
        }
        // 更新节点的高度,获取左子树与右子树的最大高度,叶子节点高度为0,再加1即为当前节点的高度
        avlTreeNode.height = Math.max(getHeight(avlTreeNode.leftChild), getHeight(avlTreeNode.rightChild)) + 1;
        return avlTreeNode;
    }

    /**
     * LL旋转,30为失衡点(左右子树高度差大于1),对失衡点的左子树,即对结点20进行左旋
     *           30                        20
     *          /  \                      /  \
     *        20  40                   10   30
     *       /  \      --LL旋转--       /   /  \
     *     10   25                   5   25   40
     *    /
     *   56
     */
    private AVLTreeNode LLRotate(AVLTreeNode avlTreeNode) {
        // 将失衡点(30)的左子节点 20 作为新节点
        AVLTreeNode newRoot = avlTreeNode.leftChild;
        // 将新节点(20)的右子节点 25 作为失衡点(30)的左子节点
        avlTreeNode.leftChild = newRoot.rightChild;
        // 失衡点(30)作为新节点(20)的右子节点
        newRoot.rightChild = avlTreeNode;
        // 更新失衡点和新根节点的高度
        avlTreeNode.height = Math.max(getHeight(avlTreeNode.leftChild), getHeight(avlTreeNode.rightChild)) + 1;
        newRoot.height = Math.max(getHeight(newRoot.leftChild), getHeight(newRoot.rightChild)) + 1;
        // 新节点取代原失衡点
        return newRoot;
    }

    /**
     * RR旋转,20为失衡点(左右子树高度差大于1),对失衡点的右子树,即对结点30进行左旋
     *      20                          30
     *     /  \                        /  \
     *    10  30                     20   40
     *       /  \     --RR旋转--     /  \   \
     *      25  40                 10  25  50
     *           \
     *           50
     */
    private AVLTreeNode RRRotate(AVLTreeNode avlTreeNode) {
        // 将失衡点(20)的右子节点 30 作为新节点
        AVLTreeNode newRoot = avlTreeNode.rightChild;
        // 将新节点(30)的左子节点 25 作为失衡点(20)的右子节点
        avlTreeNode.rightChild = newRoot.leftChild;
        // 失衡点(20)作为新节点(30)的左子节点
        newRoot.leftChild = avlTreeNode;
        // 更新失衡点和新根节点的高度
        avlTreeNode.height = Math.max(getHeight(avlTreeNode.leftChild), getHeight(avlTreeNode.rightChild)) + 1;
        newRoot.height = Math.max(getHeight(newRoot.leftChild), getHeight(newRoot.rightChild)) + 1;
        // 新节点取代原失衡点
        return newRoot;
    }

    /**
     * LR旋转,先将失衡点(30)的左子树(20)进行 RR 旋转,再将失衡点(30)进行 LL 旋转
     *           30                         30                    25
     *          /  \                       /  \                  /  \
     *        20  40                     25   40                20  30
     *       /  \      --先RR旋转--      /  \      --再LL旋转--   /   / \
     *     10   25                    20   28                 10  28  40
     *            \                  /
     *             28               10
     */
    private AVLTreeNode LRRotate(AVLTreeNode avlTreeNode) {
        // 将失衡点(30)的左子节点(20)进行 RR 旋转
        avlTreeNode.leftChild = RRRotate(avlTreeNode.leftChild);
        // 将失衡点(30)进行 LL 旋转,并返回新节点代替原失衡点
        return LLRotate(avlTreeNode);
    }

    /**
     * RL旋转,先将失衡点(20)的右子树(30)进行 LL 旋转,再将失衡点(20)进行 RR 旋转
     *      20                          20                       25
     *     /  \                        /  \                     /  \
     *    10  30                     10   25                   20  30
     *       /  \     --LL旋转--             \    --RR旋转--    /   / \
     *      25  40                          30               10  28  40
     *       \                             /  \
     *       28                           28  40
     */
    private AVLTreeNode RLRotate(AVLTreeNode avlTreeNode) {
        // 将失衡点(20)的右子节点(30)进行 LL 旋转
        avlTreeNode.rightChild = LLRotate(avlTreeNode.rightChild);
        // 将失衡点(20)进行 RR 旋转,并返回新节点代替原失衡点
        return RRRotate(avlTreeNode);
    }
}

测试类

public class AVLTreeTest {
    public static void main(String[] args) {
        AVLTree avlTree = new AVLTree();
        avlTree.root = avlTree.insert(avlTree.root, 20);
        avlTree.root = avlTree.insert(avlTree.root, 10);
        avlTree.root = avlTree.insert(avlTree.root, 30);
        avlTree.root = avlTree.insert(avlTree.root, 25);
        avlTree.root = avlTree.insert(avlTree.root, 40);
        avlTree.printTree(avlTree.root);
        System.out.println();

        avlTree.root = avlTree.insert(avlTree.root, 28);
        avlTree.printTree(avlTree.root);
        System.out.println();

        System.out.println("树的高度:" + avlTree.getHeight(avlTree.root));
        System.out.println("树的节点数:" + avlTree.getSize());
    }
}

在这里插入图片描述

4.6 红黑树

4.6.1 概述

  • 节点非红即黑
  • 根节点是黑色的
  • 所有 Null 节点称为叶节点,且颜色为黑色
  • 所有红色节点的子结点都是黑色,即从每个叶子到根的所有路径上不能有两个连续的红色结点
  • 任意节点到其叶节点的所有路径都包含相同数目的黑色节点

AVL 所有节点的左右子树高度差的绝对值不超过1,由于红黑树每个红色节点都必须有两个黑色的子节点,并且从任一节点到叶子节点的所有路径都包含相同数目的黑色节点,则红黑树最差情况下高度比是2:1

插入原则:假如插入节点的颜色为黑色的话,就破坏了红黑树的性质,所以每次插入的首先都是红节点。如果插入节点的父节点是黑色,能维持性质 。如果插入节点的父节点是红色,破坏了性质,需要通过重新着色或旋转,来维持性质

  1. 为根节点
    根节点为空,则直接插入为根节点,颜色为黑色
    在这里插入图片描述

  2. 插入节点的父结点为黑色
    直接插入
    在这里插入图片描述

  3. 插入节点的父结点和叔父节点都是红色节点,先插入新节点(红色),插入节点的父结点、叔父节点和祖父节点都要变色。假如祖父节点为根节点,则变为黑色,否则继续向上更新
    在这里插入图片描述
    在这里插入图片描述

  4. 插入节点的父结点是红色,同时叔父节点是黑色,同时插入节点是其父结点的左子节点,而父结点是祖父节点的左子节点,这时要进行一次LL旋转调整插入节点和其父节点的角色(以父结点为轴)

    这里由于旋转后根节点是红色所以进行了一次变色,为了满足红黑树的性质,叔父节点也进行一次变色

    在这里插入图片描述

  5. 插入节点的父结点是红色,同时叔父节点都是黑色,同时插入节点是其父节点的右子节点,而父结点是祖父节点的右子节点,此时需要对祖父节点进行一次RR旋转(以父结点为轴),变色情况同 4
    在这里插入图片描述

  6. 插入节点的父节点是红色,同时叔父节点都是黑色,同时插入节点是其父节点的右子节点,而父节点又是其父节点的左子节点(LR旋转)。先进行一次RR旋转调换新节点和其父节点的角色,旋转后发现节点符合情况 4,再进行一次LL旋转。变色情况同 4
    在这里插入图片描述

  7. 插入节点的父结点是红色,同时叔父节点是黑色,同时插入节点是其父节点的左子节点,而父节点是祖父节点的右子节点(RL),先进行一次LL旋转变成情况5,再进行一次RR旋转。变色情况同 4
    在这里插入图片描述

整体构建过程
在这里插入图片描述

4.6.2 实现

1、创建节点类

public class RBTreeNode<T extends Comparable<T>> {
    boolean color; // red or black
    T key; // 键
    RBTreeNode leftChildren; // 左子节点
    RBTreeNode rightChildren; // 右子节点
    RBTreeNode parent; // 父节点

    public RBTreeNode(T key, boolean color) {
        this.key = key;
        this.color = color;
    }
}

2、实现类(待改善)

public class RBTree<T extends Comparable<T>> {
    private RBTreeNode<T> mRoot;    // 根结点

    private static final boolean RED   = false;
    private static final boolean BLACK = true;

    public RBTree() {
        mRoot=null;
    }

    private boolean isRed(RBTreeNode<T> node) {
        return ((node!=null)&&(node.color==RED)) ? true : false;
    }
    private boolean isBlack(RBTreeNode<T> node) {
        return !isRed(node);
    }

    // 前序遍历"红黑树"
    private void preOrder(RBTreeNode<T> tree) {
        if(tree != null) {
            System.out.print(tree.key+"-" + tree.color + " ");
            preOrder(tree.leftChildren);
            preOrder(tree.rightChildren);
        }
    }

    public void preOrder() {
        preOrder(mRoot);
    }

    // 中序遍历"红黑树"
    private void inOrder(RBTreeNode<T> tree) {
        if(tree != null) {
            inOrder(tree.leftChildren);
            System.out.print(tree.key+" ");
            inOrder(tree.rightChildren);
        }
    }

    public void inOrder() {
        inOrder(mRoot);
    }


    // 后序遍历"红黑树"
    private void postOrder(RBTreeNode<T> tree) {
        if(tree != null)
        {
            postOrder(tree.leftChildren);
            postOrder(tree.rightChildren);
            System.out.print(tree.key+" ");
        }
    }

    public void postOrder() {
        postOrder(mRoot);
    }


    // (递归实现)查找"红黑树x"中键值为key的节点
    private RBTreeNode<T> search(RBTreeNode<T> x, T key) {
        if (x==null)
            return x;

        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            return search(x.leftChildren, key);
        else if (cmp > 0)
            return search(x.rightChildren, key);
        else
            return x;
    }

    public RBTreeNode<T> search(T key) {
        return search(mRoot, key);
    }

    //  (非递归实现)查找"红黑树x"中键值为key的节点
    private RBTreeNode<T> iterativeSearch(RBTreeNode<T> x, T key) {
        while (x!=null) {
            int cmp = key.compareTo(x.key);

            if (cmp < 0)
                x = x.leftChildren;
            else if (cmp > 0)
                x = x.rightChildren;
            else
                return x;
        }

        return x;
    }

    public RBTreeNode<T> iterativeSearch(T key) {
        return iterativeSearch(mRoot, key);
    }

    // 查找最小结点:返回tree为根结点的红黑树的最小结点。
    private RBTreeNode<T> minimum(RBTreeNode<T> tree) {
        if (tree == null)
            return null;

        while(tree.leftChildren != null)
            tree = tree.leftChildren;
        return tree;
    }

    public T minimum() {
        RBTreeNode<T> p = minimum(mRoot);
        if (p != null)
            return p.key;

        return null;
    }

    // 查找最大结点:返回tree为根结点的红黑树的最大结点。
    private RBTreeNode<T> maximum(RBTreeNode<T> tree) {
        if (tree == null)
            return null;

        while(tree.rightChildren != null)
            tree = tree.rightChildren;
        return tree;
    }

    public T maximum() {
        RBTreeNode<T> p = maximum(mRoot);
        if (p != null)
            return p.key;

        return null;
    }

    // 找结点(x)的后继结点。即,查找"红黑树中数据值大于该结点"的"最小结点"。
    public RBTreeNode<T> successor(RBTreeNode<T> x) {
        // 如果x存在右孩子,则"x的后继结点"为 "以其右孩子为根的子树的最小结点"。
        if (x.rightChildren != null)
            return minimum(x.rightChildren);

        // 如果x没有右孩子。则x有以下两种可能:
        // (01) x是"一个左孩子",则"x的后继结点"为 "它的父结点"。
        // (02) x是"一个右孩子",则查找"x的最低的父结点,并且该父结点要具有左孩子",找到的这个"最低的父结点"就是"x的后继结点"。
        RBTreeNode<T> y = x.parent;
        while ((y!=null) && (x==y.rightChildren)) {
            x = y;
            y = y.parent;
        }

        return y;
    }

    // 找结点(x)的前驱结点。即,查找"红黑树中数据值小于该结点"的"最大结点"。
    public RBTreeNode<T> predecessor(RBTreeNode<T> x) {
        // 如果x存在左孩子,则"x的前驱结点"为 "以其左孩子为根的子树的最大结点"。
        if (x.leftChildren != null)
            return maximum(x.leftChildren);

        // 如果x没有左孩子。则x有以下两种可能:
        // (01) x是"一个右孩子",则"x的前驱结点"为 "它的父结点"。
        // (01) x是"一个左孩子",则查找"x的最低的父结点,并且该父结点要具有右孩子",找到的这个"最低的父结点"就是"x的前驱结点"。
        RBTreeNode<T> y = x.parent;
        while ((y!=null) && (x==y.leftChildren)) {
            x = y;
            y = y.parent;
        }

        return y;
    }

    /*
     * 对红黑树的节点(x)进行左旋转
     *
     * 左旋示意图(对节点x进行左旋):
     *      px                              px
     *     /                               /
     *    x                               y
     *   /  \      --(左旋)-.             / \                #
     *  lx   y                          x  ry
     *     /   \                       /  \
     *    ly   ry                     lx  ly
     *
     *
     */
    private void leftRotate(RBTreeNode<T> rbTreeNode) {
        // 将当前节点的右子节点设为新节点
        RBTreeNode<T> newRoot = rbTreeNode.rightChildren;
        // 将新节点的左子节点设为当前节点的右子节点
        rbTreeNode.rightChildren = newRoot.leftChildren;

        // 将 “x的父亲” 设为 “y的父亲”
        newRoot.parent = rbTreeNode.parent;

        if (rbTreeNode.parent == null) {
            this.mRoot = newRoot;            // 如果 “x的父亲” 是空节点,则将y设为根节点
        } else {
            if (rbTreeNode.parent.leftChildren == rbTreeNode)
                rbTreeNode.parent.leftChildren = newRoot;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
            else
                rbTreeNode.parent.rightChildren = newRoot;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
        }

        // 将 “x” 设为 “y的左孩子”
        newRoot.leftChildren = rbTreeNode;
    }

    /*
     * 对红黑树的节点(y)进行右旋转
     *
     * 右旋示意图(对节点y进行左旋):
     *            py                               py
     *           /                                /
     *          y                                x
     *         /  \      --(右旋)-.            /  \                     #
     *        x   ry                           lx   y
     *       / \                                   / \                   #
     *      lx  rx                                rx  ry
     *
     */
    private void rightChildrenRotate(RBTreeNode<T> y) {
        // 设置x是当前节点的左孩子。
        RBTreeNode<T> x = y.leftChildren;

        // 将 “x的右孩子” 设为 “y的左孩子”;
        // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
        y.leftChildren = x.rightChildren;
        if (x.rightChildren != null)
            x.rightChildren.parent = y;

        // 将 “y的父亲” 设为 “x的父亲”
        x.parent = y.parent;

        if (y.parent == null) {
            this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
        } else {
            if (y == y.parent.rightChildren)
                y.parent.rightChildren = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
            else
                y.parent.leftChildren = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
        }

        // 将 “y” 设为 “x的右孩子”
        x.rightChildren = y;

        // 将 “y的父节点” 设为 “x”
        y.parent = x;
    }

    // 插入节点
    public void insert(T key) {
        insert(new RBTreeNode<T>(key, BLACK));
    }

    // 插入节点后,修正红黑树
    private void insert(RBTreeNode<T> node) {
        int cmp;
        RBTreeNode<T> y = null;
        RBTreeNode<T> x = this.mRoot;

        // 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
        while (x != null) {
            y = x;
            cmp = node.key.compareTo(x.key); //当前key h和插入的key进行比较
            if (cmp < 0)
                x = x.leftChildren;
            else
                x = x.rightChildren;
        }

        node.parent = y;
        if (y!=null) {
            cmp = node.key.compareTo(y.key); //判断插入到左边还是右边
            if (cmp < 0)
                y.leftChildren = node;
            else
                y.rightChildren = node;
        } else {
            this.mRoot = node;
        }

        // 2. 设置节点的颜色为红色
        node.color = RED;

        // 3. 将它重新修正为一颗二叉查找树
        insertFixUp(node);
    }

    /*
     * 红黑树插入修正函数
     *
     * 在向红黑树中插入节点之后(失去平衡),再调用该函数;
     * 目的是将它重新塑造成一颗红黑树。
     *1.Z是根节点(即Z插入前是一颗空树)
     * 2.Z的叔节点是红色的
     * 3.Z的叔节点是黑色的,并且局部呈现三角行(左右三角)
     * 3.Z的叔节点是黑色的,并且局部呈现直线角行(左右直线)
     *
     * 参数说明:
     *     node 插入的结点
     *
     */
    private void insertFixUp(RBTreeNode<T> addNode) {
        RBTreeNode<T> parent, gparent;

        // 若“父节点存在,并且父节点的颜色是红色”
        while (((parent = addNode.parent)!=null) && isRed(parent)) {
            gparent = parent.parent;

            //若“父节点”是“祖父节点的左孩子”
            if (parent == gparent.leftChildren) {
                // Case 1条件:叔叔节点是红色
                RBTreeNode<T> uncle = gparent.rightChildren;
                if ((uncle!=null) && isRed(uncle)) {
                    uncle.color = BLACK;
                    parent.color = BLACK;
                    gparent.color = RED;
                    addNode = gparent;
                    continue;
                }

                // Case 2条件:叔叔是黑色,且当前节点是右孩子
                if (parent.rightChildren == addNode) {
                    RBTreeNode<T> tmp;
                    leftRotate(parent);
                    tmp = parent;
                    parent = addNode;
                    addNode = tmp;
                }

                // Case 3条件:叔叔是黑色,且当前节点是左孩子。
                parent.color = BLACK;
                gparent.color = RED;
                rightChildrenRotate(gparent);
            } else {    //若“z的父节点”是“z的祖父节点的右孩子”
                // Case 1条件:叔叔节点是红色
                RBTreeNode<T> uncle = gparent.leftChildren;
                if ((uncle!=null) && isRed(uncle)) {
                    uncle.color = BLACK;
                    parent.color = BLACK;
                    gparent.color = RED;
                    addNode = gparent;
                    continue;
                }

                // Case 2条件:叔叔是黑色,且当前节点是左孩子
                if (parent.leftChildren == addNode) {
                    RBTreeNode<T> tmp;
                    rightChildrenRotate(parent);
                    tmp = parent;
                    parent = addNode;
                    addNode = tmp;
                }

                // Case 3条件:叔叔是黑色,且当前节点是右孩子。
                parent.color = BLACK;
                gparent.color = RED;
                leftRotate(gparent);
            }
        }

        // 将根节点设为黑色
        mRoot.color = BLACK;
    }

    /*
     * 红黑树删除修正函数
     *
     * 在从红黑树中删除插入节点之后(红黑树失去平衡),再调用该函数;
     * 目的是将它重新塑造成一颗红黑树。
     *
     *
     *
     * 参数说明:
     *     node 待修正的节点
     */
    private void removeFixUp(RBTreeNode<T> fixNode, RBTreeNode<T> parent) {
        RBTreeNode<T> other;

        while ((fixNode==null || isBlack(fixNode)) && (fixNode != this.mRoot)) {
            if (parent.leftChildren == fixNode) {//删除的节点是左节点
                other = parent.rightChildren;
                if (isRed(other)) {
                    // Case 1: x的兄弟w是红色的
                    other.color = BLACK;
                    parent.color = RED;
                    leftRotate(parent);
                    other = parent.rightChildren;
                }

                if ((other.leftChildren==null || isBlack(other.leftChildren)) &&
                        (other.rightChildren==null || isBlack(other.rightChildren))) {
                    // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
                    other.color = RED;
                    fixNode = parent;
                    parent = fixNode.parent;
                } else {

                    if (other.rightChildren==null || isBlack(other.rightChildren)) {
                        // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
                        other.leftChildren.color = BLACK;
                        other.color = RED;
                        rightChildrenRotate(other);
                        other = parent.rightChildren;
                    }
                    // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
                    other.color = parent.color;
                    parent.color = BLACK;
                    other.rightChildren.color = BLACK;
                    leftRotate(parent);
                    fixNode = this.mRoot;
                    break;
                }
            } else { //删除的节点是右节点

                other = parent.leftChildren;
                if (isRed(other)) {
                    // Case 1: x的兄弟w是红色的
                    other.color = BLACK;
                    parent.color = RED;
                    rightChildrenRotate(parent);
                    other = parent.leftChildren;
                }

                if ((other.leftChildren==null || isBlack(other.leftChildren)) &&
                        (other.rightChildren==null || isBlack(other.rightChildren))) {
                    // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
                    other.color = RED;
                    fixNode = parent;
                    parent = fixNode.parent;
                } else {

                    if (other.leftChildren==null || isBlack(other.leftChildren)) {
                        // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
                        other.rightChildren.color = BLACK;
                        other.color = RED;
                        leftRotate(other);
                        other = parent.leftChildren;
                    }

                    // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
                    other.color = parent.color;


                    parent.color = BLACK;
                    other.leftChildren.color = BLACK;
                    rightChildrenRotate(parent);
                    fixNode = this.mRoot;
                    break;
                }
            }
        }

        if (fixNode!=null)
            fixNode.color = BLACK;
    }

    // 删除结点(delNode),并返回被删除的结点
    private void remove(RBTreeNode<T> delNode) {
        RBTreeNode<T> child, parent;
        boolean color;

        // 被删除节点的"左右孩子都不为空"的情况。
        if ( (delNode.leftChildren!=null) && (delNode.rightChildren!=null) ) {
            // 被删节点的后继节点。(称为"取代节点")
            // 用它来取代"被删节点"的位置,然后再将"被删节点"去掉。
            RBTreeNode<T> replace = delNode;

            // 获取后继节点
            replace = replace.rightChildren;
            while (replace.leftChildren != null)
                replace = replace.leftChildren;

            // "node节点"不是根节点(只有根节点不存在父节点)
            if (delNode.parent!=null) {
                if (delNode.parent.leftChildren == delNode)
                    delNode.parent.leftChildren = replace;
                else
                    delNode.parent.rightChildren = replace;
            } else {
                // "node节点"是根节点,更新根节点。
                this.mRoot = replace;
            }

            // child是"取代节点"的右孩子,也是需要"调整的节点"。
            // "取代节点"肯定不存在左孩子!因为它是一个后继节点。
            child = replace.rightChildren;
            parent = replace.parent;
            // 保存"取代节点"的颜色
            color = replace.color;

            // "被删除节点"是"它的后继节点的父节点"
            if (parent == delNode) {
                parent = replace;
            } else {
                // child不为空
                if (child!=null)
                    child.parent = parent;
                parent.leftChildren = child;

                replace.rightChildren = delNode.rightChildren;
                delNode.rightChildren.parent = replace;
            }

            replace.parent = delNode.parent;
            replace.color = delNode.color;
            replace.leftChildren = delNode.leftChildren;
            delNode.leftChildren.parent = replace;

            if (color == BLACK) //红色直接删除 黑色删除后维护
                removeFixUp(child, parent);

            delNode = null;
            return ;
        }

        if (delNode.leftChildren !=null) {
            child = delNode.leftChildren;
        } else {
            child = delNode.rightChildren;
        }

        parent = delNode.parent;
        // 保存"取代节点"的颜色
        color = delNode.color;

        if (child!=null)
            child.parent = parent;

        // "node节点"不是根节点
        if (parent!=null) {
            if (parent.leftChildren == delNode)
                parent.leftChildren = child;
            else
                parent.rightChildren = child;
        } else {
            this.mRoot = child;
        }

        if (color == BLACK)
            removeFixUp(child, parent);
        delNode = null;
    }

    /*
     * 删除结点(z),并返回被删除的结点
     *
     * 参数说明:
     *     tree 红黑树的根结点
     *     z 删除的结点
     */
    public void remove(T key) {
        RBTreeNode<T> node;

        if ((node = search(mRoot, key)) != null)
            remove(node);
    }

    /*
     * 销毁红黑树
     */
    private void destroy(RBTreeNode<T> tree) {
        if (tree==null)
            return ;

        if (tree.leftChildren != null)
            destroy(tree.leftChildren);
        if (tree.rightChildren != null)
            destroy(tree.rightChildren);

        tree=null;
    }

    public void clear() {
        destroy(mRoot);
        mRoot = null;
    }

    /*
     * 打印"红黑树"
     *
     * key        -- 节点的键值
     * direction  --  0,表示该节点是根节点;
     *               -1,表示该节点是它的父结点的左孩子;
     *                1,表示该节点是它的父结点的右孩子。
     */
    private void print(RBTreeNode<T> tree, T key, int direction) {

        if(tree != null) {

            if(direction==0)    // tree是根节点
                System.out.printf("%2d(B) is root\n", tree.key);
            else                // tree是分支节点
                System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"B", key, direction==1?"rightChildren" : "leftChildren");

            print(tree.leftChildren, tree.key, -1);
            print(tree.rightChildren,tree.key,  1);
        }
    }

    public void print() {
        if (mRoot != null)
            print(mRoot, mRoot.key, 0);
    }
}

3、测试类

public class RBTreeNode<T extends Comparable<T>> {
    boolean color; // red or black
    T key; // 键
    RBTreeNode leftChildren; // 左子节点
    RBTreeNode rightChildren; // 右子节点
    RBTreeNode parent; // 父节点

    public RBTreeNode(T key, boolean color) {
        this.key = key;
        this.color = color;
    }
}

在这里插入图片描述

posted @ 2022-12-21 11:59  凡223  阅读(51)  评论(0编辑  收藏  举报