排序算法分为内排和外排,区别是是否需要外存(什么意思??算了,先不care),内排不需要,外排需要,这里学习的是内排(以下说的排序都指内排)!
排序分为5大类:
- 插入排序(直接插入排序、希尔排序)
- 选择排序(简单选择排序、堆排序)
- 交换排序(冒泡排序、快速排序)
- 归并排序
- 基数排序
插入排序 - 直接插入排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)将第一个数和第二个数排序,然后构成一个有序序列。
(2)将第三个数插入进去,构成一个新的有序序列。
(3)对第四个数、第五个数……直到最后一个数,重复第二步。
/**
* 直接插入排序
* @param arrays
*/
private static void sort1(int[] arrays) {
int length = arrays.length;
for (int i = 1; i < length; i++) {
// 待插入值
int temp = arrays[i];
// 从待插入元素的前一个开始比较
int j = i - 1;
// 如果待插入值大于arrays[j]的值
while (j >= 0 && arrays[j] > temp) {
// 那么arrays[j]的值往后挪一位,赋值给arrays[j + 1],此时arrays[j]还是原来arrays[j]的值,相当于位置j被占用了
arrays[j + 1] = arrays[j];
// 递减
j --;
}
// 此时待插入的值要么在数组第一个位置,要么前面的数值比它小,把占用的位置赋值为待插入值
arrays[j + 1] = temp;
System.out.print("第" + i + "次排序:");
print(arrays);
}
}
打印结果:
第1次排序:12 35 (已排好序) 22 (等待插入值) 8 6 26 18 5 28 14
第2次排序:12 22 35 (已排好序) 8 (等待插入值) 6 26 18 5 28 14
第3次排序:8 12 22 35 (已排好序) 6 (等待插入值) 26 18 5 28 14
第4次排序:6 8 12 22 35 (已排好序) 26 (等待插入值) 18 5 28 14
第5次排序:6 8 12 22 26 35 (已排好序) 18 (等待插入值) 5 28 14
第6次排序:6 8 12 18 22 26 35 (已排好序) 5 (等待插入值) 28 14
第7次排序:5 6 8 12 18 22 26 35 (已排好序) 28 (等待插入值) 14
第8次排序:5 6 8 12 18 22 26 28 35 (已排好序) 14 (等待插入值)
第9次排序:5 6 8 12 14 18 22 26 28 35 (已排好序)
插入排序 - 希尔排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)数组的个数为length,取step=length/2,将下标差值为step的数分为一组,构成有序序列。
示例数组的个数length为10,那么step=5(5组),则[12, 26]为一组,[35, 18]为一组,如此类推,然后利用直接插入排序为有序序列
(2)再取step=step/2 ,将下标差值为step的数分为一组,构成有序序列。
此时step=2, 将(1)排好序的数组分为2组,再利用直接插入排序为有序序列
(3)重复第二步,直到step=1执行简单插入排序。
/**
* 希尔排序
* @param arrays
*/
private static void sort2(int[] arrays) {
// 数组长度
int length = arrays.length;
// 差值,也就是组数
int step = length / 2;
// 如果差值不为0
while (step != 0) {
// 每组数据使用直接插入算法进行排序
for (int i = 0; i < step; i++) {
// 直接插入算法进行排序,每组数据的下标差值都是step
for (int j = i + step; j < length; j = j + step) {
// 待插入值
int temp = arrays[j];
// 从待插入元素的前一个开始比较
int k = j - step;
// 如果待插入值大于arrays[k]的值
while (k >= 0 && arrays[k] > temp) {
// 那么arrays[k]的值往后挪一位,赋值给arrays[k + step],此时arrays[k]还是原来arrays[k]的值,相当于位置k被占用了
arrays[k + step] = arrays[k];
// 按step递减
k -= step;
}
// 此时待插入的值要么在数组第i个位置(小数组的首位),要么前面的数值比它小,把占用的位置赋值为待插入值
arrays[k + step] = temp;
System.out.print("有" + step + "个数组,第" + (i + 1) + "个小数组第" + (j / step) +"次排序:");
print(arrays);
}
}
// 差值每次除2,一直到差值为1
step = step / 2;
}
}
打印结果:
12(A), 35, 22, 8, 6, 26(A), 18, 5, 28, 14 (小数组使用直接插入算法排序)
有5个数组,第1个小数组第1次排序:12(A) 35 22 8 6 26(A) 18 5 28 14
有5个数组,第2个小数组第1次排序:12 18 22 8 6 26 35 5 28 14
有5个数组,第3个小数组第1次排序:12 18 5 8 6 26 35 22 28 14
有5个数组,第4个小数组第1次排序:12 18 5 8 6 26 35 22 28 14
有5个数组,第5个小数组第1次排序:12 18 5 8 6 26 35 22 28 14
12(A) 18 5(A) 8 6(A) 26 35(A) 22 28(A) 14 (小数组使用直接插入算法排序)
有2个数组,第1个小数组第1次排序:5(A) 18 12(A) 8 6(A) 26 35(A) 22 28(A) 14 (A组前2个数据排序结果)
有2个数组,第1个小数组第2次排序:5(A) 18 6(A) 8 12(A) 26 35(A) 22 28(A) 14 (A组前3个数据排序结果)
有2个数组,第1个小数组第3次排序:5(A) 18 6(A) 8 12(A) 26 35(A) 22 28(A) 14 (A组前4个数据排序结果)
有2个数组,第1个小数组第4次排序:5(A) 18 6(A) 8 12(A) 26 28(A) 22 35(A) 14 (A组5个数据排序结果)
有2个数组,第2个小数组第1次排序:5 8 6 18 12 26 28 22 35 14
有2个数组,第2个小数组第2次排序:5 8 6 18 12 26 28 22 35 14
有2个数组,第2个小数组第3次排序:5 8 6 18 12 22 28 26 35 14
有2个数组,第2个小数组第4次排序:5 8 6 14 12 18 28 22 35 26
有1个数组,第1个小数组第1次排序:5 8 6 14 12 18 28 22 35 26
有1个数组,第1个小数组第2次排序:5 6 8 14 12 18 28 22 35 26
有1个数组,第1个小数组第3次排序:5 6 8 14 12 18 28 22 35 26
有1个数组,第1个小数组第4次排序:5 6 8 12 14 18 28 22 35 26
有1个数组,第1个小数组第5次排序:5 6 8 12 14 18 28 22 35 26
有1个数组,第1个小数组第6次排序:5 6 8 12 14 18 28 22 35 26
有1个数组,第1个小数组第7次排序:5 6 8 12 14 18 22 28 35 26
有1个数组,第1个小数组第8次排序:5 6 8 12 14 18 22 28 35 26
有1个数组,第1个小数组第9次排序:5 6 8 12 14 18 22 26 28 35
选择排序 - 简单选择排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)遍历整个序列,将最小的数放在最前面。
(2)遍历剩下的序列,将最小的数放在最前面。
(3)重复第二步,直到只剩下一个数。
/**
* 简单选择排序
* @param arrays
*/
private static void sort3(int[] arrays) {
int length = arrays.length;
for (int i = 0; i < length; i++) {
int temp = arrays[i];
int position = i;
// 选出剩下数组最小的数及其下标
for (int j = i + 1; j < length; j++) {
if (temp > arrays[j]) {
temp = arrays[j];
position = j;
}
}
// 交换位置
arrays[position] = arrays[i];
arrays[i] = temp;
System.out.print("第" + (i + 1) + "次排序:");
print(arrays);
}
}
打印结果:
取最小的放到前面
12 35 22 8 6 26 18 5 28 14 (5最小)
第1次排序:5 35 22 8 6 26 18 12 28 14 (6最小)
第2次排序:5 6 22 8 35 26 18 12 28 14 (8最小)
第3次排序:5 6 8 22 35 26 18 12 28 14
第4次排序:5 6 8 12 35 26 18 22 28 14
第5次排序:5 6 8 12 14 26 18 22 28 35
第6次排序:5 6 8 12 14 18 26 22 28 35
第7次排序:5 6 8 12 14 18 22 26 28 35
第8次排序:5 6 8 12 14 18 22 26 28 35
第9次排序:5 6 8 12 14 18 22 26 28 35
第10次排序:5 6 8 12 14 18 22 26 28 35
选择排序 - 堆排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)将序列构建成最大堆(最大堆的特性是,某个节点的值最多和其父节点的值一样大。这样,堆中的最大元素存放在根节点中;并且,在以某一个节点为根的子树中,各节点的值都不大于该子树根节点的值)。
(2)将根节点与最后一个节点交换,然后断开最后一个节点。
(3)重复第一、二步,直到所有节点断开。
构建堆的图:
调整成最大堆的图:
/**
* 堆排序
* @param arrays
*/
private static void heapSort(int[] arrays) {
int length = arrays.length;
for (int i = length - 1; i > 0; i--) {
// 建立最大堆
buildMaxHeap(arrays, i);
// 此时,数组最大的数值是第一个,交换位置,把最大的放到剩下数组的最后,不参与下一次的排序
swap(arrays, i, 0);
System.out.print("第" + (length - i) + "次排序:");
print(arrays);
}
}
/**
* 建立最大堆
* @param arrays
* @param lastIndex
*/
private static void buildMaxHeap(int[] arrays, int lastIndex) {
// i 为父节点
for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
// i * 2 + 1 为左子节点,暂时设为较大节点
int biggerIndex = i * 2 + 1;
// 如果左子节点存在
if (biggerIndex <= lastIndex) {
// biggerIndex + 1 为右节点,如果右节点存在
if (biggerIndex < lastIndex) {
// 如果左节点比右节点小
if (arrays[biggerIndex] < arrays[biggerIndex + 1]) {
// 右节点为较大节点
biggerIndex += 1;
}
}
// 如果父节点比较大的子节点小
if (arrays[i] < arrays[biggerIndex]) {
// 交换位置
System.out.println(i + "<->" + biggerIndex);
swap(arrays, i, biggerIndex);
}
}
}
}
/**
* 交换
* @param arrays
* @param i
* @param j
*/
private static void swap(int[] arrays, int i, int j) {
if (i == j) {
return;
}
int temp = arrays[i];
arrays[i] = arrays[j];
arrays[j] = temp;
}
打印结果:
4<->9
3<->8
2<->5
0<->1
第1次排序:6 12 26 28 14 22 18 5 8 35
1<->3
0<->1
第2次排序:8 6 26 12 14 22 18 5 28 35
1<->4
0<->2
第3次排序:5 14 8 12 6 22 18 26 28 35
2<->5
0<->2
第4次排序:18 14 5 12 6 8 22 26 28 35
2<->5
第5次排序:5 14 8 12 6 18 22 26 28 35
0<->1
第6次排序:6 5 8 12 14 18 22 26 28 35
1<->3
0<->1
第7次排序:5 6 8 12 14 18 22 26 28 35
0<->2
第8次排序:5 6 8 12 14 18 22 26 28 35
0<->1
第9次排序:5 6 8 12 14 18 22 26 28 35
交换排序 - 冒泡排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)将序列中所有元素两两比较,将最大的放在最后面。
(2)将剩余序列中所有元素两两比较,将最大的放在最后面。
(3)重复第二步,直到只剩下一个数。
/**
* 下冒泡排序
* @param arrays
*/
private static void bubbleSort(int[] arrays) {
int length = arrays.length;
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - i - 1; j ++) {
if (arrays[j] > arrays[j + 1]) {
swap(arrays, j, j + 1);
}
}
System.out.print("第" + (i + 1) + "次排序:");
print(arrays);
}
}
打印结果:
第1次排序:12 22 8 6 26 18 5 28 14 35
第2次排序:12 8 6 22 18 5 26 14 28 35
第3次排序:8 6 12 18 5 22 14 26 28 35
第4次排序:6 8 12 5 18 14 22 26 28 35
第5次排序:6 8 5 12 14 18 22 26 28 35
第6次排序:6 5 8 12 14 18 22 26 28 35
第7次排序:5 6 8 12 14 18 22 26 28 35
第8次排序:5 6 8 12 14 18 22 26 28 35
第9次排序:5 6 8 12 14 18 22 26 28 35
第10次排序:5 6 8 12 14 18 22 26 28 35
/**
* 上冒泡排序
* @param arrays
*/
private static void bubbleSort(int[] arrays) {
int length = arrays.length;
for (int i = 0; i < length; i++) {
for (int j = length - 1; j > i; j--) {
if (arrays[j] < arrays[j - 1]) {
swap(arrays, j, j - 1);
}
}
System.out.print("第" + (i + 1) + "次排序:");
print(arrays);
}
}
打印结果:
第1次排序:5 12 35 22 8 6 26 18 14 28
第2次排序:5 6 12 35 22 8 14 26 18 28
第3次排序:5 6 8 12 35 22 14 18 26 28
第4次排序:5 6 8 12 14 35 22 18 26 28
第5次排序:5 6 8 12 14 18 35 22 26 28
第6次排序:5 6 8 12 14 18 22 35 26 28
第7次排序:5 6 8 12 14 18 22 26 35 28
第8次排序:5 6 8 12 14 18 22 26 28 35
第9次排序:5 6 8 12 14 18 22 26 28 35
第10次排序:5 6 8 12 14 18 22 26 28 35
交换排序 - 快速排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)数组中选择一个基准点,一般为数组第一个数。
(2)设两个指示标志(low指向起始位置,high指向末尾位置),首先从high向前开始递减,如果发现有元素比该基准点的值小,就交换low和high位置的值;然后从low向后开始递增,发现有元素大于基准点的值,就交换low和high位置的值。
(3)重复第二步,直到low等于high,设相等值i。
(4)然后后把数组分为2数组,arrays[low, i-1]和arrays[i+1, high],继续(1)-(4)的步骤。
(1)-(3)步骤图:
/**
* 快速排序
* @param arrays
*/
private static void quickSort(int[] arrays) {
quickSort(arrays, 0, arrays.length - 1);
}
/**
* 快速排序
* @param arrays
* @param low
* @param high
*/
private static void quickSort(int[] arrays, int low, int high) {
if (low >= high) {
return;
}
int base = arrays[low];
int i = low, j = high;
while (i != j) {
while (arrays[j] > base && i != j) {
j --;
}
while (arrays[i] < base && i != j) {
i ++;
}
swap(arrays, i, j);
}
System.out.print("i=" + i + ":");
print(arrays);
quickSort(arrays, low, i - 1);
quickSort(arrays, i + 1, high);
}
打印结果:
i=3:5 6 8 12 22 26 18 35 28 14
i=0:5 6 8 12 22 26 18 35 28 14
i=1:5 6 8 12 22 26 18 35 28 14
i=6:5 6 8 12 14 18 22 35 28 26
i=4:5 6 8 12 14 18 22 35 28 26
i=9:5 6 8 12 14 18 22 26 28 35
i=7:5 6 8 12 14 18 22 26 28 35
归并排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)分解,就是把整个集合的元素一直除2化分,一直化为到没有两个元素开始合并
(2)合并的时候,两个指标i,j,把值比较小的放入temp数组中,然后放入那个值的那一边的指标自加,继续比较。一直把值合部排完合并为一个数组,然后把temp数组复制回到原来数组就可以进入下一个递归。
图解:
因为合并的过程比较复杂,所以详细图解合并过程:
/**
* 归并排序
* @param arrays
*/
private static void mergeSort(int[] arrays) {
mergeSort(arrays, 0, arrays.length - 1);
}
/**
* 归并排序
* @param arrays
* @param start
* @param end
*/
private static void mergeSort(int[] arrays, int start, int end) {
if (start >= end) {
return;
}
int middle = (start + end) / 2;
// 分解 middle 前面的数组(包括middle)
mergeSort(arrays, start, middle);
// 分解 middle 后面的数组
mergeSort(arrays, middle + 1, end);
// 合并
merge(arrays, start, middle, end);
System.out.println(start + " -> " + end);
print(arrays);
}
/**
* 合并
* @param arrays
* @param start
* @param middle
* @param end
*/
private static void merge(int[] arrays, int start, int middle, int end) {
// 临时数组的个数 = 2个数组的个数和
int[] tempArrays = new int[end - start + 1];
// 分解的左边数组第一个
int i = start;
// 分解的右边数组第一个
int j = middle + 1;
// 从0开始赋值
int k = 0;
// 左右数组进行比较,按顺序依次放入临时数组,从而组成有序序列
while (i <= middle && j <= end) {
// 比较 左边数组第i个数值和右边数组第j个数值
if (arrays[i] < arrays[j]) {
// 如果 左边数组第i个数值 < 右边数组第j个数值,左边数组第i个数值放入临时数组,i+1,j不变,继续比较
tempArrays[k] = arrays[i];
i ++;
} else {
// 如果 左边数组第i个数值 > 右边数组第j个数值,右边数组第j个数值放入临时数组,j+1,i不变,继续比较
tempArrays[k] = arrays[j];
j ++;
}
// 每次k+1
k ++;
}
// 把左边剩下数组的放入临时数组
// 如左边数组最后2个数值都比右边的数组所有数值大,上面循环因j=end+1跳出循环,
// 因为每一边的数组都是已经排好序的了,所以直接放入临时文件即可
while (i <= middle) {
tempArrays[k] = arrays[i];
k ++;
i ++;
}
// 把右边剩下数组的放入临时数组
// 如右边数组最后2个数值都比左边的数组所有数值大,上面循环因i=middle+1跳出循环,
// 因为每一边的数组都是已经排好序的了,所以直接放入临时文件即可
while (j <= end) {
tempArrays[k] = arrays[j];
k ++;
j ++;
}
// 临时数组的值覆盖原来数组对应的值
for (int m = 0; m < tempArrays.length; m++) {
arrays[start + m] = tempArrays[m];
}
}
打印结果:
0 -> 1
12 35 22 8 6 26 18 5 28 14
0 -> 2
12 22 35 8 6 26 18 5 28 14
3 -> 4
12 22 35 6 8 26 18 5 28 14
0 -> 4
6 8 12 22 35 26 18 5 28 14
5 -> 6
6 8 12 22 35 18 26 5 28 14
5 -> 7
6 8 12 22 35 5 18 26 28 14
8 -> 9
6 8 12 22 35 5 18 26 14 28
5 -> 9
6 8 12 22 35 5 14 18 26 28
0 -> 9
5 6 8 12 14 18 22 26 28 35
基数排序
示例数组:
int[] arrays = {12, 35, 22, 8, 6, 26, 18, 5, 28, 14};
(1)将所有的数的个位数取出,按照个位数进行排序,构成一个序列。
(2)将新构成的所有的数的十位数取出,按照十位数进行排序,构成一个序列。
(3)如此类推,直至最大数的最高位。
解释:(如示例数组)
个位排序:
0:
1:
2:12, 22
3:
4:14
5:35, 5
6:6, 26
7:
8:8, 18, 28
9:
个位数排序结果:{12, 22, 14, 35, 5, 6, 26, 8, 18, 28};
十位数排序:
0:5, 6, 8
1:12, 14, 18
2:22, 26, 28
3:35
4:
5:
6:
7:
8:
9:
十位数排序结果:{5, 6, 8, 12, 14, 18, 22, 26, 28, 35};
/**
* 基数排序
* @param arrays
*/
private static void radixSort(int[] arrays) {
int length = arrays.length;
// 取出数组中最大数
int max = arrays[0];
for (int i = 1; i < length; i++) {
if (arrays[i] > max) {
max = arrays[i];
}
}
// 计算最大数的位数
int count = 0;
while (max != 0) {
max = max / 10;
count ++;
}
// 临时保存数值的二维数组
int[][] tempArrays = new int[10][length];
// 记录有多少个同时在tempArrays[i]中
int[] recordArrays = new int[10];
// 从最低位开始
for (int i = 0; i < count; i++) {
// 每个数值取出第i位数据保存到tempArrays数组中
// 如i=0,123的个位数是3,此时recordArrays[3]=0,所以保存在tempArrays[3][0],recordArrays[3]+1=1
// 再来一个数为223,个位数还是3,此时recordArrays[3]=1,所以保存在tempArrays[3][1],recordArrays[3]+1=2
for (int j = 0; j < length; j++) {
// 取出第i位数值
int number = arrays[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
// 保存在二维数组对应的位置
tempArrays[number][recordArrays[number]] = arrays[j];
// 对应的记录数+1
recordArrays[number] ++;
}
// 把临时保存的数值排列到arrays数组中,重组
int index = 0;
// 从0开始排序
for (int m = 0; m < 10; m++) {
if (recordArrays[m] == 0) continue;
// 如recordArrays[3]=2,tempArrays[3][0]和tempArrays[3][1]必定有值,赋值给arrays数组
for (int k = 0; k < recordArrays[m]; k++) {
arrays[index] = tempArrays[m][k];
// 从0递增,最后肯定是length-1
index ++;
}
// 清除记录数,下次循环用到
recordArrays[m] = 0;
}
System.out.print("第" + (i + 1) + "位数排序:");
print(arrays);
}
}
打印结果:
第1位数排序:12 22 14 35 5 6 26 8 18 28
第2位数排序:5 6 8 12 14 18 22 26 28 35
时间、空间复杂度、稳定程度表
最后附上各种排序时间、空间复杂度、稳定程度表
排序方法 | 最差时间分析 | 平均时间复杂度 | 稳定度 | 空间复杂度 |
---|---|---|---|---|
直接插入排序 | O(n²) | O(n²) | 稳定 | O(1) |
希尔排序 | O(n²) | O(n¹³) | 不稳定 | O(1) |
选择排序 | O(n²) | O(n²) | 不稳定 | O(1) |
堆排序 | O(n*log₂n) | O(n*log₂n) | 不稳定 | O(1) |
冒泡排序 | O(n²) | O(n²) | 稳定 | O(1) |
快速排序 | O(n²) | O(n*log₂n) | 不稳定 | O(log₂n) |
归并排序 | O(n*log₂n) | O(n*log₂n) | 稳定 | 不一定 |
基数排序 | O(d(r+n)) | O(d(r+n)) | 稳定 | O(rd+n) |
注:基数排序的复杂度中,r代表关键字的基数,d代表长度,r代表关键字的个数
比较
快速排序是所有排序算法中实际性能最好的,然而快速排序在最坏情况下的时间性能不如堆排序和归并排序。这一点可以通过对快速排序进行改进来避免,一种通过随机选择枢轴元素的随机快速排序,可以使得出现最坏情况出现的几率非常小,在实际的运用中可以认为不存在。在堆排序和归并排序的比较中,当n 较大时,归并排序所需时间较少,然而它需要较多的辅助存储空间。
小结
如果要求排序速度快的话,选择快速排序;
如果只需要取最大或最小的几个,选择选择排序和堆排序;
如果要求使用内存小和速度快的话,选择归并排序;
如果把一个有序序列的数组和无序序列数组合并成有序数组,选择插入排序和希尔排序;
如果待排序的数组数量很大的话,而且内存充足,选择基数排序;