我是靠谱客的博主 疯狂毛豆,这篇文章主要介绍1.常用排序算法总结,现在分享给大家,希望可以做个参考。

常见的排序算法有8种:


表格:

排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性
冒泡排序O(n2)O(n2)O(n)O(1)稳定
直接选择排序O(n2)O(n2)O(n2)O(1)不稳定
直接插入排序O(n2)O(n2)O(n)O(1)稳定
希尔排序O(nlog2n)O(n2)O(n)O(1)不稳定
堆排序O(nlog2n)O(nlog2n)O(nlog2n)O(1)不稳定
快速排序O(nlog2n)O(n2)O(nlog2n)O(nlog2n)不稳定
归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(n)稳定
基数排序O(d(n+r))O(d(n+r))O(d(n+r))O(n+r)稳定

图片为转载,如侵删:

这里写图片描述


  • 冒泡排序选择排序用的不是很多,因为时间复杂度高,但是因为常数项少,小规模排序执行时间不一定比其他排序长。

  • 插入排序 虽然时间复杂度高,但是因为常数项比较小,通常用在大致排序完成后最后的调整阶段。比如Java中Array.sort()的底层实现便用了二分插入排序(长度小于32的Tim sort算法)。

  • 快速排序归并排序是最常用的。堆排序的思想比较重要,涉及到优先队列的问题。

  • 基数排序 能够解决一些特定的问题。


下面排序都会用到的辅助代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
//交换位置 public static void swap(int[] arr, int i, int j) { /* 不能用于浮点数和交换自身 arr[i] = arr[i] ^ arr[j]; arr[j] = arr[i] ^ arr[j]; arr[i] = arr[i] ^ arr[j]; */ int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }

1. Bubble Sort(冒泡排序)

过程:
1. 从0位置开始,比较相邻两个数的大小,如果后面的数小于前面,则交换位置。
2. 遍历一遍下来,最后一个数为整个数组中的最大值。
3. 把最后一个数排除,继续比较剩下的数组。
4. 总共比较次数为N*N,时间复杂度为O(n²)。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Bubble Sort public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = arr.length - 1; i > 0; i--) { for (int j = 0; j < i; j++) { if (arr[j] > arr[j + 1]) { swap(arr, j, j + 1); } } } }

2. Selection Sort (选择排序)

过程:

  1. 遍历一遍,找到整个数组中最小的数,与位置0的数交换位置。
  2. 从1位置开始,继续遍历,找到最小的数,与1位置交换。以此类推。
  3. 冒泡排序,复杂度为O(n²)。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Selection Sort public static void selectionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 0; i < arr.length - 1; i++) { int minIndex = i; for (int j = i + 1; j < arr.length; j++) { minIndex = arr[j] < arr[minIndex] ? j : minIndex; } swap(arr, i, minIndex); } }

3. Insertion Sort(插入排序)

过程:

  1. 1位置开始,比较与前面数的大小,如果小于前面的数,则交换位置,直到不再小于停止。
  2. 接着从2位置开始,重复这个过程。直到最后位置为止。
  3. 时间复杂度取决于数组的排序情况,当数组基本有序时候,复杂度很低,接近O(n)。当数组完全无序时,每个数都要经过多次移动,复杂度趋近于O(n²)。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
//Insertion Sort public static void insertionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 1; i < arr.length; i++) { for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { swap(arr, j, j + 1); } } }

4. Shell Sort(希尔排序)

过程:
1. 过程类似于插入排序,算是插入排序的一种优化。
2. 首先,需要确定一个步长k,根据步长,把数组分为N/k部分,每一部分单独排序。
3. 把步长缩短,继续排序,直到步长为1。
4. 通过步长,减少了数组需要移动的次数,从而降低了复杂度。
5. 所以复杂度的高低完全取决于步长的好坏,是一种特别不稳定的算法,也是一种实现简单分析困难的算法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//维基上找的,没自己写 # Sort an array a[0...n-1]. gaps = [701, 301, 132, 57, 23, 10, 4, 1] # Start with the largest gap and work down to a gap of 1 foreach (gap in gaps) { # Do a gapped insertion sort for this gap size. # The first gap elements a[0..gap-1] are already in gapped order # keep adding one more element until the entire array is gap sorted for (i = gap; i < n; i += 1) { # add a[i] to the elements that have been gap sorted # save a[i] in temp and make a hole at position i temp = a[i] # shift earlier gap-sorted elements up until the correct location for a[i] is found for (j = i; j >= gap and a[j - gap] > temp; j -= gap) { a[j] = a[j - gap] } # put temp (the original a[i]) in its correct location a[j] = temp } }

5. Quick Sort(快速排序)

需要重点掌握:

  • 快速排序是一种很重要也很常用的排序,也有一些很重要的应用,比如说BFPRT算法,荷兰国旗问题。
  • 快速排序如果每次都选到最大值,或者最小值,就会产生最坏的情况,使复杂度达到O(n²)级别。但是可以通过随机选择partition值,从数学期望上避免这种情况的发生。所以可以默认其复杂度为O(N * lg N)。
  • 一般默认快速排序是非稳定的。但是有论文级别的方法,可以使其实现稳定(0-1 stable sort)。看看就好。。。

过程:

  1. 随机选出一个partition值,把大于partition值的放在它右边,小于它的放在它左边。
  2. 从partition值的左右两边分割,调用自己,开始递归。
  3. 这里有一点优化,因为partition值在数组中可能不止一个,因此返回一个长度为2的数组,代表partition的左右边界,从边界两端进行递归,更加快速。
复制代码
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
public static void quickSort(int[] arr) { if ( arr == null || arr.length < 2) { return; } quickSort(arr, 0, arr.length-1); } public static void quickSort(int[] arr, int left, int right) { if ( left >= right ) { return; } //随机产生partition值,防止出现最坏情况 swap(arr, right, (int) (Math.random() * ( right - left + 1) )+ left ); //返回的数组p为partition的左右边界 int[] p = partition(arr, left, right); quickSort(arr, left, p[0]-1); quickSort(arr, p[1]+1, right); } public static int[] partition(int[] arr, int left, int right) { int less = left - 1, more = right; while ( left < more ) { if ( arr[left] < arr[right] ) { swap(arr, ++less, left++); } else if ( arr[left] > arr[right] ) { swap(arr, --more, left); } else { left++; } } swap(arr, more, right); return new int[] {less+1,more}; }

6. Merge Sort(归并排序)

需要重点掌握:

  • 归并排序的优势很明显,它是稳定排序。同时相对于快排,它占用较多的空间。
  • 递归的思想很重要,分治法的应用也很广泛,把大问题分解成小问题一步步解决。
  • 递归过程要掌握,递归过程一定要有一个终止条件。
  • 压栈的过程,空间的占用要理解。
  • 同样有论文级别的算法可以把归并排序的空间省下来,知道就好。ORZ。。。 默认空间复杂度 O(n)。

过程:

  1. 把数组分成两部分,分别比较大小,最后合并。
  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
public static void mergeSort(int[] arr) { if ( arr == null || arr.length < 2 ) { return; } mergeSort(arr, 0, arr.length - 1 ) ; } public static void mergeSort(int[] arr, int l, int r) { if (l == r) { return; } int mid = l + ((r - l) >> 1); mergeSort(arr, l, mid); mergeSort(arr, mid + 1, r); merge(arr, l, mid, r); } public static void merge(int[] arr, int l, int m, int r) { int[] help = new int[r - l + 1]; int i = 0; int p1 = l; int p2 = m + 1; while (p1 <= m && p2 <= r) { help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } while (p1 <= m) { help[i++] = arr[p1++]; } while (p2 <= r) { help[i++] = arr[p2++]; } for (i = 0; i < help.length; i++) { arr[l + i] = help[i]; } }

7. Heap Sort(堆排序)

重点:

  • 堆排序是一种复杂度很稳定的算法,没有最差或者最好,稳定的 n * lg N。
  • 堆排序可以拓展到优先队列的实现,在贪心算法中经常用到。

过程:

  1. 首先用数组构造一个大根堆。
  2. 把大根堆队顶和最后位置的元素交换位置,最后一个元素脱离大根堆,即数组长度减一。
  3. 队顶下沉,再次构造大根堆,重复这个过程,直至完全排序。
复制代码
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
public static void HeapSort(int[] arr) { if ( arr == null || arr.length < 2 ) { return; } for ( int i = 1; i < arr.length; i++ ) { insertHeap(arr, i); } int r = arr.length - 1; while(r > 0 ) { swap(arr, 0, r); heapify(arr, 0, r--); } } public static void insertHeap(int[] arr, int i) { while (arr[i] > arr[(i - 1) / 2]) { swap(arr, i, (i - 1) / 2); i = (i - 1) / 2; } } public static void heapify(int[] arr, int index, int size) { int left = index * 2 + 1; while ( left < size ) { int largest = left+1 < size && arr[left] < arr[left + 1] ? left+1 : left; if ( arr[index] > arr[largest] ) { return; } swap(arr, index, largest); index = largest; left = index * 2 + 1; } }

8. Radix Sort / Count Sort(基数排序 / 计数排序)

  • 桶排序(Bucket Sort)属于非比较排序,因此适用范围很窄,但是在特定问题上可以把时间复杂度做到 O(n)。
  • 桶排序只是一个概念,一种非比较排序的思想。基数排序和计数排序都算桶排序的一种落地(实现)。

计数排序(Count Sort)

适用范围:

  • 计数排序适用于明确范围的数字排序

过程:

  1. 找的数组最大值max,new出来max+1个桶。
  2. 遍历整个数组,把数组里的数全部扔到对应的桶里。
  3. 从第一个桶开始遍历所有桶,把遍历的值填充到原来的数组中。

其他:

  • 计算排序其实可以优化,但这个瓶颈会一直存在。即必须为一定范围内的数组集合。

代码:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// only for 0~200 value public static void bucketSort(int[] arr) { if (arr == null || arr.length < 2) { return; } int max = Integer.MIN_VALUE; for (int i = 0; i < arr.length; i++) { max = Math.max(max, arr[i]); } int[] bucket = new int[max + 1]; for (int i = 0; i < arr.length; i++) { bucket[arr[i]]++; } int i = 0; for (int j = 0; j < bucket.length; j++) { while (bucket[j]-- > 0) { arr[i++] = j; } } }

基数排序(Radix Sort)

过程:

  1. 首先需要理解一点:当个位排好顺序时,再对十位开始排序时,个位的相对位置不变。
  2. 什么叫相对位置?比如对23,26,58,93进行排序,个位排好后是23,93,26,58。
  3. 这时再对十位进行排序,23,26的相对位置是不会变的,排序结束为23,26,58,93。
  4. 这个思维过程有点像动态规划,把问题分为n个小步骤,每个下一步都会用到上一步的结果。

代码:

复制代码
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
// only for no-negative value public static void radixSort(int[] arr) { if (arr == null || arr.length < 2) { return; } radixSort(arr, 0, arr.length - 1, maxbits(arr)); } public static int maxbits(int[] arr) { int max = Integer.MIN_VALUE; for (int i = 0; i < arr.length; i++) { max = Math.max(max, arr[i]); } int res = 0; while (max != 0) { res++; max /= 10; } return res; } public static void radixSort(int[] arr, int begin, int end, int digit) { final int radix = 10; int i = 0, j = 0; int[] count = new int[radix]; int[] bucket = new int[end - begin + 1]; for (int d = 1; d <= digit; d++) { for (i = 0; i < radix; i++) { count[i] = 0; } for (i = begin; i <= end; i++) { j = getDigit(arr[i], d); count[j]++; } for (i = 1; i < radix; i++) { count[i] = count[i] + count[i - 1]; } for (i = end; i >= begin; i--) { j = getDigit(arr[i], d); bucket[count[j] - 1] = arr[i]; count[j]--; } for (i = begin, j = 0; i <= end; i++, j++) { arr[i] = bucket[j]; } } } public static int getDigit(int x, int d) { return ((x / ((int) Math.pow(10, d - 1))) % 10); }

最后

以上就是疯狂毛豆最近收集整理的关于1.常用排序算法总结的全部内容,更多相关1内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(95)

评论列表共有 0 条评论

立即
投稿
返回
顶部