选择排序
思考过程
给定一个数组,[0,1,…..n-1]
- n个数选一个最小值,放在0位置
- n-1个数选一个n-1个数中的最小值,放在1位置
……
直到整个数组有序。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Sort01_SelectSort { public static void SelectSort(int[] arr){ if(arr == null || arr.length < 2) return;
for (int minValueIndex = 0; minValueIndex < arr.length;minValueIndex++) { for (int j = minValueIndex + 1 ; j < arr.length ; j++) { if (arr[minValueIndex] > arr[j]) { swap(arr,minValueIndex,j); } } } }
public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
时间复杂度分析
每遍历一轮,找到最大或者最小值的下标,和第一个数交换,结束一轮运算,乱序数组减一,周而复始,直到整个数组有序。
分析一下数组选择排序时间复杂度:
第一次遍历:N*(取数+比较) + 1次交换
… (N-1)(取数+比较) + 1次交换
..
.
= 2 * (N + (N-1) + … + 1) + N
= 2*(aN^2 + bN + c) +N
= 2aN^2 + (2b+1)N + 2c
= aN^2 + bN + c
= N^2
忽略低阶项和常数得到时间复杂度
===> O(n^2)
时间复杂度的意义: a算法:3_N^3 + 2_N^2 + 1 b算法:100w_N^2 + 500w_N + 1000w 当N→无穷大时,常数项就可以忽略,复杂度只与数据量有关。
冒泡排序
思考过程
- n个数相邻两两比较,最大值冒泡到最右边
- n-1个数相邻两两比较,最大值冒泡到n-1个数的最右边
……
直到整个数组有序
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Sort02_BubbleSort { public static void BubbleSort(int[] arr) { if (arr == null || arr.length < 2) return;
for (int end = arr.length -1 ; end > 0; end--) { for (int j = 0; j < end ; j++) { if (arr[j] > arr[j+1]) { swap(arr,j,j+1); } } } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
时间复杂度分析
每遍历一轮,相邻的两个数比较交换,谁大谁在后面,直到找到最大或最小值,结束一轮运算,乱序数组减一,周而复始,直到整个数组有序。
分析:
第一次遍历 : (N-1)*(比较+交换)
…
..
.
冒泡排序和选择排序对所有不同情况的数据样本,复杂度的表现都是一样的,O(N^2).
插入排序
一个数组,先让0-0范围有序,再让0-1范围内有序,周而复始,直到整个数组有序。 先局部有序,再逐一插入单个数。,类比打牌,把牌一个一个插入到前面有序的牌中。
- 指针停在0位置,[0]有序
- 指针停在1位置, 与前面的数依次比较,小的放前面,直到[0,1]有序
- 指针停在2位置, 与前面的数依次比较,小的放前面,指针左移一位,直到[0,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
| public class Sort03_InsertSort {
public static void insertSort(int[] arr) { if (arr == null || arr.length < 2) return;
for (int end = 1; end < arr.length; end++) { int maxIndex = end; while (maxIndex - 1 >= 0 && arr[maxIndex -1] > arr[maxIndex]) { swap(arr,maxIndex - 1, maxIndex); maxIndex--; } } }
public static void insertSort2(int[] arr) { if (arr == null || arr.length < 2) return;
for (int end = 1; end < arr.length; end++) { for (int pre = end - 1; pre >= 0 && arr[pre] > arr[pre + 1]; pre--) { swap(arr, pre, pre + 1); } } }
public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|
归并排序
建立的归并操作的基础上,稳定的排序算法,时间复杂度O(nlogn) . 先将每个子序列有序,再使子序列段间有序,最后合成一个有序序列。 又叫做二路归并。
*
递归实现思路
思考过程:
- 保证左边数组有序
- 保证右边数组有序
- 合并左右两边数组使整个数组有序。
非递归实现思路
相比递归实现,非递归使用变化的步长(step)来代表拆分的子序列;
- 步长为1时: [1][2][4][3][5][0] 交替 ,每个序列中只有一个数
- 步长为2时: [12][34][05] 交替 , 第1步时的左右序列已合并变有序, 每个序列中有两个数
- 步长为4时: [1234][05] ,第2步时的左右序列已合并变为左序列且有序.
- 步长为8,超过数组本身长度,直接合并为一个序列,排序完毕。
时间复杂度估算: 步长逼近N,需要O(logN)次,每一次所有数需要合并一次,所以是O(n) ==> 所以复杂度为O(nlogn)
代码实现
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| public class Sort04_MergeSort {
public static void mergeSortByRecursive(int[] arr) { if (arr == null || arr.length < 2) return; process(arr,0,arr.length - 1); }
public static void process(int[] arr,int L , int R) { if (L == R) return; int mid = L + ((R - L) >> 1); process(arr, L , mid); process(arr, mid + 1, R); merge(arr,L,mid,R); }
private 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 (int j = 0; j < help.length; j++) { arr[L + i] = help[i]; } }
public static void mergeSortNotRecursive(int[] arr) { if (arr == null || arr.length < 2) { return; } int step = 1; int N = arr.length; while (step < N) { int L = 0; while (L < N) { int M = 0; if (N - L >= step) { M = L + step - 1; } else { M = N - 1; } if (M == N - 1) { break; } int R = 0; if (N - 1 - M >= step) { R = M + step; } else { R = N - 1; } merge(arr, L, M, R); if (R == N - 1) { break; } else { L = R + 1; } } if (step > N / 2) { break; } step *= 2; }
}
}
|
快速排序
D & C 思想
- 基线条件:最简单的数组: 空数组或只有一个元素的数组,不需要排序直接返回。
- 缩小问题规模:选择基准值,把整个数组分解 [小于基准值 , 基准值, 大于基准值]
荷兰国旗问题
现在有若干个红、白、蓝三种颜色的球随机排列成一条直线。现在我们的任务是把这些球按照红、白、蓝排序。
红 = 0 ,白 = 1 ,蓝 = 2
[0,1,2,1,0,2,1,0,2,1] => 变成 [0,0,0,1,1,1,1,2,2,2]
简单场景:
一个数组[x,x,x,p], 小于等于p的放左边,大于p的放右边
数组分为[Less区,待排序区,P], 初始化时,Less区大小为0, 指针从左往右移动,指向的数为当前数
- 当前数 <= p(基准值),当前数和Less区外的下一个数做交换,Less区右扩,当前数跳下一个,越界了停止。
- 当前数 > P(基准值),当前数直接跳下一个,越界了停止。
进阶场景:
一个数组[x,x,x,x,x,p],小于p的放左边,等于p的放中间,大于p的放右边。
数组要分为三个区域,[Less区,待排序区,(More区,p)].
- 当前数< 基准值, 当前数与Less区的下个数做交换,Less区右扩, 当前数跳下一个。
- 当前数> 基准值, 当前区与More区的前一个数做交换,More区向左扩,当前数不动(交换过还没看)。
- 当前数 = 基准值,直接跳下一个。
- 最后,基准值和More区的第一个数换,排序结束
快排的递归实现
快排最小问题规模等价于荷兰国旗问题:一串数字,选一个数为基准值,把小于基准值的数放在它的左边,大于基准值的数放在它的右边。 要求时间复杂度为O(n),并且只能使用有限几个变量
a. 先按单个数分层,返回单个数的左右边界.
b. 递归函数
非递归版本思路
用Stack栈做任务,每一个最小任务就是一个荷兰国旗问题,还是使用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 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
| import java.util.Stack; public class Sort05_QuickSort {
public static void splitNum01(int[] arr) { int lessEqualR = -1; int index = 0; int mostR = arr.length - 1; while(index < arr.length) { if(arr[index] <= arr[mostR]){ swap(arr,lessEqualR + 1 , index); lessEqualR++; index++; }else { index++; } } }
public static void splitNum02(int[] arr) { int lessR = -1; int moreL = arr.length - 1; int index = 0; int poivt = arr.length - 1; while(index < moreL) { if(arr[index] < arr[poivt]){ swap(arr,++lessR,index++); }else if (arr[index] > arr[poivt]){ swap(arr,--moreL,index); }else { index++; } } swap(arr,moreL,poivt); }
public static int[] partition(int[] arr,int L,int R) { int lessR = L-1; int moreL = R; int index = L; int poivt = R; while(index < moreL) { if(arr[index] < arr[poivt]){ swap(arr,++lessR,index++); }else if (arr[index] > arr[poivt]){ swap(arr,--moreL,index); }else { index++; } } swap(arr,moreL,poivt); return new int[]{lessR+1,moreL}; }
public static void quickSortByRecursive(int[] arr) { if(arr == null || arr.length < 2) return; process(arr,0,arr.length -1); } public static void process(int[] arr,int L , int R) { if(L >= R) return; int[] equalRange = partition(arr,L,R); process(arr,L,equalRange[0]-1); process(arr,equalRange[1]+1,R); }
public static class Job { public int L; public int R; public Job(int left, int right) { L = left; R = right; } } public static void quickSortByNotRecursive(int[] arr) { if (arr == null || arr.length < 2) { return; } Stack<Job> stack = new Stack<>(); stack.push(new Job(0, arr.length - 1)); while (!stack.isEmpty()) { Job cur = stack.pop(); int[] equals = partition(arr, cur.L, cur.R); if (equals[0] > cur.L) { stack.push(new Job(cur.L, equals[0] - 1)); } if (equals[1] < cur.R) { stack.push(new Job(equals[1] + 1, cur.R)); } } }
public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
|