排序算法分类
排序算法分为内排序和外排序。所有数据已经读入内存,排序过程中无需对磁盘进行读写称为内排序。内存中无法保存全部数据,需要用到外存的称为外排序。文章讲的排序都属于内排序,下面以从小到大排序举例进行描述。
内排序算法可以分为以下五类:
- 插入排序:直接插入排序、二分法插入排序,希尔排序
- 选择排序:简单选择排序、堆排序
- 交换排序:冒泡排序、快速排序
- 归并排序
- 基数排序
算法理解与实现
插入排序
算法思想:插入排序分为已排好序的部分,待插入排序的当前元素,要解决的问题即是寻找到合适位置进行插入。即每次将一个待排序的记录,按照顺序码大小插入到有序序列的合适位置,直到全部插入排序完成。
关键点:找到已经排好序的序列中合适插入位置。
直接插入排序
算法思想:
代码:
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
| package main.sort;
public class InsertSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void directInsertSort(int[] numbers) { int temp = numbers[0]; int j = 0;
for (int i = 0; i < numbers.length; i++) { temp = numbers[i]; for (j = i; j > 0 && temp < numbers[j-1]; j--) { numbers[j] = numbers[j-1]; } numbers[j] = temp; } }
public static void main(String[] args) { int[] numbers = new int[]{9, 5, -11, 3, 1, 9};
directInsertSort(numbers);
print(numbers); } }
|
二分法插入排序
算法思想:
同样地,二插入排序也分为已排好序的部分,待插入排序的元素temp,二分的目的是比较减少次数。
如果待插入元素temp大于已排好序的下标为mid的元素,表明应该插入mid右边,则更新left为mid + 1;否则,表明应该插入mid的左边,更新right为mid - 1.
代码:
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
| package main.sort;
public class InsertSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void binInsertSort(int[] numbers) { int temp = numbers[0]; int left = 0; int right = 0; int mid = 0;
for (int i = 0; i < numbers.length; i++) { temp = numbers[i]; left = 0; right = i - 1; while (left <= right) { mid = (left + right) / 2; if (temp < numbers[mid]) { right = mid - 1; } else { left = mid + 1; } } for (int j = i - 1; j >= left; j--) { numbers[j + 1] = numbers[j]; } numbers[left] = temp; } }
public static void main(String[] args) { int[] numbers = new int[]{9, 5, -11, 3, 1, 9};
binInsertSort(numbers);
print(numbers); } }
|
希尔排序
算法思想:
将待排序的序列分为若干个组分别进行直接插入排序,带整个序列的记录基本有序时,对全体序列进行直接插入排序。
具体来说,划分组依据步长increment;组(列)排序使用插入排序,只是相邻元素下标关系为一个步长increment;步长为1时,即直接插入排序。
代码:
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
| package main.sort;
public class InsertSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void shellSort(int[] numbers) { int temp = 0; int j = 0;
for (int increment = numbers.length / 2; increment > 0; increment /= 2) { for (int i = increment; i < numbers.length; i++) { temp = numbers[i]; for (j = i; j >= increment; j -= increment) { if (temp < numbers[j - increment]) { numbers[j] = numbers[j - increment]; } else { break; } } numbers[j] = temp; } } }
public static void main(String[] args) { int[] numbers = new int[]{9, 5, -11, 3, 1, 9};
shellSort(numbers);
print(numbers); } }
|
选择排序
算法思想:每趟从待排序记录序列中选择最小的元素放在已排序表的最前面,直至全部排完。
关键点:在待排序序列中找到最小的元素(下标)。
直接选择排序
算法思想:
每次找出未排序部分最小序列最小元素的下标,然后将其与第i个(第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
| package main.sort;
public class SelectSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void simpleSelectSort(int[] numbers) { int temp;
for (int i = 0; i < numbers.length - 1; i++) { int k = i; for (int j = i + 1; j < numbers.length; j++) { if (numbers[k] > numbers[j]) { k = j; } } temp = numbers[i]; numbers[i] = numbers[k]; numbers[k] = temp; } }
public static void main(String[] args) { int[] numbers = new int[]{-13, 5, -11, 11, 4, 9, 3, 1, -90, 12};
simpleSelectSort(numbers);
print(numbers); } }
|
堆排序
算法思想:
堆非官方定义:满足完全二叉树;大顶堆每个节点的都比其左右子节点大。
堆排序把要排序的序列看作一棵顺序存储的二叉树,调整存储顺序,使之成为一个堆。
堆排序分为两步:a.构建堆;b.根元素与最后一个元素作交换,剔除最后一个元素。
其中构建堆是大顶堆还是小顶堆决定升降序排列。
代码:
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
| package main.sort;
public class SelectSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void heapSort(int[] numbers) { for (int i = 0; i < numbers.length; i++) { buildMaxHeap(numbers, numbers.length - 1 - i); swap(numbers, 0, numbers.length - 1 - i); } }
public static void buildMaxHeap(int[] numbers, int lastIndex) { for (int i = (lastIndex - 1) / 2; i >= 0; i--) { int fatherIndex = i; int biggerIndex = 2 * fatherIndex + 1;
while (biggerIndex <= lastIndex) { if (biggerIndex < lastIndex) { if (numbers[biggerIndex] < numbers[biggerIndex + 1]) { biggerIndex++; } } if (numbers[fatherIndex] < numbers[biggerIndex]) { swap(numbers, fatherIndex, biggerIndex); fatherIndex = biggerIndex; } else { break; } } } }
public static void swap(int[] numbers, int i, int j) { int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; }
public static void main(String[] args) { int[] numbers = new int[]{-13, 5, -11, 11, 4, 9, 3, 1, -90, 12};
heapSort(numbers);
print(numbers); } }
|
交换排序
算法思想:两两比较,交换元素的排序算法
关键点:何时进行交换。
冒泡排序
算法思想:
对于待排序部分,自上而下的相邻元素两两比较,如果顺序与要求相反,则交换两个元素,则大的元素往下沉,轻的元素往上冒,冒泡就是这么的形象简单。
代码:
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
| package main.sort;
public class ExchangeSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void bubbleSort(int[] numbers) { int temp; for (int i = 0; i < numbers.length; i++) { for (int j = 0; j < numbers.length - 1 - i; j++) { if (numbers[j] > numbers[j + 1]) { temp = numbers[j]; numbers[j] = numbers[j + 1]; numbers[j + 1] = temp; } } } }
public static void main(String[] args) { int[] numbers = new int[]{90, 5, -11, 11, 4, 3, 3, 1, -90, 12};
bubbleSort(numbers);
print(numbers); } }
|
快速排序
算法思想:
划分方法partition选择一个基准元素,通过一趟扫描,找到基准元素应该在的位置,将待排序序列分为大于等于基准元素的部分,小于基准元素的部分。接下来分别对两部分进行同样的方式划分,直至排序完成。Hoarse划分和三路划分。
代码:
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 73 74 75 76 77 78 79 80 81 82 83 84 85
| package main.sort;
public class ExchangeSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void quickSort(int[] numbers, int low, int high) { if (low < high) { int pivotIndex = partition(numbers, low, high); quickSort(numbers, low, pivotIndex - 1); quickSort(numbers, pivotIndex + 1, high); } }
public static int partition(int[] numbers, int low, int high, int pivotIndex) { int pivotValue = numbers[pivotIndex]; int storeIndex = low;
swap(numbers, pivotIndex, high); for (int i = low; i < high; i++) if (numbers[i] <= pivotValue) { swap(numbers, i, storeIndex); ++storeIndex; } swap(numbers, storeIndex, high); return storeIndex; }
public static int partition(int[] numbers, int low, int hight) { int pivot = numbers[low];
while (low < hight) { while (low < hight && numbers[hight] >= pivot) { hight--; } numbers[low] = numbers[hight]; while (low < hight && numbers[low] < pivot) { low++; } numbers[hight] = numbers[low]; } numbers[low] = pivot; return low; }
public static void swap(int[] numbers, int i, int j) { int temp = numbers[i]; numbers[i] = numbers[j]; numbers[j] = temp; }
public static void quick(int[] numbers) { if (numbers.length > 0) { quickSort(numbers, 0, numbers.length - 1); } }
public static void main(String[] args) { int[] numbers = new int[]{90, 5, -11, 11, 4, 3, 3, 1, -90, 12};
quick(numbers);
print(numbers); } }
|
归并排序
算法思想:
将两个或两个以上的有序表合并成一个新的有序表。先将序列分为若干个子序列,这若干个子序列又是有序的,然后将其合并为一个整体有序序列。
这里划分直接以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 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
| package main.sort;
public class MergeSort { public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void mergeSort(int[] numbers, int low, int high) { if (low < high) { int mid = (low + high) / 2; mergeSort(numbers, low, mid); mergeSort(numbers, mid + 1, high); merge(numbers, low, mid, high); } }
public static void merge(int[] numbers, int originLow, int middle, int originHigh) { int[] tempNumbers = new int[originHigh - originLow + 1]; int low = originLow; int high = originHigh; int mid = middle + 1; int third = 0;
while (low <= middle && mid <= high) { if (numbers[low] <= numbers[mid]) { tempNumbers[third++] = numbers[low++]; } else { tempNumbers[third++] = numbers[mid++]; } } while (low <= middle) { tempNumbers[third++] = numbers[low++]; } while (mid <= high) { tempNumbers[third++] = numbers[mid++]; }
for (int i = 0; i < tempNumbers.length; i++) { numbers[originLow + i] = tempNumbers[i]; } }
public static void main(String[] args) { int[] numbers = new int[]{-13, 5, -11, 11, 4, 9, 3, 1, -90, 12};
mergeSort(numbers, 0, numbers.length - 1);
print(numbers); } }
|
基数排序
算法思想:
以前比较两个正整数的大小是这样的,如果一个数的位数比另一个多,则这个数就大。如果位数相同,那么按位数递减依次比较大小,哪个数在这一位上更大则停止比较,得到这个数较大的结果。而我们也可以从个位到高位进行比较。
那么怎么保存每次按照数的某一位比较得出的排序结果呢?数的每一位是由0-9中的一个构成的,如果按照某一位进行比较,最多会是,该位数都相等,那么共有n个数的数组会排成1列共n个,故需要用二维数组bucket[10][n]来存放排序结果。
[72 25 18 90 35 25 12 81 97 11] 十个数进行基数排序
按个位排序:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
90 |
11 |
72 |
|
|
25 |
|
97 |
18 |
|
|
81 |
12 |
|
|
35 |
|
|
|
|
|
|
|
|
|
25 |
|
|
|
|
按照从0到9元素顺序,桶内先进先出顺序,排序得到下列数组
[90 11 81 72 12 25 35 25 97 18]
按十位排序:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
|
11 |
25 |
35 |
|
|
|
72 |
81 |
90 |
|
12 |
25 |
|
|
|
|
|
|
97 |
|
18 |
|
|
|
|
|
|
|
|
按同样方法,排序得到下面有序序列
[11 12 18 25 25 72 81 90 97]
代码:
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
| package main.sort;
public class RadixSort {
public static void print(int[] numbers) { for (int i = 0; i < numbers.length; i++) { System.out.print(numbers[i] + " "); } System.out.println(); }
public static void radixSort(int[] numbers) { int n = 1; int k = 0; int len = numbers.length;
int max = numbers[0]; for (int i = 0; i < len; i++) { if (max < numbers[i]) { max = numbers[i]; } }
int times = 0; while (max != 0) { times++; max /= 10; }
int[][] bucket = new int[10][len]; int[] order = new int[10]; while (n <= times) { for (int num : numbers) { int digit = num / (int)Math.pow(10, n - 1) % 10; bucket[digit][order[digit]] = num; order[digit]++; }
for (int i = 0; i < len; i++) { if (order[i] != 0) { for (int j = 0; j < order[i]; j++) { numbers[k] = bucket[i][j]; k++; } } order[i] = 0; } n++; k = 0; } }
public static void main(String[] args) { int[] numbers = new int[]{5, 411, 11, 4, 9, 3, 1, 90, 12};
radixSort(numbers);
print(numbers); } }
|
总结
类别 |
排序算法 |
最坏时间复杂度 |
最好时间复杂度 |
平均时间复杂度 |
稳定性 |
插入 |
直接插入排序 |
O(n2) |
O(n) |
O(n2) |
稳定 |
插入 |
二分法插入排序 |
O(n2) |
O(log2n) |
O(n2) |
稳定 |
插入 |
希尔排序 |
O(n2) |
O(n) |
O(n1.3) |
不稳定 |
选择 |
简单选择排序 |
O(n2) |
O(n2) |
O(n2) |
不稳定 |
选择 |
堆排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
不稳定 |
交换 |
冒泡排序 |
O(n2) |
O(n2) |
O(n2) |
稳定 |
交换 |
快速排序 |
O(n2) |
O(nlog2n) |
O(nlog2n) |
不稳定 |
归并 |
二路归并排序 |
O(nlog2n) |
O(nlog2n) |
O(nlog2n) |
稳定 |
基数 |
基数排序 |
O(d(r+n)) |
O(d(n+rd)) |
O(rd+n) |
稳定 |
其中基数排序的r表示关键字基数,d表示长度,n表示关键字个数。
声明:本站所有文章均为原创或翻译,遵循署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名 (Tsukasa) 及原文地址