diff --git a/README.md b/README.md index a67f321..565a2fb 100644 --- a/README.md +++ b/README.md @@ -2,31 +2,132 @@ 本项目主要用于自己在工作之余记录用Java实现的算法和数据结构的源码;同时还会记录自己刷leetcode的题解思路等; +> Tip:如果读者电脑无法浏览到github图片,则需要设置hosts配置文件, 解决办法: +[解决方案](https://zhuanlan.zhihu.com/p/107691233) + # 经典排序算法 -- [冒泡排序]() -- [选择排序]() -- [插入排序]() -- [归并排序]() -- [快速排序]() -- [希尔排序]() -- [桶排序]() -- [基数排序]() - -# 经典数据结构 - -- [数组]() -- [栈和队列]() -- [链表]() -- [二分搜索树]() -- [集合和映射]() -- [堆和优先队列](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) -- [红黑树]() -- [哈希表]() +✅ [冒泡排序](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. 1 根节点为黑色;2 红黑树节点要么是红色,要么是黑色;3 任意节点到叶子节点经过的黑色节点数量相同;4 每个叶子节点(没有子节点的节点)都是黑色的;5 如果一个节点是红色的,那么它的子节点都是黑色的;
2. 红黑树是"近似平衡",因为红色节点的存在,让红黑树能够实现红色节点和其父亲节点进行融合,从而成为类似于2-3树这种"绝对平衡的树";
3. 红黑树的高度近似于2logN;
4. 红黑树由于是近似平衡,所以维护成本比AVL树的要低,并且实践证明,红黑树的插入、查找和删除性能都比较稳定;| JDK中的TreeMap使用了红黑树;linux中的epoll底层,用红黑树来存储epoll_fd; | +| B+树 | 非线性结构 | 1. 多节点,层数低;2. 时间复杂度为O(logn); | InnoDB底层使用的B+树 | # leetcode专区 @@ -34,24 +135,24 @@ 题目名称 | 难度 | 地址 | 题解 ---|---|---|--- -两数之和 | 简单 | [https://leetcode-cn.com/problems/two-sum/](https://leetcode-cn.com/problems/two-sum/) | a -三数之和 | 中等 | [https://leetcode-cn.com/problems/3sum/](https://leetcode-cn.com/problems/3sum/) | a -乘积最大子数组 | 中等 | [https://leetcode-cn.com/problems/maximum-product-subarray/](https://leetcode-cn.com/problems/maximum-product-subarray/) | b -和为K的子数组 | 中等 | [https://leetcode-cn.com/problems/subarray-sum-equals-k/](https://leetcode-cn.com/problems/subarray-sum-equals-k/) | c - -数组类总数:4 +两数之和 | 简单 | [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/) | a - -堆栈类总数:1 +最小栈 | 简单 | [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. 链表 -==================== 持续更新 =================== # 支持 diff --git "a/notes/algorithms/345円206円222円346円263円241円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/345円206円222円346円263円241円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..a2127c7 --- /dev/null +++ "b/notes/algorithms/345円206円222円346円263円241円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -0,0 +1,104 @@ + +- [前言](#前言) +- [正文](#正文) + - [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) diff --git "a/notes/algorithms/345円237円272円346円225円260円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/345円237円272円346円225円260円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..e69de29 diff --git "a/notes/algorithms/345円237円272円347円241円200円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/345円237円272円347円241円200円346円216円222円345円272円217円347円256円227円346円263円225円.md" index 37d745e..c83af68 100644 --- "a/notes/algorithms/345円237円272円347円241円200円346円216円222円345円272円217円347円256円227円346円263円225円.md" +++ "b/notes/algorithms/345円237円272円347円241円200円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -357,11 +357,18 @@ public void sort(int[] nums) { | 基数排序 | O(d(n+r)) | O(M) | | | | 稳定 | | 桶排序 | | | | | | 稳定 | | 选择排序 | O(n^2) | O(1) | | | | 不稳定 | -| 归并排序 | O(nlogn) | O(N) | | | | 稳定 | +| 归并排序 | 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)的排序来说,时间复杂度是有常数级别的区别的,但是快速排序是相对更快的一种排序。 + +这里还需要注意的是,对于插入排序、快速排序、堆排序来说,都是原地排序的,即不需要额外的空间;而归并排序则是非原地排序,需要借助额外空间。 + +> 排序算法的稳定性:对于相等的元素,在排序后,原来靠前的元素依然靠前,相等元素的相对位置没有发生改变。 + > 除非面试题特别说明,否则认为要排序的数据范围是均匀分布的。 > 快速排序之所以叫快速排序,并不代表它比堆排序和归并排序优良。在最好情况下,它的渐进复杂度与 堆排序和归并排序是相同的。知识快速排序的常量系数比较小而已。 diff --git "a/notes/algorithms/345円240円206円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/345円240円206円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..88ef6b7 --- /dev/null +++ "b/notes/algorithms/345円240円206円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -0,0 +1,267 @@ + +- [前言](#前言) +- [正文](#正文) + - [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) diff --git "a/notes/algorithms/345円270円214円345円260円224円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/345円270円214円345円260円224円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..0d3cd26 --- /dev/null +++ "b/notes/algorithms/345円270円214円345円260円224円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -0,0 +1,46 @@ +## 前言 + +希尔排序(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; + } + } +} +``` diff --git "a/notes/algorithms/345円275円222円345円271円266円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/345円275円222円345円271円266円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..d5c8dea --- /dev/null +++ "b/notes/algorithms/345円275円222円345円271円266円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -0,0 +1,50 @@ + +- [前言](#前言) +- [正文](#正文) + - [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) diff --git "a/notes/algorithms/345円277円253円351円200円237円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/345円277円253円351円200円237円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..4e43714 --- /dev/null +++ "b/notes/algorithms/345円277円253円351円200円237円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -0,0 +1,316 @@ + +- [前言](#前言) +- [正文](#正文) + - [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) diff --git "a/notes/algorithms/346円217円222円345円205円245円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/346円217円222円345円205円245円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..4d18a62 --- /dev/null +++ "b/notes/algorithms/346円217円222円345円205円245円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -0,0 +1,86 @@ + +- [前言](#前言) +- [正文](#正文) + - [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) \ No newline at end of file diff --git "a/notes/algorithms/346円241円266円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/346円241円266円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..e69de29 diff --git "a/notes/algorithms/351円200円211円346円213円251円346円216円222円345円272円217円347円256円227円346円263円225円.md" "b/notes/algorithms/351円200円211円346円213円251円346円216円222円345円272円217円347円256円227円346円263円225円.md" new file mode 100644 index 0000000..82b1ae2 --- /dev/null +++ "b/notes/algorithms/351円200円211円346円213円251円346円216円222円345円272円217円347円256円227円346円263円225円.md" @@ -0,0 +1,52 @@ + +- [前言](#前言) +- [正文](#正文) + - [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) diff --git "a/notes/datastructures/AVL346円240円221円.md" "b/notes/datastructures/AVL346円240円221円.md" index 2ac0089..1ead175 100644 --- "a/notes/datastructures/AVL346円240円221円.md" +++ "b/notes/datastructures/AVL346円240円221円.md" @@ -1,3 +1,17 @@ + +- [前言](#前言) +- [正文](#正文) + - [1. 基础准备](#1-基础准备) + - [2. 计算树高度和平衡因子](#2-计算树高度和平衡因子) + - [3. 判断是否为二叉树](#3-判断是否为二叉树) + - [4. 判断是否为平衡二叉树](#4-判断是否为平衡二叉树) + - [5. 旋转操作的基本原理](#5-旋转操作的基本原理) + - [6. 右旋](#6-右旋) + - [7. 左旋](#7-左旋) + - [8. LR和RL](#8-lr和rl) + - [9. 删除节点](#9-删除节点) + + ## 前言 AVL树本质上还是一颗二叉树,它的特点是: diff --git "a/notes/datastructures/345円240円206円345円222円214円344円274円230円345円205円210円351円230円237円345円210円227円.md" "b/notes/datastructures/345円240円206円345円222円214円344円274円230円345円205円210円351円230円237円345円210円227円.md" index d253091..b08ea49 100644 --- "a/notes/datastructures/345円240円206円345円222円214円344円274円230円345円205円210円351円230円237円345円210円227円.md" +++ "b/notes/datastructures/345円240円206円345円222円214円344円274円230円345円205円210円351円230円237円345円210円227円.md" @@ -1,4 +1,11 @@ - + +- [前言](#前言) + - [1. 二叉堆](#1-二叉堆) + - [1.1 用数组存储二叉堆](#11-用数组存储二叉堆) + - [1.2 原地堆排序](#12-原地堆排序) + - [1.3 索引堆](#13-索引堆) + - [2. 优先队列](#2-优先队列) + # 前言 下面先了解下二叉堆的基础知识 @@ -258,6 +265,394 @@ public class MaxHeap { 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. 优先队列 diff --git a/notes/pictures/heap07.png b/notes/pictures/heap07.png new file mode 100644 index 0000000..8846ce5 Binary files /dev/null and b/notes/pictures/heap07.png differ diff --git a/notes/pictures/heapSort01.gif b/notes/pictures/heapSort01.gif new file mode 100644 index 0000000..653e17b Binary files /dev/null and b/notes/pictures/heapSort01.gif differ diff --git a/notes/pictures/heapSort02.gif b/notes/pictures/heapSort02.gif new file mode 100644 index 0000000..f73ccf2 Binary files /dev/null and b/notes/pictures/heapSort02.gif differ diff --git a/notes/pictures/indexHeap01.jpg b/notes/pictures/indexHeap01.jpg new file mode 100644 index 0000000..1cf5603 Binary files /dev/null and b/notes/pictures/indexHeap01.jpg differ diff --git a/notes/pictures/indexHeap02.jpg b/notes/pictures/indexHeap02.jpg new file mode 100644 index 0000000..a086fe3 Binary files /dev/null and b/notes/pictures/indexHeap02.jpg differ diff --git a/notes/pictures/indexHeap03.jpg b/notes/pictures/indexHeap03.jpg new file mode 100644 index 0000000..4f11922 Binary files /dev/null and b/notes/pictures/indexHeap03.jpg differ diff --git a/notes/pictures/indexHeap04.jpg b/notes/pictures/indexHeap04.jpg new file mode 100644 index 0000000..fd7329a Binary files /dev/null and b/notes/pictures/indexHeap04.jpg differ diff --git a/notes/pictures/quickSort.gif b/notes/pictures/quickSort.gif new file mode 100644 index 0000000..6a3faec Binary files /dev/null and b/notes/pictures/quickSort.gif differ diff --git a/notes/pictures/shellSort.gif b/notes/pictures/shellSort.gif new file mode 100644 index 0000000..08820ad Binary files /dev/null and b/notes/pictures/shellSort.gif differ diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/huawei/Question01.java b/src/main/java/com/bruis/algorithminjava/algorithm/huawei/Question01.java new file mode 100644 index 0000000..6f1092a --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/huawei/Question01.java @@ -0,0 +1,54 @@ +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); +*/ + } + +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ContainsDuplicate_217.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ContainsDuplicate_217.java new file mode 100644 index 0000000..924d68c --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/ContainsDuplicate_217.java @@ -0,0 +1,111 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/LongestPalindromicSubstring_5.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/LongestPalindromicSubstring_5.java new file mode 100644 index 0000000..2c84640 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/LongestPalindromicSubstring_5.java @@ -0,0 +1,9 @@ +package com.bruis.algorithminjava.algorithm.leetcode; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/6/28 + */ +public class LongestPalindromicSubstring_5 { +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/MaximumSubarray_53.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/MaximumSubarray_53.java new file mode 100644 index 0000000..f59db25 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/MaximumSubarray_53.java @@ -0,0 +1,68 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSum.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSum.java new file mode 100644 index 0000000..c3dea2f --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSum.java @@ -0,0 +1,90 @@ +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]; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSumII.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSumII.java new file mode 100644 index 0000000..79147f1 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/TwoSumII.java @@ -0,0 +1,62 @@ +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]; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java new file mode 100644 index 0000000..d75488f --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/IsPalindrome.java @@ -0,0 +1,41 @@ +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)); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java new file mode 100644 index 0000000..522d1f0 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumGap.java @@ -0,0 +1,170 @@ +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)); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/array/MaximumProductSubarray.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumProductSubarray.java similarity index 96% rename from src/main/java/com/bruis/algorithminjava/algorithm/array/MaximumProductSubarray.java rename to src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumProductSubarray.java index 2df3ade..b0785f4 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/array/MaximumProductSubarray.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/MaximumProductSubarray.java @@ -1,4 +1,4 @@ -package com.bruis.algorithminjava.algorithm.array; +package com.bruis.algorithminjava.algorithm.leetcode.array; /** * @author LuoHaiYang diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReversePairs.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReversePairs.java new file mode 100644 index 0000000..fc2d98c --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReversePairs.java @@ -0,0 +1,179 @@ +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)); + } + + +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReverseVowels.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReverseVowels.java new file mode 100644 index 0000000..5dbb8aa --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ReverseVowels.java @@ -0,0 +1,58 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SortColors.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SortColors.java new file mode 100644 index 0000000..26fe731 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SortColors.java @@ -0,0 +1,56 @@ +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); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/array/SubarraySumEqualsK.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SubarraySumEqualsK.java similarity index 98% rename from src/main/java/com/bruis/algorithminjava/algorithm/array/SubarraySumEqualsK.java rename to src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SubarraySumEqualsK.java index 82e703f..c7a7819 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/array/SubarraySumEqualsK.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/SubarraySumEqualsK.java @@ -1,4 +1,4 @@ -package com.bruis.algorithminjava.algorithm.array; +package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/array/ThreeSum.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ThreeSum.java similarity index 98% rename from src/main/java/com/bruis/algorithminjava/algorithm/array/ThreeSum.java rename to src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ThreeSum.java index 65d6ea4..435fa3a 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/array/ThreeSum.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/ThreeSum.java @@ -1,4 +1,4 @@ -package com.bruis.algorithminjava.algorithm.array; +package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TopKFrequentElements.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TopKFrequentElements.java new file mode 100644 index 0000000..705e368 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TopKFrequentElements.java @@ -0,0 +1,69 @@ +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); + + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/array/TwoSum.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSum.java similarity index 97% rename from src/main/java/com/bruis/algorithminjava/algorithm/array/TwoSum.java rename to src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSum.java index 6d8097e..08d7dcc 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/array/TwoSum.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSum.java @@ -1,4 +1,4 @@ -package com.bruis.algorithminjava.algorithm.array; +package com.bruis.algorithminjava.algorithm.leetcode.array; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSumII.java b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSumII.java new file mode 100644 index 0000000..deb5cff --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/leetcode/array/TwoSumII.java @@ -0,0 +1,38 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java new file mode 100644 index 0000000..b6de14f --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BinarySearch.java @@ -0,0 +1,31 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java new file mode 100644 index 0000000..08c7be4 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/BucketSort.java @@ -0,0 +1,57 @@ +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)); + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/Heap.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Heap.java new file mode 100644 index 0000000..f593c19 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/Heap.java @@ -0,0 +1,122 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort01.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort01.java new file mode 100644 index 0000000..00328b6 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort01.java @@ -0,0 +1,101 @@ +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!"); + } + } + + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort02.java similarity index 95% rename from src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort.java rename to src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort02.java index ca959cd..825987a 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/HeapSort02.java @@ -1,16 +1,14 @@ package com.bruis.algorithminjava.algorithm.sort; -import java.util.Random; - /** * * 原地堆排序! 不需要额外的空间 * - * 这里构造出来的是一个最小堆!!!! 即数组的值依次递增 + * 这里构造出来的是一个最小堆 * * @author LuoHaiYang */ -public class HeapSort { +public class HeapSort02 { public static void sort(int[] arr) { int n = arr.length; diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/MergeSortBU.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/MergeSortBU.java new file mode 100644 index 0000000..92dc91a --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/MergeSortBU.java @@ -0,0 +1,44 @@ +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)); + } + } + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort.java index d10c21b..fd9c3ed 100644 --- a/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort.java +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort.java @@ -28,8 +28,8 @@ private static int partition(int[] arr, int left, int right) { j++; swap(arr, j, i); } - swap(arr, left, j); } + swap(arr, left, j); return j; } diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2.java new file mode 100644 index 0000000..0c1b172 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2.java @@ -0,0 +1,65 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2Ways.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2Ways.java new file mode 100644 index 0000000..7c8bf9d --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort2Ways.java @@ -0,0 +1,64 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort3Ways.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort3Ways.java new file mode 100644 index 0000000..fbf5c50 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/QuickSort3Ways.java @@ -0,0 +1,58 @@ +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; + } +} diff --git a/src/main/java/com/bruis/algorithminjava/algorithm/sort/ShellSort.java b/src/main/java/com/bruis/algorithminjava/algorithm/sort/ShellSort.java new file mode 100644 index 0000000..d084aef --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/algorithm/sort/ShellSort.java @@ -0,0 +1,32 @@ +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; + } + } +} diff --git a/src/main/java/com/bruis/algorithminjava/datastructures/heap/IndexMapHeap.java b/src/main/java/com/bruis/algorithminjava/datastructures/heap/IndexMapHeap.java new file mode 100644 index 0000000..6de104a --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/datastructures/heap/IndexMapHeap.java @@ -0,0 +1,239 @@ +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); + + } + +} diff --git a/src/main/java/com/bruis/algorithminjava/utils/SortTestHelper.java b/src/main/java/com/bruis/algorithminjava/utils/SortTestHelper.java new file mode 100644 index 0000000..12ab6b2 --- /dev/null +++ b/src/main/java/com/bruis/algorithminjava/utils/SortTestHelper.java @@ -0,0 +1,156 @@ +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; + } +}

AltStyle によって変換されたページ (->オリジナル) /