一貼看完java常用的查找、排序算法,後續將更新二叉樹算法、遞歸、談心、回溯等算法。
今天被面試官刁難了一番(問的不是排序查找),想借此複習一下算法基礎。
主要內容:
二分查找
冒泡排序算法
插入排序算法
快速排序(含優化)算法
希爾排序算法
歸併排序算法
堆排序算法
桶排序算法
基數排序算法
二叉樹算法
紅黑樹算法(手寫紅黑樹)
import java.util.*;
/**
* 二分查找
* 冒泡排序算法
* 插入排序算法
* 快速排序算法
* 希爾排序算法
* 歸併排序算法
* 堆排序算法
* 桶排序算法
* 基數排序算法
* @author cherry
* @create 2020-04-23-19:19
*/
public class AllSort {
public static void main(String[] args) {
int[] a = {49, 38, 65, 97, 76, 13, 27, 50};
int[] b1 = {49, 38, 65, 97, 76, 13, 27, 50};
int[] b2 = {49, 38, 65, 97, 76, 13, 27, 50};
int[] c = {49, 38, 65, 97, 76, 13, 27, 50};
int[] d = {49, 38, 65, 97, 76, 13, 27, 50};
int[] e = {49, 38, 65, 97, 76, 13, 27, 50};
int[] f = {49, 38, 65, 97, 76, 13, 27, 50};
int[] g = {49, 38, 65, 97, 76, 13, 27, 50};
int[] h = {49, 38, 65, 97, 76, 13, 27, 50};
int[] i = {49, 38, 65, 97, 76, 13, 27, 50};
MergeSort.mergeSort(a, 0, a.length - 1);
QuickSort.quickSort(b1, 0, b1.length - 1);
QuickSort.newQuickSort(b2);
HeapSort.heapSort(c);
BubbleSort.bubbleSort(e);
InsertSort.insertSort(f);
ShellSort.shellSort(g);
BucketSort.bucketSort(h);
RadixSort.radixSort(i);
System.out.println("歸排排好序的數組:" + Arrays.toString(a));
System.out.println("快排排好序的數組:" + Arrays.toString(b1));
System.out.println("快排優化後排好序的數組:" + Arrays.toString(b2));
System.out.println("堆排排好序的數組:" + Arrays.toString(c));
System.out.println("元素'38'的下標爲:" + BSearch.biSearch(d, 38));
System.out.println("冒泡排好序的數組:" + Arrays.toString(e));
System.out.println("插排好序的數組:" + Arrays.toString(f));
System.out.println("希爾排好序的數組:" + Arrays.toString(f));
System.out.println("桶排排好序的數組:" + Arrays.toString(f));
System.out.println("基排排好序的數組:" + Arrays.toString(f));
}
}
/**
* 歸併排序
* 歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列
* 分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列。
*/
class MergeSort {
//兩路歸併算法,兩個排好序的子序列合併爲一個子序列
private static void merge(int[] a, int left, int mid, int right) {
int[] tmp = new int[a.length];//輔助數組
int p1 = left, p2 = mid + 1, k = left;//p1、p2是檢測指針,k是存放指針
while (p1 <= mid && p2 <= right) {
if (a[p1] <= a[p2]) tmp[k++] = a[p1++];
else tmp[k++] = a[p2++];
}
while (p1 <= mid) tmp[k++] = a[p1++];//如果第一個序列未檢測完,直接將後面所有元素加到合併的序列中
while (p2 <= right) tmp[k++] = a[p2++];//同上
for (int i = left; i <= right; i++) a[i] = tmp[i];//複製回原素組
}
static void mergeSort(int[] a, int start, int end) {
if (start < end) {//當子序列中只有一個元素時結束遞歸
int mid = (start + end) / 2;//劃分子序列
mergeSort(a, start, mid);//對左側子序列進行遞歸排序
mergeSort(a, mid + 1, end);//對右側子序列進行遞歸排序
merge(a, start, mid, end);//合併
}
}
}
/**
* 快速排序
* 快速排序的原理:選擇一個關鍵值作爲基準值。比基準值小的都在左邊序列(一般是無序的),
* 比基準值大的都在右邊(一般是無序的)。一般選擇序列的第一個元素。
* 一次循環:從後往前比較,用基準值和最後一個值比較,如果比基準值小的交換位置,如果沒有
* 繼續比較下一個,直到找到第一個比基準值小的值才交換。找到這個值之後,又從前往後開始比
* 較,如果有比基準值大的,交換位置,如果沒有繼續比較下一個,直到找到第一個比基準值大的
* 值才交換。直到從前往後的比較索引>從後往前比較的索引,結束第一次循環,此時,對於基準值
* 來說,左右兩邊就是有序的了。
*/
class QuickSort {
//優化前的快排:最好平均最差時間複雜度爲O(nlogn)、O(nlogn)、O(n2)
static void quickSort(int[] arr, int start, int end) {
int i, j, temp, t;
if (start > end) return;
i = start;
j = end;
temp = arr[start];//temp就是基準位
while (i < j) {
while (temp <= arr[j] && i < j) j--;//先看右邊,依次往左遞減
while (temp >= arr[i] && i < j) i++;//再看左邊,依次往右遞增
t = arr[j];//如果滿足條件則交換
arr[j] = arr[i];
arr[i] = t;
}
arr[start] = arr[i];//最後將基準爲與i和j相等位置的數字交換
arr[i] = temp;
quickSort(arr, start, j - 1);//遞歸調用左半數組
quickSort(arr, j + 1, end);//遞歸調用右半數組
}
//優化後的快排:最好平均最差時間複雜度均爲O(nlogn)
static void newQuickSort(int[] arr) {
int random = new Random().nextInt(arr.length + 1);
int temp = arr[random];//將基準設置爲隨機元素
arr[random] = arr[0];
arr[0] = temp;
quickSort(arr, 0, arr.length - 1);
}
}
/**
* 堆排序
* 堆排序的基本思想是:將待排序序列構造成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。
* 將其與末尾元素進行交換,此時末尾就爲最大值。然後將剩餘n-1個元素重新構造成一個堆,這樣會得到n個元素的次小值。
* 如此反覆執行,便能得到一個有序序列。
*/
class HeapSort {
static void heapSort(int[] arr) {
//創建堆,從第一個非葉子結點從下至上,從右至左調整結構
for (int i = (arr.length - 1) / 2; i >= 0; i--) adjustHeap(arr, i, arr.length);
//調整堆結構+交換堆頂元素與末尾元素
for (int i = arr.length - 1; i > 0; i--) {
int temp = arr[i];//將堆頂元素與末尾元素進行交換
arr[i] = arr[0];
arr[0] = temp;
adjustHeap(arr, 0, i);//重新對堆進行調整
}
}
//調整堆@param arr待排序列 @param parent 父節點 @param length 待排序列尾元素索引
private static void adjustHeap(int[] arr, int parent, int length) {
int temp = arr[parent];//將temp作爲父節點
int lChild = 2 * parent + 1;//左孩子
while (lChild < length) {
int rChild = lChild + 1;//右孩子
if (rChild < length && arr[lChild] < arr[rChild]) lChild++;// 如果有右孩子結點,並且右孩子結點的值大於左孩子結點,則選取右孩子結點
if (temp >= arr[lChild]) break;// 如果父結點的值已經大於孩子結點的值,則直接結束
arr[parent] = arr[lChild];// 把孩子結點的值賦給父結點
parent = lChild;//選取孩子結點的左孩子結點,繼續向下篩選
lChild = 2 * lChild + 1;
}
arr[parent] = temp;
}
}
/**
* 二分查找
* 又叫折半查找,要求待查找的序列有序。每次取中間位置的值與待查關鍵字比較,如果中間位置
* 的值比待查關鍵字大,則在前半部分循環這個查找的過程,如果中間位置的值比待查關鍵字小,
* 則在後半部分循環這個查找的過程。直到查找到了爲止,否則序列中沒有待查的關鍵字。
*/
class BSearch {
static int biSearch(int[] arr, int num) {
int lo = 0;
int hi = arr.length - 1;
int mid;
while (lo < hi) {
mid = (lo + hi) >> 1;
if (arr[mid] == num) return mid + 1;
else if (arr[mid] > num) hi = mid - 1;
else lo = mid + 1;
}
return -1;
}
}
/**
* 冒泡排序(看不懂請學java基礎)
* (1)比較前後相鄰的二個數據,如果前面數據大於後面的數據,就將這二個數據交換。
* (2)這樣對數組的第 0 個數據到 N-1 個數據進行一次遍歷後,最大的一個數據就“沉”到數組第
* N-1 個位置。
* (3)N=N-1,如果 N 不爲 0 就重複前面二步,否則排序完成。
*/
class BubbleSort {
public static void bubbleSort(int[] a) {
for (int i = 0; i < a.length; i++) {//表示 n 次排序過程。
for (int j = 1; j < a.length - i; j++) {
if (a[j - 1] > a[j]) {//前面的數字大於後面的數字就交換
int temp;
temp = a[j - 1];
a[j - 1] = a[j];
a[j] = temp;
}
}
}
}
}
/**
* 插入排序
* 通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應的位置並插入。
* 插入排序非常類似於整撲克牌。在開始摸牌時,左手是空的,牌面朝下放在桌上。接着,一次從
* 桌上摸起一張牌,並將它插入到左手一把牌中的正確位置上。爲了找到這張牌的正確位置,要將
* 它與手中已有的牌從右到左地進行比較。無論什麼時候,左手中的牌都是排好序的。
* 如果輸入數組已經是排好序的話,插入排序出現最佳情況,其運行時間是輸入規模的一個線性函
* 數。如果輸入數組是逆序排列的,將出現最壞情況。平均情況與最壞情況一樣,其時間代價是(n2)。
*/
class InsertSort {
public static void insertSort(int arr[]) {
for (int i = 1; i < arr.length; i++) {
//插入的數
int insertVal = arr[i];
//被插入的位置(準備和前一個數比較)
int index = i - 1;
//如果插入的數比被插入的數小
while (index >= 0 && insertVal < arr[index]) {
//將把 arr[index] 向後移動
arr[index + 1] = arr[index];
//讓 index 向前移動
index--;
}
//把插入的數放入合適位置
arr[index + 1] = insertVal;
}
}
}
/**
* 希爾排序
* 基本思想:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列
* 中的記錄“基本有序”時,再對全體記錄進行依次直接插入排序。
* 1. 操作方法:
* 選擇一個增量序列 t1,t2,…,tk,其中 ti>tj,tk=1;
* 2. 按增量序列個數 k,對序列進行 k 趟排序;
* 3. 每趟排序,根據對應的增量 ti,將待排序列分割成若干長度爲 m 的子序列,分別對各子表進
* 行直接插入排序。僅增量因子爲1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長
* 度。
*/
class ShellSort {
static void shellSort(int[] a) {
int dk = a.length / 2;
while (dk >= 1) {
shellInsertSort(a, dk);
dk = dk / 2;
}
}
private static void shellInsertSort(int[] a, int dk) {
//類似插入排序,只是插入排序增量是 1,這裏增量是 dk,把 1 換成 dk 就可以了
for (int i = dk; i < a.length; i++) {
if (a[i] < a[i - dk]) {
int j;
int x = a[i];//x 爲待插入元素
a[i] = a[i - dk];
//通過循環,逐個後移一位找到要插入的位置。
for (j = i - dk; j >= 0 && x < a[j]; j = j - dk) a[j + dk] = a[j];
a[j + dk] = x;//插入
}
}
}
}
/**
* 桶排序
* 桶排序的基本思想是: 把數組 arr 劃分爲 n 個大小相同子區間(桶),每個子區間各自排序,最
* 後合併 。計數排序是桶排序的一種特殊情況,可以把計數排序當成每個桶裏只有一個元素的情況。
* 1.找出待排序數組中的最大值 max、最小值 min
* 2.我們使用 動態數組 ArrayList 作爲桶,桶裏放的元素也用 ArrayList 存儲。桶的數量爲(max-
* min)/arr.length+1
* 3.遍歷數組 arr,計算每個元素 arr[i] 放的桶
* 4.每個桶各自排序
*/
class BucketSort {
public static void bucketSort(int[] arr) {
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int item : arr) {
max = Math.max(max, item);
min = Math.min(min, item);
}
//創建桶
int bucketNum = (max - min) / arr.length + 1;
List<List<Integer>> bucketArr = new ArrayList<>(bucketNum);
for (int i = 0; i < bucketNum; i++) bucketArr.add(new ArrayList<>());
//將每個元素放入桶
for (int value : arr) {
int num = (value - min) / (arr.length);
bucketArr.get(num).add(value);
}
//對每個桶進行排序
for (List<Integer> integers : bucketArr) Collections.sort(integers);
}
}
/**
* 基數排序
* 將所有待比較數值(正整數)統一爲同樣的數位長度,數位較短的數前面補零。然後,從最低位
* 開始,依次進行一次排序。這樣從最低位排序一直到最高位排序完成以後,數列就變成一個有序序
* 列。
*/
class RadixSort {
public static void radixSort(int[] array) {
//首先確定排序的趟數;
int max = array[0];
for (int i = 1; i < array.length; i++) if (array[i] > max) max = array[i];
int time = 0;
//判斷位數;
while (max > 0) {
max /= 10;
time++;
}
//建立 10 個隊列;
List<List> queue = new ArrayList<>();
for (int i = 0; i < 10; i++) {
List<Integer> queue1 = new ArrayList<>();
queue.add(queue1);
}
//進行 time 次分配和收集;
for (int i = 0; i < time; i++) {
//分配數組元素;
for (int j = 0; j < array.length; j++) {
//得到數字的第 time+1 位數;
int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
List<Integer> queue2 = queue.get(x);
queue2.add(array[j]);
queue.set(x, queue2);
}
int count = 0;//元素計數器;
//收集隊列元素;
for (int k = 0; k < 10; k++) {
while (queue.get(k).size() > 0) {
List<Integer> queue3 = queue.get(k);
array[count] = queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
}
各排序算法時間與空間複雜度、穩定性表格
二叉樹算法
public class BinaryTree {
public static void main(String[] args) {
TreeNode treeNode = new TreeNode(new TreeNode(new TreeNode(3),
new TreeNode(4), 2, false),
new TreeNode(new TreeNode(6),
new TreeNode(7), 5, false), 1, false);
BinaryTree binaryTree = new BinaryTree();
System.out.println("前序遍歷");
binaryTree.preOrder(treeNode);//1 2 3 4 5 6 7
System.out.println("\n中序遍歷");
binaryTree.inOrder(treeNode);//3 2 4 1 6 5 7
System.out.println("\n後序遍歷");
binaryTree.postOrder(treeNode);//3 4 2 6 7 5 1
}
// 根節點
private TreeNode root;
public TreeNode getRoot() {
return root;
}
/**
* 插入操作
*
* @param value
*/
public void insert(int value) {
TreeNode newNode = new TreeNode(value);
if (root == null) {
root = newNode;
root.setLefTreeNode(null);
root.setRightNode(null);
} else {
TreeNode currentNode = root;
TreeNode parentNode;
while (true) {
parentNode = currentNode;
// 往右放
if (newNode.getValue() > currentNode.getValue()) {
currentNode = currentNode.getRightNode();
if (currentNode == null) {
parentNode.setRightNode(newNode);
return;
}
} else {
// 往左放
currentNode = currentNode.getLefTreeNode();
if (currentNode == null) {
parentNode.setLefTreeNode(newNode);
return;
}
}
}
}
}
/**
* 查找
*
* @param key
* @return
*/
public TreeNode find(int key) {
TreeNode currentNode = root;
if (currentNode != null) {
while (currentNode.getValue() != key) {
if (currentNode.getValue() > key) currentNode = currentNode.getLefTreeNode();
else currentNode = currentNode.getRightNode();
if (currentNode == null) return null;
}
if (currentNode.isDelete()) return null;
else return currentNode;
} else return null;
}
/**
* 前序遍歷
*
* @param treeNode
*/
public void preOrder(TreeNode treeNode) {
if (treeNode != null && !treeNode.isDelete()) {
System.out.print(treeNode.getValue() + " ");
preOrder(treeNode.getLefTreeNode());
preOrder(treeNode.getRightNode());
}
}
/**
* 中序遍歷
*
* @param treeNode
*/
public void inOrder(TreeNode treeNode) {
if (treeNode != null && !treeNode.isDelete()) {
inOrder(treeNode.getLefTreeNode());
System.out.print(treeNode.getValue() + " ");
inOrder(treeNode.getRightNode());
}
}
/**
* 後序遍歷
*
* @param treeNode
*/
public void postOrder(TreeNode treeNode) {
if (treeNode != null && !treeNode.isDelete()) {
postOrder(treeNode.getLefTreeNode());
postOrder(treeNode.getRightNode());
System.out.print(treeNode.getValue() + " ");
}
}
}
class TreeNode {
public TreeNode left;
// 左節點
private TreeNode lefTreeNode;
// 右節點
private TreeNode rightNode;
// 數據
private int value;
private boolean isDelete;
public TreeNode getLefTreeNode() {
return lefTreeNode;
}
public void setLefTreeNode(TreeNode lefTreeNode) {
this.lefTreeNode = lefTreeNode;
}
public TreeNode getRightNode() {
return rightNode;
}
public void setRightNode(TreeNode rightNode) {
this.rightNode = rightNode;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public boolean isDelete() {
return isDelete;
}
public void setDelete(boolean isDelete) {
this.isDelete = isDelete;
}
public TreeNode() {
super();
}
public TreeNode(int value) {
this(null, null, value, false);
}
public TreeNode(TreeNode lefTreeNode, TreeNode rightNode, int value, boolean isDelete) {
super();
this.lefTreeNode = lefTreeNode;
this.rightNode = rightNode;
this.value = value;
this.isDelete = isDelete;
}
@Override
public String toString() {
return lefTreeNode + ", rightNode=" + rightNode + ", value=" + value + ", isDelete=" + isDelete;
}
}
紅黑樹算法,學會手寫紅黑樹
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Stack;
/*
平衡二叉樹-紅黑樹 紅黑樹性質:
1、根節點是黑色
2、節點顏色只能是紅色或黑色的
3、紅色節點的兩個兒子只能是黑色的
4、NIL節點看作是黑色的
5、任意節點到子孫的空節點的路徑上的黑色節點數目相同
*/
public class RedBlackTree {
public static void main(String[] args) {
RedBlackTree redBlackTree = new RedBlackTree();
redBlackTree.insert(6);
redBlackTree.insert(4);
redBlackTree.insert(5);
redBlackTree.insert(7);
redBlackTree.insert(3);
redBlackTree.insert(8);
/*redBlackTree.remove(6);
redBlackTree.remove(3);
redBlackTree.remove(8);
redBlackTree.remove(7);
redBlackTree.remove(4);
redBlackTree.remove(5);*/
System.out.println("MAX:" + redBlackTree.findMax());
System.out.println("MIN:" + redBlackTree.findMin());
redBlackTree.preOrder(redBlackTree.root);//5 4 3 7 6 8
redBlackTree.inOrder(redBlackTree.root);//5 4 3 7 6 8
redBlackTree.postOrder(redBlackTree.root);//3 4 6 8 7 5
redBlackTree.levelOrder(redBlackTree.root);//5--Black 4--Black 7--Black 3--Red 6--Red 8--Red
}
//NIL節點(無元素)
private RedBlackNode NIL = new RedBlackNode(Color.Black);
//紅黑樹的根
private RedBlackNode root = NIL;
//紅黑樹節點數目
private int size = 0;
public RedBlackTree(RedBlackNode root) {
this.root = root;
}
public RedBlackTree(RedBlackNode NIL, RedBlackNode root, int size) {
this.NIL = NIL;
this.root = root;
this.size = size;
}
public RedBlackTree() {
}
//紅黑樹中是否含有node節點
public boolean containsRec(RedBlackNode node, RedBlackNode root) {
//紅黑樹爲空
if (root == NIL) {
return false;
}
//搜索右子樹
if (node.value > root.value) {
return contains(node, root.right);
}
//搜索左子樹
if (node.value < root.value) {
return contains(node, root.left);
}
//包含node節點
return true;
}
//紅黑樹中是否含有node節點
public boolean contains(RedBlackNode node, RedBlackNode root) {
//紅黑樹爲空
if (root == NIL) {
return false;
}
while (node.value != root.value) {
//搜索右子樹
if (node.value > root.value) {
root = root.right;
}
//搜索左子樹
else {
root = root.left;
}
//未在紅黑樹中爲找到node節點
if (root == NIL) {
return false;
}
}
return true;
}
//返回紅黑樹中的最小節點
public int findMin() {
RedBlackNode value = findMin(root);
if (value == null) {
return -1;
} else {
return value.value;
}
}
//在指定紅黑樹中搜尋最小節點 遞歸搜索
private RedBlackNode findMin(RedBlackNode root) {
//指定紅黑樹爲空樹
if (root == NIL) {
return null;
}
//如果紅黑樹的左子樹非空,則搜索其左子樹
if (root.left != NIL) {
return findMin(root.left);
}
return root;
}
//返回紅黑樹中的最大節點
public int findMax() {
RedBlackNode value = findMax(root);
if (value == null) {
return -1;
} else {
return value.value;
}
}
//搜索指定紅黑樹中的最大節點 遞歸實現
private RedBlackNode findMax(RedBlackNode root) {
//紅黑樹爲空
if (root == NIL) {
return null;
}
//搜索右子樹
if (root.right != NIL) {
return findMax(root.right);
}
return root;
}
//得到祖父節點
private RedBlackNode getGrandParent(RedBlackNode node) {
//父親節點爲空
if (node.parent == null) {
return null;
}
return node.parent.parent;
}
//得到叔父節點
private RedBlackNode getUncle(RedBlackNode node) {
RedBlackNode grandParent = getGrandParent(node);
//祖父節點爲空
if (grandParent == null) {
return null;
}
//父親節是祖父節點的左兒子
if (node.parent == grandParent.left) {
return grandParent.right;
}
//父親節點時祖父節點的右兒子
else {
return grandParent.left;
}
}
//左旋 RR型 注意更新root、rightChild、rightChild.left的父親指針指向
private RedBlackNode rolateWithRightChild(RedBlackNode root) {
//parent記錄root的父親節點
RedBlackNode parent = root.parent;
//rightChild root節點的右兒子
RedBlackNode rightChild = root.right;
//如果rightChild的左兒子非空,更新rightChild.left的parent
if (rightChild.left != NIL) {
rightChild.left.parent = root;
}
root.right = rightChild.left;
rightChild.left = root;
//更新root的parent
root.parent = rightChild;
//root節點爲根節點時,根節點改變
if (parent == null) {
this.root = rightChild;
}
//root節點爲父親的右兒子
else if (root == parent.right) {
parent.right = rightChild;
}
//root節點爲父親的左兒子
else {
parent.left = rightChild;
}
//更新rightChild的parent
rightChild.parent = parent;
return rightChild;
}
//右旋 LL型
private RedBlackNode rolateWithLeftChild(RedBlackNode root) {
//parent記錄root的父親節點
RedBlackNode parent = root.parent;
//leftChild root節點的左兒子
RedBlackNode leftChild = root.left;
root.left = leftChild.right;
//如果leftChild的右兒子非空,則更新leftChild.right的parent
if (leftChild.right != NIL) {
leftChild.right.parent = root;
}
leftChild.right = root;
//更新root的parent
root.parent = leftChild;
//如果root節點之前是根節點,則根節點指向更新
if (parent == null) {
this.root = leftChild;
}
//root節點爲父親的左兒子
else if (root == parent.left) {
parent.left = leftChild;
}
//root節點爲父親的右兒子
else {
parent.right = leftChild;
}
//更新leftChild的parent
leftChild.parent = parent;
return leftChild;
}
//雙旋轉 RL型
private RedBlackNode doubleWithRightChild(RedBlackNode root) {
//先右旋再左旋
rolateWithLeftChild(root.right);
return rolateWithRightChild(root);
}
//雙旋轉 LR型
private RedBlackNode doubleWithLeftChild(RedBlackNode root) {
//先左旋再右旋
rolateWithRightChild(root.left);
return rolateWithLeftChild(root);
}
//插入值爲value的節點
public void insert(int value) {
//插入節點的左右兒子爲NIL,顏色爲紅色
RedBlackNode node = new RedBlackNode(value, NIL, NIL, Color.Red);
//如果已經含有此節點
if (contains(node, root)) {
return;
}
insert(node, root);
//紅黑樹節點數目增加一個
size++;
}
//向紅黑樹中插入節點node
public void insert(RedBlackNode node, RedBlackNode root) {
//如果根節點爲空,則將插入的節點染成黑色即可
if (this.root == NIL) {
node.color = Color.Black;
this.root = node;
} else {
//記錄插入節點的父親
RedBlackNode parent = root;
//找到node節點應該插入的位置
while (node.value != root.value) {
//更新parent
parent = root;
//插入到右子樹
if (node.value > root.value) {
root = root.right;
//到達NIl節點,作爲右兒子插入
if (root == NIL) {
node.parent = parent;
parent.right = node;
break;
}
}
//插入到左子樹
else {
root = root.left;
//作爲左兒子插入
if (root == NIL) {
node.parent = parent;
parent.left = node;
break;
}
}
}
//執行插入修復操作
insertFixUp(node);
}
}
//node節點插入後進行修復
private void insertFixUp(RedBlackNode node) {
//添加修復操作不會超過2次,node節點經過前一次處理後上升到根節點,顏色爲紅色,染成黑色,更新根節點指針
if (node.parent == null) {
node.color = Color.Black;
//更新root引用
this.root = node;
return;
}
//node節點的父親顏色是黑色,無需調整
if (node.parent.color == Color.Black) {
return;
}
//得到node節點的叔父、父親、祖父節點
RedBlackNode uncle = getUncle(node);
RedBlackNode grandParent = getGrandParent(node);
RedBlackNode parent = node.parent;
/*node節點的父節點、叔父節點顏色爲紅色,祖父節點爲黑色
策略:將node節點的父節點、叔父節點顏色染成黑色,祖父節點染成紅色。
此時祖父節點可能與其父節點顏色衝突,遞歸解決*/
if (uncle.color == Color.Red) {
node.parent.color = Color.Black;
uncle.color = Color.Black;
grandParent.color = Color.Red;
//遞歸修復grandParent
insertFixUp(grandParent);
}
//LL型 叔父節點是黑色 策略:將父親節點染成黑色,祖父節點染成紅色, 右旋轉
else if (node == parent.left && parent == grandParent.left) {
parent.color = Color.Black;
grandParent.color = Color.Red;
rolateWithLeftChild(grandParent);
}
//RL型 叔父節點是黑色 策略:node節點染成黑色,祖父節點染成紅色,先右旋轉再左旋轉
else if (node == parent.left && parent == grandParent.right) {
node.color = Color.Black;
grandParent.color = Color.Red;
doubleWithRightChild(grandParent);
}
//RR型 叔父節點黑色策略:將父親節點染成黑色、祖父節點染成紅色,左旋轉
else if (node == parent.right && parent == grandParent.right) {
parent.color = Color.Black;
grandParent.color = Color.Red;
rolateWithRightChild(grandParent);
}
//LR型 叔父節點黑色 策略:node節點染成黑色,祖父節點染成紅色,先左旋,再右旋
else {
node.color = Color.Black;
grandParent.color = Color.Red;
doubleWithLeftChild(grandParent);
}
}
//刪除值爲value的節點
public void remove(int val) {
RedBlackNode node = new RedBlackNode(val, Color.Red);
remove(node, root);
}
//刪除root中的節點node
private RedBlackNode remove(RedBlackNode node, RedBlackNode root) {
//紅黑樹爲空
if (root == NIL) {
return null;
}
//節點在右子樹
if (node.value > root.value) {
root.right = remove(node, root.right);
}
//節點在左子樹
if (node.value < root.value) {
root.left = remove(node, root.left);
}
if (node.value == root.value) {
//待刪除節點的左右子樹非空
if (root.left != NIL && root.right != NIL) {
//用右子樹的最小值節點替代待刪除的節點
RedBlackNode replace = findMin(root.right);
root.value = replace.value;
//問題轉化爲刪除右子樹中的replace節點
root.right = remove(replace, root.right);
}
//待刪除節點只有左子樹或只有右子樹或無左右子樹
else {
//被刪除節點的父節點
RedBlackNode parent = root.parent;
//被刪除的節點
RedBlackNode deleteNode = root;
//被刪除節點的位置尤其兒子取代
root = (root.left != NIL) ? root.left : root.right;
//如果被刪除節點是根節點, 將後繼節點染黑後作爲新的根節點
if (parent == null) {
deleteNode.left.parent = parent;
deleteNode.right.parent = parent;
root.color = Color.Black;
this.root = root;
} else {
//node節點是作爲parent的左兒子還是右兒子
boolean isLeftChild = false;
if (deleteNode == parent.left) {
isLeftChild = true;
}
//被刪除節點的兒子的父親指向祖父
root.parent = parent;
// 將root接到其祖父
if (isLeftChild) {
parent.left = root;
} else {
parent.right = root;
}
//修復被刪除節點
removeFixUp(root, deleteNode);
//清除deleteNode的所有引用
deleteNode.parent = null;
deleteNode.left = null;
deleteNode.right = null;
}
}
}
//將NIL節點的父親置爲null,NIL節點的顏色在修復過程中可能會被染成紅色,將其恢復成黑色
NIL.parent = null;
NIL.color = Color.Black;
//紅黑樹節點數目減少一個
size--;
return root;
}
//刪除node的父節點後進行修復紅黑樹性質的操作,修復操作旋轉次數不會超過3次
private void removeFixUp(RedBlackNode node, RedBlackNode deleteNode) {
//如果被刪除節點是根節點,直接將node節點顏色染黑讓其作爲新的根節點
if (deleteNode.parent == null) {
node.color = Color.Black;
this.root = node;
return;
}
//如果被刪除節點的顏色是紅色的,對紅黑樹的五條性質不造成影響,無需處理
if (deleteNode.color == Color.Red) {
return;
}
//如果被刪除節點的後繼節點顏色爲紅色,將其染成黑色既可
if (node.color == Color.Red) {
node.color = Color.Black;
return;
}
//如果被刪除節點的後繼節點顏色爲黑色,那麼從被刪除節點父親節點到被刪除節點NIL節點的路徑上將少一個黑色節點
if (node.color == Color.Black) {
//得到node的叔叔節點,叔叔節點的左兒子、右兒子, 祖父
RedBlackNode uncle = getBrother(node);
RedBlackNode uncleLeftChild = uncle.left;
RedBlackNode uncleRightChild = uncle.right;
RedBlackNode grandParent = node.parent;
while (true) {
//node節點現在是其祖父的左兒子
if (node == grandParent.left) {
/*狀態 - 1 如果叔叔節點顏色是紅色
策略:將祖父節點染成紅色,叔叔節點染成染成黑色後左旋,使其裝態轉化爲2或3或4以便進一步調整*/
if (uncle.color == Color.Red) {
uncle.color = Color.Black;
grandParent.color = Color.Red;
rolateWithRightChild(grandParent);
//更新node指向
node = grandParent.right;
grandParent = node.parent;
uncle = getBrother(node);
uncleLeftChild = uncle.left;
uncleRightChild = uncle.right;
}
/*狀態 - 2
叔叔節點爲黑色,其左右兒子也爲黑色(此時叔叔節點可能爲NIL節點,如果其爲NIL節點,其左右兒子將爲NULL
,要注意空指針判斷) 策略:將叔叔節點染成紅色,現在祖父是多了一重黑色,向狀態1,3,4 轉化*/
else if (isBlack(uncleLeftChild)
&& isBlack(uncleRightChild)) {
//更新叔叔節點顏色
uncle.color = Color.Red;
//更新node指向
node = grandParent;
grandParent = node.parent;
//node現在指向了根節點
if (grandParent == null) {
node.color = Color.Black;
this.root = node;
break;
}
uncle = getBrother(node);
uncleLeftChild = uncle.left;
uncleRightChild = uncle.right;
//當前節點是紅黑型的節點,將當前節點顏色染成黑色,調整完成
if (grandParent.color == Color.Red) {
grandParent.color = Color.Black;
break;
}
}
/*狀態 - 3 叔叔節點顏色爲黑色,其左兒子顏色爲紅色,右兒子顏色爲黑色
策略:將叔叔節點的左兒子染成黑色,叔叔節點染成紅色,右旋向狀態4轉化*/
else if (uncleLeftChild.color == Color.Red
&& uncleRightChild.color == Color.Black) {
uncle.color = Color.Red;
uncleLeftChild.color = Color.Black;
rolateWithLeftChild(uncle);
}
/*狀態 - 4 叔叔節點顏色爲黑色,右兒子顏色爲紅色,左兒子顏色任意
策略:將uncle節點顏色與祖父節點顏色互換,叔叔節點右兒子染成黑色,左旋轉調整完成。
此次操作是祖父的左子樹路徑增加了一個黑色節點,但叔叔節點右子樹少了一個黑色節點,把叔叔節點的右兒子染成黑色彌補*/
else {
Color temp = uncle.color;
uncle.color = grandParent.color;
grandParent.color = temp;
uncleRightChild.color = Color.Black;
rolateWithRightChild(grandParent);
//調整完成
break;
}
}
//node節點現在使其祖父的右兒子 鏡像對稱的四種情形
if (node == grandParent.right) {
//狀態 - 1 如果叔叔節點顏色是紅色
//策略:將祖父節點染成紅色,叔叔節點染成黑色後右旋,使其狀態轉化爲2或3或4以便進一步調整
if (uncle.color == Color.Red) {
uncle.color = Color.Black;
grandParent.color = Color.Red;
rolateWithLeftChild(grandParent);
//更新node指向
node = grandParent.left;
grandParent = node.parent;
uncle = getBrother(node);
uncleLeftChild = uncle.left;
uncleRightChild = uncle.right;
}
/*狀態 - 2 叔叔節點爲黑色,叔叔節點的左右兒子顏色爲黑色
策略:將叔叔節點染成紅色,現在祖父節點多了一重黑色。如果祖父節點自身顏色爲紅色
,將祖父節點染成黑色,調整結束。否則修改node指向使其指向祖父*/
else if (isBlack(uncleRightChild)
&& isBlack(uncleLeftChild)) {
uncle.color = Color.Red;
//修改node指向
node = grandParent;
grandParent = node.parent;
//node節點提升到了根節點位置
if (grandParent == null) {
node.color = Color.Black;
this.root = node;
break;
}
uncle = getBrother(node);
uncleLeftChild = uncle.left;
uncleRightChild = uncle.right;
//如果祖父節點自身顏色爲紅色,將祖父節點染成黑色 ,調整完成
if (node.color == Color.Red) {
node.color = Color.Black;
break;
}
}
/*狀態 - 3 叔叔節點爲黑色,其左兒子爲黑色,右兒子爲紅色
策略:將叔叔節點染成紅色,右兒子染成黑色,右旋向狀態 - 4 轉換*/
else if (uncleLeftChild.color == Color.Black
&& uncleRightChild.color == Color.Red) {
uncle.color = Color.Red;
uncleRightChild.color = Color.Black;
rolateWithRightChild(uncle);
}
/*狀態 - 4 叔叔節點爲黑色,左兒子爲紅色,右兒子顏色任意
策略:將叔叔節點的顏色與祖父顏色互換,叔叔節點的左兒子染成黑色,右旋。調整完成。*/
else {
Color temp = uncle.color;
uncle.color = grandParent.color;
grandParent.color = temp;
uncleLeftChild.color = Color.Black;
rolateWithLeftChild(grandParent);
//調整完成
break;
}
}
}
}
//最後再次把根節點染成黑色(應爲node可能上升到根節點 處
this.root.color = Color.Black;
}
//node顏色爲黑色或是null節點是認爲其實黑色
private boolean isBlack(RedBlackNode node) {
if (node == null || node.color == Color.Black) {
return true;
}
return false;
}
//得到node節點兄弟節點
private RedBlackNode getBrother(RedBlackNode node) {
//當前節點是根節點
if (node.parent == null) {
return null;
}
//兄弟節點是右兒子
if (node == node.parent.left) {
return node.parent.right;
}
//兄弟節點是左兒子
return node.parent.left;
}
//獲取當前紅黑樹的節點數目
public int getSize() {
return size;
}
//非遞歸先序遍歷紅黑樹
public void preOrder(RedBlackNode root) {
//紅黑樹節點數目爲零
if (root == NIL) {
return;
}
System.out.println("先序遍歷:");
Stack<RedBlackNode> stack = new Stack<RedBlackNode>();
stack.push(root);
while (!stack.isEmpty()) {
root = stack.pop();
System.out.print(root.value + " ");
if (root.right != NIL) {
stack.push(root.right);
}
if (root.left != NIL) {
stack.push(root.left);
}
}
System.out.println();
}
//非遞歸中序遍歷紅黑樹
public void inOrder(RedBlackNode root) {
if (root == NIL) {
return;
}
System.out.println("中序遍歷:");
Stack<RedBlackNode> stack = new Stack<RedBlackNode>();
while (root != NIL || !stack.isEmpty()) {
while (root != NIL) {
stack.push(root);
root = root.left;
}
root = stack.pop();
System.out.print(root.value + " ");
root = root.right;
}
System.out.println();
}
//非遞歸後序遍歷紅黑樹
public void postOrder(RedBlackNode root) {
if (root == NIL) {
return;
}
System.out.println("後序遍歷:");
RedBlackNode pre = NIL;
Stack<RedBlackNode> stack = new Stack<RedBlackNode>();
while (root != NIL || !stack.isEmpty()) {
while (root != NIL) {
stack.push(root);
root = root.left;
}
root = stack.peek();
while (root.right == NIL || root.right == pre) {
System.out.print(root.value + " ");
stack.pop();
pre = root;
if (stack.isEmpty()) {
System.out.println();
return;
}
root = stack.peek();
}
root = root.right;
}
}
//層序遍歷紅黑樹
public void levelOrder(RedBlackNode root) {
if (root == NIL) {
return;
}
System.out.println("層序遍歷:");
Queue<RedBlackNode> queue = new ArrayDeque<RedBlackNode>();
queue.offer(root);
while (!queue.isEmpty()) {
root = queue.poll();
System.out.print(root.value + "--" + root.color + " ");
if (root.left != NIL) {
queue.offer(root.left);
}
if (root.right != NIL) {
queue.offer(root.right);
}
}
System.out.println();
}
}
//紅黑樹節點類
class RedBlackNode {
/*@value 節點值
@color 節點顏色
@left @right 節點左右兒子
@parent 父節點*/
int value;
Color color;
RedBlackNode left;
RedBlackNode right;
RedBlackNode parent;
public RedBlackNode(int value, RedBlackNode left, RedBlackNode right, Color color) {
this.value = value;
this.left = left;
this.right = right;
this.color = color;
}
public RedBlackNode(int value, Color color) {
this.value = value;
this.color = color;
}
//用來構造NIL節點
public RedBlackNode(Color color) {
this.color = color;
}
}
//枚舉類-節點顏色
enum Color {
Black, Red;
}