1.快速排序
思想
1)在待排序区间中,找到一个基准值(常见的可以取区间的第一个元素或者最后一个元素)
2)以基准值为中心,把整个区间整理成三个部分,左侧部分的元素都小于等于基准值,右侧部分都大于等于基准值
3)再次针对左侧整理好的区间和右侧整理好的区间,进一步进行递归,重复刚才的整理过程
步骤
1)取最右侧元素为基准值
2)从左往右找到一个大于基准值的元素
3)从右往左找到一个小于基准值的元素
4)交换left和right的位置的元素
5)循环刚才的动作直至right和left重合
递归的整理左侧区间和右侧区间
注意
1)快速排序的效率和基准值取的好坏密切相关,基准值是中位数的时候效率最高,反之,基准值若是最大值或者最小值,快速排序的效率就比较低
2)如果取最左侧元素为基准值,则首先从右往左找,载从左往右找
3)如果数组正好反序,此时快排就变成慢排
此时快速排序效率很低,时间复杂度是O(N^2)
private static void quickSort(int[] array){
//辅助完成递归过程
//为了代码简单,区间设定成前闭后闭
quickSortHelper(array,0,array.length - 1);
System.out.println(Arrays.toString(array));
}
private static void quickSortHelper(int[] array, int left, int right) {
if(left >= right){
//区间中有0个或者一个元素,此时不需要排序
return;
}
//针对[left,right)区间进行整理
//index返回值就是整理完毕后left和right的重合位置,知道了这个位置
//才能进一步进行递归,
int index = partition(array,left,right);
quickSortHelper(array,left,index - 1);
quickSortHelper(array,index + 1,right);
}
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
//取最右侧元素为基准值
int base = array[right];
while(i < j){
//从左往右找到比基准值大的元素
while(i < j && array[i] <= base){
i++;
}
//当上面的循环结束时,i要么和j重合要么就指向一个大于base的值
//从右往左找比基准值小的元素
while(i < j && array[j] >= base){
j--;
}
//当上面的循环结束时,i要么和j重合要么就指向一个小于base的值
//交换i和j的值
swap(array,i,j);
}
//当i和j重合的时候,最后一步,要把重合位置的元素和基准值交换
swap(array,i,right);
return i;
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
快速排序的优化
1)优化基准值的取法,三个元素取中(最左侧元素,最右侧元素,取中间值作为基准值,把确认的基准值swap到数组末尾开始,为了后面的整理动作做铺垫)
2)当区间已经比较小的时候,再去递归其实效率就不高了,不再继续进行递归,而是直接进行插入排序
3)如果区间特别大,递归区间也会非常深,当递归深度达到一定程度时的时候,把当前区间的排序使用堆排序来进行优化
非递归实现
private static void quickSortByLoop(int[] array){
//借助栈,模拟递归过程
//stack用来存放数组下标,通过下标来表示接下来要处理的区间是啥
Stack<Integer> stack = new Stack<>();
//初始情况下,先把右侧边界下标入栈,再把左侧下标入栈,左右下标
//仍然构成前闭后闭区间
stack.push(array.length - 1);
stack.push(0);
while(!stack.isEmpty()){
//这个取元素的顺序要和push的顺序正好相反
int left = stack.pop();
int right = stack.pop();
if(left >= right){
//区间中只有一个或0个元素,不需要整理
return;
}
//通过partition把区间整理成以基准值为中心,左侧元素
//小于等于基准值,右侧元素大于等于基准值的形式
int index = partition(array,left,right);
//准备处理下个区间
//[index + 1,right]基准值右侧区间
stack.push(right);
stack.push(index + 1);
//[left,index - 1]基准值左侧区间
stack.push(index - 1);
stack.push(left);
}
}
2.归并排序
特点
可以适用于外部排序(数据存在磁盘上),也可以使用于链表排序(希尔,堆排序,快速排序 依赖随机访问能力,所以就不适合用链表排序)
思想
来源于一个经典问题(把两个有序链表/数组合并成一个)
//[low,mid)有序区间
//[mid,high)有序区间
//把这两个有序区间合并成一个有序区间
public static void merge(int[] array,int low,int mid,int high){
int[] output = new int[high - low];
int outputIndex = 0;//记录当前output数组中放入多少个元素了
int cur1 = low;
int cur2 = mid;
while(cur1 < mid && cur2 < high){
//这里写成小于等于才能保证稳定性
if(array[cur1] <= array[cur2]){
output[outputIndex] = array[cur1];
outputIndex++;
cur1++;
}else{
output[outputIndex] = array[cur2];
outputIndex++;
cur2++;
}
}
//上面的循环结束的时候,肯定是cur1或者cur2有一个先到达末尾,另一个还剩下一些内容
while(cur1 < mid){
output[outputIndex] = array[cur1];
outputIndex++;
cur1++;
}
while(cur2 < high){
output[outputIndex] = array[cur2];
outputIndex++;
cur2++;
}
//把output中的元素再搬回原来的数组
for(int i = 0;i < high - low;i++){
array[low + 1] = output[i];
}
}
private static void mergeSort(int[] array){
mergeSortHelper(array,0,array.length);
}
//[low,high)前闭后开区间,两者差值小于等于1,区间中就只有一个或者0个元素
private static void mergeSortHelper(int[] array, int low, int high) {
if(high - low <= 1){
return;
}
int mid = (low + high) / 2;
//这个方法执行完,就认为low,mid已经排序OK
mergeSortHelper(array,low,mid);
//这个方法执行完,就认为mid,high也已经排序OK
mergeSortHelper(array,mid,high);
//当把左右区间已经归并完了,说明左右去家已经是有序区间了
//接下来就可以针对两个有序区间进行合并了
merge(array,low,mid,high);
}
非递归实现
private static void mergeSortByLoop(int[] array){
//引入一个gap变量进行分组
//当gap为1的时候,[0] [1]进行合并,[2] [3]进行合并,[4] [5]进行合并,[6] [7]进行合并
//当gap为2的时候,[0,1]进行合并,[2,3]进行合并,[4,5]进行合并和[6,7]进行合并
//当gap为4的时候,[0,1,2,3]和[4,5,6,7]进行合并
for(int gap = 1;gap < array.length;gap *= 2){
//接下来进行具体合并
//下面的循环执行一次,就完成了一次相邻两个组的合并
for(int i = 0;i < array.length;i += 2*gap){
//当前相邻组
//[beg,mid)
//[mid,end)
//beg => i
//mid => i + gap
//end => i + 2 * gap
int beg = i;
int mid = i + gap;
int end = i + 2 * gap;
if(mid > array.length){
mid = array.length;
}
if(end > array.length){
end = array.length;
}
merge(array,beg,mid,end);
}
}
}