Skip to content

Commit 3b41961

Browse files
committed
[A]堆排序、归并排序算法;[U] 更新堆排序算法文章以及readme
1 parent ece145e commit 3b41961

9 files changed

Lines changed: 592 additions & 21 deletions

File tree

README.md

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,38 @@
44

55
# 经典排序算法
66

7-
- [冒泡排序]()
8-
- [选择排序]()
9-
- [插入排序]()
10-
- [归并排序]()
11-
- [快速排序]()
12-
- [希尔排序]()
13-
- [桶排序]()
14-
- [基数排序]()
15-
16-
# 经典数据结构
17-
18-
- [数组]()
19-
- [栈和队列]()
20-
- [链表]()
21-
- [二分搜索树]()
22-
- [集合和映射]()
7+
- [冒泡排序]() (待续)
8+
- [选择排序]()(待续)
9+
- [插入排序]()(待续)
10+
- [归并排序]()(待续)
11+
- [快速排序]()(待续)
12+
- [希尔排序]()(待续)
13+
- [桶排序]() (待续)
14+
- [基数排序]()(待续)
15+
16+
# 经典算法
17+
18+
- [KMP算法]()(待续)
19+
- [马拉车算法]()(待续)
20+
- [Prim算法]()(待续)
21+
- [Krusk]() (待续)
22+
- [Dijkstra算法]() (待续)
23+
- [Bellman-Ford算法]() (待续)
24+
25+
# 经典数据结构
26+
27+
- [数组]() (待续)
28+
- [栈和队列]()(待续)
29+
- [链表]()(待续)
30+
- [二分搜索树]()(待续)
31+
- [集合和映射]()(待续)
2332
- [堆和优先队列](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)
24-
- [线段树]()
25-
- [Trie树]()
26-
- [并查集]()
33+
- [线段树]() (待续)
34+
- [Trie树]() (待续)
35+
- [并查集]()(待续)
2736
- [AVL树](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/datastructures/AVL%E6%A0%91.md)
28-
- [红黑树]()
29-
- [哈希表]()
37+
- [红黑树]() (待续)
38+
- [哈希表]()(待续)
3039

3140
# leetcode专区
3241

notes/algorithms/基础排序算法.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,13 @@ public void sort(int[] nums) {
362362
| 希尔排序 | O(nlogn) | O(1) | | | | 不稳定 |
363363
| 堆排序 | O(nlogn) | O(1) | | | | 不稳定 |
364364

365+
注意这里总结的都是平均时间复杂度,例如插入排序,如果是有序的数组,则时间复杂度会退化成O(n)。而快速排序,对于每次选择的p位置都是末尾位置,则会退化成时间复杂度为O(n^2)(通过让p的位置随机来解决这个问题)。
366+
对于归并排序、快速排序、堆排序这三个O(nlogn)的排序来说,时间复杂度是有常数级别的区别的,但是快速排序是相对更快的一种排序。
367+
368+
这里还需要注意的是,对于插入排序、快速排序、堆排序来说,都是原地排序的,即不需要额外的空间;而归并排序则是非原地排序,需要借助额外空间。
369+
370+
> 排序算法的稳定性:对于相等的元素,在排序后,原来靠前的元素依然靠前,相等元素的相对位置没有发生改变。
371+
365372
> 除非面试题特别说明,否则认为要排序的数据范围是均匀分布的。
366373
367374
> 快速排序之所以叫快速排序,并不代表它比堆排序和归并排序优良。在最好情况下,它的渐进复杂度与 堆排序和归并排序是相同的。知识快速排序的常量系数比较小而已。
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## 前言
2+
3+
对于堆这种数据结构,详细讲解在这:
4+
5+
[堆排序讲解](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)
6+
7+
学完堆这种数据结构后,再来学堆排序,会简单的多。
8+
9+
## 正文
10+
11+
### 1. 借助辅助空间
12+
13+
14+
15+
### 2. 原地堆排序
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## 前言
2+
3+
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
4+
5+
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
6+
7+
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
8+
- 自下而上的迭代;
9+
10+
11+
归并排序算法是一个平均时间复杂度为O(NlogN)级别的算法,并且空间复杂度为O(N)级别。
12+
13+
![归并过程动态图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/mergeSort.gif)
14+
![分治思想](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/1557906108-5066-20161218163120151-452283750.png)
15+
16+
## 正文
17+
18+
### 1.(自顶向下)归并排序算法
19+
20+
自顶向下的特点就是——递归。
21+
22+
### 2.(自顶向下)归并排序算法——优化
23+
24+
### 3.(自低向上)归并排序算法
25+
26+
自底向上的特点就是——迭代。
27+
28+
除此之外,自底向上排序的算法不需要额外的空间,空间复杂度为O(1)。自底向上有一个非常重要的作用,它可以通过O(NlogN)的
29+
时间复杂度去对链表这种数据结构进行排序。
30+
31+
**对链表进行O(NlogN)级别的排序**

notes/datastructures/堆和优先队列.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,128 @@ public class MaxHeap {
258258
heapify二叉堆化,即从最后一个非叶子节点开始siftDown,即图中蓝色图表示的节点元素。
259259
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap06.png)
260260

261+
### 1.2 原地堆排序
262+
263+
原地堆排序,就是指不需要借助额外的空间进行的排序。
264+
265+
对于最大堆,数组中最大元素都存放在堆顶。每次排序将堆顶和数组最后一位元素进行swap操作,所以最后一位元素就是最大值,此时0~(n-2)就不是堆结构,即需要对
266+
0~(n-2)进行堆化,即将0位置的元素进行下沉操作,之后继续再对0~(n-2)作同样的操作。
267+
268+
![二叉堆图](https://github.com/coderbruis/AlgorithmsInJava/blob/master/notes/pictures/heap07.png)
269+
270+
```
271+
public class HeapSort {
272+
273+
public static void sort(int[] arr) {
274+
int n = arr.length;
275+
276+
// 注意,此时我们的堆是从0开始索引的
277+
// 从(最后一个元素的索引-1)/2开始
278+
// 最后一个元素的索引 = n-1
279+
// 其实这里 i = (n-1) 也行
280+
for (int i = (n - 1 - 1) / 2; i >= 0; i--) {
281+
siftDown2(arr, n, i);
282+
}
283+
284+
// [a.....v,k]
285+
// [.......]k
286+
// [.....] ba
287+
for (int i = n-1; i > 0; i--) {
288+
// 由于上面执行过下沉操作,所以已经是最大堆(但没有排序完)。所以此时swap就将最大值替换到数组末尾。
289+
swap(arr, 0, i);
290+
// 由于siftDown中是判断 2*k+1 < n ,所以就是对n-1进行下沉操作;
291+
siftDown2(arr, i, 0);
292+
}
293+
}
294+
295+
// 下浮
296+
public static void siftDown(int[] arr, int n, int k) {
297+
298+
while (2 * k + 1 < n) {
299+
int j = 2 * k + 1;
300+
if (j + 1 < n && arr[j+1] > arr[j]) {
301+
j += 1;
302+
}
303+
if (arr[k] >= arr[j]) {
304+
break;
305+
}
306+
swap(arr, k, j);
307+
k = j;
308+
}
309+
}
310+
311+
/**
312+
* 优化下沉过程, 不适用swap交换,通过赋值来代替。
313+
*
314+
* @param arr
315+
* @param n
316+
* @param k
317+
*/
318+
private static void siftDown2(int[] arr, int n, int k) {
319+
320+
int e = arr[k];
321+
322+
while (2 * k + 1 < n) {
323+
int j = 2 * k + 1;
324+
if (j + 1 < n && arr[j + 1] > arr[j]) {
325+
j++;
326+
}
327+
328+
if (e >= arr[j]) {
329+
break;
330+
}
331+
// 此时说明arr[j] > arr[k]; 所以让大值上浮;
332+
arr[k] = arr[j];
333+
k = j;
334+
}
335+
// 将最小元素替换到k的位置
336+
arr[k] = e;
337+
}
338+
339+
public static void swap(int[] arr, int i, int j) {
340+
int tmp = arr[i];
341+
arr[i] = arr[j];
342+
arr[j] = tmp;
343+
}
344+
345+
public static void main(String[] args) {
346+
/*
347+
int n = 100;
348+
int[] test = new int[n];
349+
Random random = new Random();
350+
for (int i = 0; i < n; i++) {
351+
test[i] = random.nextInt(1000);
352+
}
353+
*/
354+
int n = 10;
355+
int[] test = {10, 41, 30, 28, 16, 22, 13, 19, 17, 15};
356+
357+
sort(test);
358+
359+
for (int i = 1; i < n; i++) {
360+
if (test[i-1] > test[i]) {
361+
throw new IllegalArgumentException("Error!");
362+
}
363+
}
364+
}
365+
}
366+
```
367+
368+
### 1.3 索引堆
369+
370+
对于我们所关心的这个通过数组实现的堆而言,数组中的元素位置发生了改变。正是因为这些元素的位置发生了改变,我们才能将其构建为最大堆。
371+
372+
可是由于数组中元素位置的改变,我们将面临着几个局限性。
373+
374+
1.如果我们的元素是十分复杂的话,比如像每个位置上存的是一篇10万字的文章。那么交换它们之间的位置将产生大量的时间消耗。(不过这可以通过技术手段解决)
375+
376+
2.由于我们的数组元素的位置在构建成堆之后发生了改变,那么我们之后就很难索引到它,很难去改变它。例如我们在构建成堆后,想去改变一个原来元素的优先级(值),将会变得非常困难。
377+
378+
可能我们在每一个元素上再加上一个属性来表示原来位置可以解决,但是这样的话,我们必须将这个数组遍历一下才能解决。(性能低效)
379+
380+
针对以上问题,我们就需要引入索引堆(Index Heap)的概念。
381+
382+
对于索引堆来说,我们将数据和索引这两部分分开存储。真正表征堆的这个数组是由索引这个数组构建成的。
261383

262384
## 2. 优先队列
263385

notes/pictures/heap07.png

34 KB
Loading
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.bruis.algorithminjava.algorithm.sort;
2+
3+
import java.util.Arrays;
4+
5+
/**
6+
*
7+
* 归并排序(自底向上)
8+
*
9+
* @author LuoHaiYang
10+
*/
11+
public class MergeSortBU {
12+
private static void merge(int[] arr, int left, int mid, int right) {
13+
int[] aux = Arrays.copyOfRange(arr, left, right + 1);
14+
15+
int i = left, j = right + 1;
16+
for (int k = 0; k <= right; k++) {
17+
if (i > mid) {
18+
arr[k] = aux[j - left];
19+
j++;
20+
} else if (j > right) {
21+
arr[k] = aux[i - left];
22+
i++;
23+
} else if (aux[i - left] > aux[j - left]) {
24+
arr[k] = aux[j - left];
25+
j++;
26+
} else {
27+
arr[k] = aux[i - left];
28+
i++;
29+
}
30+
}
31+
}
32+
33+
// [a, b, c, d, e, f, g, h, i]
34+
// [a,b] [c,d] [e,f] [g,h]
35+
public static void sort(int[] arr) {
36+
int n = arr.length;
37+
38+
for (int sz = 1; sz < n; sz *= 2) {
39+
for (int i = 0; i < n - sz; i += sz + sz) {
40+
merge(arr, i, i+sz-1, Math.min(i+sz+sz-1, n-1));
41+
}
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)