文章目录
1、数组 Array
扩容
在Java中,当需要将一个数组的大小扩大时,通常会使用Arrays.copyOf()
或System.arraycopy()
方法来实现。以下是使用System.arraycopy()
方法进行数组扩容的示例:
假设我们有一个长度为originalSize
的原始数组originalArray
,现在需要将其扩大到newSize
。
- 首先,创建一个新的目标数组
newArray
,其长度为newSize
:
int[] originalArray = {1, 2, 3, 4, 5};
int originalSize = originalArray.length;
int newSize = originalSize * 2; // 假设要将数组大小扩大为原来的两倍
int[] newArray = new int[newSize];
- 使用
System.arraycopy()
方法将原始数组的元素复制到新数组中:
System.arraycopy(originalArray, 0, newArray, 0, originalSize);
这里的参数解释如下:
originalArray
:原始数组0
:原始数组的起始位置(从0开始)newArray
:目标数组0
:目标数组的起始位置(从0开始)originalSize
:要复制的元素数量
- 对于新数组中超出原始数组长度的部分,你可以根据需要进行初始化,例如将其填充为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;
// }
}
链表中寻找环(弗洛伊德龟兔赛跑算法)
/**
* 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接口有很多实现类。以下是一些常见的实现类:
- LinkedList:LinkedList类实现了Queue接口,因此你可以将LinkedList当成Queue来使用。这种队列按元素插入的顺序进行排序。
- PriorityQueue:PriorityQueue保存了一个元素列表,其元素可以自然排序或者通过提供的Comparator在队列实施排序。
- ArrayDeque:ArrayDeque类也实现了Queue接口,它在处理非线程安全的大型数据集合时比LinkedList更高效。
- LinkedBlockingQueue:LinkedBlockingQueue是一个线程安全的队列,它实现了BlockingQueue接口,LinkedBlockingQueue内部以一个链表形式进行元素的存储。
- ArrayBlockingQueue:ArrayBlockingQueue是一个线程安全的队列,它实现了BlockingQueue接口,它是基于数组结构实现的,它内部采用数组结构来存储元素。
- SynchronousQueue:SynchronousQueue是一个没有容量的阻塞队列。每个插入操作必须等待一个相应的删除操作,反之亦然。并发线程可以通过该队列互相等待彼此释放资源。
- ConcurrentLinkedDeque:ConcurrentLinkedDeque类是一个双端队列,在处理非线程安全的大型数据集合时比LinkedList更高效。
这些队列的使用取决于你的具体需求。例如,如果你需要在多线程环境下操作,那么 LinkedBlockingQueue 或者 ArrayBlockingQueue 可能是更好的选择。如果你需要一个大型的非线程安全的队列,那么 ArrayDeque 或者 LinkedList 可能是更好的选择。
offer() 和 add() 的区别:
两者都是在队列尾部加入元素,add是在队列满时抛出异常,offer是返回false,成功都是返回true,按需调用即可!
这里注意实现类和子类,new的时候要注意!
常用方法
Java中的Queue接口提供了许多常用的方法,以下是一些常用的方法:
- add(E e):将指定的元素添加到队列中。
- remove():移除并返回队列中的第一个元素。如果队列为空,则返回null。
- element():返回队列中的第一个元素,但不移除它。如果队列为空,则返回null。
- offer(E e):将指定的元素添加到队列中,如果队列已满,则返回false。
- poll():移除并返回队列中的第一个元素,如果队列为空,则返回null。
- peek():返回队列中的第一个元素,但不移除它,如果队列为空,则返回null。
- isEmpty():判断队列是否为空。
- isFull():判断队列是否已满(对于有容量限制的队列来说)。
- size():返回队列中元素的数量。
- clear():移除队列中的所有元素。
- contains(Object o) :判断队列中是否包含指定的元素。
- 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.Stack
是Stack
接口的主要实现,但它并不是唯一的实现。理论上,任何实现了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
接口中的所有方法:
push(E item)
: 将项压入此堆栈的顶部。pop()
: 移除此堆栈的顶部元素,并返回被移除的元素。peek()
: 查看此堆栈的顶部元素,但不移除它。empty()
: 测试此堆栈是否为空。search(Object o)
: 返回对象在堆栈中的位置,以 1 为基数。toString()
: 返回此堆栈的字符串表示形式。
以上就是Java的Stack
接口中所有的方法。需要注意的是,Stack
类并没有实现这些方法,而是通过继承Vector
类来实现的。在编程实践中,我们通常会直接使用Deque
接口的实现类(如ArrayDeque
),因为它们提供了更丰富的方法,并且性能通常更好。
逆波兰表达式
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()
: 获取并删除队列的第一个元素。
注意:push
,pop
,peek
和poll
方法也可以用在Queue
接口,但是他们在Deque
接口中有不同的行为。在Deque
中,这些操作都应用在队列的开头,而不是末尾。
在Java中,ArrayDeque
和LinkedList
是实现Deque
接口的两个常用类。
实现
可以基于链表和环形数组实现
6、优先队列 PriorityQueue
数组实现
// 基于有序数组实现
// Type parameters: <E>-队列中元素类型,必须实现 Priority接口
public class PriorityQueue2<E extends Priority> implements Queue<E>
如果使用无序数组,实现插入可能快,出列慢于有序数组 最差都是O(n)
有序数组在出列方面有优势,但是在插入和其他操作最差情况下是O(n)
堆实现
解释 !!!
在Java中,PriorityQueue是一种优先队列,它根据具体实现的Comparator或者Comparable来排序元素。在你提到的代码中,‘(a, b) -> a - b’ 是一个Lambda表达式,用于定义Comparator。这个表达式表示的是一个接受两个参数的函数,参数为a和b,函数返回值为a-b。在PriorityQueue中,这将被用来决定元素的优先级。如果a - b的结果是负数,那么元素a的优先级将高于元素b;如果结果是正数,元素a的优先级将低于元素b;如果结果是0,元素a和元素b的优先级相同。
Java中的PriorityQueue
类是一个基于优先堆的无界队列。这个优先队列中的元素可以默认自然排序或者通过提供的Comparator
在队列实例化的时进行排序。
PriorityQueue
的一些重要属性包括:
queue
:这是一个数组,用于存储队列中的元素。size
:这是一个整数,表示队列中当前元素的数量。lock
:这是一个用于同步的锁对象。所有访问队列的方法都需要获取这个锁,以防止并发修改。notEmpty
:这是一个等待队列,当队列为空时,线程可以在此等待。allFull
:这是一个等待队列,当队列满时,线程可以在此等待。comparator
:这是一个用于比较队列中元素的比较器。如果没有提供比较器,并且队列中的元素实现了Comparable
接口,那么将使用元素的自然顺序。modCount
:这是一个计数器,用于跟踪队列的结构性修改(如添加或删除元素)。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);
7、阻塞队列 BlockingQueue
引入
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 提供了几种不同的阻塞队列,包括:
ArrayBlockingQueue
:一个由数组结构组成的有界阻塞队列。LinkedBlockingQueue
:一个由链表结构组成的有界阻塞队列,但默认大小为 Integer.MAX_VALUE,实际是个无界队列。PriorityBlockingQueue
:一个支持优先级排序的无界阻塞队列。DelayQueue
:一个使用优先级队列实现的无界阻塞队列,用于存放实现了 Delayed 接口的对象,只有在延迟期满时才能从中提取元素。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()
大顶堆 MaxHeap
- 找到最后一个非叶子节点
- 从后向前,对每个节点执行下潜
非叶子节点计算公式
就是找到第一个非叶子节点,当然也是在有效索引范围之内
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));
}
利用优先队列(底层就是堆实现)
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
基本知识
二叉树是树形结构的一个重要类型,它有以下几个基本概念:
- 节点:二叉树中的每个元素称为一个节点。每个节点最多只能有两个子节点,分别称为左子节点和右子节点。
- 根节点:二叉树中只有一个节点,即树的起点,称为根节点。
- 子树:从任意一个节点出发,向它的一个子节点或两个子节点所形成的子图称为子树。
- 左子树和右子树:对于任意一个节点,它的左子节点和右子节点分别形成的子树称为左子树和右子树。
- 父节点和子节点:对于任意一个节点,它的父节点就是它的一个或两个子节点的根节点。
- 空树:没有任何节点的二叉树称为空树。
- 叶节点:没有子节点的节点称为叶节点或终端节点。
- 非叶节点:有子节点的节点称为非叶节点或内部节点。
- 二叉树的存储结构:二叉树的每个节点可以用一个结构体表示,其中包含三个域:数据域、指向左孩子的指针和指向右孩子的指针。这种存储结构简化了二叉树的操作。
- 二叉树的遍历:遍历二叉树的所有节点,有三种遍历方式:前序遍历(先访问根节点,然后左子树,最后右子树)、中序遍历(先访问左子树,然后根节点,最后右子树)和后序遍历(先访问左子树,然后右子树,最后根节点)。
如果想要了解更多有关二叉树的概念,可以查阅计算机科学或数据结构的教材或专业文献。
性质
- 二叉树中,第 i 层最多有 2i-1 个结点。
- 如果二叉树的深度为 K,那么此二叉树最多有 2K-1 个结点。
- 二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1。
完全二叉树
完全二叉树(Complete Binary Tree)是一类特殊的二叉树,其具有以下特征:
- 如果二叉树的深度为h,除第h层外,其它各层(1~h-1)的结点数都达到最大个数,第h层所有的结点都连续集中在最左边。
- 对于深度为K的、有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。也就是说,完全二叉树中的每个节点都有与之对应的满二叉树中的节点位置。
这也是完全二叉树效率较高的原因,因为在完全二叉树中,除最后一层外,其它各层的节点数都达到了最大,没有空节点,这意味着我们可以利用更多的节点来存储数据,从而减少了空间的浪费。
对于任意一个完全二叉树来说,如果将含有的结点按照层次从左到右依次标号(如图 3a)),对于任意一个结点 i ,完全二叉树还有以下几个结论成立:
- 当 i>1 时,父亲结点为结点 [i/2] 。(i=1 时,表示的是根结点,无父亲结点)
- 如果 2i>n(总结点的个数) ,则结点 i 肯定没有左孩子(为叶子结点);否则其左孩子是结点 2i 。
- 如果 2i+1>n ,则结点 i 肯定没有右孩子;否则右孩子是结点 2i+1 。
满二叉树
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
满二叉树除了满足普通二叉树的性质,还具有以下性质:
- 满二叉树中第 i 层的节点数为 2n-1 个。
- 深度为 k 的满二叉树必有 2k-1 个节点 ,叶子数为 2k-1。
- 满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。
- 具有 n 个节点的满二叉树的深度为 log2(n+1)。
遍历
广度优先遍历
-
数组实现,直接遍历数组就行,默认就是层序遍历。
-
使用队列是面向对象(TreeNode)的方法,进行层序遍历:
大概就是先加入根节点,在while循环里,先存入本层节点,再判断是否还有左右节点,有就加入队列,在下次循环中根据队列的大小就能知道上一层有多少节点,反复执行就行,直到队列为空,意味着取出的节点为叶子结点,遍历完毕。
/** * 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; } }
深度优先遍历
前序遍历(根左右)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都小
相应方法
-
前驱节点
-
后继节点
-
删除节点
示例代码
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
概念
AVL树中的“AVL”是英文单词“Adelson-Velsky和Landis”的缩写。他们两人于1962年提出了这种自平衡二叉搜索树,并在论文中使用了这个名称。AVL树是一种特殊的二叉搜索树,它的每个节点都保留了平衡因子,以确保树的平衡。在AVL树中进行插入和删除等操作时,可能会打破树的平衡,这时就需要进行旋转操作来恢复平衡。
AV树又叫平衡二叉排序树,它是一棵平衡二叉搜索树。AV树的定义包括以下要点:
- 任何节点的左、右子树的高度差都不超过1;
- 无论是左子树还是右子树,都必须是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视为黑色
- 红色节点不能相邻
- 根节点是黑色
- 从根到任意一个叶子结点,路径中的黑色节点数一样(黑色完美平衡)
代码示例
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;
}
}