基本数据结构(数组、链表、队列、栈、堆、树)和对应的力扣题

1、数组 Array

扩容

在Java中,当需要将一个数组的大小扩大时,通常会使用Arrays.copyOf()System.arraycopy()方法来实现。以下是使用System.arraycopy()方法进行数组扩容的示例:

假设我们有一个长度为originalSize的原始数组originalArray,现在需要将其扩大到newSize

  1. 首先,创建一个新的目标数组newArray,其长度为newSize
int[] originalArray = {1, 2, 3, 4, 5};
int originalSize = originalArray.length;
int newSize = originalSize * 2; // 假设要将数组大小扩大为原来的两倍
int[] newArray = new int[newSize];
  1. 使用System.arraycopy()方法将原始数组的元素复制到新数组中:
System.arraycopy(originalArray, 0, newArray, 0, originalSize);

这里的参数解释如下:

  • originalArray:原始数组
  • 0:原始数组的起始位置(从0开始)
  • newArray:目标数组
  • 0:目标数组的起始位置(从0开始)
  • originalSize:要复制的元素数量
  1. 对于新数组中超出原始数组长度的部分,你可以根据需要进行初始化,例如将其填充为0:
for (int i = originalSize; i < newSize; i++) {
    newArray[i] = 0;
}

示例代码

int[] originalArray = {1, 2, 3, 4, 5};
int originalSize = originalArray.length;
int newSize = originalSize * 2;
int[] newArray = new int[newSize];

System.arraycopy(originalArray, 0, newArray, 0, originalSize);

for (int i = originalSize; i < newSize; i++) {
    newArray[i] = 0;
}

// 打印新数组
for (int i : newArray) {
    System.out.print(i + " ");
}

输出结果为:

1 2 3 4 5 0 0 0 0 0

哈希表

给与默认值无需 if ==> getOrDefault

    public static int totalFruit(int[] fruits) {
        Map<Integer, Integer> map = new HashMap<>();
        int res = 0;
        for (int i = 0, j = 0; i < fruits.length; i++) {
            int x = fruits[i];
            map.put(x, map.getOrDefault(x, 0) + 1); // !!!!!!!!
            while (map.size() > 2) {
                int y = fruits[j++];
                map.put(y, map.get(y) - 1);
                if (map.get(y) == 0) {
                    map.remove(y);
                }
            }
            res = Math.max(res, i - j + 1);
        }
        return res;
    }

2、链表 ListNode

节拍思想

	/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {

    // 新知识get!
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 新思想:节拍
        if(headA == null || headB == null){
            return null;
        }
        ListNode pa = headA, pb = headB;
        // 可以这样理解  如果一样长 有交点 肯定同时到达  没有交点同时为null
        // 不一样长 即节拍不同步 进行交换路线完成同长路线
        while(pa != pb){
            // 这里转向B的路线 进行同步
            pa = pa == null ? headB : pa.next;
            // 转向A 进行同步
            pb = pb == null ? headA : pb.next;
        }
        return pa;
    }

    // // 哈希算法
    // public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    //     Set<ListNode> set = new HashSet<>();
    //     // 先把 A 遍历一遍全部加入 set中
    //     ListNode p = headA;
    //     while(p != null){
    //         set.add(p);
    //         p = p.next;
    //     }
    //     // 在遍历 B 进行比较
    //     p = headB;
    //     while(p != null){
    //         if(set.contains(p)){
    //             return p;
    //         }
    //         p = p.next;
    //     }
    //     return null;
    // }
}

链表中寻找环(弗洛伊德龟兔赛跑算法)

image-20230907190919957

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
        // 快慢指针判断是否有环
        ListNode p1 = head, p2 = p1;

        while(p2 != null && p2.next != null){
            p1 = p1.next;
            p2 = p2.next.next;
            if(p1 == p2){
                return true;
            }
        }
        return false;
    }
}

3、队列 Queue

实现类

在Java中,Queue接口有很多实现类。以下是一些常见的实现类:

  1. LinkedList:LinkedList类实现了Queue接口,因此你可以将LinkedList当成Queue来使用。这种队列按元素插入的顺序进行排序。
  2. PriorityQueue:PriorityQueue保存了一个元素列表,其元素可以自然排序或者通过提供的Comparator在队列实施排序。
  3. ArrayDeque:ArrayDeque类也实现了Queue接口,它在处理非线程安全的大型数据集合时比LinkedList更高效。
  4. LinkedBlockingQueue:LinkedBlockingQueue是一个线程安全的队列,它实现了BlockingQueue接口,LinkedBlockingQueue内部以一个链表形式进行元素的存储。
  5. ArrayBlockingQueue:ArrayBlockingQueue是一个线程安全的队列,它实现了BlockingQueue接口,它是基于数组结构实现的,它内部采用数组结构来存储元素。
  6. SynchronousQueue:SynchronousQueue是一个没有容量的阻塞队列。每个插入操作必须等待一个相应的删除操作,反之亦然。并发线程可以通过该队列互相等待彼此释放资源。
  7. ConcurrentLinkedDeque:ConcurrentLinkedDeque类是一个双端队列,在处理非线程安全的大型数据集合时比LinkedList更高效。

这些队列的使用取决于你的具体需求。例如,如果你需要在多线程环境下操作,那么 LinkedBlockingQueue 或者 ArrayBlockingQueue 可能是更好的选择。如果你需要一个大型的非线程安全的队列,那么 ArrayDeque 或者 LinkedList 可能是更好的选择。

offer() 和 add() 的区别:

两者都是在队列尾部加入元素,add是在队列满时抛出异常,offer是返回false,成功都是返回true,按需调用即可!

这里注意实现类和子类,new的时候要注意!

常用方法

Java中的Queue接口提供了许多常用的方法,以下是一些常用的方法:

  1. add(E e):将指定的元素添加到队列中。
  2. remove():移除并返回队列中的第一个元素。如果队列为空,则返回null。
  3. element():返回队列中的第一个元素,但不移除它。如果队列为空,则返回null。
  4. offer(E e):将指定的元素添加到队列中,如果队列已满,则返回false。
  5. poll():移除并返回队列中的第一个元素,如果队列为空,则返回null。
  6. peek():返回队列中的第一个元素,但不移除它,如果队列为空,则返回null。
  7. isEmpty():判断队列是否为空。
  8. isFull():判断队列是否已满(对于有容量限制的队列来说)。
  9. size():返回队列中元素的数量。
  10. clear():移除队列中的所有元素。
  11. contains(Object o) :判断队列中是否包含指定的元素。
  12. iterator():返回队列的迭代器。

以上是Queue接口中常用的方法,但具体使用方法要根据具体的实现类来定。例如,如果使用LinkedList实现Queue接口,则add()方法可以在链表的尾部添加元素,而remove()方法将移除并返回链表的第一个元素。

Deque

Deque是Java中的双端队列接口,它表示元素按照顺序排序的线性集合。Deque接口不能直接实例化,它的实现类有LinkedList、ArrayDeque、LinkedBlockingDeque等,其中LinkedList是最常用的。

4、栈 Stack

实现类

在Java中,Stack接口有一个主要的实现类,那就是java.util.Stack。这个类是Java集合框架的一部分,提供了栈(后进先出,Last-In-First-Out,LIFO)的数据结构实现。

java.util.Stack类继承了Vector类,它的操作主要在Vector类的基础上进行了优化,使其更符合栈这种数据结构的特性。

需要注意的是,虽然java.util.StackStack接口的主要实现,但它并不是唯一的实现。理论上,任何实现了Stack接口的类都可以视为Stack接口的实现。

此外,在Java中还有其他的类可以用作栈的实现,比如Deque接口的实现类ArrayDeque。尽管Deque接口和Stack接口有所不同,但是Deque接口的子接口Deque(双端队列)也可以用作栈的实现。在这种情况下,可以使用push()pop()peek()方法,它们提供了与栈相似的行为。

总结来说,Java中Stack接口的主要实现类是java.util.Stack,但其他类如ArrayDeque也可以用作栈的实现。

常用方法

Java的Stack接口是Vector类的子类,继承了Vector的所有方法。然而,Stack接口还为LIFO(后进先出)的数据结构提供了额外的方法。以下是Stack接口中的所有方法:

  1. push(E item): 将项压入此堆栈的顶部。
  2. pop(): 移除此堆栈的顶部元素,并返回被移除的元素。
  3. peek(): 查看此堆栈的顶部元素,但不移除它。
  4. empty(): 测试此堆栈是否为空。
  5. search(Object o): 返回对象在堆栈中的位置,以 1 为基数。
  6. toString(): 返回此堆栈的字符串表示形式。

以上就是Java的Stack接口中所有的方法。需要注意的是,Stack类并没有实现这些方法,而是通过继承Vector类来实现的。在编程实践中,我们通常会直接使用Deque接口的实现类(如ArrayDeque),因为它们提供了更丰富的方法,并且性能通常更好。

逆波兰表达式

150. 逆波兰表达式求值 - 力扣(LeetCode)

image-20230908165442697

class Solution {
    public int evalRPN(String[] tokens) {
        // 利用栈
        Stack<Integer> stack = new Stack<>();
        int res, a, b;
        for(int i = 0; i < tokens.length; i++){
            String t = tokens[i];
            if(t.equals("+")){
                b = stack.pop();
                a = stack.pop();
                res = a + b;
            }else if(t.equals("-")){
                b = stack.pop();
                a = stack.pop();
                res = a - b;
            }else if(t.equals("*")){
                b = stack.pop();
                a = stack.pop();
                res = a * b;
            }else if(t.equals("/")){
                b = stack.pop();
                a = stack.pop();
                res = a / b;
            }else{
                stack.push(Integer.parseInt(t));
                continue;
            }
            stack.push(res);
        }
        return stack.pop();
    }
}

5、双端队列 QueueDeque

deque的全称是double-ended queue,翻译过来就是双端队列,也有人称之为双向队列,这两个名称都可以表示该数据结构。

常用

在Java中,双端队列(Deque)是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列可以在队列任意一端入队和出队。

Java的java.util.Deque接口就代表了这种数据结构,此接口扩展了Queue接口,它将队列的插入和删除操作添加到队列的两端。

以下是一些Deque接口的主要方法:

  • addFirst(E e): 将指定的元素插入到队列的开头。
  • addLast(E e): 将指定的元素插入到队列的末尾。
  • offerFirst(E e): 在队列的开头插入指定的元素。
  • offerLast(E e): 在队列的末尾插入指定的元素。
  • peekFirst(): 获取但不删除队列的第一个元素。
  • peekLast(): 获取但不删除队列的最后一个元素。
  • pollFirst(): 获取并删除队列的第一个元素。
  • pollLast(): 获取并删除队列的最后一个元素。
  • push(E e): 将指定的元素插入到队列的开头。
  • pop(): 获取并删除队列的第一个元素。

注意:pushpoppeekpoll方法也可以用在Queue接口,但是他们在Deque接口中有不同的行为。在Deque中,这些操作都应用在队列的开头,而不是末尾。

在Java中,ArrayDequeLinkedList是实现Deque接口的两个常用类。

实现

可以基于链表和环形数组实现

image-20230908182324729

6、优先队列 PriorityQueue

数组实现

// 基于有序数组实现
// Type parameters: <E>-队列中元素类型,必须实现 Priority接口
public class PriorityQueue2<E extends Priority> implements Queue<E>

如果使用无序数组,实现插入可能快,出列慢于有序数组 最差都是O(n)

有序数组在出列方面有优势,但是在插入和其他操作最差情况下是O(n)

堆实现

image-20230908190617479

image-20230908190942423

解释 !!!

在Java中,PriorityQueue是一种优先队列,它根据具体实现的Comparator或者Comparable来排序元素。在你提到的代码中,‘(a, b) -> a - b’ 是一个Lambda表达式,用于定义Comparator。这个表达式表示的是一个接受两个参数的函数,参数为a和b,函数返回值为a-b。在PriorityQueue中,这将被用来决定元素的优先级。如果a - b的结果是负数,那么元素a的优先级将高于元素b;如果结果是正数,元素a的优先级将低于元素b;如果结果是0,元素a和元素b的优先级相同。

23. 合并 K 个升序链表 - 力扣(LeetCode)

Java中的PriorityQueue类是一个基于优先堆的无界队列。这个优先队列中的元素可以默认自然排序或者通过提供的Comparator在队列实例化的时进行排序。

PriorityQueue的一些重要属性包括:

  1. queue:这是一个数组,用于存储队列中的元素。
  2. size:这是一个整数,表示队列中当前元素的数量。
  3. lock:这是一个用于同步的锁对象。所有访问队列的方法都需要获取这个锁,以防止并发修改。
  4. notEmpty:这是一个等待队列,当队列为空时,线程可以在此等待。
  5. allFull:这是一个等待队列,当队列满时,线程可以在此等待。
  6. comparator:这是一个用于比较队列中元素的比较器。如果没有提供比较器,并且队列中的元素实现了Comparable接口,那么将使用元素的自然顺序。
  7. modCount:这是一个计数器,用于跟踪队列的结构性修改(如添加或删除元素)。
  8. capacity:这是队列的容量。如果队列已满并且尝试添加更多元素,那么会触发扩容操作。

注意,上述属性的名称和具体实现可能因Java版本的不同而略有差异。上述信息基于Oracle的Java 8文档。要获取最准确的信息,应参考您正在使用的Java版本的官方文档。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {

        PriorityQueue<ListNode> pq = new PriorityQueue<>(
            // 通过比较器完成自动排序
            (a, b) -> a.val - b.val
        );

        // 将头节点加入堆中
        for(ListNode head : lists){
            if(head != null){
                pq.offer(head);
            }
        }
        
        ListNode head = new ListNode();
        ListNode cur = head;
        while(!pq.isEmpty()){
            ListNode node = pq.poll(); // 取到最小值
            if(node.next != null){ // 还有子节点
                pq.offer(node.next); // 可能是最小节点 入堆
            }
            cur.next = node;// 把拿出来的最小值合并到新链表
            cur = cur.next;// 指针后移
        }

        return head.next;
    }
}

是的,PriorityQueue在Java中的实现是基于堆数据结构的。它用于处理元素的队列,这些元素根据其自然排序或者比较器比较的结果排序。PriorityQueue保证队列头部总是最小元素。你可以使用PriorityQueue来实现类似于栈的数据结构,但它不是栈,因为它不允许后进先出(LIFO)的操作。

(a, b) -> a.val - b.val 这个lambda表达式是一个比较器(Comparator),用于定义PriorityQueue中元素的排序规则。在这个例子中,它表示将优先队列中的ListNode对象按照其val字段的值进行排序。具体地说,当a.val - b.val的结果为正数时,表示a的val大于b的val,a将被放在b之后;当结果为负数时,表示a的val小于b的val,a将被放在b之前;当结果为零时,a和b的位置不变。

也可以写作

// 后面省略不写就是 a - b 就是小顶堆
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> Integer.compare(b, a));
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);

image-20230910180131665

7、阻塞队列 BlockingQueue

引入

image-20230908220423703

synchronized 关键字 功能少

ReentrantLock 可重入锁, 功能多

Condition接口是一个Java中的接口类,其作用是让线程先等待,当条件允许后,再通过condition唤醒等待的线程。这个接口通常和Lock对象一起使用,因为它是java.util.concurrent包提供的一个接口,而Lock对象是这个包的内部实现。简单来说,Condition可以理解为条件队列,当一个线程满足特定条件时,它就可以从等待状态进入运行状态。

...
	ReentrantLock lock = new ReentrantLock();// 锁对象
	Condition tailWait = lock.newConditioin(); // 条件变量对象 集合
...{
 	...   
	-- lock.lock(); // 加锁 多个线程同时到达时 只有一个线程能抢到 其余线程进入 wait 状态  t2 得一直等到 t1 结束
    -- lock.lockInterruptibly(); // 在阻塞时可以随时打断
    try{
        ..
        tailWaits.await(); // 当前线程加入集合 并且让此线程阻塞 tailWaits.signal()
    }finally{
        lock.unlock(); // 解锁
    }
    ...
}

常用

Java 阻塞队列(Blocking Queue)是一种支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程将会阻塞,直到有元素可获取;当队列已满时,尝试添加元素的线程也将阻塞,直到队列有空余空间。

Java 提供了几种不同的阻塞队列,包括:

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  2. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列,但默认大小为 Integer.MAX_VALUE,实际是个无界队列。
  3. PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  4. DelayQueue:一个使用优先级队列实现的无界阻塞队列,用于存放实现了 Delayed 接口的对象,只有在延迟期满时才能从中提取元素。
  5. SynchronousQueue:一个不存储元素的阻塞队列,也就是直接交接队列。此队列的每个插入操作必须等待一个相应的删除操作,反之亦然。

这些阻塞队列都实现了 BlockingQueue 接口,它们的方法声明如下:

public interface BlockingQueue<E> extends Queue<E> {

    // 将指定的元素插入此队列中
    boolean add(E e);

    // 将指定的元素插入此队列中
    boolean offer(E e);

    // 检索并删除此队列的头部
    E take();

    // 检索并删除此队列的头部,在元素变得可用之前一直等待
    E poll(long timeout, TimeUnit unit);
}

使用阻塞队列可以很好地解决多线程间的数据共享问题,从而实现线程间的合作。

原子变量(AtomicInteger)

Java中的AtomicInteger是一个原子类,用于在多线程环境中进行线程安全的整数操作。AtomicInteger位于java.util.concurrent.atomic包中,它提供了原子操作的方法,如get()、set()、getAndIncrement()、getAndDecrement()等,这些方法可以保证多个线程在并发访问AtomicInteger时不会出现数据不一致的情况。

AtomicInteger的实现基于CAS(Compare and Swap)算法,该算法可以保证原子操作的正确性和高效性。CAS算法的核心思想是将要执行的操作和当前内存中的值进行比较,如果两者相同,则执行操作并更新内存中的值,否则重试直到执行成功。

下面是一个使用AtomicInteger的例子:

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

在这个例子中,Counter类使用AtomicInteger来实现一个线程安全的计数器。increment()方法使用AtomicInteger的incrementAndGet()方法来增加计数器的值,getCount()方法使用get()方法获取计数器的当前值。由于AtomicInteger保证了线程安全性,因此多个线程可以同时调用increment()方法而不会导致计数器的值出现错误。

BlockingQueue实现类

Java中的BlockingQueue是一个接口,它继承了Queue接口。BlockingQueue提供了一种阻塞式的队列操作方式,当队列为空时,从队列中获取元素的操作将会被阻塞,直到队列中有新的元素添加进来;当队列已满时,向队列中添加元素的操作将会被阻塞,直到队列中有元素被移除。

BlockingQueue常用的实现类有ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue等。

ArrayBlockingQueue是一个基于数组实现的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue是一个基于链表实现的无界阻塞队列,此队列按照FIFO原则对元素进行排序。线程安全,内部使用了ReentrantLock锁。

PriorityBlockingQueue是一个具有优先级的无界阻塞队列,这种队列按照元素的优先级对元素进行排序。

使用BlockingQueue可以简化多线程编程中的并发控制,提高程序的性能和可靠性。

8、堆 Heap

Floyd 建堆算法

建堆 heapify()

image-20230910110313832

大顶堆 MaxHeap

  1. 找到最后一个非叶子节点
  2. 从后向前,对每个节点执行下潜

非叶子节点计算公式

就是找到第一个非叶子节点,当然也是在有效索引范围之内

size / 2 - 1;

根据二叉树的性质,非叶子节点的个数可以通过以下公式进行推导

n1 + n2 = floor( n / 2 ) = ceiling( (n – 1) / 2 )

其中,n表示二叉树中节点的个数,n1和n2分别表示二叉树中左子树和右子树的节点个数。

对于一个有n个节点的二叉树,其中非叶子节点的个数为x,则有:

x = n - (n mod 2) - 1

其中mod是取余运算符。

如果将公式中的n用n-1替换,则有:

x = (n-1) - (n-1 mod 2) - 1

化简后得:

x = floor((n-1) / 2)

这个公式可以用来快速计算二叉树中非叶子节点的个数。

左孩子计算公式

left = parent * 2 + 1;

右孩子计算公式

如果有的话,就是在左孩子下一索引位置

right = left + 1;

源代码示例

重点掌握:heapify 建堆 down 下浮 up 上浮

package com.zky;

// 1. 找到最后一个非叶子节点
// 2. 从后向前,对每个节点执行下潜

import java.util.Arrays;

public class MaxHeap {
    int[] array;
    int size;

    public MaxHeap(int capacity) {
        this.array = new int[capacity];
    }

    // 建堆
    private void heapify() {
        // 找到一个非叶子节点 size / 2 - 1
        for (int i = size / 2 - 1; i >= 0; i--) {
            down(i);
        }
    }

    // 删除堆顶元素
    public int poll() {
        // 记录一下堆顶元素
        int top = array[0];
        // 和最后一个元素进行交换
        swap(0,size - 1);
        size--; // 基本类型无需重置为 null 引用类型需要 help gc
        // 交换完之后在进行下潜
        down(0);
        return top;
    }

    // 删除指定索引的元素
    public int pool(int index) {
        int deleted = array[index];
        swap(index, size - 1);
        size--;
        down(index);
        return deleted;
    }

    // 替换堆顶元素
    public void replace(int replaced){
        array[0] = replaced;
        down(0);
    }

    // 获取堆顶元素
    public int peek() {
        return array[0];
    }

    // 堆的尾部添加元素
    public boolean offer(int offered) {
        if(size == array.length){
            return false;
        }
        up(offered);
        size++;
        return true;
    }

    // 将inserted 元素上浮:offered 小于父元素或到堆顶
    private void up(int offered) {
        int child = size;
        while (child > 0) {
            int parent = (child - 1) / 2;
            if(offered > array[parent]){
                array[child] = array[parent];
                child = parent;
            }else{
                break;
            }
        }
        array[child] = offered;
    }

    // 构造方法
    public MaxHeap(int[] array) {
        this.array = array;
        this.size = array.length;
        // 建堆
        heapify();
    }

    // 将 parent 索引处的元素下潜: 与两个孩子较大者交换,直至没孩子或者孩子没他大
    private void down(int parent) {
        int left = parent * 2 + 1;
        int right = left + 1;
        int max = parent;
        // 有效索引范围
        if (left < size && array[left] > array[max]) {
            max = left;
        }
        if (right < size && array[right] > array[max]) {
            max = right;
        }
        // 判断是否找到更大的孩子
        if (max != parent) { // 找到了更大的孩子
            // 交换孩子
            swap(max, parent);
            // 递归调用继续找 起点就是更大的孩子
            down(max);
        }
    }

    // 交换两索引处的元素
    private void swap(int i, int j) {
        int t = array[i];
        array[i] = array[j];
        array[j] = t;
    }

    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 5, 6, 7};
        MaxHeap maxHeap = new MaxHeap(array);
        System.out.println(Arrays.toString(maxHeap.array));
    }

}

小顶堆 MinHeap

和大顶堆只有以下两个方法不同,简单说就是条件变一下

	...
    // 将inserted 元素上浮:offered 小于父元素或到堆顶
    private void up(int offered) {
        int child = size;
        while (child > 0) {
            int parent = (child - 1) / 2;
            if(offered < array[parent]){
                array[child] = array[parent];
                child = parent;
            }else{
                break;
            }
        }
        array[child] = offered;
    }
    
    ...

    // 将 parent 索引处的元素下潜: 与两个孩子较小者交换,直至没孩子或者孩子没他小
    private void down(int parent) {
        int left = parent * 2 + 1;
        int right = left + 1;
        int min = parent;
        // 有效索引范围
        if (left < size && array[left] < array[min]) {
            min = left;
        }
        if (right < size && array[right] < array[min]) {
            min = right;
        }
        // 判断是否找到更大的孩子
        if (min != parent) { // 找到了更大的孩子
            // 交换孩子
            swap(min, parent);
            // 递归调用继续找 起点就是更大的孩子
            down(min);
        }
    }
    ...

堆排序

算法描述:

  • heapify 建立大顶堆
  • 将堆顶与堆底交换(最大元素被交换到堆底),缩小并下潜调整堆
  • 重复第二步直至堆里只剩下一个元素
    public static void main(String[] args) {
        int[] array = {2,3,1,10,4,2,8,2,9,3};
        MaxHeap heap = new MaxHeap(array);
        System.out.println(Arrays.toString(heap.array));
		
        while(heap.size > 1){
            heap.swap(0, heap.size - 1);
            heap.size--;
            heap.down(0);
        }
        System.out.println(Arrays.toString(heap.array));
    }

利用优先队列(底层就是堆实现)

295. 数据流的中位数 - 力扣(LeetCode)

class MedianFinder {
    // 大顶堆
    PriorityQueue<Integer> maxHeap;
    // 小顶堆
    PriorityQueue<Integer> minHeap;

    public MedianFinder() {
        maxHeap = new PriorityQueue<>((a, b) -> b - a); 
        minHeap = new PriorityQueue<>((a, b) -> a - b); 
    }
    
    public void addNum(int num) {
        if(maxHeap.size() == minHeap.size()){
            minHeap.offer(num);
            maxHeap.offer(minHeap.poll());
        }else{
            maxHeap.offer(num);
            minHeap.offer(maxHeap.poll());
        }
    }
    
    public double findMedian() {
        if(maxHeap.size() == minHeap.size()){
            return (maxHeap.peek() + minHeap.peek()) / 2.0;
        }else{
            return maxHeap.peek();
        }
    }
}

9、二叉树 Tree

基本知识

二叉树是树形结构的一个重要类型,它有以下几个基本概念:

  1. 节点:二叉树中的每个元素称为一个节点。每个节点最多只能有两个子节点,分别称为左子节点和右子节点。
  2. 根节点:二叉树中只有一个节点,即树的起点,称为根节点。
  3. 子树:从任意一个节点出发,向它的一个子节点或两个子节点所形成的子图称为子树。
  4. 左子树和右子树:对于任意一个节点,它的左子节点和右子节点分别形成的子树称为左子树和右子树。
  5. 父节点和子节点:对于任意一个节点,它的父节点就是它的一个或两个子节点的根节点。
  6. 空树:没有任何节点的二叉树称为空树。
  7. 叶节点:没有子节点的节点称为叶节点或终端节点。
  8. 非叶节点:有子节点的节点称为非叶节点或内部节点。
  9. 二叉树的存储结构:二叉树的每个节点可以用一个结构体表示,其中包含三个域:数据域、指向左孩子的指针和指向右孩子的指针。这种存储结构简化了二叉树的操作。
  10. 二叉树的遍历:遍历二叉树的所有节点,有三种遍历方式:前序遍历(先访问根节点,然后左子树,最后右子树)、中序遍历(先访问左子树,然后根节点,最后右子树)和后序遍历(先访问左子树,然后右子树,最后根节点)。

如果想要了解更多有关二叉树的概念,可以查阅计算机科学或数据结构的教材或专业文献。

性质

  1. 二叉树中,第 i 层最多有 2i-1 个结点。
  2. 如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
  3. 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。

完全二叉树

完全二叉树(Complete Binary Tree)是一类特殊的二叉树,其具有以下特征:

  1. 如果二叉树的深度为h,除第h层外,其它各层(1~h-1)的结点数都达到最大个数,第h层所有的结点都连续集中在最左边。
  2. 对于深度为K的、有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。也就是说,完全二叉树中的每个节点都有与之对应的满二叉树中的节点位置。

这也是完全二叉树效率较高的原因,因为在完全二叉树中,除最后一层外,其它各层的节点数都达到了最大,没有空节点,这意味着我们可以利用更多的节点来存储数据,从而减少了空间的浪费。

​ 对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号(如图 3a)),对于任意一个结点 i ,完全二叉树还有以下几个结论成立:

  1. 当 i>1 时,父亲结点为结点 [i/2] 。(i=1 时,表示的是根结点,无父亲结点)
  2. 如果 2i>n(总结点的个数) ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2i 。
  3. 如果 2i+1>n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2i+1 。

满二叉树

如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。

满二叉树除了满足普通二叉树的性质,还具有以下性质:

  1. 满二叉树中第 i 层的节点数为 2n-1 个。
  2. 深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。
  3. 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
  4. 具有 n 个节点的满二叉树的深度为 log2(n+1)。

遍历

image-20230910181416085

广度优先遍历

  • 数组实现,直接遍历数组就行,默认就是层序遍历。

  • 使用队列是面向对象(TreeNode)的方法,进行层序遍历:

    大概就是先加入根节点,在while循环里,先存入本层节点,再判断是否还有左右节点,有就加入队列,在下次循环中根据队列的大小就能知道上一层有多少节点,反复执行就行,直到队列为空,意味着取出的节点为叶子结点,遍历完毕。

    102. 二叉树的层序遍历 - 力扣(LeetCode)

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode() {}
     *     TreeNode(int val) { this.val = val; }
     *     TreeNode(int val, TreeNode left, TreeNode right) {
     *         this.val = val;
     *         this.left = left;
     *         this.right = right;
     *     }
     * }
     */
    class Solution {
        public List<List<Integer>> levelOrder(TreeNode root) {
            // 结果封装
            List<List<Integer>> res = new ArrayList<>();
    
            Queue<TreeNode> queue = new ArrayDeque<>();
            if(root != null){
                queue.add(root);
            }
            while(!queue.isEmpty()){
                // 存储节点元素值
                List<Integer> level = new ArrayList<>();
                // 循环遍历队列中所有的节点 找他们的子节点
                int n = queue.size();// 当前队列中上一节点的子节点
                for(int i = 0; i < n; i++){
                    TreeNode node = queue.poll();// 依次弹出节点
                    // 存入相关值
                    level.add(node.val);
                    // 如果有孩子就继续入队
                    if(node.left != null){
                        queue.add(node.left);
                    }
                    if(node.right != null){
                        queue.add(node.right);
                    }
                }
                // 将存储节点元素值得集合放入结果集合中
                res.add(level);
            }
    
            return res;
        }
    }
    

深度优先遍历

image-20230910183158554

前序遍历(根左右)pre-order

124356

中序遍历(左根右)in-order

421536

后序遍历(左右根)post-order

425631

递归实现

    // 前序遍历
    static void preOrder(TreeNode node){
        if(node == null) return;
        System.out.print(node.val + "\t");
        preOrder(node.left);
        preOrder(node.right);
    }

    // 中序遍历
    static void inOrder(TreeNode node){
        if(node == null) return;
        inOrder(node.left);
        System.out.print(node.val + "\t");
        inOrder(node.right);
    }

    // 后序遍历
    static void postOrder(TreeNode node){
        if(node == null) return;
        postOrder(node.left);
        postOrder(node.right);
        System.out.print(node.val + "\t");
    }

非递归实现

  • 前序 和 中序遍历
        // 用栈记录来时的路
        LinkedList<TreeNode> stack = new LinkedList<>();

        TreeNode cur = root;// 当前节点
        while(cur != null || !stack.isEmpty()){
            if(cur != null){
//                System.out.print(cur.val + " "); // 前序
                stack.push(cur);// 压入栈,记住来时的路
                cur = cur.left;
            }else{
                TreeNode pop = stack.pop();
                System.out.print(pop.val + " "); // 中序
                cur = pop.right;
            }
        }
  • 后续遍历
        // 用栈记录来时的路
        LinkedList<TreeNode> stack = new LinkedList<>();

        TreeNode cur = root;// 当前节点
        TreeNode pop = null; // 最近一次弹栈的元素 !!!!!!!!
        while (cur != null || !stack.isEmpty()) {
            if (cur != null) {
//                System.out.print(cur.val + " "); // 前序
                stack.push(cur);// 压入栈,记住来时的路
                cur = cur.left;
            } else {
                TreeNode peek = stack.peek(); // 栈顶元素 !!!!!!!!!!!!!!
                if (peek.right == null || peek.right == pop) { // 右子树处理完成 !!!!!!!!!!!
                    pop = stack.pop();
                    System.out.print(pop.val + " "); // 后续 !!!!!
                } else {
                    cur = peek.right;
                }
            }
        }
  • 通用模板
        // 用栈记录来时的路
        LinkedList<TreeNode> stack = new LinkedList<>();

        TreeNode cur = root;// 当前节点
        TreeNode pop = null; // 最近一次弹栈的元素
        while (cur != null || !stack.isEmpty()) {
            // 处理左子树
            if (cur != null) {
                stack.push(cur);
//                System.out.print(cur + " ");// 前序
                cur = cur.left;
            } 
            // 处理右子树
            else {
                TreeNode peek = stack.peek();
                // 没有右子树
                if (peek.right == null) {
//                    System.out.print(peek.val + " "); // 中序
                    pop = stack.pop();
//                    System.out.print(pop.val + " "); // 后序
                }
                // 右子树处理完成
                else if (peek.right == pop) {
                    pop = stack.pop();
//                    System.out.print(pop.val + " "); // 后序
                }
                // 处理右子树
                else {
//                    System.out.print(peek.val + " "); // 中序
                    cur = peek.right;
                }
            }
        }

10、二叉搜索树 BST

binary search tree

特点

  • 树节点增加key属性,用来比较谁大谁小,key不可重复
  • 对于任意一个数节点,它的key比左子树的key都大,同时也比右子树的key都小

相应方法

  • 前驱节点

    image-20230911170202971

  • 后继节点

    image-20230911171913908

  • 删除节点

    image-20230911172544410

示例代码

package com.zky;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

// Binary Search Tree 二叉搜索树
public class BSTTree1 {

    BSTNode root; // 根节点

    // 节点类
    static class BSTNode {
        int key;
        Object value;
        BSTNode left;
        BSTNode right;

        public BSTNode(int key) {
            this.key = key;
        }

        public BSTNode(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public BSTNode(int key, Object value, BSTNode left, BSTNode right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    // 查找关键字对应的值
    // 1、递归
    /*
    public Object get(int key){
        return doGet(key);
    }

    private Object doGet(BSTNode node, int key){
        if(node == null) {
            return null; // 没找到
        }
        if(key < node.key){
            return doGet(node.left, key); // 向左找
        }
        if(key > node.key){
            return doGet(node.right, key); // 向右找
        }
        return node.value; // 找到了
    }
    */
    // 2、非递归
    public Object get(int key) {
        BSTNode node = root;
        while (node != null) {
            if (key < node.key) {
                node = node.left;
            } else if (key > node.key) {
                node = node.right;
            } else {
                return node.value;
            }
        }
        return null;
    }

    // 查找最小关键字对应的值
    public Object min() {
        return min(root);
    }

    public Object min(BSTNode node) {
        if (node == null) return null;
        BSTNode p = node;
        while (p.left != null) {
            p = p.left;
        }
        return p.value;
    }

    // 查找最大关键字对应的值
    public Object max() {
        return max(root);
    }

    private Object max(BSTNode node) {
        if (node == null) return null;
        BSTNode p = node;
        while (p.right != null) {
            p = p.right;
        }
        return p.value;
    }

    // 存储关键字和对应值
    public void put(int key, Object value) {
        BSTNode node = root;
        BSTNode parent = null;
        while (node != null) {
            parent = node;
            if (key < node.key) {
                node = node.left;
            } else if (key > node.key) {
                node = node.right;
            } else {
                // 1、有 key 更新
                node.value = value;
                return;
            }
        }
        // 2、无 key 新增
        if (parent == null) {
            root = new BSTNode(key, value);
            return;
        }
        if (key < parent.key) {
            parent.left = new BSTNode(key, value);
        } else {
            parent.right = new BSTNode(key, value);
        }
    }

    // 查找关键字的前驱值
    public Object successor(int key) {
        BSTNode p = root;
        // 自左而来的祖先 只记住从左来的节点,也就是向右走时,更新前的那个节点的值
        BSTNode ancestorFromLeft = null;
        while (p != null) {
            if (key < p.key) {
                p = p.left;
            } else if (key > p.key) {
                ancestorFromLeft = p;
                p = p.right;
            } else {
                break;
            }
        }

        // 没找到节点的情况
        if (p == null) {
            return null;
        }
        // 找到的情况

        // 如果有左子树 此时前任就是左子树的最大值
        // 没有左子树 若离他最近、自左而来的祖先就是前任
        if (p.left != null) {
            return max(p.left);
        }
        return ancestorFromLeft != null ? ancestorFromLeft.value : null;
    }

    // 查找关键字的后继值
    public Object predecessor(int key) {
        BSTNode p = root;
        // 自左而来的祖先 只记住从左来的节点,也就是向右走时,更新前的那个节点的值
        BSTNode ancestorFromRight = null;
        while (p != null) {
            if (key < p.key) {
                ancestorFromRight = p;
                p = p.left;
            } else if (key > p.key) {
                p = p.right;
            } else {
                break;
            }
        }

        // 没找到节点的情况
        if (p == null) {
            return null;
        }
        // 找到的情况
        // 与前驱是相反的  找后继 找右子树最小的 没有右子树去左边找 找自右而来的祖先是后任
        if (p.right != null) {
            return min(p.right);
        }
        return ancestorFromRight != null ? ancestorFromRight.value : null;
    }

    // 根据关键字删除
    public Object delete(int key) {
        BSTNode p = root;
        BSTNode parent = null; // 待删除节点的父亲
        while (p != null) {
            if (key < p.key) {
                parent = p;
                p = p.left;
            } else if (key > p.key) {
                parent = p;
                p = p.right;
            } else {
                break;
            }
        }
        // 没找到
        if (p == null) {
            return null;
        }
        // 找到就进行删除
        if (p.left == null) {
            // 情况1
            shift(parent, p, p.right);
        } else if (p.right == null) {
            // 情况2
            shift(parent, p, p.left);
        } else {
            // 情况4
            // 4.1 被删除节点找后继 s节点
            BSTNode s = p.right;
            BSTNode sParent = p; // 后继父亲
            while (s.left != null) {
                sParent = s;
                s = s.left;
            }
            if (sParent != p) { // 不相邻
                // 4.2 删除和后继不相邻 处理后继的后事
                shift(sParent, s, s.right); // 不可能有左孩子
                s.right = p.right;
            }
            // 4.3 后继取代被删除节点
            shift(parent, p, s);
            s.left = p.left;
        }
        return p.value;
    }

    // 托孤方法

    /**
     * @param parent  被删除节点的父亲
     * @param deleted 被删除的节点
     * @param child   被顶上去的节点
     */
    private void shift(BSTNode parent, BSTNode deleted, BSTNode child) {
        // 删除的就是根节点
        if (parent == null) {
            root = child;
        } else if (deleted == parent.left) {
            parent.left = child;
        } else {
            parent.right = child;
        }
    }

    // 递归删除
    private Object delete1(int key) {
        ArrayList<Object> result = new ArrayList<>();
        root = doDelete(root, key, result);
        return result.isEmpty() ? null : result.get(0);
    }

    // 返回值是剩下的孩子
    private BSTNode doDelete(BSTNode node, int key, ArrayList<Object> result) {
        if (node == null) {
            return null;
        }
        if (key < node.key) {
            node.left = doDelete(node.left, key, result);
            return node;
        }
        if (key > node.key) {
            node.right = doDelete(node.right, key, result);
            return node;
        }
        result.add(node.value);
        // 1、只有右孩子
        if (node.left == null) {
            return node.right;
        }
        // 2、只有左孩子
        if (node.right == null) {
            return node.left;
        }
        // 3、两个孩子都有
        BSTNode s = node.right;
        while (s.left != null) {
            s = s.left;
        }
        s.right = doDelete(node.right, s.key, new ArrayList<>());
        s.left = node.left;
        return s;
    }

    // 二叉搜索树 中序遍历

    // 找 < key 的所有value
    public List<Object> less(int key) {
        ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.left;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if (pop.key < key) {
                    result.add(pop);
                } else {
                    break;
                }
                p = pop.right;
            }
        }
        return result;
    }

    // 找 > key 的所有value
    public List<Object> greater(int key) {
        // 中序遍历
        /*        ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.left;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if (pop.key > key) {
                    result.add(pop);
                } else {
                    break;
                }
                p = pop.right;
            }
        }*/
        // 反向中序遍历
        ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.right;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if(pop.key > key){
                    result.add(pop.value);
                }else{
                    break;
                }
                p = pop.left;
            }
        }
        return result;
    }

    // 找 >= key1 且 <= key2 的所有值
    public List<Object> between(int key1, int key2) {
        ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.left;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if (pop.key >= key1 && pop.key <= key2) {
                    result.add(pop);
                } else if (pop.key > key2) {
                    break;
                }
                p = pop.right;
            }
        }
        return result;
    }
}

11、平衡二叉搜索树 AVL

image-20230914122521050

概念

AVL树中的“AVL”是英文单词“Adelson-Velsky和Landis”的缩写。他们两人于1962年提出了这种自平衡二叉搜索树,并在论文中使用了这个名称。AVL树是一种特殊的二叉搜索树,它的每个节点都保留了平衡因子,以确保树的平衡。在AVL树中进行插入和删除等操作时,可能会打破树的平衡,这时就需要进行旋转操作来恢复平衡。

AV树又叫平衡二叉排序树,它是一棵平衡二叉搜索树。AV树的定义包括以下要点:

  1. 任何节点的左、右子树的高度差都不超过1;
  2. 无论是左子树还是右子树,都必须是AV树。

代码示例(旋转与平衡)

public class AVLTree {
    
    static class AVLNode {
        int key;
        Object value;
        AVLNode left;
        AVLNode right;
        int height = 1; // 节点高度

        public AVLNode(int key) {
            this.key = key;
        }

        public AVLNode(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public AVLNode(int key, Object value, AVLNode left, AVLNode right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    // 求节点的高度
    private int height(AVLNode node) {
        return node == null ? 0 : node.height;
    }

    // 更新节点高度(增删 旋转)
    private void updateHeight(AVLNode node) {
        node.height = Integer.max(height(node.left), height(node.right)) + 1;
    }

    // 平衡因子 (balance factor) = 左子树高度 - 右子树高度
    private int bf(AVLNode node) {
        return height(node.left) - height(node.right);
    }

    // LL
    private AVLNode rightRotate(AVLNode red) {
        AVLNode yellow = red.left;
        red.left = yellow.right;
        yellow.right = red;
        updateHeight(red);
        updateHeight(yellow);
        return yellow;
    }

    // RR
    private AVLNode leftRotate(AVLNode red) {
        AVLNode yellow = red.right;
        red.right = yellow.right;
        yellow.left = red;
        updateHeight(red);
        updateHeight(yellow);
        return yellow;
    }

    // LR
    private AVLNode leftRightRotate(AVLNode node) {
        node.left = leftRotate(node.left);
        return rightRotate(node);
    }

    // RL
    private AVLNode rightLeftRotate(AVLNode node) {
        node.right = rightRotate(node.right);
        return leftRotate(node);
    }

    // 检查节点是否失衡
    private AVLNode balance(AVLNode node) {
        if (node == null) {
            return null;
        }
        int bf = bf(node);
        if (bf > 1 && bf(node.left) >= 0) { // LL
            return rightRotate(node);
        } else if (bf > 1 && bf(node.left) < 0) { // LR
            return leftRightRotate(node);
        } else if (bf < -1 && bf(node.right) > 0) { // RL
            return rightLeftRotate(node);
        } else if (bf < -1 && bf(node.right) <= 0) { // RR
            return leftRotate(node);
        }
        return node;
    }

    AVLNode root;

    // 新增
    public void put(int key, Object value) {
        root = doPut(root, key, value);
    }

    private AVLNode doPut(AVLNode node, int key, Object value) {
        // 1、找到空位了 创建新节点
        if (node == null) {
            return new AVLNode(key, value);
        }
        // 2、key 在树中已经存在,更新
        if (key == node.key) {
            node.value = value;
            return node;
        }
        // 3、继续查找
        if (key < node.key) {
            node.left = doPut(node.left, key, value);
        } else {
            node.right = doPut(node.right, key, value);
        }
        updateHeight(node);
        return balance(node);
    }

    // 删除节点
    public void remove(int key) {
        root = doRemove(root, key);
    }

    // 递归代码
    private AVLNode doRemove(AVLNode node, int key) {
        if (node == null) {
            return node;
        }
        // 没找到 递归调用
        if (key < node.key) {
            node.left = doRemove(node.left, key);
        } else if (key > node.key) {
            node.right = doRemove(node.right, key);
        }
        // 找到了 =========>   1、没有孩子   2、只有一个孩子   3、两个孩子都有
        else{
            if (node.left == null && node.right == null) {
                return null;
            } else if(node.left == null) {
                node = node.right;
            } else if (node.right == null) {
                node = node.left;
            } else {
                AVLNode s = node.right;
                while (s.left != null) {
                    s = s.left;
                }
                // s 代表后继节点
                s.right = doRemove(node.right, s.key);
                s.left = node.left;
                node = s;
            }
        }
        // 更新高度 在进行平衡
        updateHeight((node));
        return balance(node);
    }
}

12、红黑树 RBT

红黑树也是一种自平衡的二叉搜索树,比较AVL,插入和删除时旋转次数更少

特性

  • 所有节点都有两种颜色:红与黑
  • 所有null视为黑色
  • 红色节点不能相邻
  • 根节点是黑色
  • 从根到任意一个叶子结点,路径中的黑色节点数一样(黑色完美平衡)

image-20230915193809366

代码示例

public class RedBlackTree {

    // 颜色
    enum Color {
        RED, BLACK
    }

    // 根节点
    private Node root;

    // 节点
    private static class Node {
        int key;
        Object value;
        Node left;
        Node right;
        Node parent;    // 父节点
        Color color = Color.RED;

        public Node(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        // 是否是左孩子
        boolean isLeftChild() {
            return parent != null && parent.left == this;
        }

        // 叔叔
        Node uncle() {
            if (parent == null || parent.parent == null) {
                return null;
            }
            if (parent.isLeftChild()) {
                return parent.parent.right;
            } else {
                return parent.parent.left;
            }
        }

        // 兄弟
        Node sibling() {
            if (parent == null) {
                return null;
            }
            if (this.isLeftChild()) {
                return parent.right;
            } else {
                return parent.left;
            }
        }
    }

    // 判断红
    boolean isRed(Node node) {
        return node != null && node.color == Color.RED;
    }

    // 判断黑
    boolean isBlack(Node node) {
        return node == null || node.color == Color.BLACK;
    }

    // 右旋 1. parent的处理  2. 旋转后新根的父子关系
    private void rightRotate(Node pink) {
        Node parent = pink.parent;
        Node yellow = pink.left;
        Node green = yellow.right;
        if (green != null) {
            green.parent = pink;
        }
        yellow.right = pink;
        yellow.parent = parent;
        pink.left = green;
        pink.parent = yellow;
        if (parent == null) {
            root = yellow;
        } else if (parent.left == pink) {
            parent.left = yellow;
        } else {
            parent.right = yellow;
        }
    }

    // 左旋
    private void leftRotate(Node pink) {
        Node parent = pink.parent;
        Node yellow = pink.right;
        Node green = yellow.left;
        if (green != null) {
            green.parent = pink;
        }
        yellow.left = pink;
        yellow.parent = parent;
        pink.right = green;
        pink.parent = yellow;
        if (parent == null) {
            root = yellow;
        } else if (parent.right == pink) {
            parent.right = yellow;
        } else {
            parent.left = yellow;
        }
    }

    // 新增或更新
    // 正常增、遇到红红不平衡进行调整
    public void put(int key, Object value) {
        Node p = root;
        Node parent = null;
        while (p != null) {
            parent = p;
            if (key < p.key) {
                p = p.left;
            } else if (key > p.key) {
                p = p.right;
            } else {
                p.value = value;
                return;
            }
        }
        Node inserted = new Node(key, value);
        if (parent == null) {
            root = inserted;
        } else if (key < parent.key) {
            parent.left = inserted;
            inserted.parent = parent;
        } else {
            parent.right = inserted;
            inserted.parent = parent;
        }
        fixRedRed(inserted);
    }

    private void fixRedRed(Node x) {
        // case 1. 插入节点是根节点,变黑即可
        if (x == root) {
            x.color = Color.BLACK;
            return;
        }
        // case 2. 插入节点父亲是黑色,无需调整
        if (isBlack(x.parent)) {
            return;
        }
        // 插入节点的父亲为红色,触发 红红相邻!
        // case 3. 叔叔为红色
        Node parent = x.parent;
        Node uncle = x.uncle();
        Node grandparent = parent.parent;
        if (isRed(uncle)) {
            parent.color = Color.BLACK;
            uncle.color = Color.BLACK;
            grandparent.color = Color.RED;
            fixRedRed(grandparent);
            return;
        }
        // case 4. 叔叔为黑色
        if (parent.isLeftChild() && x.isLeftChild()) {  // LL
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        } else if (parent.isLeftChild()) { // LR
            leftRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        } else if (!x.isLeftChild()) { // RR
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        } else { // RL
            rightRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        }
    }

    // 删除
    // 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整
    public void remove(int key) {
        Node deleted = find(key);
        if (deleted == null) {
            return;
        }
        doRemove(deleted);
    }

    // 处理双黑(case 3 4 5)
    private void fixDoubleBlack(Node x) {
        if (x == root) {
            return;
        }
        Node parent = x.parent;
        Node sibling = x.sibling();
        // case 3 兄弟节点是红色
        if (isRed(sibling)) {
            if (x.isLeftChild()) {
                leftRotate(parent);
            } else {
                rightRotate(parent);
            }
            parent.color = Color.RED;
            sibling.color = Color.BLACK;
            fixDoubleBlack(x);// 递归调用
            return;
        }
        if (sibling != null) {
            // case 4 兄弟是黑色 两个之子也是黑色
            if (isBlack(sibling.left) && isBlack(sibling.right)) {
                sibling.color = Color.RED;
                if (isRed(parent)) {
                    parent.color = Color.BLACK;
                } else {
                    fixDoubleBlack(parent);
                }
            }
            // case 5 兄弟是黑色,侄子有红色
            else {
                // LL
                if (sibling.isLeftChild() && isRed(sibling.left)) {
                    rightRotate(parent);
                    sibling.left.color = Color.BLACK;
                    sibling.color = parent.color;
                }
                // LR
                else if (sibling.isLeftChild() && isRed(sibling.right)) {
                    sibling.right.color = parent.color;
                    leftRotate(sibling);
                    rightRotate(parent);
                }
                // RL
                else if (!sibling.isLeftChild() && isRed(sibling.right)){
                    sibling.left.color = parent.color;
                    rightRotate(sibling);
                    leftRotate(parent);
                }
                // RR
                else {
                    leftRotate(parent);
                    sibling.right.color = Color.BLACK;
                    sibling.color = parent.color;
                }
                parent.color = Color.BLACK;
            }
        } else {
            fixDoubleBlack(parent);
        }
    }

    // 递归执行删除代码
    private void doRemove(Node deleted) {
        Node replaced = findReplaced(deleted);
        Node parent = deleted.parent;
        // 没有孩子
        if (replaced == null) {
            // case 1 删的就是根节点
            if (deleted == root) {
                root = null;
            } else {
                // 删掉的节点是黑色
                if (isBlack(deleted)) {
                    // 复杂调整
                    fixDoubleBlack(deleted);
                } else {
                    // 红色叶子 无需任何处理
                }
                if (deleted.isLeftChild()) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
                deleted.parent = null;// help gc
            }
            return;
        }
        // 有一个孩子
        if (deleted.left == null || deleted.right == null) {
            // case 1
            if (deleted == root) {
                root.key = replaced.key;
                root.value = replaced.value;
                root.left = root.right = null;
            } else {
                if (deleted.isLeftChild()) {
                    parent.left = replaced;
                } else {
                    parent.right = replaced;
                }
                replaced.parent = parent;
                deleted.parent = deleted.left = deleted.right = null; // help gc
                // 删掉的节点是黑色 剩余的节点也是黑色
                if (isBlack(deleted) && isBlack(replaced)) {
                    // 复杂处理
                    fixDoubleBlack(replaced);
                } else {
                    // case 2
                    replaced.color = Color.BLACK;
                }
            }
            return;
        }
        // case 0 有两个孩子 => 有一个孩子 或 没有孩子
        int t = deleted.key;
        deleted.key = replaced.key;
        replaced.key = t;

        Object v = deleted.value;
        deleted.value = replaced.value;
        replaced.value = v;
        doRemove(replaced);
    }

    // 查找删除节点
    private Node find(int key) {
        Node p = root;
        while (p != null) {
            if (key < p.key) {
                p = p.left;
            } else if (key > p.key) {
                p = p.right;
            } else {
                return p;
            }
        }
        return null;
    }

    // 查找剩余节点
    private Node findReplaced(Node deleted) {
        // 判断有无孩纸
        if (deleted.left == null && deleted.right == null) {
            return null;
        }
        if (deleted.left == null) {
            return deleted.right;
        }
        if (deleted.right == null) {
            return deleted.left;
        }
        // 查找后继节点
        Node s = deleted.right;
        while (s.left != null) {
            s = s.left;
        }
        return s;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kaiyue.zhao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值