Repository: coderbruis/AlgorithmsInJava Branch: master Commit: f77c4d3020b1 Files: 57 Total size: 144.1 KB Directory structure: gitextract_qjmsbrz4/ ├── .gitignore ├── README.md ├── notes/ │ ├── algorithms/ │ │ ├── 冒泡排序算法.md │ │ ├── 基数排序算法.md │ │ ├── 基础排序算法.md │ │ ├── 堆排序算法.md │ │ ├── 希尔排序算法.md │ │ ├── 归并排序算法.md │ │ ├── 快速排序算法.md │ │ ├── 插入排序算法.md │ │ ├── 桶排序算法.md │ │ └── 选择排序算法.md │ └── datastructures/ │ ├── AVL树.md │ └── 堆和优先队列.md ├── pom.xml └── src/ └── main/ └── java/ └── com/ └── bruis/ └── algorithminjava/ ├── algorithm/ │ ├── huawei/ │ │ └── Question01.java │ ├── leetcode/ │ │ ├── ContainsDuplicate_217.java │ │ ├── LongestPalindromicSubstring_5.java │ │ ├── MaximumSubarray_53.java │ │ ├── TwoSum.java │ │ ├── TwoSumII.java │ │ └── array/ │ │ ├── IsPalindrome.java │ │ ├── MaximumGap.java │ │ ├── MaximumProductSubarray.java │ │ ├── ReversePairs.java │ │ ├── ReverseVowels.java │ │ ├── SortColors.java │ │ ├── SubarraySumEqualsK.java │ │ ├── ThreeSum.java │ │ ├── TopKFrequentElements.java │ │ ├── TwoSum.java │ │ └── TwoSumII.java │ ├── sort/ │ │ ├── BinarySearch.java │ │ ├── BubbleSort.java │ │ ├── BucketSort.java │ │ ├── Heap.java │ │ ├── HeapSort01.java │ │ ├── HeapSort02.java │ │ ├── InsertionSort.java │ │ ├── MergeSort.java │ │ ├── MergeSortAdvanced01.java │ │ ├── MergeSortBU.java │ │ ├── QuickSort.java │ │ ├── QuickSort2.java │ │ ├── QuickSort2Ways.java │ │ ├── QuickSort3Ways.java │ │ └── ShellSort.java │ └── stack/ │ └── MinStack.java ├── datastructures/ │ ├── array/ │ │ └── MyArray.java │ ├── heap/ │ │ ├── IndexMapHeap.java │ │ └── MaxHeap.java │ ├── queue/ │ │ ├── MyLoopQueue.java │ │ ├── MyQueue.java │ │ └── Queue.java │ └── stack/ │ ├── MyStack.java │ └── Stack.java └── utils/ └── SortTestHelper.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/** ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ ### VS Code ### .vscode/ ================================================ FILE: README.md ================================================ # 用Java实现算法和数据结构 本项目主要用于自己在工作之余记录用Java实现的算法和数据结构的源码;同时还会记录自己刷leetcode的题解思路等; > Tip:如果读者电脑无法浏览到github图片,则需要设置hosts配置文件, 解决办法: [解决方案](https://zhuanlan.zhihu.com/p/107691233) # 经典排序算法 ✅ [冒泡排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [选择排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [插入排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [插入排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [归并排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [快速排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [希尔排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [桶排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E6%A1%B6%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [基数排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ✅ [堆排序](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/algorithms/%E5%A0%86%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.md) ## 排序算法总结 | 排序名 | 平均时间复杂度 | 空间复杂度 | 优势 | 劣势 | 适用场景 | 稳定性 | | :--: | :------: | :----------: | :--: | :--: | :-----------------------: | :--: | | 冒泡排序 | O(n^2) | O(1) | | | | 稳定 | | 插入排序 | O(n^2) | O(1) | | | | 稳定 | | 计数排序 | O(n + k) | O(M) | | | 对于用例少的数据,比如对人的年龄排序或者身高排序。 | 稳定 | | 基数排序 | O(d(n+r)) | O(M) | | | | 稳定 | | 桶排序 | O(n+k) | O(n + k) | | | | 稳定 | | 选择排序 | O(n^2) | O(1) | | | | 不稳定 | | 归并排序 | O(nlogn) | O(N) | | | | 稳定 | | 快速排序 | O(nlogn) | O(logn)~O(n) | | | | 不稳定 | | 希尔排序 | O(nlogn) | O(1) | | | | 不稳定 | | 堆排序 | O(nlogn) | O(1) | | | | 不稳定 | ## 其他算法总结 - 二分查找法,时间复杂度为O(logn) ## 其他 注意这里总结的都是平均时间复杂度。 例如插入排序,如果是有序的数组,则时间复杂度会退化成O(n)。而快速排序,对于每次选择的p位置都是末尾位置,则会退化成时间复杂度为O(n^2)(通过让p的位置随机来解决这个问题)。 对于归并排序、快速排序、堆排序这三个O(nlogn)的排序来说,时间复杂度是有常数级别的区别的,但是快速排序是相对更快的一种排序。 这里还需要注意的是,对于插入排序、快速排序、堆排序来说,都是原地排序的,即不需要额外的空间;而归并排序则是非原地排序,需要借助额外空间。 1. 排序算法的稳定性:对于相等的元素,在排序后,原来靠前的元素依然靠前,相等元素的相对位置没有发生改变。 2. 对于算法面试来说,除非面试题特别说明,否则认为要排序的数据范围是均匀分布的。 3. 快速排序之所以叫快速排序,并不代表它比堆排序和归并排序优良。在最好情况下,它的渐进复杂度与 堆排序和归并排序是相同的。知识快速排序的常量系数比较小而已。 4. 类库上提供的排序,并不是某一种算法的实现,而是综合了多种排序的综合排序。当数组比较小时,使用插入排序;当数组较大时,选择快速排序或其他的O(nlogn)的排序。 # 经典算法 KMP算法 马拉车算法 Prim算法 Krusk算法 Dijkstra算法 Bellman-Ford算法 # 经典数据结构 数组 栈和队列 链表 二分搜索树 集合和映射 ✅ [堆和优先队列](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/datastructures/%E5%A0%86%E5%92%8C%E4%BC%98%E5%85%88%E9%98%9F%E5%88%97.md) 线段树 Trie树 并查集 ✅ [AVL树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/datastructures/AVL%E6%A0%91.md) 红黑树 哈希表 # 数据结构总结 线性结构和非线性结构数据结构的区别 > 线性结构 1. 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。 2. 线性结构拥有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的,链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。 3. 线性结构中存在两种操作受限的使用场景,即队列和栈。栈的操作只能在线性表的一端进行,就是我们常说的先进后出(FILO),队列的插入操作在线性表的一端进行而其他操作在线性表的另一端进行,先进先出(FIFO),由于线性结构存在两种存储结构,因 此队列和栈各存在两个实现方式。 4. 常见的线性结构包括:数组、链表、堆栈和队列等。 > 非线性结构 1. 树作为一种应用广泛的一对多非线性数据结构,不仅有数据间的指向关系,还有层级关系,常见的有二分搜索树(二叉树)、AVL树、B树以及红黑树等。 | 名称 | 类型 | 特性 | 常见应用 | | :----: | :----: | :----: | :----: | | 数组 | 线性结构 | 1. 基础数据机构;底层通过索引快速取得元素值;
2. 查询快O(1),增删慢O(n); | JDK中的ArrayList | | 链表 | 线性结构 | 1. 以链表节点为元素,元素中包含指向下一节点的引用;
2. 链表分为单向链表和双向链表,区别在于双向链表有前驱节点和后继节点;
3. 查询慢O(n),增删快O(1);| JDK中的LinkedList、LinkedHashMap等 | | 栈 | 线性结构 | 1. 先进后出的数据结构;| JDK中的SynchronousQueue | | 队列 | 线性结构 | 1. 先进先出的数据结构;| JDK中的线程池底层的队列,包括:LinkedBlockingQueue、SynchronousQueue、ArraysBlockingQueue、DelayQueue等 | | 二分搜索树 | 非线性结构 | 1. 二分搜索树是一颗二叉树,每棵树节点都包含了节点值;
2. 每个节点值都大于左子树的所有节点的值,而小于右子树的所有节点的值;
| x | | AVL树 | 非线性结构 | 1. AVL树是一个平衡二叉树,平衡二叉树的特性就是指左右加点的差值不能大于一;
2. AVL树每次插入、删除都需要进行左旋、右旋来保持AVL树的平衡性,因而这种维护需要更多代价;
3. AVL树的高度平衡,查找节点效率高;
4. AVL树的高度为logN;| windows对进程地址空间的管理用到了AVL树 | | 红黑树 | 非线性结构 | 1. ① 根节点为黑色;② 红黑树节点要么是红色,要么是黑色;③ 任意节点到叶子节点经过的黑色节点数量相同;④ 每个叶子节点(没有子节点的节点)都是黑色的;⑤ 如果一个节点是红色的,那么它的子节点都是黑色的;
2. 红黑树是"近似平衡",因为红色节点的存在,让红黑树能够实现红色节点和其父亲节点进行融合,从而成为类似于2-3树这种"绝对平衡的树";
3. 红黑树的高度近似于2logN;
4. 红黑树由于是近似平衡,所以维护成本比AVL树的要低,并且实践证明,红黑树的插入、查找和删除性能都比较稳定;| JDK中的TreeMap使用了红黑树;linux中的epoll底层,用红黑树来存储epoll_fd; | | B+树 | 非线性结构 | 1. 多节点,层数低;2. 时间复杂度为O(logn); | InnoDB底层使用的B+树 | # leetcode专区 ## 1. 数组 题目名称 | 难度 | 地址 | 题解 ---|---|---|--- 两数之和 | 简单 | [https://leetcode-cn.com/problems/two-sum/](https://leetcode-cn.com/problems/two-sum/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/array/TwoSum.java) 两数之和 II - 输入有序数组 | 简单 | [https://leetcode-cn.com/problems/two-sum-ii/](https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/description/?utm_source=LCUS&utm_medium=ip_redirect_q_uns&utm_campaign=transfer2china) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/array/TwoSumII.java) 反转字符串中的元音字母 | 简单 | [https://leetcode-cn.com/problems/reverse-vowels-of-a-string/](https://leetcode-cn.com/problems/reverse-vowels-of-a-string/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/array/ReverseVowels.java) 验证回文串 | 简单 | [https://leetcode-cn.com/problems/valid-palindrome/](https://leetcode-cn.com/problems/valid-palindrome/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/array/IsPalindrome.java) 三数之和 | 中等 | [https://leetcode-cn.com/problems/3sum/](https://leetcode-cn.com/problems/3sum/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/array/ThreeSum.java) 乘积最大子数组 | 中等 | [https://leetcode-cn.com/problems/maximum-product-subarray/](https://leetcode-cn.com/problems/maximum-product-subarray/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/array/MaximumProductSubarray.java) 和为K的子数组 | 中等 | [https://leetcode-cn.com/problems/subarray-sum-equals-k/](https://leetcode-cn.com/problems/subarray-sum-equals-k/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/array/SubarraySumEqualsK.java) 逆序对 | 中等 | [https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/](https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ReversePairs.java) 颜色分类 | 中等 | [https://leetcode-cn.com/problems/sort-colors/](https://leetcode-cn.com/problems/sort-colors/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/sort-colors.java) ## 2. 堆栈 题目名称 | 难度 | 地址 | 题解 ---|---|---|--- 最小栈 | 简单 | [https://leetcode-cn.com/problems/min-stack/](https://leetcode-cn.com/problems/min-stack/) | [题解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/src/main/java/com/bruis/algorithminjava/algorithm/stack/MinStack.java) ## 3. 链表 # 支持 ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/zan.jpg) ================================================ FILE: notes/algorithms/冒泡排序算法.md ================================================ - [前言](#前言) - [正文](#正文) - [1. 实现代码](#1-实现代码) - [参考](#参考) ## 前言 思路:每一趟遍历将最大元素冒泡到数组最后的位置; 动态图 ![冒泡排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/bubbleSort.gif) - 时间复杂度:O(n^2) - 空间复杂度:O(1) - 稳定性:稳定 ## 正文 ### 1. 实现代码 ``` public class BubbleSort { // 方式; public static void sort(int[] arr) { int n = arr.length; boolean swapped = false; do { swapped = false; for (int i = 1; i < n; i++) { if (arr[i - 1] > arr[i]) { swap(arr, i - 1, i); swapped = true; } } } while(swapped); } // 方式2 public static void sort2(int[] arr) { int n = arr.length; if (n <= 1) { return; } // 使用newn来进行优化 int newn; do { newn = 0; for (int i = 1; i < n; i++) { if (arr[i - 1] > arr[i]) { swap(arr, i - 1, i); // 记录当前排序最后一次交换的位置,在此之后的元素在下一轮扫描中均不考虑 newn = i; } } n = newn; } while (newn > 0); } // 方式3 public static void sort3(int[] arr) { int n = arr.length; if (n <= 1) { return; } for (int i = 0; i < n; i++) { boolean flag = false; // n - i - 1 表示每轮排序都会有一个最大元素冒泡到最大位置,因而每轮排序都会少一个遍历的元素 for (int j = 0; j < n - i - 1; j++) { if (arr[j] < arr[j + 1]) { swap(arr, j, j + 1); flag = true; } } // 此轮排序没有数据交换,则退出排序 if (!flag) { break; } } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` > 分析:对于方式一,由于每一趟排序都会将最大元素冒泡到最后一位,所以下一次排序就不用考虑最后一个位置的元素了,从而加速下一次排序的速度。代码中的方式2和方式3都是同样的优化思路。 ## 参考 - [动态图参考](https://www.runoob.com/w3cnote/bubble-sort.html) ================================================ FILE: notes/algorithms/基数排序算法.md ================================================ ================================================ FILE: notes/algorithms/基础排序算法.md ================================================ - [进入算法的世界](#进入算法的世界) - [1. 冒泡排序](#1-冒泡排序) - [2. 选择排序](#2-选择排序) - [3. 插入排序](#3-插入排序) - [4. 归并排序](#4-归并排序) - [5. 快速排序](#5-快速排序) - [6. 希尔排序](#6-希尔排序) - [7. 基数排序](#7-基数排序) - [8. 桶排序](#8-桶排序) - [排序总结](#排序总结) 前言 - 深入总结八大排序算法以及相关的面试算法题 - 总结其他算法 ## 进入算法的世界 #### 1. 冒泡排序 思路:每一趟遍历将最大元素冒泡到数组最后的位置; ![冒泡排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/bubbleSort.gif) ``` public class BubbleSort { // 方式; public static void sort(int[] arr) { int n = arr.length; boolean swapped = false; do { swapped = false; for (int i = 1; i < n; i++) { if (arr[i - 1] > arr[i]) { swap(arr, i - 1, i); swapped = true; } } } while(swapped); } // 方式2 public static void sort2(int[] arr) { int n = arr.length; if (n <= 1) { return; } // 使用newn来进行优化 int newn; do { newn = 0; for (int i = 1; i < n; i++) { if (arr[i - 1] > arr[i]) { swap(arr, i - 1, i); // 记录当前排序最后一次交换的位置,在此之后的元素在下一轮扫描中均不考虑 newn = i; } } n = newn; } while (newn > 0); } // 方式3 public static void sort3(int[] arr) { int n = arr.length; if (n <= 1) { return; } for (int i = 0; i < n; i++) { boolean flag = false; // n - i - 1 表示每轮排序都会有一个最大元素冒泡到最大位置,因而每轮排序都会少一个遍历的元素 for (int j = 0; j < n - i - 1; j++) { if (arr[j] < arr[j + 1]) { swap(arr, j, j + 1); flag = true; } } // 此轮排序没有数据交换,则退出排序 if (!flag) { break; } } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` > 分析:对于方式一,由于每一趟排序都会将最大元素冒泡到最后一位,所以下一次排序就不用考虑最后一个位置的元素了,从而加速下一次排序的速度。代码中的方式2和方式3都是同样的优化思路。 #### 2. 选择排序 思路:依次寻找[i, n)区间里的最小值的索引。 ![选择排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/selectionSort.gif) ``` public class SelectionSort { public static void sort(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { int min = i; for (int j = i+1; i < n; i++) { if (arr[j] < arr[min]) { min = j; } } swap(arr, i, min); } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` #### 3. 插入排序 思路:插入排序就跟玩扑克牌一样,先把一部分牌给排好顺序,然后依次往后排剩下的牌,最终达到将所有牌都排序的效果。 ![插入排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/insertionSort.gif) ``` public class InsertionSort { // 方式1 public static void sort(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { for (int j = i; j > 0; j--) { // 注意这里j-1没有越界,因为j > 0进行了判断 if (arr[j] < arr[j-1]) { swap(arr, j, j-1); } else { break; } } } } // 方式2 public static void sort2(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { for (int j = i; j > 0 && arr[j] < arr[j-1]; j--) { // 注意这里j-1没有越界,因为j > 0进行了判断 swap(arr, j, j-1); } } } // 方式3,优化版 public static void sort3(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { // 获取需要比较的元素 int e = arr[i]; int j = i; for (; j > 0 && e < arr[j-1] ; j--) { // 如果满足条件,则前一位元素复制给后一位元素 arr[j] = arr[j-1]; } // 跳出循环,则将需要比较的e元素替换到j位置,j位置即最终停留的位置 arr[j] = e; } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { int [] arr = {6,2,1,5,4,3}; sort2(arr); for (int n : arr) { System.out.println(n); } } } ``` > 分析:方式3相比方式1和2,少了每次排序时的swap过程。 #### 4. 归并排序 归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法: - 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法); - 自下而上的迭代; ![归并过程动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/mergeSort.gif) ![分治思想](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/1557906108-5066-20161218163120151-452283750.png) ``` public class MergeSort { /** * * 将arr[left...mid]和arr[mid+1...right]两部分进行归并 * * @param arr * @param left * @param mid * @param right */ public static void merge(int[] arr, int left, int mid, int right) { int[] aux = Arrays.copyOfRange(arr, left, right + 1); // i表示左边;j表示右边; int i = left, j = mid + 1; // 从左left遍历到右right, 左闭又开 for (int k = left; k <= right; k++) { // 如果左边指针大于mid,则表示左半边数据已经归并完毕 if (i > mid) { // j-left计算出相对aux的位置 arr[k] = aux[j-left]; j++; } else if (j > right) { // j大于right值,则表示右半边数据已经归并完毕 arr[k] = aux[i-left]; i++; } else if (aux[i-left] < aux[j-left]) { arr[k] = aux[i-left]; i++; } else { arr[k] = aux[j-left]; j++; } } } /** * * 对[left, right]范围进行排序 * * @param arr * @param left * @param right */ public static void sort(int[] arr, int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; sort(arr, left, mid); sort(arr, mid + 1, right); merge(arr, left, mid, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } } ``` #### 5. 快速排序 ``` //快速排序 nums [a,b,c,d,e,f,g] p j k i private int partition2(int[] nums, int lt, int gt) { int p = nums[n]; int j = lt; for(int i = lt; i <= gt; i++) { if(nums[i] < p) { swap(nums, ++j, i); } } swap(nums, lt, j); return j; } private int partition(int[] nums, int n, int m) { int p = nums[n]; int j = n; int k = m + 1; int i = n + 1; while(i < k) { if(nums[i] < p) { swap(nums, ++j, i++); } else if(nums[i] > p) { swap(nums, --k, i); } else {// nums[i] == p i++; } } swap(nums, n, j); return j; } private void swap(int[] nums, int n, int m) { int tmp = nums[n]; nums[n] = nums[m]; nums[m] = tmp; } private void sort(int[] nums, int n, int m) { if(n > m) { return; } int p = partition(nums, n, m); sort(nums, n, p - 1); sort(nums, p + 1, m); } public void sort(int[] nums) { sort(nums, 0, nums.length); } ``` #### 6. 希尔排序 #### 7. 基数排序 #### 8. 桶排序 #### 9. 堆排序 - [堆排序详细分析]() #### 排序总结 | 排序名 | 平均时间复杂度 | 空间复杂度 | 优势 | 劣势 | 适用场景 | 稳定性 | | :--: | :------: | :----------: | :--: | :--: | :-----------------------: | :--: | | 冒泡排序 | O(n^2) | O(1) | | | | 稳定 | | 插入排序 | O(n^2) | O(1) | | | | 稳定 | | 计数排序 | | O(M) | | | 对于用例少的数据,比如对人的年龄排序或者身高排序。 | 稳定 | | 基数排序 | O(d(n+r)) | O(M) | | | | 稳定 | | 桶排序 | | | | | | 稳定 | | 选择排序 | O(n^2) | O(1) | | | | 不稳定 | | 归并排序 | O(nlogn) | O(n) | | | | 稳定 | | 快速排序 | O(nlogn) | O(logn)~O(n) | | | | 不稳定 | | 希尔排序 | O(nlogn) | O(1) | | | | 不稳定 | | 堆排序 | O(nlogn) | O(1) | | | | 不稳定 | 注意这里总结的都是平均时间复杂度,例如插入排序,如果是有序的数组,则时间复杂度会退化成O(n)。而快速排序,对于每次选择的p位置都是末尾位置,则会退化成时间复杂度为O(n^2)(通过让p的位置随机来解决这个问题)。 对于归并排序、快速排序、堆排序这三个O(nlogn)的排序来说,时间复杂度是有常数级别的区别的,但是快速排序是相对更快的一种排序。 这里还需要注意的是,对于插入排序、快速排序、堆排序来说,都是原地排序的,即不需要额外的空间;而归并排序则是非原地排序,需要借助额外空间。 > 排序算法的稳定性:对于相等的元素,在排序后,原来靠前的元素依然靠前,相等元素的相对位置没有发生改变。 > 除非面试题特别说明,否则认为要排序的数据范围是均匀分布的。 > 快速排序之所以叫快速排序,并不代表它比堆排序和归并排序优良。在最好情况下,它的渐进复杂度与 堆排序和归并排序是相同的。知识快速排序的常量系数比较小而已。 > 类库上提供的排序,并不是某一种算法的实现,而是综合了多种排序的综合排序。当数组比较小时,使用插入排序;当数组较大时,选择快速排序或其他的O(nlogn)的排序。 ================================================ FILE: notes/algorithms/堆排序算法.md ================================================ - [前言](#前言) - [正文](#正文) - [1. 借助辅助空间](#1-借助辅助空间) - [2. 原地堆排序](#2-原地堆排序) - [参考](#参考) ## 前言 对于堆这种数据结构,详细讲解在这: [堆排序讲解](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/datastructures/%E5%A0%86%E5%92%8C%E4%BC%98%E5%85%88%E9%98%9F%E5%88%97.md) 学完堆这种数据结构后,再来学堆排序,会简单的多。 - 时间复杂度:O(nlogn) - 空间复杂度:O(1) - 稳定性:不稳定 > 动画演示 借助动画演示能更好的理解算法原理。 ![堆排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heapSort01.gif) ![堆排序动态图2](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heapSort02.gif) ## 正文 ### 1. 借助辅助空间 此方式的排序步骤分为以下两步: 1. heapify对目标数组进行堆化 2. extractMax弹出最大堆堆顶元素,赋值到新数组中 此种堆排序算法 - 平均时间复杂度为:O(nlogn) - 空间复杂度为:O(n) ``` import java.util.Random; /** * * 借助辅助空间进行堆排序 * * @author LuoHaiYang */ public class HeapSort01 { /** * 堆化完后并没有排序完成 * @param arr */ private static void heapify(int[] arr) { int n = arr.length; for (int i = n/2; i > 0; i--) { shiftDown(i, n, arr); } } /** * 获取最大堆中堆顶元素 */ private static int extractMax(int[] arr) { int n = arr.length; int max = arr[0]; swap(arr, 0, --n); // 让最后一个元素置0 arr[n] = 0; shiftDown(0, n, arr); return max; } /** * 下沉操作 * @param k */ private static void shiftDown(int k, int n, int[] arr) { while (k * 2 + 1 < n) { // 左子树节点 int j = k * 2 + 1; if (j + 1 < n && arr[j + 1] > arr[j]) { j++; } if (arr[k] >= arr[j]) { break; } swap(arr, k, j); k = j; } } /** * 上浮操作 * @param k */ private void shiftUp(int k, int[] arr) { while (k > 0 && arr[(k-1)/ 2] < arr[k]) { swap(arr, (k-1)/2, k); k = (k-1)/2; } } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static int[] sort(int[] arr) { int n = arr.length; heapify(arr); int[] result = new int[n]; for (int i = 0; i < n; i++) { result[i] = extractMax(arr); } return result; } public static void main(String[] args) { int n = 100; int[] test = new int[n]; Random random = new Random(); for (int i = 0; i < n; i++) { test[i] = random.nextInt(1000); } sort(test); // 测试 for (int i = 1; i < n; i++) { if (test[i-1] < test[i]) { System.out.println("Error!"); } } } } ``` ### 2. 原地堆排序 不借助辅助空间,实现原地堆排序;算法实现步骤为: 1. 对最后一个非叶子节点至堆顶开始进行下沉操作 2. 然后将0~n-1的堆顶元素替换到最后一位,然后n--,再进行下一轮排序 此方式堆排序 - 平均时间复杂度为:O(nlogn) - 空间复杂度为:O(1) ``` /** * * 原地堆排序! 不需要额外的空间 * * 这里构造出来的是一个最小堆 * * @author LuoHaiYang */ public class HeapSort02 { public static void sort(int[] arr) { int n = arr.length; // 注意,此时我们的堆是从0开始索引的 // 从(最后一个元素的索引-1)/2开始 // 最后一个元素的索引 = n-1 // 其实这里 i = (n-1) 也行 for (int i = (n - 1 - 1) / 2; i >= 0; i--) { siftDown2(arr, n, i); } // [a.....v,k] // [.......]k // [.....] ba for (int i = n-1; i > 0; i--) { // 由于上面执行过下沉操作,所以已经是最大堆(但没有排序完)。所以此时swap就将最大值替换到数组末尾。 swap(arr, 0, i); // 由于siftDown中是判断 2*k+1 < n ,所以就是对n-1进行下沉操作; siftDown2(arr, i, 0); } } // 下浮 public static void siftDown(int[] arr, int n, int k) { while (2 * k + 1 < n) { int j = 2 * k + 1; if (j + 1 < n && arr[j+1] > arr[j]) { j += 1; } if (arr[k] >= arr[j]) { break; } swap(arr, k, j); k = j; } } /** * 优化下沉过程, 不适用swap交换,通过赋值来代替。 * * @param arr * @param n * @param k */ private static void siftDown2(int[] arr, int n, int k) { int e = arr[k]; while (2 * k + 1 < n) { int j = 2 * k + 1; if (j + 1 < n && arr[j + 1] > arr[j]) { j++; } if (e >= arr[j]) { break; } // 此时说明arr[j] > arr[k]; 所以让大值上浮; arr[k] = arr[j]; k = j; } // 将最小元素替换到k的位置 arr[k] = e; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { /* int n = 100; int[] test = new int[n]; Random random = new Random(); for (int i = 0; i < n; i++) { test[i] = random.nextInt(1000); } */ int n = 10; int[] test = {10, 41, 30, 28, 16, 22, 13, 19, 17, 15}; sort(test); for (int i = 1; i < n; i++) { if (test[i-1] > test[i]) { throw new IllegalArgumentException("Error!"); } } } } ``` - 平均时间复杂度:O(nlogn) - 空间复杂度:O(1) ## 参考 - [动态图参考](https://www.runoob.com/w3cnote/bubble-sort.html) ================================================ FILE: notes/algorithms/希尔排序算法.md ================================================ ## 前言 希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。 希尔排序是基于插入排序的以下两点性质而提出改进方法的: - 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率; - 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位; 希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。 动态图: ![希尔排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/shellSort.gif) ## 正文 ### 代码实现 ``` public class ShellSort { public static void sort(int[] arr) { int n = arr.length; int h = 1; while (h < n / 3) { h = 3 * h + 1; } while (h >= 1) { for (int i = h; i < n; i++) { int e = arr[i]; int j = i; for (; j >= h && e < arr[j-h]; j -= h) { arr[j] = arr[j-h]; } arr[j] = e; } h /= 3; } } } ``` ================================================ FILE: notes/algorithms/归并排序算法.md ================================================ - [前言](#前言) - [正文](#正文) - [1.(自顶向下)归并排序算法](#1自顶向下归并排序算法) - [2.(自顶向下)归并排序算法——优化](#2自顶向下归并排序算法优化) - [3.(自低向上)归并排序算法](#3自低向上归并排序算法) - [参考](#参考) ## 前言 归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法: - 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法); - 自下而上的迭代; 归并排序算法是一个平均时间复杂度为O(nlogn)级别的算法,并且空间复杂度为O(n)级别。 ![归并过程动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/mergeSort.gif) ![分治思想](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/1557906108-5066-20161218163120151-452283750.png) - 时间复杂度:O(nlogn) - 空间复杂度:O(n) - 稳定性:不稳定 ## 正文 ### 1.(自顶向下)归并排序算法 自顶向下的特点就是——递归。 ### 2.(自顶向下)归并排序算法——优化 ### 3.(自低向上)归并排序算法 自底向上的特点就是——迭代。 除此之外,自底向上排序的算法不需要额外的空间,空间复杂度为O(1)。自底向上有一个非常重要的作用,它可以通过O(nlogn)的 时间复杂度去对链表这种数据结构进行排序。 **对链表进行O(nlogn)级别的排序** ## 参考 - [动态图参考](https://www.runoob.com/w3cnote/bubble-sort.html) ================================================ FILE: notes/algorithms/快速排序算法.md ================================================ - [前言](#前言) - [正文](#正文) - [1. 普通版快速排序](#1-普通版快速排序) - [2. 快速排序算法的优化](#2-快速排序算法的优化) - [3. 双路(两路)快排](#3-双路两路快排) - [4. 三路快排](#4-三路快排) - [5. 三路快排的应用](#5-三路快排的应用) - [参考](#参考) ## 前言 快速排序又是一种分而治之思想在排序算法上的典型应用。 - 时间复杂度:O(nlogn) - 空间复杂度:O(n) - 稳定性:稳定 排序动画图: ![堆排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/quickSort.gif) ## 正文 ### 1. 普通版快速排序 此版本快速排序没有考虑特殊情况,为最简易版本。 参考代码如下: ``` /** * * 快速排序 * * @author LuoHaiYang */ public class QuickSort { /** * 对arr[left...right]部分进行partition操作 * 返回p, 使得arr[left...p-1] < arr[p] ; arr[p+1...right] > arr[p] * * @param arr * @param left * @param right * @return */ private static int partition(int[] arr, int left, int right) { int p = arr[left]; // arr[left+1...j] < p; arr[j+1...i) > p int j = left; for (int i = left + 1; i <= right; i++) { if (arr[i] < p) { j++; swap(arr, j, i); } } swap(arr, left, j); return j; } private static void sort(int[] arr, int left, int right) { if (left >= right) { return; } int p = partition(arr, left, right); sort(arr, left, p-1); sort(arr, p+1, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` ### 2. 快速排序算法的优化 快速排序的最坏运行情况是 O(n^2),比如说顺序数列的快排。但它的平摊期望时间是 O(nlogn),且 O(nlogn) 记号中隐含的常数因子很小,比复杂度稳定等于 O(nlogn) 的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。 此处有两点可以进行优化。 1. 对于数组数小于15时,使用插入排序; 数据量较小时,插入排序效率跟高; 2. 对于快速排序的基准值,取随机数,防止数组退化为顺序数组,即快速排序平均时间复杂度退化为O(n^2) 参考代码如下: ``` /** * 快速排序的优化 * * 对于近乎有序的数组,快速排序会退化为O(n^2)。 * * @author LuoHaiYang */ public class QuickSort2 { /** * 对arr[left...right]部分进行partition操作 * 返回p, 使得arr[left...p-1] < arr[p] ; arr[p+1...right] > arr[p] * * @param arr * @param left * @param right * @return */ private static int partition(int[] arr, int left, int right) { //int p = arr[left]; // ===================================== 优化2 ===================================== // 避免快排退化为O(n^2) swap(arr, left, (int)Math.random()*(right - left + 1) + left); int p = arr[left]; // arr[left+1...j] < p; arr[j+1...i) > p int j = left; for (int i = left + 1; i <= right; i++) { if (arr[i] < p) { j++; swap(arr, j, i); } } swap(arr, left, j); return j; } private static void sort(int[] arr, int left, int right) { // ===================================== 优化1 ===================================== // 如果左右数值小于15,则通过插入排序来进行排序 if (right - left <= 15) { InsertionSort.sort(arr); return; } int p = partition(arr, left, right); sort(arr, left, p-1); sort(arr, p+1, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` ### 3. 双路(两路)快排 如果存在和 基准值p相等的值,则会出现不平衡的情况,而此时快速排序的平均时间复杂度会退化为O(n^2)。而双路(两路)快排就是为了解决这种情况。 下面直接展示实现代码: ``` /** * * 双路快排 * * @author LuoHaiYang */ public class QuickSort2Ways { private static int partition(int[] arr, int left, int right) { swap(arr, left, (int)Math.random()*(right - left + 1) + left); int p = arr[left], i = left + 1, j = right; while(true) { /** * 这里arr[i] < p 和 arr[j] > p 是为了避免出现 arr[i] == p 和 arr[j] == p的情况。 * 如果arr[i] == p,则直接进行了i++了,则数组的p会变得极度不平衡,即 所有小于等于p的值都分在了左边, * 这种情况下,快速排序的平均时间复杂度会退化成:O(n^2) * */ while(i <= right && arr[i] < p) { i++; } while(j >= left + 1 && arr[j] > p) { j--; } if (i > j) { break; } swap(arr, i++, j--); } swap(arr, left, j); return j; } private static void sort(int[] arr, int left, int right) { if (right - left <= 15) { InsertionSort.sort(arr); return; } int p = partition(arr, left, right); sort(arr, left, p - 1); sort(arr, p + 1, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n - 1); } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` 这里需要注意一点,对于双路快排的小于基准值p和大于基准值p的判断,是不能用相等的,正如上述代码中while循环中的: ``` while(i <= right && arr[i] < p) { i++; } while(j >= 0 && arr[j] > p) { j--; } ``` 这里就是为了避免出现极度不平衡的情况出现,上述代码注释以及解释了。 ### 4. 三路快排 然而,对于大量重复值的情况,两路快排还有优化的空间。下面先看下三路快排的代码实现: ``` /** * * 三路快排 * * @author LuoHaiYang */ public class QuickSort3Ways { private static void sort(int[] arr, int left, int right) { if (right - left <= 15) { InsertionSort.sort(arr); return; } // 增加随机值,防止快排退化为O(n^2) swap(arr,left, (int)Math.random()*(right - left - 1) + left); int p = arr[left]; // [p...................................................right] // p // lt // i // gt // arr[left+1...lt] < p arr[lt+1...i) = p arr[gt...right] > p int lt = left, gt = right + 1, i = left + 1; while ( i < gt) { if (arr[i] < p) { swap(arr, lt+1, i); i++; lt++; } else if (arr[i] > p) { swap(arr, i, gt-1); gt--; } else {// arr[i] == v i++; } } swap(arr, left, lt); // 继续对[left,lt]进行排序 sort(arr, left, lt-1); // 继续对[gt, right]进行排序 sort(arr, gt, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` 三路快排相对于两路快排的优化之处在于,当出现大量重复元素时直接进行判断加速排序过程,直接进行lt++或者是gt--,这样避免了大量重复元素是的替换过程。 ### 5. 三路快排的应用 待续... ## 参考 - [动态图参考](https://www.runoob.com/w3cnote/bubble-sort.html) ================================================ FILE: notes/algorithms/插入排序算法.md ================================================ - [前言](#前言) - [正文](#正文) - [1. 代码实现](#1-代码实现) - [参考](#参考) ## 前言 思路:插入排序就跟玩扑克牌一样,先把一部分牌给排好顺序,然后依次往后排剩下的牌,最终达到将所有牌都排序的效果。 动态图 ![插入排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/insertionSort.gif) - 时间复杂度:O(n^2) - 空间复杂度:O(1) - 稳定性:稳定 ## 正文 ### 1. 代码实现 ``` public class InsertionSort { // 方式1 public static void sort(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { for (int j = i; j > 0; j--) { // 注意这里j-1没有越界,因为j > 0进行了判断 if (arr[j] < arr[j-1]) { swap(arr, j, j-1); } else { break; } } } } // 方式2 public static void sort2(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { for (int j = i; j > 0 && arr[j] < arr[j-1]; j--) { // 注意这里j-1没有越界,因为j > 0进行了判断 swap(arr, j, j-1); } } } // 方式3,优化版 public static void sort3(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { // 获取需要比较的元素 int e = arr[i]; int j = i; for (; j > 0 && e < arr[j-1] ; j--) { // 如果满足条件,则前一位元素复制给后一位元素 arr[j] = arr[j-1]; } // 跳出循环,则将需要比较的e元素替换到j位置,j位置即最终停留的位置 arr[j] = e; } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { int [] arr = {6,2,1,5,4,3}; sort2(arr); for (int n : arr) { System.out.println(n); } } } ``` ## 参考 - [动态图参考](https://www.runoob.com/w3cnote/bubble-sort.html) ================================================ FILE: notes/algorithms/桶排序算法.md ================================================ ================================================ FILE: notes/algorithms/选择排序算法.md ================================================ - [前言](#前言) - [正文](#正文) - [1. 代码实现](#1-代码实现) - [参考](#参考) ## 前言 思路:依次寻找[i, n)区间里的最小值的索引。 动态图 ![选择排序动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/selectionSort.gif) - 时间复杂度:O(n^2) - 空间复杂度:O(1) - 稳定性:不稳定 ## 正文 ### 1. 代码实现 ``` public class SelectionSort { public static void sort(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { int min = i; for (int j = i+1; j < n; i++) { if (arr[j] < arr[min]) { min = j; } } swap(arr, i, min); } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ``` ## 参考 - [动态图参考](https://www.runoob.com/w3cnote/bubble-sort.html) ================================================ FILE: notes/datastructures/AVL树.md ================================================ - [前言](#前言) - [正文](#正文) - [1. 基础准备](#1-基础准备) - [2. 计算树高度和平衡因子](#2-计算树高度和平衡因子) - [3. 判断是否为二叉树](#3-判断是否为二叉树) - [4. 判断是否为平衡二叉树](#4-判断是否为平衡二叉树) - [5. 旋转操作的基本原理](#5-旋转操作的基本原理) - [6. 右旋](#6-右旋) - [7. 左旋](#7-左旋) - [8. LR和RL](#8-lr和rl) - [9. 删除节点](#9-删除节点) ## 前言 AVL树本质上还是一颗二叉树,它的特点是: 1. 自身是一颗二叉树; 2. 满足平衡二叉树条件,即对于任意一个左子树和右子树,其二者高度不能超过一,并且左右两棵子树都是一棵平衡二叉树; ## 正文 ### 1. 基础准备 在学习AVL树之前,先了解一下相关基础知识: - 二叉树 啥是二叉树?二叉树就是树形结构的一个重要类型。 - 平衡二叉树 定义:平衡二叉树,对于任意一个左子树和右子树,其二者高度差不能超过一;并且左右两棵子树都是一棵平衡二叉树。 如下图: ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl01.png) 对于平衡二叉树,这里定义两个概念: 1. 树的高度; 2. 平衡因子; **树的高度** 对于平衡二叉树的高度,计算公式:当前节点树高度 = 左右子树最大高度 + 1。 ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl02.png) **平衡因子** 对于平衡二叉树的平衡因子为左右子树高度的差。 如下图红色数字: ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl03.png) 另外,可以通过判断一棵树的平衡因子是否大于1来判断该树是否为平衡二叉树,是的话,就不是平衡二叉树,所以很明显上面这棵树不是平衡二叉树。 - 满二叉树 定义:一棵二叉树的结点要么是叶子结点,要么它有两个子结点,这样的树就是满二叉树。 下图这个图,就是一棵满二叉树: ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl04.png) - 完全二叉树 定义: (1)所有的叶结点都出现在第k层或k-l层(层次最大的两层)。 (2)对任一结点,如果其右子树的最大层次为L,则其左子树的最大层次为L或L+l。 ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl05.png) > 平衡二叉树的高度和节点数量之间的关系 h = O(logn) ### 2. 计算树高度和平衡因子 ``` //在定义的内部类-Node节点中,定义一个height作为树的高度 private class Node{ public K key; public V value; public Node left, right; //定义 public int height; public Node(K key, V value){ this.key = key; this.value = value; left = null; right = null; height = 1; } } // 获得节点node的高度 private int getHeight(Node node){ if(node == null) return 0; return node.height; } // 获得节点node的平衡因子 private int getBalanceFactor(Node node){ if(node == null) return 0; return getHeight(node.left) - getHeight(node.right); } ``` ### 3. 判断是否为二叉树 因为对二叉树中序遍历之后,遍历后的元素是有序的,所以可以以这个性质来判断一棵树是否为二叉树 ``` // 判断该二叉树是否是一棵二分搜索树 public boolean isBST(){ ArrayList keys = new ArrayList<>(); inOrder(root, keys); for(int i = 1 ; i < keys.size() ; i ++) if(keys.get(i - 1).compareTo(keys.get(i)) > 0) return false; return true; } ``` ### 4. 判断是否为平衡二叉树 ``` // 判断该二叉树是否是一棵平衡二叉树 public boolean isBalanced(){ return isBalanced(root); } // 判断以Node为根的二叉树是否是一棵平衡二叉树,递归算法 private boolean isBalanced(Node node){ if(node == null) return true; int balanceFactor = getBalanceFactor(node); if(Math.abs(balanceFactor) > 1) return false; return isBalanced(node.left) && isBalanced(node.right); } ``` ### 5. 旋转操作的基本原理 旋转(左旋转、右旋转)节点是为了维持树的平衡,那么在什么时候需要维护树的平衡呢? 添加一个节点的时候,如下图: ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl06.png) 添加完以后是这样的: ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl07.png) 此时就已经不满足平衡二叉树的定义了,就需要进行旋转操作。所以,平衡的时机,就是在加入节点以后,沿着节点向上维护(回溯)平衡性。 ### 6. 右旋 有两种情况,需要进行右旋操作。 (1) ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl08.png) (2) ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl09.png) 下面,来进行右旋操作,为了不失去一般性,每个节点用变量来表示: ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl10.png) 右旋操作: ``` x.right = y; y.left = T3; ``` ``` // 对节点y进行向右旋转操作,返回旋转后新的根节点x // y x // / \ / \ // x T4 向右旋转 (y) z y // / \ - - - - - - - -> / \ / \ // z T3 T1 T2 T3 T4 // / \ // T1 T2 private Node rightRotate(Node y) { Node x = y.left; Node T3 = x.right; //向右旋转 x.right = y; y.left = T3; //旋转过后,需要重新维护树的高度,并且需要先计算y的高度,再计算x的高度,因为此时y已经为x的子节点了 y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; x.heigt = Math.max(getHeight(x.left), getiHeight(x.right)) + 1; return x; } private int getHeight(Node node) { if(node == null) { return 0; } return node.height; } ``` ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl11.png) 下面,通过分析一下树的高度,来判断,到底合不合理,旋转之后是否真的是平衡二叉树? **旋转前:** ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl12.png) **旋转后:** ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl13.png) 可以观察高度是完全合理的,所以,旋转之后的高度完全合理。 ### 7. 左旋 有两种情况,需要进行左旋操作。 ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl15.png) ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl16.png) 左旋操作: ``` x.left = y; y.right = T2; ``` ``` // 对节点y进行向左旋转操作,返回旋转后新的根节点x // y x // / \ / \ // T1 x 向左旋转 (y) y z // / \ - - - - - - - -> / \ / \ // T2 z T1 T2 T3 T4 // / \ // T3 T4 private Node leftRotate(Node y) { Node x = y.right; Node T2 = x.left; //向左旋转 x.left = y; y.right = T2; //旋转过后,需要重新维护树的高度,并且需要先计算y的高度,再计算x的高度,因为此时y已经为x的子节点了 y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; x.heigt = Math.max(getHeight(x.left), getiHeight(x.right)) + 1; return x; } private int getHeight(Node node) { if(node == null) { return 0; } return node.height; } ``` ### 8. LR和RL 普通的左旋和右旋,可以称作为LL和RR。怎么理解呢?下面来看看LL和LR、RR和RL的区别吧。 > LL和LR ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl15.png) ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl16.png) > RR和RL ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl17.png) ![平衡二叉树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/avl18.png) 对于RL和LR,是不能通过普通的旋转操作,来维持树的平衡性的。 **对于RL:** ``` y y / \ / \ z T1 x T1 z / \ / \ 对x进行右旋 / \ 然后对z进行左旋 y x z T4 --------------> T2 x ----------------> / \ / \ / \ / \ T1 T2 T3 T2 T3 T3 T4 ``` ``` //当node为y节点的时候,此时的平衡因子是小于-1的,对x节点进行右旋操作,然后再进行左旋操作。 if(balanceFactor < -1 && getBalanceFactor(node.right) > 0) { node.right = rightRotate(node.right); return leftRotate(node); } ``` **对于LR:** ``` y y z / \ / \ / \ x T4 对x进行左旋 z T4 对z进行右旋操作 x y / \ ---------------> / \ -----------------> / \ / \ T1 z x T3 T1 T2 T3 T4 / \ / \ T2 T3 T1 T2 ``` ``` //当node为y节点的时候,此时的平衡因子是大于1的,对x节点进行左旋操作,然后再进行右旋操作。 if(balanceFactor > 1 && getBalanceFactor(node.left) < 0) { node.left = leftRotate(node.left); return rightRotate(node); } ``` 至此,对于所有的情况,都已经分析完了。 ### 9. 删除节点 删除的节点,分为两种: **叶子节点** **非叶子结点** 这里需要注意的一点就是,在删除非叶子节点的时候,需要选取找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点,用这个节点顶替待删除节点的位置。然后才能进行旋转操作。 ``` //从二分搜索树种删除键为key的节点 public V remove(K key) { Node node = getNode(root, key); if(node != null) { root = remove(root, key); return node.value; } return null; } private Node remove(Node node, K key) { if(node == null) { return null; } Node retNode; if(key.compareTo(node.key) < 0) { node.left = remove(node.left, key); retNode = node; } else if(key.compareTo(node.key) > 0) { node.right = remove(node.right, key); retNode = node; } else { if(node.left == null) {//待删除节点左子树为空的情况 Node rightNode = node.right; size --; retNode = rightNode; } else if(node.right == null) {//待删除节点右子树为空的情况 Node leftNode = node.left; size --; retNode = leftNode; } else {//待删除的节点的左右子节点都不为空 // 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点 // 用这个节点顶替待删除节点的位置 Node successor = minimum(node.right); //重新维护以successor为根节点的这个子树 succesor.right = remove(node.right, successor.key); succesor.left = node.left; node.left = node.right = null; retNode = successor; } } if(retNode == null) { return null; } // 更新height retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right)); // 计算平衡因子 int balanceFactor = getBalanceFactor(retNode); // 平衡维护 // LL if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) return rightRotate(retNode); // RR if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0) return leftRotate(retNode); // LR if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) { retNode.left = leftRotate(retNode.left); return rightRotate(retNode); } // RL if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) { retNode.right = rightRotate(retNode.right); return leftRotate(retNode); } return retNode; } //返回以node为根的二分搜索树的最小值所在的节点 private Node minimum(Node node) { if(node.left == null) { return node; } return minimum(node.left); } ``` ``` public class AVLTree,V> {// private class Node { public K key; public V value; public Node left, right; public int height; public Node(K key, V value) { this.key = key; this.value = value; left = null; right = null; height = 1;//初始高度都为1 } } private Node root; private int size; public AVLTree() { root = null; size = 0; } //向二分搜索树种添加一个新的元素(key, value) public void add(K key, V value) { root = add(root, key, value); } //使用递归算法来添加新元素 private Node add(Node node, K key, V valu) { if(node == null) { size ++; return Node(key, value); } if(key.compareTo(node.key) < 0) { node.left = add(node.left, key, value); } else if(key.compareTo(node.key) > 0) { node.right = add(node.right, key, value); } else { node.value = value; } //更新height node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1; //计算平衡因子 int balanceFactor = getBalanceFactor(node); //维护树的平衡 if(balanceFactor > 1 && getBalanceFactor(node.left) >= 0) { return rightRotate(node); } if(balanceFactor < -1 && getBalanceFactor(node.right) <= 0) { return leftRotate(node); } if(balanceFactor > 1 && getBalanceFactor(node.left) < 0) { node.left = leftRotate(node.left); return rightRotate(node); } if(balanceFactor < -1 && getBalanceFactor(node.right) > 0) { node.right = rightRotate(node.right); return leftRotate(node); } } } ``` ================================================ FILE: notes/datastructures/堆和优先队列.md ================================================ - [前言](#前言) - [1. 二叉堆](#1-二叉堆) - [1.1 用数组存储二叉堆](#11-用数组存储二叉堆) - [1.2 原地堆排序](#12-原地堆排序) - [1.3 索引堆](#13-索引堆) - [2. 优先队列](#2-优先队列) # 前言 下面先了解下二叉堆的基础知识 ## 1. 二叉堆 二叉堆是一棵完全二叉树,如下图: ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap01.png) 完全二叉树性质如下: - 元素顺序排列成树的形状 - 堆中某节点的值总是不大于其父节点的值 - 上图这种父亲节点值最大,则表示的是最大堆 同理,可以让父亲节点值最小,则可以被称为最小堆。 这里,给出一个最大堆和最小堆的定义: **最大堆** > 堆中某个节点的值总是不大于其父节点的值 **最小堆** > 堆中某个节点的值总是不小于其父节点的值 ### 1.1 用数组存储二叉堆 如将下列数组存储为二叉堆: ``` [62, 41, 30, 28, 16, 22, 13, 19, 17, 15] 0 1 2 3 4 5 6 7 8 9 ``` 将数组转化为二叉堆: ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap02.png) 对于上面的二叉堆,有以下三个比较重要的公式: ``` (i从0开始计算) parent(i) = (i - 1) / 2 求当前节点的父节点的索引 left child(i) = 2 * i + 1 求当前节点的左子节点的索引 right child(i) = 2 * i + 2 求当前节点的右子节点的索引 ``` 构建代码: ``` public class MaxHeap { /** * 注意,由于数组元素默认值为0,所以堆中元素不可为0 */ private int[] data; /** * 记录数组中元素个数 */ private int size; /** * 数组容量(初始化大小) */ private int capacity; public MaxHeap(int capacity) { data = new int[capacity]; this.capacity = capacity; size = 0; } public MaxHeap() { data = new int[10]; capacity = 10; size = 0; } // ================================ heapify(二叉堆化) ======================== public MaxHeap(int[] data) { int n = data.length; this.data = new int[n+1]; capacity = n; for (int i = 0; i < n; i++) { this.data[i+1] = data[i]; } size = n; // 这里需要注意,size / 2 得到的索引是二叉堆中最后一个非叶子节点。 for (int i = size / 2; i > 0; i++) { siftDown(i); } } /** * 返回堆中真实存在元素个数 * @return */ public int size() { return size; } /** * 返回堆中是否为空 * @return */ public boolean isEmpty() { return size == 0; } /** * 返回index索引其父亲节点 * @param index * @return */ private int parent(int index) { if (index == 0) { throw new IllegalArgumentException("index-0 doesn't have parent"); } return (index - 1) / 2; } /** * 返回index索引的左子节点 * @param index * @return */ private int leftChild(int index) { return index * 2 + 1; } /** * 返回index索引的右子节点 * @param index * @return */ private int rightChild(int index) { return index * 2 + 2; } // ================================ 上浮 siftUp ======================== /** * 向堆中添加元素 */ public void add(int i) { // 注意size和capacity的关系,判断是否容量已经满了 // 向数组最后一位添加新元素 data[size] = i; siftUp(size++); } /** * 上浮过程 * @param k */ private void siftUp(int k) { // k 索引大于0,并且k索引元素值大于k父亲节点元素值 while (k > 0 && data[parent(k)] < data[k]) { // k和parent(k)互换元素 swap(data, k, parent(k)); // 向上移动,让k为parent(k)再进行判断 k = parent(k); } } // ================================ 下浮 siftDown ======================== /** * 获取堆中最大元素 * @return */ public int findMax() { if (size == 0) { throw new IllegalArgumentException("Can't find Max value!"); } return data[0]; } /** * 取出堆中最大元素 * @return */ public int extractMax() { int max = findMax(); swap(data, 0, size - 1); data[size - 1] = 0; siftDown(0); return max; } /** * 下沉操作 * @param k */ private void siftDown(int k) { // 左子节点比数组元素小,则表示有子节点 while (leftChild(k) < size()) { int j = leftChild(k); // 如果k的有右子节点 if (j + 1 < size() && data[j + 1] > data[j]) { // 所以让j为右子节点 // j = rightChild(k); 同 j++; } // 此时data[k] 比leftChild和rightChild中的最大值都要大 if (data[k] > data[j]) { break; } // leftChild和rightChild都比data[k]大,在互换之后,继续下一轮判断 swap(data, k, j); // 互换位置,继续下一轮判断 k = j; } } // ================================ 替换操作 ======================== private void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { int n = 100; MaxHeap maxHeap = new MaxHeap(n); Random random = new Random(); for (int i = 0; i < n; i++) { maxHeap.add(random.nextInt(1000)); } int[] result = new int[n]; for (int i = 0; i < n; i++) { result[i] = maxHeap.extractMax(); } // 测试是否是顺序的 for (int j = 1; j < n; j++) { if (result[j-1] < result[j]) { throw new IllegalArgumentException("Error!"); } } } } ``` 上浮Sift Up过程如下图: ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap03.png) 插入的新节点与父亲节点比较大小,如果比父亲节点大,则进行swap交换,直至到符合完全二叉树性质。 取出最大值以及下沉过程: 先取出最大值,让数组最后一位元素替换堆顶元素,然后往下沉。 ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap04.png) ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap05.png) heapify二叉堆化,即从最后一个非叶子节点开始siftDown,即图中蓝色图表示的节点元素。 ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap06.png) ### 1.2 原地堆排序 原地堆排序,就是指不需要借助额外的空间进行的排序。 对于最大堆,数组中最大元素都存放在堆顶。每次排序将堆顶和数组最后一位元素进行swap操作,所以最后一位元素就是最大值,此时0~(n-2)就不是堆结构,即需要对 0~(n-2)进行堆化,即将0位置的元素进行下沉操作,之后继续再对0~(n-2)作同样的操作。 ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap07.png) ``` public class HeapSort { public static void sort(int[] arr) { int n = arr.length; // 注意,此时我们的堆是从0开始索引的 // 从(最后一个元素的索引-1)/2开始 // 最后一个元素的索引 = n-1 // 其实这里 i = (n-1) 也行 for (int i = (n - 1 - 1) / 2; i >= 0; i--) { siftDown2(arr, n, i); } // [a.....v,k] // [.......]k // [.....] ba for (int i = n-1; i > 0; i--) { // 由于上面执行过下沉操作,所以已经是最大堆(但没有排序完)。所以此时swap就将最大值替换到数组末尾。 swap(arr, 0, i); // 由于siftDown中是判断 2*k+1 < n ,所以就是对n-1进行下沉操作; siftDown2(arr, i, 0); } } // 下浮 public static void siftDown(int[] arr, int n, int k) { while (2 * k + 1 < n) { int j = 2 * k + 1; if (j + 1 < n && arr[j+1] > arr[j]) { j += 1; } if (arr[k] >= arr[j]) { break; } swap(arr, k, j); k = j; } } /** * 优化下沉过程, 不适用swap交换,通过赋值来代替。 * * @param arr * @param n * @param k */ private static void siftDown2(int[] arr, int n, int k) { int e = arr[k]; while (2 * k + 1 < n) { int j = 2 * k + 1; if (j + 1 < n && arr[j + 1] > arr[j]) { j++; } if (e >= arr[j]) { break; } // 此时说明arr[j] > arr[k]; 所以让大值上浮; arr[k] = arr[j]; k = j; } // 将最小元素替换到k的位置 arr[k] = e; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { /* int n = 100; int[] test = new int[n]; Random random = new Random(); for (int i = 0; i < n; i++) { test[i] = random.nextInt(1000); } */ int n = 10; int[] test = {10, 41, 30, 28, 16, 22, 13, 19, 17, 15}; sort(test); for (int i = 1; i < n; i++) { if (test[i-1] > test[i]) { throw new IllegalArgumentException("Error!"); } } } } ``` ### 1.3 索引堆 首先,我们先来看一个由普通数组构建的普通堆 ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/indexHeap01.jpg) 然后我们通过前面的方法对它进行堆化(heapify),将其构建为最大堆。 结果是这样的: ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/indexHeap02.jpg) 对于我们所关心的这个通过数组实现的堆而言,数组中的元素位置发生了改变。正是因为这些元素的位置发生了改变,我们才能将其构建为最大堆。 可是由于数组中元素位置的改变,我们将面临着几个局限性。 1.如果我们的元素是十分复杂的话,比如像每个位置上存的是一篇10万字的文章。那么交换它们之间的位置将产生大量的时间消耗。(不过这可以通过技术手段解决) 2.由于我们的数组元素的位置在构建成堆之后发生了改变,那么我们之后就很难索引到它,很难去改变它。例如我们在构建成堆后,想去改变一个原来元素的优先级(值),将会变得非常困难。 可能我们在每一个元素上再加上一个属性来表示原来位置可以解决,但是这样的话,我们必须将这个数组遍历一下才能解决。(性能低效) 针对以上问题,我们就需要引入索引堆(Index Heap)的概念。 对于索引堆来说,我们将数据和索引这两部分分开存储。真正表征堆的这个数组是由索引这个数组构建成的。(像下图中那样,每个结点的位置写的是索引号) ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/indexHeap03.jpg) 而在构建堆(以最大索引堆为例)的时候,比较的是data中的值(即原来数组中对应索引所存的值),构建成堆的却是index域 而构建完之后,data域并没有发生改变,位置改变的是index域。 ![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/indexHeap04.jpg) 那么现在这个最大堆该怎么解读呢? 例如,堆顶元素为Index=10代表的就是索引为10的data域的值,即62。 这时我们来看,构建堆的过程就是简单地索引之间的交换,索引就是简单的int型。效率很高。 现在如果我们想对这个数组进行一些改变,比如我们想将索引为7的元素值改为100,那我们需要做的就是将索引7所对应data域的28改为100。时间复杂度为O(1)。 当然改完之后,我们还需要进行一些操作来维持最大堆的性质。不过调整的过程改变的依旧是index域的内容。 ``` /** * * 索引堆 * * @author LuoHaiYang */ public class IndexMapHeap { /** * 存储数据的数组 */ private int[] arr; /** * index元素值就是arr的索引 */ private int[] index; /** * 数组存储容量大小 */ private int count; /** * 容量 */ private int capacity; public IndexMapHeap(int capacity) { this.capacity = capacity; this.count = 0; // 让索引从0开始 arr = new int[capacity + 1]; // 让索引从0开始 index = new int[capacity + 1]; } public boolean isEmpty() { return count == 0; } public int size() { return count; } /** * 插入新元素 * @param i * @param item */ public void insert(int i, int item) { if (i + 1 > capacity) { throw new IllegalArgumentException("容量已满, 插入失败"); } arr[++i] = item; index[++count] = i; shiftUp(count); } /** * 获取最大索引堆中所以为i的元素 * @param i * @return */ public int getItem(int i) { if (i < 0 && i + 1 > capacity) { throw new IllegalArgumentException("错误!索引越界."); } return arr[i+1]; } /** * 获取索引堆中堆顶的元素(注意堆中元素是arr的索引,即index数组的元素) * @return */ public int getMaxIndex() { if (count < 1) { throw new IllegalArgumentException("异常!堆中没有元素!"); } // 由于索引堆中元素也是从1开始,所以需要-1,从0开始。 return index[1] - 1; } /** * 获取最大索引堆中堆顶元素的值,即索引堆中存储的最大数据 * @return */ public int extractMax() { if (count < 1) { throw new IllegalArgumentException("错误!索引堆中不存在元素"); } int ret = arr[index[1]]; // 取出堆顶元素数据后,需要把最大元素和index末尾元素进行替换,然后做下沉操作 swap(index, 1, count); count--; shiftDown(1); return ret; } /** * 获取最大索引堆中堆顶的索引 * @return */ public int extractMaxIndex() { if (count < 1) { throw new IllegalArgumentException("错误!索引堆中不存在元素"); } int ret = index[1] - 1; swap(index, 1, count); count--; shiftDown(1); return ret; } // ============================= 上浮操作 ============================= /** * 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 * @param k */ private void shiftUp(int k) { // 堆顶元素则直接跳过 while (k > 1 && arr[index[k/2]] < arr[index[k]]) { swap(index, k, k/2); k /= 2; } } // ============================= 下沉操作 ============================= /** * 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 * * 由于是由1开始计算索引,所以左子树为 2*k * * @param k */ private void shiftDown(int k) { // 如果左子树所以 <= 元素总数 while ( 2 * k <= count) { int j = 2 * k; if (j + 1 <= count && arr[index[j+1]] > arr[index[j]]) { j++; } if (arr[index[k]] >= arr[index[j]]) { break; } // 更换索引值 swap(index, k, j); k = j; } } // ============================= 更新堆元素优先级 ============================= /** * * * * @param i * @param item */ public void change(int i, int item) { i++; // 将所有堆中索引为i的元素修改为item arr[i] = item; // 由于直接在i位置修改为新元素; // 所以需要查找出 index[j] = i的位置,即arr[index[j]] = item; // 然后上浮和下沉(先后顺序不影响) for (int j = 1; j <= count; j++) { if (index[j] == i) { shiftUp(j); shiftDown(j); return; } } } private void swap(int[] arr, int i, int k) { int tmp = arr[i]; arr[i] = arr[k]; arr[k] = tmp; } // ============================= 更新堆元素优先级 ============================= public boolean testIndexes() { int[] copyIndex = new int[count + 1]; for (int i = 0; i <= count; i++) { copyIndex[i] = index[i]; } copyIndex[0] = 0; Arrays.sort(copyIndex); for (int i = 2; i <= count; i++) { if (copyIndex[i-1] + 1 != copyIndex[i]) { System.out.println("错误,索引堆排序错误!"); break; } } return true; } public void testSort(int[] arr) { for (int i = 1; i < arr.length; i++) { if (arr[i-1] < arr[i]) { System.out.println("索引堆排序失败!"); } } } public static void main(String[] args) { int n = 10; IndexMapHeap indexMapHeap = new IndexMapHeap(n); Random random = new Random(); for (int i = 0; i < n; i++) { // insert中仅仅一个shiftUp操作是不能保证索引堆数据的排好序了 indexMapHeap.insert(i,random.nextInt(1000)); } // 判断索引堆索引是否是连续 System.out.println(indexMapHeap.testIndexes()); int[] result = new int[n]; for (int i = 0; i < n; i++) { result[i] = indexMapHeap.extractMax(); } indexMapHeap.testSort(result); } } ``` ## 2. 优先队列 > 普通队列:先进先出,后进后出; > 优先队列:出队顺序和入队顺序无关;和优先级有关; 在Windows操作系统中,任务管理器的任务就是根据优先级别来进行管理的,**动态选择优先级最高的任务执行。** 普通线性结构和顺序线性结构对比 | | 入队 | 出队(拿出最大元素) | | :----: | :-----: | :--------: | | 普通线性结构 | O(1) | O(n) | | 顺序线性结构 | O(n) | O(1) | | 堆 | O(logn) | O(logn) | 对于线性结构出队时(数组),需要扫描整个队列;对于顺序线性结构(链表),入队时由于需要维持顺序性则需要扫描整个队列; **普通线性结构**入队O(1)分析,因为入队不考虑顺序性,所以排在队尾即可。 **顺序线性结构**入队O(n)分析,入队需要考虑顺序性,需要进行调度。 ================================================ FILE: pom.xml ================================================ com.bruis algorithminjava 1-SNAPSHOT algorithminjava AlgorithmInJava org.apache.maven.plugins maven-compiler-plugin 8 8 ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/huawei/Question01.java ================================================ package com.bruis.algorithminjava.algorithm.huawei; import java.util.Scanner; /** * @author LuoHaiYang * * 题目描述:输入一个int型的正整数,计算出该int型数据在内存中存储时1的个数。 * * 输入描述: 输入一个整数(int类型) * * 输出描述: 这个数转换成2进制后,输出1的个数 * * 实例1: * * 输入:5 * 输出:2 * */ public class Question01 { /** * 笨办法 * @param args */ public static void main(String[] args) { // 巧妙法,通过二进制位运算 Scanner in = new Scanner(System.in); int count = in.nextInt(), result = 0; while (count > 0) { if ((count & 1) > 0) { result++; } count = count >> 1; } System.out.println(result); /* 笨办法 Scanner scanner = new Scanner(System.in); int input = scanner.nextInt(); char[] bytes = Integer.toBinaryString(input).toCharArray(); int result = 0; for (char c : bytes) { if (c == '1') { result++; } } System.out.println(result); */ } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ContainsDuplicate_217.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode; import java.util.*; /** * 217: Contains Duplicate * @Description * @Author luohaiyang * @Date 2022/6/24 */ public class ContainsDuplicate_217 { public static void main(String[] args) { int[] test = {1, 2, 30, 22, 3, 4, 5, 1}; System.out.println(containsDuplicate(test)); } public static boolean containsDuplicate(int[] nums) { // 数组类型题思考步骤: // 1) 入参是否合法,是否越界等; // 2) 是否是有序; // 3) 是否有负数; // 题解思路: // 1. 暴力破解法; O(n^2); // 2. 桶排序? // 2.1 需要Map数据结构; // 2.2 不需要Map数据结构; // 3. 排序之后,判断前后数字是否一样; return false; } public static boolean setSolution(int[] nums) { int n = nums.length; if (n < 1) { return false; } Set set = new HashSet(); for (int x : nums) { if (!set.add(x)) { return true; } } return false; } /** * 通过排序来进行筛选 * @param nums * @return */ public static boolean sortSolution(int[] nums) { int n = nums.length; if (n < 1) { return false; } Arrays.sort(nums); for (int i = 1; i < nums.length - 1; i++) { if (nums[i] == nums[i+1]) { return true; } } return false; } /** * 基于桶排序 * @param nums * @return */ public static boolean bucketMapSolution(int[] nums) { int n = nums.length; if (n < 1) { return false; } Map duplicateMap = new HashMap<>(n); for (int i = 0; i < n; i++) { if (!duplicateMap.containsKey(nums[i])) { duplicateMap.put(nums[i], 1); continue; } return true; } return false; } /** * 暴力解法 * @param nums * @return */ public static boolean violentSolution(int[] nums) { if (nums.length < 1) { return false; } // 方法一:暴力解法 int n = nums.length; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (i != j && nums[i] == nums[j]) { return true; } } } return false; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/LongestPalindromicSubstring_5.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode; /** * @Description * @Author luohaiyang * @Date 2022/6/28 */ public class LongestPalindromicSubstring_5 { } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/MaximumSubarray_53.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode; /** * @Description * @Author luohaiyang * @Date 2022/6/24 */ public class MaximumSubarray_53 { public static void main(String[] args) { // 条件: // 1. 连续数组(不可以排序); // 解题思路: // 1. 暴力解法;(超时) // 2. 正确解法-动态规划; // 有卡顿、纠结的点: // 1. 算法起始条件、结束条件; int[] nusm = {-2,1,-3,4,-1,2,1,-5,4}; System.out.println(violentSolution(nusm)); } public static int violentSolution(int[] nums) { int n = nums.length; if (n < 2) { return nums[0]; } int max = nums[0];; for (int i = 0; i < n; i++) { int subTotal = nums[i]; for (int j = i + 1; j < n; j++) { if (max < nums[j]) { max = nums[j]; } subTotal += nums[j]; if (max < subTotal) { max = subTotal; } } } return max; } public static int maxSubArray(int[] nums) { int n = nums.length; if (n < 2) { return nums[0]; } int max = nums[0]; int i = 0, j = i + 1; while (j < n) { if (max < nums[j]) { max = nums[j]; } int totalMax = 0; for (int k = i; k < j; k++) { totalMax += nums[i] + nums[j]; } if (max < totalMax) { max = totalMax; } j++; } return max; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSum.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode; import java.util.HashMap; import java.util.Map; /** * @Description * @Author luohaiyang * @Date 2022/4/28 */ public class TwoSum { public int[] twoSum(int[] nums, int target) { // 暴力破解法 // return forceSolution(nums, target); return optimizeSolution01(nums, target); } /** * 对optimizeSolution01进行优化,少进行一次for循环 * 时间复杂度: O(n) * 空间复杂度:O(n) * @param nums * @param target * @return */ private int[] optimizeSolution02(int[] nums, int target) { int n = nums.length; Map arrayMap = new HashMap<>(); for (int i = 0; i < n; i++) { int num = nums[i]; if (arrayMap.containsKey(target - num)) { return new int[]{arrayMap.get(target - num), i}; } arrayMap.put(num, i); } return new int[0]; } /** * 借助jdk hashmap, * 时间复杂度: O(n) * 空间复杂度:O(n) * @param nums * @param target * @return */ private int[] optimizeSolution01(int[] nums, int target) { int n = nums.length; Map arrayMap = new HashMap<>(); for (int i = 0; i < n; i++) { int num = nums[i]; int targetVal = target - num; arrayMap.put(targetVal, i); } for (int i = 0; i < n; i++) { int num = nums[i]; if (arrayMap.containsKey(num)) { int index = arrayMap.get(num); if (index > i) { return new int[]{i, index}; } else { return new int[]{index, i}; } } } return new int[0]; } /** * 暴力破解法 * 时间复杂度:o(n^2) * 空间复杂度:O(n) * @param nums * @param target * @return */ private int[] forceSolution(int[] nums, int target) { int n = nums.length; for (int i = 0; i < n; i++) { int num = nums[i]; int val = target - num; for (int j = i + 1; j < n; j++) { if (nums[j] == val) { return new int[]{i, j}; } } } return new int[0]; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSumII.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode; /** * @Description * @Author luohaiyang * @Date 2022/4/28 */ public class TwoSumII { public int[] twoSum(int[] numbers, int target) { return twoPointer(numbers, target); } /** * 双指针 * 时间复杂度:O(n) * 空间复杂度:O(1) * @param numbers * @param target * @return */ private int[] twoPointer(int[] numbers, int target) { int n = numbers.length; if (n < 2) { return numbers; } int i = 0, j = n - 1; while (i < j) { if (numbers[i] + numbers[j] == target) { return new int[]{i + 1, j + 1}; } if (numbers[i] + numbers[j] > target) { j--; } else { i++; } } return new int[0]; } /** * 暴力法: * 时间复杂度:O(n^2) * 空间复杂度:O(1) * @param numbers * @param target * @return */ private int[] forceSolution(int[] numbers, int target) { int n = numbers.length; if (n < 2) { return numbers; } for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (numbers[i] + numbers[j] == target) { return new int[]{i + 1, j + 1}; } } } return new int[0]; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; /** * * 125 * * 验证回文串 * * https://leetcode-cn.com/problems/valid-palindrome/ * * * @author LuoHaiYang */ public class IsPalindrome { public boolean isPalindrome(String str) { int head = 0, tail = str.length() - 1; char a, b; while(head < tail) { a = str.charAt(head); b = str.charAt(tail); if(!Character.isLetterOrDigit(a)) { head ++; } else if(!Character.isLetterOrDigit(b)) { tail --; } else { if(Character.toLowerCase(a) != Character.toLowerCase(b)) { return false; } head ++; tail --; } } return true; } public static void main(String[] args) { IsPalindrome isPalindrome = new IsPalindrome(); String test = "race a car"; System.out.println(isPalindrome.isPalindrome(test)); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.Arrays; /** * 最大间距 * * url:https://leetcode-cn.com/problems/maximum-gap/ * * @author LuoHaiYang */ public class MaximumGap { /** * 基于桶排序 * 时间复杂度:O(N) * 空间复杂度:O(N) * @param nums * @return */ public int maximumGapOptimize2(int[] nums) { if (nums.length < 2) return 0; int len = nums.length; // 找出最大值和最小值 为了方便后面确定桶的数量 int max = -1, min = Integer.MAX_VALUE; for (int i = 0; i < len; i++) { max = Math.max(nums[i], max); min = Math.min(nums[i], min); } // 排除nums全部为一样的数字,nums = [1,1,1,1,1,1]; if (max - min == 0) return 0; // 用于存放每个桶的最大值 int[] bucketMin = new int[len - 1]; // 用于存放每个桶的最小值 int[] bucketMax = new int[len - 1]; Arrays.fill(bucketMax, -1); Arrays.fill(bucketMin, Integer.MAX_VALUE); // 确定桶的间距 int interval = (int)Math.ceil((double)(max - min) / (len - 1)); for (int i = 0; i < len; i++) { // 找到每一个值所对应桶的索引 int index = (nums[i] - min) / interval; if (nums[i] == min || nums[i] == max) continue; // 更新每个桶的数据 bucketMax[index] = Math.max(bucketMax[index], nums[i]); bucketMin[index] = Math.min(bucketMin[index], nums[i]); } // maxGap 表示桶之间最大的差距 int maxGap = 0; // preMax 表示前一个桶的最大值 int preMax = min; for (int i = 0; i < len - 1; i++) { // 表示某一个桶为空 // 但凡某一个桶不为空,都会在前面的数据中更新掉bucketMax的值 if (bucketMax[i] == -1) continue; maxGap = Math.max(bucketMin[i] - preMax, maxGap); preMax = bucketMax[i]; } // [1,10000000] maxGap = Math.max(maxGap, max - preMax); return maxGap; } /** * 基数排序: * 时间复杂度:O(N) * 空间复杂度:O(N) * @param nums * @return */ public int maximumGapOptimize(int[] nums) { int n = nums.length; if (n < 2) { return 0; } long exp = 1; int[] buf = new int[n]; int maxVal = Arrays.stream(nums).max().getAsInt(); while (maxVal >= exp) { int[] cnt = new int[10]; for (int i = 0; i < n; i++) { int digit = (nums[i] / (int) exp) % 10; cnt[digit]++; } for (int i = 1; i < 10; i++) { cnt[i] += cnt[i - 1]; } for (int i = n - 1; i >= 0; i--) { int digit = (nums[i] / (int) exp) % 10; buf[cnt[digit] - 1] = nums[i]; cnt[digit]--; } System.arraycopy(buf, 0, nums, 0, n); exp *= 10; } int ret = 0; for (int i = 1; i < n; i++) { ret = Math.max(ret, nums[i] - nums[i - 1]); } return ret; } public int maximumGap(int[] nums) { if (nums == null || nums.length < 2) { return 0; } // 排序 quickSort(nums); int n = nums.length; int max = nums[1] - nums[0]; for (int i = 2; i < n; i++) { max = max(max, nums[i] - nums[i-1]); } return max; } private void quickSort(int[] nums) { int n = nums.length; quickSort3ways(nums, 0, n-1); } private void quickSort3ways(int[] nums, int left, int right) { if (left >= right) { return; } int p = nums[left]; int i = left + 1, lt = left, gt = right + 1; while (i < gt) { if (nums[i] < p) { swap(nums, i, lt + 1); i++; lt++; } else if (nums[i] > p) { swap(nums, i, gt - 1); gt--; } else { i++; } } swap(nums, left, lt); quickSort3ways(nums, left, lt - 1); quickSort3ways(nums, gt, right); } private int max(int i, int j) { return Math.max(i, j); } private void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } public static void main(String[] args) { int[] test = {3,6,9,1,20,15,11,30,31}; MaximumGap maximumGap = new MaximumGap(); // System.out.println(maximumGap.maximumGap(test)); System.out.println(maximumGap.maximumGapOptimize2(test)); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumProductSubarray.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; /** * @author LuoHaiYang * @Date 2020-05-18 * * url: https://leetcode-cn.com/problems/maximum-product-subarray/ * */ public class MaximumProductSubarray { /** * 问题1 :算法没有包括数组头部元素的比较; * 问题2 : 数组没有考虑到每个元素本身大小的比较; * * * @param nums * @return */ public int maxProduct(int[] nums) { // 2, 3, -2, 4, 5, 8, -1, -3, 10 // [ ] // int length = nums.length; if (length == 1) { return nums[0]; } if (length == 2) { int result = nums[0] * nums[1]; int anotherResult = nums[0] > nums[1] ? nums[0] : nums[1]; return result > anotherResult ? result : anotherResult; } int max = nums[0] * nums[1] > nums[0] ? nums[0] * nums[1] : nums[0]; for (int i = 0; i < length; i++) { int[] mul = new int[length]; for (int j = i; j < length; j++) { mul[j] = nums[j]; } for (int j = i + 1; j < length; j++) { int result = nums[j] * mul[j-1]; //mul[j] = mul[j] > result ? mul[j] : result; mul[j] = result; if (nums[j] > max) { max = nums[j]; } if (result > max) { max = result; } } } return max; } public static void main(String[] args) { MaximumProductSubarray maximumProductSubarray = new MaximumProductSubarray(); int[] nums = {2, -1, 1, 1}; //int[] nums = {2, 3, -2, 4, 5, 8, -1, -3, 10}; System.out.println(maximumProductSubarray.maxProduct(nums)); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReversePairs.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.Arrays; /** * 逆序对 *

* url: https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/ * * @author LuoHaiYang */ public class ReversePairs { /* ================================ 解法一 ================================*/ /** * 暴力解法O(n^2),超时 * * @param nums * @return */ public int reversePairs2(int[] nums) { int n = nums.length; if (n < 2) { return 0; } int reverseNum = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (nums[i] > nums[j]) { reverseNum++; } } } return reverseNum; } /* ================================ 解法二 ================================*/ /** * 使用自顶向下的归并排序算法计算逆序对,用来额外的空间。 * * @param nums * @return */ public int reversePairs(int[] nums) { int n = nums.length; if (n < 2) { return 0; } return getReversePairs(nums); } private int getReversePairs(int[] nums) { int n = nums.length; return getReversePairs(nums, 0, n - 1); } private int getReversePairs(int[] nums, int left, int right) { if (left >= right) { return 0; } int result = 0; int mid = (left + right) / 2; result += getReversePairs(nums, left, mid) + getReversePairs(nums, mid + 1, right) + reversePairs(nums, left, mid, right); return result; } private int reversePairs(int[] nums, int left, int mid, int right) { int[] aux = Arrays.copyOfRange(nums, left, right + 1); int i = left, j = mid + 1; int res = 0; for (int k = left; k <= right; k++) { if (i > mid) { nums[k] = aux[j - left]; j++; } else if (j > right) { nums[k] = aux[i - left]; i++; } else if (aux[i - left] <= aux[j - left]) { nums[k] = aux[i - left]; i++; } else { nums[k] = aux[j - left]; j++; res += (mid - i) + 1; } } return res; } /* ================================ 题解三(优化) ================================*/ /** * * 相比解法二时间复杂度常数和空间复杂度更低 * * @param nums * @return */ public int reversePairs3(int[] nums) { if (nums == null || nums.length < 2) { return 0; } int[] temp = new int[nums.length]; System.arraycopy(nums, 0, temp, 0, nums.length); int count = mergeCount(nums, temp, 0, nums.length - 1); return count; } private int mergeCount(int[] nums, int[] temp, int start, int end) { if (start >= end) { return 0; } int mid = (start + end) >> 1; int left = mergeCount(temp, nums, start, mid); int right = mergeCount(temp, nums, mid + 1, end); int count = 0; //merge() //遍历左区域指针 int i = mid; //遍历右区域指针 int j = end; //临时区域指针 int k = end; while (i >= start && j >= mid + 1) { if (nums[i] > nums[j]) { count += j - mid; temp[k--] = nums[i--]; } else { temp[k--] = nums[j--]; } } //如果还有剩下没遍历的 while (i >= start) { temp[k--] = nums[i--]; } while (j >= mid + 1) { temp[k--] = nums[j--]; } return count + left + right; } public int reversePairs4(int[] nums) { if (nums == null || nums.length < 2) { return 0; } int[] temp = new int[nums.length]; System.arraycopy(nums, 0, temp, 0, nums.length); //int count = mergeCount2(); return 0; } private int mergeCount2(int[] nums, int[] temp, int start, int end) { if (start >= end) { return 0; } int mid = (start + end) << 1; //int left = mergeCount2(nums, ); return 0; } public static void main(String[] args) { ReversePairs reversePairs = new ReversePairs(); int[] nums = {7, 5, 6, 4}; //int[] nums = {1,3,2,3,1}; System.out.println(reversePairs.reversePairs3(nums)); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReverseVowels.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; /** * * 345 * * https://leetcode-cn.com/problems/reverse-vowels-of-a-string/ * * 反转字符串中的元音字母 * * @author LuoHaiYang */ public class ReverseVowels { public String reverseVowels(String s) { char[] arr = s.toCharArray(); int n = arr.length, left = 0, right = n - 1; while (left <= right) { // 如果不是元音,则指针右移 while (left < n && !isVowel(arr[left])) { left++; } while (right >= 0 && !isVowel(arr[right])) { right--; } if (left > right) { break; } // 字符调换 swap(arr, left, right); left++; right--; } return new String(arr); } /** * * 1. 元音字母是? * */ private boolean isVowel(char ch) { return ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u' || ch == 'A' || ch == 'E' || ch == 'I' || ch == 'O' || ch == 'U'; } private void swap(char[] arr, int i, int j) { char tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SortColors.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; /** * * 颜色分类 * * url:https://leetcode-cn.com/problems/sort-colors/ * * @author LuoHaiYang */ public class SortColors { public void sortColors(int[] nums) { if (nums == null || nums.length < 2) { return; } sort(nums, 0, nums.length - 1); } private void sort(int[] nums, int left, int right) { if (left >= right) { return; } int p = nums[left]; int i = left + 1, lt = left, gt = right + 1; while (i < gt) { if (nums[i] < p) { swap(nums, i, lt + 1); i++; lt++; } else if (nums[i] > p) { swap(nums, i, gt - 1); gt--; } else { i++; } } swap(nums, left, lt); sort(nums, left, lt - 1); sort(nums, gt, right); } private void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } public static void main(String[] args) { SortColors sortColors = new SortColors(); int[] test = {0, 1}; sortColors.sortColors(test); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SubarraySumEqualsK.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.HashMap; import java.util.Map; /** * @author LuoHaiYang * * @Date 2020-05-15 * 560. Subarray Sum Equals K * url: https://leetcode-cn.com/problems/subarray-sum-equals-k/ * * */ public class SubarraySumEqualsK { /** * O (n^3) * @param nums * @param k * @return */ public int subarraySum01(int[] nums, int k) { int len = nums.length; int count = 0; for (int left = 0; left < len; left++) { for (int right = left; right < len; right++) { int sum = 0; for (int i = left; i <= right; i++) { sum += nums[i]; } if (sum == k) { count++; } } } return count; } /** * 1. 欠考虑情况 * ① 单个元素作为一个子数组; * ② 所有元素值都相同的情况; * * 时间复杂:O (n^2) * * @param nums * @param k * @return */ public int subarraySum02(int[] nums, int k) { int length = nums.length; int count = 0; // 0, 0, 0, 0 // l // r for (int left = 0; left < length; left++) { int right = left + 1; int sums = nums[left]; if (sums == k) { count++; } while (right < length) { sums += nums[right++]; if (sums == k) { count++; } } } return count; } /** * 前缀和 * * * @param nums * @param k * @return */ public int subarraySum03(int[] nums, int k) { int length = nums.length; int[] preSum = new int[length + 1]; preSum[0] = 0; for (int i = 0; i < length; i++) { preSum[i + 1] = preSum[i] + nums[i]; } // 1, 2, 3, 4 // 0 1 3 6 10 // l // r int count = 0; for (int left = 0; left < length; left++) { int right = left + 1; while (right <= length) { if (preSum[right++] - preSum[left] == k) { count++; } } } return count; } /** * * O (n) * 前缀和 + 哈希表 * * @param nums * @param k * @return */ public int subarraySum04(int[] nums, int k) { Map preSumFreq = new HashMap<>(); preSumFreq.put(0, 1); int preSum = 0; int count = 0; for (int num : nums) { preSum += num; if (preSumFreq.containsKey(preSum - k)) { count += preSumFreq.get(preSum - k); } preSumFreq.put(preSum, preSumFreq.getOrDefault(preSum, 0) + 1); } return count; } public static void main(String[] args) { // 1. 可以进行排序不? SubarraySumEqualsK subarraySumEqualsK = new SubarraySumEqualsK(); //int[] nums = {0,0,0,0,0,0,0,0,0,0}; int[] nums = {1, 2, 3, 4}; System.out.println(subarraySumEqualsK.subarraySum04(nums, 5)); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ThreeSum.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * @author LuoHaiYang * * url: https://leetcode-cn.com/problems/3sum/ * */ public class ThreeSum { public static void main(String[] args) { int[] nums = {3, 0, -2, -1, 1, 2}; threeSum(nums); } /** * nums = [-1, 0, 1, 2, -1, -4] *

* a + b + c = 0 *

* [ * [-1, 0, 1], * [-1, -1, 2] * ] * * @param nums * @return */ public static List> threeSum(int[] nums) { /** * * 解法一 * 1. 如何解决元素重复问题? * 答:解决重复的关键就是对素组进行排序; * * 结果对索引没有要求,为什么不先进行排序呢?? * ① 对结果进行排序; * 排序后固定一个数nums[i],再使用左右指针指向nums[i]后面的“两端”,值分别为nums[L]和nums[R],计算三个数的sum判断是否满足为0 * - 如果满足则添加进结果集 * - 由于排序的原因,所以如果nums[i]的值大于0,则三者之和必不=0 * * * */ List> ans = new ArrayList(); int len = nums.length; //合法性判断 if (nums == null || len < 3) { return ans; } //如果手撕算法不能使用工具类进行排序,则要可以使用QuickSort来进行排序; // 排序 Arrays.sort(nums); for (int i = 0; i < len; i++) { // 如果当前数字大于0,则三数之和一定大于0,所以结束循环 if (nums[i] > 0) { break; } // 去重 if (i > 0 && nums[i] == nums[i - 1]) { continue; } int L = i + 1; int R = len - 1; while (L < R) { int sum = nums[i] + nums[L] + nums[R]; if (sum == 0) { ans.add(Arrays.asList(nums[i], nums[L], nums[R])); while (L < R && nums[L] == nums[L + 1]) { L++; // 去重 } while (L < R && nums[R] == nums[R - 1]) { R--; // 去重 } L++; R--; } else if (sum < 0) { L++; } else if (sum > 0) { R--; } } } //return ans; //========================================================= 结题方法分割线 =============================================================== /** * */ if (nums.length < 3) { return Collections.emptyList(); } List> res = new ArrayList<>(); int minValue = Integer.MAX_VALUE; int maxValue = Integer.MIN_VALUE; int negSize = 0; int posSize = 0; int zeroSize = 0; for (int v : nums) { if (v < minValue) { minValue = v; } if (v > maxValue) { maxValue = v; } if (v > 0) { posSize++; } else if (v < 0) { negSize++; } else { zeroSize++; } } if (zeroSize >= 3) { res.add(Arrays.asList(0, 0, 0)); } if (negSize == 0 || posSize == 0) { return res; } if (minValue * 2 + maxValue > 0) { maxValue = -minValue * 2; } else if (maxValue * 2 + minValue < 0) { minValue = -maxValue * 2; } int[] map = new int[maxValue - minValue + 1]; int[] negs = new int[negSize]; int[] poses = new int[posSize]; negSize = 0; posSize = 0; for (int v : nums) { if (v >= minValue && v <= maxValue) { if (map[v - minValue]++ == 0) { if (v > 0) { poses[posSize++] = v; } else if (v < 0) { negs[negSize++] = v; } } } } Arrays.sort(poses, 0, posSize); Arrays.sort(negs, 0, negSize); int basej = 0; for (int i = negSize - 1; i >= 0; i--) { int nv = negs[i]; int minp = (-nv) >>> 1; while (basej < posSize && poses[basej] < minp) { basej++; } for (int j = basej; j < posSize; j++) { int pv = poses[j]; int cv = 0 - nv - pv; if (cv >= nv && cv <= pv) { if (cv == nv) { if (map[nv - minValue] > 1) { res.add(Arrays.asList(nv, nv, pv)); } } else if (cv == pv) { if (map[pv - minValue] > 1) { res.add(Arrays.asList(nv, pv, pv)); } } else { if (map[cv - minValue] > 0) { res.add(Arrays.asList(nv, cv, pv)); } } } else if (cv < nv) { break; } } } return res; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TopKFrequentElements.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.*; /** * * 前K个高频元素 * * url:https://leetcode-cn.com/problems/top-k-frequent-elements/ * * @author LuoHaiYang */ public class TopKFrequentElements { /** * * 桶排序 * */ public int[] topKFrequent(int[] nums, int k) { List res = new ArrayList<>(); if (nums == null || nums.length < 2) { return nums; } Map count = new LinkedHashMap<>(); int n = nums.length; for (int i = 0; i < n; i++) { if (count.containsKey(nums[i])) { count.put(nums[i], count.get(nums[i]) + 1); } else { count.put(nums[i], 1); } } List[] list = new List[nums.length]; for (int key : count.keySet()) { // 让频率作为下标 int i = count.get(key); if (list[i] == null) { list[i] = new ArrayList<>(); } // key表示的是元素 list[i].add(key); } for (int i = list.length - 1; i >= 0 && res.size() < k; i--) { if (list[i] == null) { continue; } res.addAll(list[i]); } int[] result = new int[res.size()]; for (int i = 0; i < res.size(); i++) { result[i] = res.get(i); } return result; } public static void main(String[] args) { TopKFrequentElements topKFrequentElements = new TopKFrequentElements(); //int[] test = {1,1,1,2,2,3}; int[] test = {3,0,1,0}; topKFrequentElements.topKFrequent(test,1); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSum.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.HashMap; import java.util.Map; /** * @author LuoHaiYang * * url: https://leetcode-cn.com/problems/two-sum/ * */ public class TwoSum { /** * * nums = [2, 7, 11, 15], target = 9 * * 返回: [0, 1] * * * @param nums * @param target * @return */ public static int[] twoSum(int[] nums, int target) { if (nums.length < 2) { return nums; } /** * 1. (原始)暴力解法: * * 时间换空间 * 时间复杂度:O(n^2) * 空间复杂度:O(1) * */ int[] result = {-1, -1}; for (int i = 0; i < nums.length; i++) { result[0] = i; int ret = target - nums[i]; for (int j = i + 1; j < nums.length; j++) { if (ret == nums[j]) { result[1] = j; return result; } } } //return nums; //========================================================= 结题方法分割线 =============================================================== /** * 2. 暴力解法: * * 空间换时间 * 时间复杂度:O(n) * 空间复杂度:O(n) * * 这里为什么要用数组索引作为map的value呢? * 这是为了方便当匹配到 target - nums[i]时取到对应 * 元素的索引。 * * * 总结: 对于要利用空间换时间的算法,多数都利用哈希表来实现。 * * */ Map map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int ret = target - nums[i]; if (map.containsKey(ret)) { return new int[] {map.get(nums[i]), i}; } map.put(nums[i], i); } return null; } public static void main(String[] args) { int[] nums = {3, 2, 4}; int target = 6; System.out.println(twoSum(nums, target)); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSumII.java ================================================ package com.bruis.algorithminjava.algorithm.leetcode.array; /** * 167: * * https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/description/?utm_source=LCUS&utm_medium=ip_redirect_q_uns&utm_campaign=transfer2china * * 思路:指针碰撞 * * @author LuoHaiYang */ public class TwoSumII { public int[] twoSum(int[] numbers, int target) { if (numbers.length < 2) { return numbers; } int left = 0, right = numbers.length - 1; while (left <= right) { int result = numbers[left] + numbers[right]; if (result == target) { int[] res = {left + 1, right + 1}; return res; } else if (result > target) { right --; } else { left ++; } } return numbers; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * * 二分查找法 * * @author LuoHaiYang */ public class BinarySearch { public static int binarySearch(int[] arr, int n, int target) { // 在 [left, right]范围里寻找target int left = 0, right = n - 1; while (left <= right) { int mid = (right + left) / 2; int nums = arr[mid]; if (nums == target) { return mid; } else if (nums > target) { left = mid + 1; } else { // nums < target right = mid - 1; } } return -1; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/BubbleSort.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * @author LuoHaiYang */ public class BubbleSort { // 方式1 public static void sort(int[] arr) { int n = arr.length; boolean swapped = false; do { swapped = false; for (int i = 1; i < n; i++) { if (arr[i - 1] > arr[i]) { swap(arr, i - 1, i); swapped = true; } } } while(swapped); } // 方式2 public static void sort2(int[] arr) { int n = arr.length; if (n <= 1) { return; } // 使用newn来进行优化 int newn; do { newn = 0; for (int i = 1; i < n; i++) { if (arr[i - 1] > arr[i]) { swap(arr, i - 1, i); // 记录当前排序最后一次交换的位置,在此之后的元素在下一轮扫描中均不考虑 newn = i; } } n = newn; } while (newn > 0); } // 方式3 public static void sort3(int[] arr) { int n = arr.length; if (n <= 1) { return; } for (int i = 0; i < n; i++) { boolean flag = false; // n - i - 1 表示每轮排序都会有一个最大元素冒泡到最大位置,因而每轮排序都会少一个遍历的元素 for (int j = 0; j < n - i - 1; j++) { if (arr[j] < arr[j + 1]) { swap(arr, j, j + 1); flag = true; } } // 此轮排序没有数据交换,则退出排序 if (!flag) { break; } } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java ================================================ package com.bruis.algorithminjava.algorithm.sort; import java.util.Arrays; import java.util.LinkedList; import java.util.List; /** * 桶排序 * * @author LuoHaiYang */ public class BucketSort { /* 排序原理: 桶排序本质就是空间换时间:时间复杂度为:O(n) 顺序从待排数组中取出数字,首先6被取出,然后把6入6号桶,这个过程类似这样:空桶[ 待排数组[ 0 ] ] = 待排数组[ 0 ] [6 2 4 1 5 9] 待排数组 [0 0 0 0 0 0 6 0 0 0] 空桶 [0 1 2 3 4 5 6 7 8 9] 桶编号(实际不存在) 顺序从待排数组中取出下一个数字,此时2被取出,将其放入2号桶,是几就放几号桶 [6 2 4 1 5 9] 待排数组 [0 0 2 0 0 0 6 0 0 0] 空桶 [0 1 2 3 4 5 6 7 8 9] 桶编号(实际不存在) 3,4,5,6省略,过程一样,全部入桶后变成下边这样 [6 2 4 1 5 9] 待排数组 [0 1 2 0 4 5 6 0 0 9] 空桶 [0 1 2 3 4 5 6 7 8 9] 桶编号(实际不存在) */ private int range = 0; public BucketSort(int range) { this.range = range; } public int[] doSort(int[] arr) { // 集合数组 List[] aux = new LinkedList[range]; for (int i = 0; i < aux.length; i++) { aux[i] = new LinkedList<>(); } for (int i = 0; i < arr.length; i++) { aux[arr[i]].add(arr[i]); } for (int i = 0, j = 0; i < aux.length && j < arr.length; i++) { for (int v : aux[i]) { arr[j] = v; j++; } } return arr; } public static void main(String[] args) { BucketSort bucketSort = new BucketSort(10); int[] sort = bucketSort.doSort(new int[]{4, 1, 3, 2, 20, 6, 9, 9, 21, 19}); System.out.println(Arrays.toString(sort)); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/Heap.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * 此堆索引从0开始 * * @Description * @Author luohaiyang * @Date 2022/4/20 */ public class Heap { private int[] data; private int count; private int capacity; /** * * * * a 1 b c 2 3 d e f g 4 5 6 7 q w r x 8 9 10 11 * * * * */ /** * 初始化堆 * @param capacity */ public Heap(int capacity) { this.capacity = capacity; data = new int[capacity+1]; count = 0; } public Heap(int[] data, int capacity) { this.data = data; heapify(capacity); } /** * 新增一个元素 * @param value */ public void insert(int value) { if (count + 1 > capacity) { // 抛异常 } data[++count] = value; shiftUp(count); } /** * 获取堆顶值 * @return */ public int extractMax() { if (count < 1) { // 抛异常 } int max = data[1]; swap(1, count--); shiftDown(1); return max; } /** * 堆化 */ public void heapify(int k) { while (k/2 >= 1) { shiftDown(k/2); k--; } } public int size() { return count; } public boolean isEmpty() { return count == 0; } /** * 上浮操作 * @param k */ private void shiftUp(int k) { while (k > 1 && data[k] > data[k/2]) { swap(k, k/2); k /= 2; } } /** * 下层操作 * @param k */ private void shiftDown(int k) { while (count >= k * 2) { int j = k * 2; if (j+1 <= count && data[j] < data[j+1]) j++; if (data[k] >= data[j]) break; swap(k, j); k = j; } } private void swap(int a, int b) { int tmp = data[a]; data[a] = data[b]; data[b] = tmp; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort01.java ================================================ package com.bruis.algorithminjava.algorithm.sort; import java.util.Random; /** * * 借助辅助空间进行堆排序 * * @author LuoHaiYang */ public class HeapSort01 { /** * 堆化完后并没有排序完成 * @param arr */ private static void heapify(int[] arr) { int n = arr.length; for (int i = n/2; i > 0; i--) { shiftDown(i, n, arr); } } /** * 获取最大堆中堆顶元素 */ private static int extractMax(int[] arr) { int n = arr.length; int max = arr[0]; swap(arr, 0, --n); // 让最后一个元素置0 arr[n] = 0; shiftDown(0, n, arr); return max; } /** * 下沉操作 * @param k */ private static void shiftDown(int k, int n, int[] arr) { while (k * 2 + 1 < n) { // 左子树节点 int j = k * 2 + 1; if (j + 1 < n && arr[j + 1] > arr[j]) { j++; } if (arr[k] >= arr[j]) { break; } swap(arr, k, j); k = j; } } /** * 上浮操作 * @param k */ private void shiftUp(int k, int[] arr) { while (k > 0 && arr[(k-1)/ 2] < arr[k]) { swap(arr, (k-1)/2, k); k = (k-1)/2; } } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static int[] sort(int[] arr) { int n = arr.length; heapify(arr); int[] result = new int[n]; for (int i = 0; i < n; i++) { result[i] = extractMax(arr); } return result; } public static void main(String[] args) { int n = 100; int[] test = new int[n]; Random random = new Random(); for (int i = 0; i < n; i++) { test[i] = random.nextInt(1000); } sort(test); // 测试 for (int i = 1; i < n; i++) { if (test[i-1] < test[i]) { System.out.println("Error!"); } } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort02.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * * 原地堆排序! 不需要额外的空间 * * 这里构造出来的是一个最小堆 * * @author LuoHaiYang */ public class HeapSort02 { public static void sort(int[] arr) { int n = arr.length; // 注意,此时我们的堆是从0开始索引的 // 从(最后一个元素的索引-1)/2开始 // 最后一个元素的索引 = n-1 // 其实这里 i = (n-1) 也行 for (int i = (n - 1 - 1) / 2; i >= 0; i--) { siftDown2(arr, n, i); } // [a.....v,k] // [.......]k // [.....] ba for (int i = n-1; i > 0; i--) { // 由于上面执行过下沉操作,所以已经是最大堆(但没有排序完)。所以此时swap就将最大值替换到数组末尾。 swap(arr, 0, i); // 由于siftDown中是判断 2*k+1 < n ,所以就是对n-1进行下沉操作; siftDown2(arr, i, 0); } } // 下浮 public static void siftDown(int[] arr, int n, int k) { while (2 * k + 1 < n) { int j = 2 * k + 1; if (j + 1 < n && arr[j+1] > arr[j]) { j += 1; } if (arr[k] >= arr[j]) { break; } swap(arr, k, j); k = j; } } /** * 优化下沉过程, 不适用swap交换,通过赋值来代替。 * * @param arr * @param n * @param k */ private static void siftDown2(int[] arr, int n, int k) { int e = arr[k]; while (2 * k + 1 < n) { int j = 2 * k + 1; if (j + 1 < n && arr[j + 1] > arr[j]) { j++; } if (e >= arr[j]) { break; } // 此时说明arr[j] > arr[k]; 所以让大值上浮; arr[k] = arr[j]; k = j; } // 将最小元素替换到k的位置 arr[k] = e; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { /* int n = 100; int[] test = new int[n]; Random random = new Random(); for (int i = 0; i < n; i++) { test[i] = random.nextInt(1000); } */ int n = 10; int[] test = {10, 41, 30, 28, 16, 22, 13, 19, 17, 15}; sort(test); for (int i = 1; i < n; i++) { if (test[i-1] > test[i]) { throw new IllegalArgumentException("Error!"); } } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/InsertionSort.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * @author LuoHaiYang */ public class InsertionSort { // 方式1 public static void sort(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { for (int j = i; j > 0; j--) { // 注意这里j-1没有越界,因为j > 0进行了判断 if (arr[j] < arr[j-1]) { swap(arr, j, j-1); } else { break; } } } } // 方式2 public static void sort2(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { for (int j = i; j > 0 && arr[j] < arr[j-1]; j--) { // 注意这里j-1没有越界,因为j > 0进行了判断 swap(arr, j, j-1); } } } // 方式3,优化版 public static void sort3(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { // 获取需要比较的元素 int e = arr[i]; int j = i; for (; j > 0 && e < arr[j-1] ; j--) { // 如果满足条件,则前一位元素复制给后一位元素 arr[j] = arr[j-1]; } // 跳出循环,则将需要比较的e元素替换到j位置,j位置即最终停留的位置 arr[j] = e; } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { int [] arr = {6,2,1,5,4,3}; sort2(arr); for (int n : arr) { System.out.println(n); } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/MergeSort.java ================================================ package com.bruis.algorithminjava.algorithm.sort; import java.util.Arrays; /** * * 归并排序 * * @author LuoHaiYang */ public class MergeSort { /** * * 将arr[left...mid]和arr[mid+1...right]两部分进行归并 * * @param arr * @param left * @param mid * @param right */ public static void merge(int[] arr, int left, int mid, int right) { int[] aux = Arrays.copyOfRange(arr, left, right + 1); // i表示左边;j表示右边; int i = left, j = mid + 1; // 从左left遍历到右right, 左闭又开 for (int k = left; k <= right; k++) { // 如果左边指针大于mid,则表示左半边数据已经归并完毕 if (i > mid) { // j-left计算出相对aux的位置 arr[k] = aux[j-left]; j++; } else if (j > right) { // j大于right值,则表示右半边数据已经归并完毕 arr[k] = aux[i-left]; i++; } else if (aux[i-left] < aux[j-left]) { arr[k] = aux[i-left]; i++; } else { arr[k] = aux[j-left]; j++; } } } /** * * 对[left, right]范围进行排序 * * @param arr * @param left * @param right */ public static void sort(int[] arr, int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; sort(arr, left, mid); sort(arr, mid + 1, right); merge(arr, left, mid, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/MergeSortAdvanced01.java ================================================ package com.bruis.algorithminjava.algorithm.sort; import java.util.Arrays; /** * * 归并排序优化版本1 * * @author LuoHaiYang */ public class MergeSortAdvanced01 { public static void merge(int[] arr, int left, int mid, int right) { int[] aux = Arrays.copyOfRange(arr, left, right + 1); int i = left, j = mid + 1; for (int k = left; k <= right; k++) { if (i > mid) { arr[k] = aux[j-left]; j++; } else if (j > right) { arr[k] = aux[i-left]; i++; } else if (aux[i-left] < aux[j-left]) { arr[k] = aux[i-left]; i++; } else { arr[k] = aux[j-left]; j++; } } } public static void sort(int[] arr, int left, int right) { // 优化2: 对于小规模数组, 使用插入排序 if (right - left <= 15) { InsertionSort.sort(arr); return; } int mid = (left + right) / 2; sort(arr, left, mid); sort(arr, mid + 1, right); // 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge // 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失 if (arr[mid] > arr[mid+1]) { merge(arr, left, mid, right); } } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/MergeSortBU.java ================================================ package com.bruis.algorithminjava.algorithm.sort; import java.util.Arrays; /** * * 归并排序(自底向上) * * @author LuoHaiYang */ public class MergeSortBU { private static void merge(int[] arr, int left, int mid, int right) { int[] aux = Arrays.copyOfRange(arr, left, right + 1); int i = left, j = right + 1; for (int k = 0; k <= right; k++) { if (i > mid) { arr[k] = aux[j - left]; j++; } else if (j > right) { arr[k] = aux[i - left]; i++; } else if (aux[i - left] > aux[j - left]) { arr[k] = aux[j - left]; j++; } else { arr[k] = aux[i - left]; i++; } } } // [a, b, c, d, e, f, g, h, i] // [a,b] [c,d] [e,f] [g,h] public static void sort(int[] arr) { int n = arr.length; for (int sz = 1; sz < n; sz *= 2) { for (int i = 0; i < n - sz; i += sz + sz) { merge(arr, i, i+sz-1, Math.min(i+sz+sz-1, n-1)); } } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * * 快速排序 * * @author LuoHaiYang */ public class QuickSort { /** * 对arr[left...right]部分进行partition操作 * 返回p, 使得arr[left...p-1] < arr[p] ; arr[p+1...right] > arr[p] * * @param arr * @param left * @param right * @return */ private static int partition(int[] arr, int left, int right) { int p = arr[left]; // arr[left+1...j] < p; arr[j+1...i) > p int j = left; for (int i = left + 1; i <= right; i++) { if (arr[i] < p) { j++; swap(arr, j, i); } } swap(arr, left, j); return j; } private static void sort(int[] arr, int left, int right) { if (left >= right) { return; } int p = partition(arr, left, right); sort(arr, left, p-1); sort(arr, p+1, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * 快速排序的优化 * * 对于近乎有序的数组,快速排序会退化为O(n^2)。 * * @author LuoHaiYang */ public class QuickSort2 { /** * 对arr[left...right]部分进行partition操作 * 返回p, 使得arr[left...p-1] < arr[p] ; arr[p+1...right] > arr[p] * * @param arr * @param left * @param right * @return */ private static int partition(int[] arr, int left, int right) { //int p = arr[left]; // ===================================== 优化2 ===================================== // 避免快排退化为O(n^2) swap(arr, left, (int)Math.random()*(right - left + 1) + left); int p = arr[left]; // arr[left+1...j] < p; arr[j+1...i) > p int j = left; for (int i = left + 1; i <= right; i++) { if (arr[i] < p) { j++; swap(arr, j, i); } } swap(arr, left, j); return j; } private static void sort(int[] arr, int left, int right) { // ===================================== 优化1 ===================================== // 如果左右数值小于15,则通过插入排序来进行排序 if (right - left <= 15) { InsertionSort.sort(arr); return; } int p = partition(arr, left, right); sort(arr, left, p-1); sort(arr, p+1, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2Ways.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * * 双路快排 * * @author LuoHaiYang */ public class QuickSort2Ways { private static int partition(int[] arr, int left, int right) { swap(arr, left, (int)Math.random()*(right - left + 1) + left); int p = arr[left], i = left + 1, j = right; while(true) { /** * 这里arr[i] < p 和 arr[j] > p 是为了避免出现 arr[i] == p 和 arr[j] == p的情况。 * 如果arr[i] == p,则直接进行了i++了,则数组的p会变得极度不平衡,即 所有小于等于p的值都分在了左边, * 这种情况下,快速排序的平均时间复杂度会退化成:O(n^2) * */ while(i <= right && arr[i] < p) { i++; } while(j >= 0 && arr[j] > p) { j--; } if (i > j) { break; } swap(arr, i++, j--); } swap(arr, left, j); return j; } private static void sort(int[] arr, int left, int right) { if (right - left <= 15) { InsertionSort.sort(arr); return; } int p = partition(arr, left, right); sort(arr, left, p - 1); sort(arr, p + 1, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n - 1); } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort3Ways.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * * 三路快排 * * @author LuoHaiYang */ public class QuickSort3Ways { private static void sort(int[] arr, int left, int right) { if (right - left <= 15) { InsertionSort.sort(arr); return; } // 增加随机值,防止快排退化为O(n^2) swap(arr,left, (int)Math.random()*(right - left - 1) + left); int p = arr[left]; // [p...................................................right] // p // lt // i // gt // arr[left+1...lt] < p arr[lt+1...i) = p arr[gt...right] > p int lt = left, gt = right + 1, i = left + 1; while ( i < gt) { if (arr[i] < p) { swap(arr, lt+1, i); i++; lt++; } else if (arr[i] > p) { swap(arr, i, gt-1); gt--; } else {// arr[i] == v i++; } } swap(arr, left, lt); // 继续对[left,lt]进行排序 sort(arr, left, lt-1); // 继续对[gt, right]进行排序 sort(arr, gt, right); } public static void sort(int[] arr) { int n = arr.length; sort(arr, 0, n-1); } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/sort/ShellSort.java ================================================ package com.bruis.algorithminjava.algorithm.sort; /** * * 希尔排序 * * @author LuoHaiYang */ public class ShellSort { public static void sort(int[] arr) { int n = arr.length; int h = 1; while (h < n / 3) { h = 3 * h + 1; } while (h >= 1) { for (int i = h; i < n; i++) { int e = arr[i]; int j = i; for (; j >= h && e < arr[j-h]; j -= h) { arr[j] = arr[j-h]; } arr[j] = e; } h /= 3; } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/algorithm/stack/MinStack.java ================================================ package com.bruis.algorithminjava.algorithm.stack; import java.util.Stack; /** * @author LuoHaiYang * * @Date 2020.05.13 * * url: https://leetcode-cn.com/problems/min-stack/ * * */ public class MinStack { /** * 问题: * 1. 用什么基础数据结构来存储数据? 普遍解法都是通过已有数据结构stack来解决 * 2. 如何自己实现一个栈结构? */ /** initialize your data structure here. */ private Stack stack; private Stack minStack; public MinStack() { stack = new Stack<>(); minStack = new Stack<>(); } public void push(int x) { stack.push(x); if (!minStack.isEmpty()) { int top = minStack.peek(); //小于的时候才入栈 if (x <= top) { minStack.push(x); } }else{ minStack.push(x); } } public void pop() { int pop = stack.pop(); int top = minStack.peek(); //等于的时候再出栈 if (pop == top) { minStack.pop(); } } public int top() { return stack.peek(); } public int getMin() { return minStack.peek(); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/array/MyArray.java ================================================ package com.bruis.algorithminjava.datastructures.array; /** * @author LuoHaiYang */ public class MyArray { //存储的数据 private E[] data; //数组大小,数组容量大小不一定等于数组实际大小 private int size; //设置数组大小 public MyArray(int capacity) { data = (E[])new Object[capacity]; this.size = capacity; } public MyArray() { this(10); } //数组容量大小 public int getCapacity() { return this.data.length; } //数组元素大小 public int getSize() { return this.size; } public boolean isEmpty() { return size == 0; } //在index索引位置插入新元素 public void insert(int index, E value) { if (index < 0 || index > size) { throw new IllegalArgumentException("添加失败,数组索引越界!"); } //如果数组容量不足,则扩容两倍 if (size == data.length) { resize(2 * size); } for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } data[index] = value; size ++; } public E delete(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("删除失败,数组索引越界!"); } E delValue = data[index]; for (int i = index; i < size - 1; i++) { data[i] = data[i +1]; } size --; //gc data[size] = null; return delValue; } public void update(int index, E value) { if (index < 0 || index > size) { throw new IllegalArgumentException("删除失败,数组索引越界!"); } data[index] = value; } // 向所有元素后添加一个元素 public void addLast(E e) { insert(size, e); } // 向所有元素前添加一个元素 public void addFirst(E e) { insert(0, e); } public E get(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("删除失败,数组索引越界!"); } return data[index]; } // 获取末尾元素 public E getLast() { return get(size - 1); } // 获取首部元素 public E getFirst() { return get(0); } public boolean contains(E e) { for (int i = 0; i < size; i++) { if (data[i].equals(e)) { return true; } } return false; } // 查看数组中元素e所在的索引,如果不存在则返回-1 public int find(E e) { for (int i = 0; i < size; i++) { if (data[i].equals(e)) { return i; } } return -1; } public E remove(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("删除失败,数组索引越界!"); } E del = data[index]; for (int i = index + 1; i < size; i++) { data[i-1] = data[i]; } size--; data[size] = null; if (size == data.length / 4 && data.length / 2 != 0) { resize(data.length / 2); } return del; } // 从数组中删除第一个元素, 返回删除的元素 public E removeFirst(){ return remove(0); } // 从数组中删除最后一个元素, 返回删除的元素 public E removeLast(){ return remove(size - 1); } // 从数组中删除元素e public void removeElement(E e){ int index = find(e); if(index != -1){ remove(index); } } public void resize(int newSize) { E[] newData = (E[])new Object[newSize]; for (int i = 0; i < size; i++) { newData[i] = data[i]; } data = newData; } private void printData(E[] data, int size) { System.out.println(); for (int j = 0; j < size; j++) { System.out.print(data[j]); if (j != size) { System.out.print(","); } else { System.out.println(); } } } public static void main(String[] args) { int size = 5; MyArray myArray = new MyArray(size); //增加 for (int i = 0; i < 3; i++) { myArray.insert(i, i); } myArray.printData(myArray.data, myArray.size); //删除 System.out.println(myArray.delete(1)); myArray.printData(myArray.data, myArray.size); //修改 myArray.update(1, 666); myArray.printData(myArray.data, myArray.size); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/heap/IndexMapHeap.java ================================================ package com.bruis.algorithminjava.datastructures.heap; import java.util.Arrays; import java.util.Random; /** * * 索引堆 * * @author LuoHaiYang */ public class IndexMapHeap { /** * 存储数据的数组 */ private int[] arr; /** * index元素值就是arr的索引 */ private int[] index; /** * 数组存储容量大小 */ private int count; /** * 容量 */ private int capacity; public IndexMapHeap(int capacity) { this.capacity = capacity; this.count = 0; // 让索引从0开始 arr = new int[capacity + 1]; // 让索引从0开始 index = new int[capacity + 1]; } public boolean isEmpty() { return count == 0; } public int size() { return count; } /** * 插入新元素 * @param i * @param item */ public void insert(int i, int item) { if (i + 1 > capacity) { throw new IllegalArgumentException("容量已满, 插入失败"); } arr[++i] = item; index[++count] = i; shiftUp(count); } /** * 获取最大索引堆中所以为i的元素 * @param i * @return */ public int getItem(int i) { if (i < 0 && i + 1 > capacity) { throw new IllegalArgumentException("错误!索引越界."); } return arr[i+1]; } /** * 获取索引堆中堆顶的元素(注意堆中元素是arr的索引,即index数组的元素) * @return */ public int getMaxIndex() { if (count < 1) { throw new IllegalArgumentException("异常!堆中没有元素!"); } // 由于索引堆中元素也是从1开始,所以需要-1,从0开始。 return index[1] - 1; } /** * 获取最大索引堆中堆顶元素的值,即索引堆中存储的最大数据 * @return */ public int extractMax() { if (count < 1) { throw new IllegalArgumentException("错误!索引堆中不存在元素"); } int ret = arr[index[1]]; // 取出堆顶元素数据后,需要把最大元素和index末尾元素进行替换,然后做下沉操作 swap(index, 1, count); count--; shiftDown(1); return ret; } /** * 获取最大索引堆中堆顶的索引 * @return */ public int extractMaxIndex() { if (count < 1) { throw new IllegalArgumentException("错误!索引堆中不存在元素"); } int ret = index[1] - 1; swap(index, 1, count); count--; shiftDown(1); return ret; } // ============================= 上浮操作 ============================= /** * 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 * @param k */ private void shiftUp(int k) { // 堆顶元素则直接跳过 while (k > 1 && arr[index[k/2]] < arr[index[k]]) { swap(index, k, k/2); k /= 2; } } // ============================= 下沉操作 ============================= /** * 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引 * * 由于是由1开始计算索引,所以左子树为 2*k * * @param k */ private void shiftDown(int k) { // 如果左子树所以 <= 元素总数 while ( 2 * k <= count) { int j = 2 * k; if (j + 1 <= count && arr[index[j+1]] > arr[index[j]]) { j++; } if (arr[index[k]] >= arr[index[j]]) { break; } // 更换索引值 swap(index, k, j); k = j; } } // ============================= 更新堆元素优先级 ============================= /** * * * * @param i * @param item */ public void change(int i, int item) { i++; // 将所有堆中索引为i的元素修改为item arr[i] = item; // 由于直接在i位置修改为新元素; // 所以需要查找出 index[j] = i的位置,即arr[index[j]] = item; // 然后上浮和下沉(先后顺序不影响) for (int j = 1; j <= count; j++) { if (index[j] == i) { shiftUp(j); shiftDown(j); return; } } } private void swap(int[] arr, int i, int k) { int tmp = arr[i]; arr[i] = arr[k]; arr[k] = tmp; } // ============================= 更新堆元素优先级 ============================= public boolean testIndexes() { int[] copyIndex = new int[count + 1]; for (int i = 0; i <= count; i++) { copyIndex[i] = index[i]; } copyIndex[0] = 0; Arrays.sort(copyIndex); for (int i = 2; i <= count; i++) { if (copyIndex[i-1] + 1 != copyIndex[i]) { System.out.println("错误,索引堆排序错误!"); break; } } return true; } public void testSort(int[] arr) { for (int i = 1; i < arr.length; i++) { if (arr[i-1] < arr[i]) { System.out.println("索引堆排序失败!"); } } } public static void main(String[] args) { int n = 10; IndexMapHeap indexMapHeap = new IndexMapHeap(n); Random random = new Random(); for (int i = 0; i < n; i++) { // insert中仅仅一个shiftUp操作是不能保证索引堆数据的排好序了 indexMapHeap.insert(i,random.nextInt(1000)); } // 判断索引堆索引是否是连续 System.out.println(indexMapHeap.testIndexes()); int[] result = new int[n]; for (int i = 0; i < n; i++) { result[i] = indexMapHeap.extractMax(); } indexMapHeap.testSort(result); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/heap/MaxHeap.java ================================================ package com.bruis.algorithminjava.datastructures.heap; import java.util.Random; /** * @author LuoHaiYang */ public class MaxHeap { /** * 注意,由于数组元素默认值为0,所以堆中元素不可为0 */ private int[] data; /** * 记录数组中元素个数 */ private int size; /** * 数组容量(初始化大小) */ private int capacity; public MaxHeap(int capacity) { data = new int[capacity]; this.capacity = capacity; size = 0; } public MaxHeap() { data = new int[10]; capacity = 10; size = 0; } // ================================ heapify(二叉堆化) ======================== public MaxHeap(int[] data) { int n = data.length; this.data = new int[n+1]; capacity = n; for (int i = 0; i < n; i++) { this.data[i+1] = data[i]; } size = n; // 这里需要注意,size / 2 得到的索引是二叉堆中最后一个非叶子节点。 // 注意!!! 这里size / 2 是因为 data 从1开始的,所以最后一个非叶子节点为:size / 2 // 如果是从0开始,则:size - 1 for (int i = size / 2; i > 0; i++) { siftDown(i); } } /** * 返回堆中真实存在元素个数 * @return */ public int size() { return size; } /** * 返回堆中是否为空 * @return */ public boolean isEmpty() { return size == 0; } /** * 返回index索引其父亲节点 * @param index * @return */ private int parent(int index) { if (index == 0) { throw new IllegalArgumentException("index-0 doesn't have parent"); } return (index - 1) / 2; } /** * 返回index索引的左子节点 * @param index * @return */ private int leftChild(int index) { return index * 2 + 1; } /** * 返回index索引的右子节点 * @param index * @return */ private int rightChild(int index) { return index * 2 + 2; } // ================================ 上浮 siftUp ======================== /** * 向堆中添加元素 */ public void add(int i) { // 注意size和capacity的关系,判断是否容量已经满了 // 向数组最后一位添加新元素 data[size] = i; siftUp(size++); } /** * 上浮过程 * @param k */ private void siftUp(int k) { // k 索引大于0,并且k索引元素值大于k父亲节点元素值 while (k > 0 && data[parent(k)] < data[k]) { // k和parent(k)互换元素 swap(data, k, parent(k)); // 向上移动,让k为parent(k)再进行判断 k = parent(k); } } // ================================ 下浮 siftDown ======================== /** * 获取堆中最大元素 * @return */ public int findMax() { if (size == 0) { throw new IllegalArgumentException("Can't find Max value!"); } return data[0]; } /** * 取出堆中最大元素 * @return */ public int extractMax() { int max = findMax(); swap(data, 0, size - 1); data[size - 1] = 0; siftDown(0); return max; } /** * 下沉操作 * @param k */ private void siftDown(int k) { // 左子节点比数组元素小,则表示有子节点 while (leftChild(k) < size()) { int j = leftChild(k); // 如果k的有右子节点 if (j + 1 < size() && data[j + 1] > data[j]) { // 所以让j为右子节点 // j = rightChild(k); 同 j++; } // 此时data[k] 比leftChild和rightChild中的最大值都要大 if (data[k] > data[j]) { break; } // leftChild和rightChild都比data[k]大,在互换之后,继续下一轮判断 swap(data, k, j); // 互换位置,继续下一轮判断 k = j; } } // ================================ 替换操作 ======================== private void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { int n = 100; MaxHeap maxHeap = new MaxHeap(n); Random random = new Random(); for (int i = 0; i < n; i++) { maxHeap.add(random.nextInt(1000)); } int[] result = new int[n]; for (int i = 0; i < n; i++) { result[i] = maxHeap.extractMax(); } // 测试是否是顺序的 for (int j = 1; j < n; j++) { if (result[j-1] < result[j]) { throw new IllegalArgumentException("Error!"); } } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/queue/MyLoopQueue.java ================================================ package com.bruis.algorithminjava.datastructures.queue; /** * @author LuoHaiYang */ public class MyLoopQueue implements Queue { private E[] data; private int front, tail; private int size; public MyLoopQueue(int capacity) { data = (E[])new Object[capacity + 1]; front = 0; tail = 0; size = 0; } public MyLoopQueue() { this(10); } @Override public int getSize() { return size; } @Override public boolean isEmpty() { return front == tail; } public int getCapacity() { return data.length - 1; } /** * 进队 * * head tail * [a, b, c, d, ..., z] <- addLast * * @param e */ @Override public void enqueue(E e) { if ((tail + 1) % data.length == front) { resize(getCapacity() * 2); } data[tail] = e; tail = (tail + 1) % data.length; size ++; } @Override public E dequeue() { if (isEmpty()) { throw new IllegalArgumentException("Cannot dequeue from an empty queue."); } E ret = data[front]; data[front] = null; front = (front + 1) % data.length; size--; if (size == getCapacity() / 4 && getCapacity() / 2 != 0) { resize(getCapacity() / 2); } return ret; } @Override public E getFront() { if(isEmpty()) { throw new IllegalArgumentException("Queue is empty."); } return data[front]; } private void resize(int newCapacity) { E[] newData = (E[]) new Object[newCapacity + 1]; for (int i = 0; i < size; i++) { newData[i] = data[(i + front) % data.length]; } data = newData; front = 0; tail = size; } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity())); res.append("front ["); for(int i = front ; i != tail ; i = (i + 1) % data.length){ res.append(data[i]); if((i + 1) % data.length != tail) res.append(", "); } res.append("] tail"); return res.toString(); } public static void main(String[] args){ MyLoopQueue queue = new MyLoopQueue<>(); for(int i = 0 ; i < 12 ; i ++){ queue.enqueue(i); System.out.println(queue); if(i % 3 == 2){ queue.dequeue(); System.out.println(queue); } } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/queue/MyQueue.java ================================================ package com.bruis.algorithminjava.datastructures.queue; import com.bruis.algorithminjava.datastructures.array.MyArray; /** * @author LuoHaiYang */ public class MyQueue implements Queue { private MyArray array; public MyQueue(int capacity) { array = new MyArray<>(capacity); } public MyQueue() { array = new MyArray<>(); } @Override public int getSize() { return array.getSize(); } @Override public boolean isEmpty() { return array.isEmpty(); } /** * head tail * [a, b, c, d, ..., z] <- addLast * * @param e */ @Override public void enqueue(E e) { array.addLast(e); } /** * head tail * removeFirst <- [a, b, c, d, ..., z] <- addLast * */ @Override public E dequeue() { return array.removeFirst(); } @Override public E getFront() { return array.getFirst(); } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append("Queue: "); res.append("front ["); for(int i = 0 ; i < array.getSize() ; i ++){ res.append(array.get(i)); if(i != array.getSize() - 1) res.append(", "); } res.append("] tail"); return res.toString(); } public static void main(String[] args) { MyQueue queue = new MyQueue<>(); for(int i = 0 ; i < 10 ; i ++){ queue.enqueue(i); System.out.println(queue); if(i % 3 == 2){ queue.dequeue(); System.out.println(queue); } } } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/queue/Queue.java ================================================ package com.bruis.algorithminjava.datastructures.queue; /** * @author LuoHaiYang */ public interface Queue { int getSize(); boolean isEmpty(); // 进队 void enqueue(E e); // 移除队列 E dequeue(); // 获取队列首个元素 E getFront(); } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/stack/MyStack.java ================================================ package com.bruis.algorithminjava.datastructures.stack; import com.bruis.algorithminjava.datastructures.array.MyArray; /** * @author LuoHaiYang */ public class MyStack implements Stack { private MyArray array; public MyStack(int capacity){ array = new MyArray<>(capacity); } public MyStack(){ array = new MyArray<>(); } @Override public int getSize() { return array.getSize(); } @Override public boolean isEmpty() { return array.isEmpty(); } /** * [a, b, c, d, ..., z] <- addLast * * @param e */ @Override public void push(E e) { // 由于栈是先进后出的数据结构,所以需要调用array的addLast array.addLast(e); } /** * [a, b, c, d, ..., z] -> removeLast * * @return */ @Override public E pop() { return array.removeLast(); } @Override public E peek() { return array.getLast(); } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append("Stack: "); res.append('['); for(int i = 0 ; i < array.getSize() ; i ++){ res.append(array.get(i)); if(i != array.getSize() - 1) { res.append(", "); } } res.append("] top"); return res.toString(); } public static void main(String[] args) { MyStack stack = new MyStack<>(); for(int i = 0 ; i < 5 ; i ++){ stack.push(i); System.out.println(stack); } stack.pop(); System.out.println(stack); } } ================================================ FILE: src/main/java/com/bruis/algorithminjava/datastructures/stack/Stack.java ================================================ package com.bruis.algorithminjava.datastructures.stack; /** * @author LuoHaiYang */ public interface Stack { int getSize(); boolean isEmpty(); void push(E e); // 移除元素 E pop(); E peek(); } ================================================ FILE: src/main/java/com/bruis/algorithminjava/utils/SortTestHelper.java ================================================ package com.bruis.algorithminjava.utils; import java.lang.reflect.Method; /** * @author LuoHaiYang */ public class SortTestHelper { /** * SortTestHelper不允许产生任何实例 */ private SortTestHelper(){} /** * 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR] * @param n * @param rangeL * @param rangeR * @return */ public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) { assert rangeL <= rangeR; Integer[] arr = new Integer[n]; for (int i = 0; i < n; i++) { arr[i] = new Integer((int) (Math.random() * (rangeR - rangeL + 1) + rangeL)); } return arr; } /** * 生成一个近乎有序的数组 * 首先生成一个含有[0...n-1]的完全有序数组, 之后随机交换swapTimes对数据 * swapTimes定义了数组的无序程度: * swapTimes == 0 时, 数组完全有序 * swapTimes 越大, 数组越趋向于无序 * @param n * @param swapTimes * @return */ public static Integer[] generateNearlyOrderedArray(int n, int swapTimes){ Integer[] arr = new Integer[n]; for( int i = 0 ; i < n ; i ++ ) { arr[i] = new Integer(i); } for( int i = 0 ; i < swapTimes ; i ++ ){ int a = (int)(Math.random() * n); int b = (int)(Math.random() * n); int t = arr[a]; arr[a] = arr[b]; arr[b] = t; } return arr; } /** * 打印arr数组的所有内容 * @param arr */ public static void printArray(Object[] arr) { for (int i = 0; i < arr.length; i++){ System.out.print( arr[i] ); System.out.print( ' ' ); } System.out.println(); return; } /** * 判断arr数组是否有序 * @param arr * @return */ public static boolean isSorted(Comparable[] arr){ for( int i = 0 ; i < arr.length - 1 ; i ++ ) { if (arr[i].compareTo(arr[i + 1]) > 0) { return false; } } return true; } /** * 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间 * 将算法的运行时间打印在控制台上 * @param sortClassName * @param arr */ public static void testSort(String sortClassName, Comparable[] arr){ // 通过Java的反射机制,通过排序的类名,运行排序函数 try{ // 通过sortClassName获得排序函数的Class对象 Class sortClass = Class.forName(sortClassName); // 通过排序函数的Class对象获得排序方法 Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class}); // 排序参数只有一个,是可比较数组arr Object[] params = new Object[]{arr}; long startTime = System.currentTimeMillis(); // 调用排序函数 sortMethod.invoke(null,params); long endTime = System.currentTimeMillis(); assert isSorted( arr ); System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" ); } catch(Exception e){ e.printStackTrace(); } } /** * 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间 * 将算法的运行时间以long类型返回, 单位为毫秒(ms) * * @param sortClassName * @param arr * @return */ public static long testSort2(String sortClassName, Comparable[] arr){ // 通过Java的反射机制,通过排序的类名,运行排序函数 try{ // 通过sortClassName获得排序函数的Class对象 Class sortClass = Class.forName(sortClassName); // 通过排序函数的Class对象获得排序方法 Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class}); // 排序参数只有一个,是可比较数组arr Object[] params = new Object[]{arr}; long startTime = System.currentTimeMillis(); // 调用排序函数 sortMethod.invoke(null,params); long endTime = System.currentTimeMillis(); assert isSorted( arr ); return endTime - startTime; } catch(Exception e){ e.printStackTrace(); } return 0; } }