九大排序算法总结

本文简单总结一下九大排序算法的思路,并给出java版本代码。

冒泡排序

核心: 每次比较相邻两个元素,大的放后面,这样一轮下来,数组中最大的元素就跑到最后面去了,就像河底的气泡冒到水面一样。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void bubbleSort(int[] arr){
//复杂度:O(n^2)
for(int i = 1; i < arr.length; ++i){
for(int j = 0; j < arr.length - i ; ++j){
//采用>,排序稳定
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
//带标记的冒泡排序
public static void bubbleSortWithFlag(int[] arr){
boolean flag;
for(int i = 1; i < arr.length; ++i){
flag = false;
for(int j = 0; j < arr.length - i ; ++j){
if(arr[j] > arr[j+1]){
flag = true;
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
//flag为false,说明数组已经排好序
if(!flag)
return;
}
}

插入排序

核心: 假设数组左半部分是有序的,将右半部分元素,逐一插入左半部分适当位置

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void insertSort(int[] arr){
//两重循环,O(n^2)时间复杂度
for(int i = 1; i < arr.length; ++i){
int j = i - 1;
int temp = arr[i];
//对于每个元素,向左找第一个小于等于(带等号,排序算法稳定)自己的元素
while(j >= 0 && arr[j] > temp){
arr[j+1] = arr[j];
j--;
}
//该元素放到第一个小于等于自己的元素后面
arr[j+1] = temp;
}
}

选择排序

核心: 每次遍历数组,记录最小元素所在位置,然后将该元素和数组(无序部分)前面元素交换。因此一共需要遍历n次,时间复杂度O(n^2)。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void selectSort(int[] arr){
for(int i = 0; i < arr.length - 1; ++i){
int minIndex = i;
for(int j = i + 1; j < arr.length; ++j){
//不带等号,保证排序稳定性
if(arr[j] < arr[minIndex])
minIndex = j;
}
//交换arr[i]和arr[maxIndex]
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}

快速排序

核心: 分治思想,选取一个基准值,数组中比基准值小的放左边,比基准值大的放右边。然后基准值左右两部分再分别排序。

注:基准值的选取有多种方式,直接最左边元素作为基准值、随机基准值、三数取中基准值等,另外快排还有一些优化方法。这里对这些内容均不作介绍。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void fastSort(int[] arr, int start, int end){
if(start >= end)
return;
int l = start, r = end;
int p = arr[l];//最左边元素作为基准值
while(l < r){
//从右往左找第一个小于基准值的数
while(r > l && arr[r] >= p){
r--;
}
if(r > l){
arr[l++] = arr[r];
}
//从左往右找第一个大于基准值的数
while(l < r && arr[l] < p){
l++;
}
if(l < r){
arr[r--] = arr[l];
}
}
arr[l] = p;
fastSort(arr, start, l-1);
fastSort(arr, l+1, end);
}

堆序

核心: ① 初始化堆: 将数列a[1…n]构造成最大堆。 ② 交换数据: 将a[1]和a[n]交换,使a[n]是a[1…n]中的最大值;然后将a[1…n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1…n-1]中的最大值;然后将a[1…n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static void heapSort(int[] arr){
int temp;
//时间复杂度O(n*log n),排序不稳定
for(int i = 0; i < arr.length - 1; ++i){
maxHeap(arr,arr.length-i-1);
temp = arr[0];
arr[0] = arr[arr.length-i-1];
arr[arr.length-i-1] = temp;
}
}
//这里采用自底向上调整最大堆
//如果采用自顶向下调整最大堆,就需要采用递归方式
public static void maxHeap(int[] arr, int end){
int temp, cur;
if(end%2 == 1){//最后一个节点是左孩子
cur = (end-1)/2;
if(arr[2*cur+1] > arr[cur]){
temp = arr[cur];
arr[cur] = arr[2*cur+1];
arr[2*cur+1] = temp;
}
cur--;
}else{//最后一个孩子是右孩子
cur = (end-2)/2;//父节点index
}
while(cur >= 0){
//对比左孩子
if(arr[2*cur+1] > arr[cur]){
temp = arr[cur];
arr[cur] = arr[2*cur+1];
arr[2*cur+1] = temp;
}
//对比右孩子
if(arr[2*cur+2] > arr[cur]){
temp = arr[cur];
arr[cur] = arr[2*cur+2];
arr[2*cur+2] = temp;
}
cur--;
}
}

归并排序

核心: 分治思想,大问题拆成小问题。先把左半部分排好序,再把右半部分排好序,然后合并两个有序数组。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void mergeSort(int[] arr, int start, int end){
if(start >= end)
return;
//自下而上不好理解,这里采用自上而下归并,复杂度O(n*logn)
int mid = (start+end)/2;
mergeSort(arr, start, mid);
mergeSort(arr, mid+1, end);
merge(arr, start, mid, end);
}
public static void merge(int[] arr, int start, int mid, int end){
int[] temp = new int[end-start+1];//临时数组
int i = start;
int j = mid + 1;
int count = 0;
while(i <= mid && j <= end){
//带等号,排序算法稳定
if(arr[i] <= arr[j]){
temp[count++] = arr[i++];
}else{
temp[count++] = arr[j++];
}
}
while(i <= mid){
temp[count++] = arr[i++];
}
while(j <= end){
temp[count++] = arr[j++];
}
for(int t = 0; t < temp.length; ++t){
arr[start+t] = temp[t];
}
}

Shell排序

核心: 插入排序的改进——分组插入,核心思想在于:距离gap的元素放到一组,每组分别执行插入排序,然后gap=gap/2,再分组,执行插入排序,重复上述操作,直至gap=1。

注:希尔排序复杂度和gap的选取有关,原始希尔排序初始gap为数组长度的一半,然后每次减半,最坏时间复杂度为O(n^2)。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void shellSort(int[] arr){
//步长每次减半,最坏时间复杂度为O(n^2),不稳定
for(int gap = arr.length/2; gap >= 1; gap = gap/2){
for(int i = 0; i < gap; ++i)
//每个分组分别进行插入排序
groupSort(arr, i, gap);
}
}
public static void groupSort(int[] arr, int start, int gap){
//插入排序
while(start < arr.length){
int minIndex = start;
int cur = start + gap;
while(cur < arr.length){
if(arr[cur] < arr[minIndex])
minIndex = cur;
cur += gap;
}
int temp = arr[start];
arr[start] = arr[minIndex];
arr[minIndex] = temp;
start += gap;
}
}

桶排序

核心: 建立若干个桶,第2个桶中数据大于第一个桶,第3个大于第2个,依次类推。把数组中数据放到桶中,然后把每个桶中的数据排好序,就得到了有序数组。显然,桶数量越多,空间复杂度越高,但是时间复杂度越低。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void bukcetSort(int[] arr){
int maxVal = Integer.MIN_VALUE;
for(int item : arr){
maxVal = Math.max(maxVal, item);
}
//方便起见,数组最大值作为桶数量
int[] buckets = new int[maxVal+1];
for(int item : arr){
buckets[item]++;
}
int index = 0;
for(int i = 0; i < buckets.length; ++i){
while(buckets[i] > 0){
arr[index++] = i;
buckets[i]--;
}
}
}

基数排序

核心: 将整数按位分割,先按照个位数排序,再按照十位数排序,再按照百位数排序。

示意图:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
* 获取数组a中最大值
*
* 参数说明:
* a -- 数组
* n -- 数组长度
*/
private static int getMax(int[] a) {
int max;

max = a[0];
for (int i = 1; i < a.length; i++)
if (a[i] > max)
max = a[i];

return max;
}

/*
* 对数组按照"某个位数"进行排序(桶排序)
*
* 参数说明:
* a -- 数组
* exp -- 指数。对数组a按照该指数进行排序。
*
* 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
* (01) 当exp=1表示按照"个位"对数组a进行排序
* (02) 当exp=10表示按照"十位"对数组a进行排序
* (03) 当exp=100表示按照"百位"对数组a进行排序
* ...
*/
private static void countSort(int[] a, int exp) {
//int output[a.length]; // 存储"被排序数据"的临时数组
int[] output = new int[a.length]; // 存储"被排序数据"的临时数组
int[] buckets = new int[10];

// 将数据出现的次数存储在buckets[]中
for (int i = 0; i < a.length; i++)
buckets[ (a[i]/exp)%10 ]++;

// 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。
for (int i = 1; i < 10; i++)
buckets[i] += buckets[i - 1];

// 将数据存储到临时数组output[]中
for (int i = a.length - 1; i >= 0; i--) {
output[buckets[ (a[i]/exp)%10 ] - 1] = a[i];
buckets[ (a[i]/exp)%10 ]--;
}

// 将排序好的数据赋值给a[]
for (int i = 0; i < a.length; i++)
a[i] = output[i];

output = null;
buckets = null;
}

/*
* 基数排序
*
* 参数说明:
* a -- 数组
*/
public static void radixSort(int[] a) {
int exp; // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
int max = getMax(a); // 数组a中的最大值

// 从个位开始,对数组a按"指数"进行排序
for (exp = 1; max/exp > 0; exp *= 10)
countSort(a, exp);
}

排序算法总结

参考资料

  1. Java全站知识体系
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Yin Peng
  • 引擎: Hexo   |  主题:修改自 Ayer
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信