快速排序
非递归
本质上需要用非递归模拟递归,借助一个栈
import java.util.Arrays;
import java.util.Stack;
public class quickSort {
public static void quickSort(int[] array){
//栈中保存的元素相当于当前要进行partition操作的范围下标
Stack<Integer> stack = new Stack<>();
stack.push(0);
stack.push(array.length - 1);
while (!stack.isEmpty()){
int right = stack.pop();
int left = stack.pop();
if(left >= right){
//区间为空或者只有一个元素
continue;
}
int index = partition(array, left, right);
//把右子树入栈[index+1,right]
stack.push(index + 1);
stack.push(right);
//把左子树入栈[left,index-1]
stack.push(left);
stack.push(index - 1);
}
}
private static int partition(int[] array, int left, int right) {
int baseValue = array[right];
int i = left;
int j = right;
while (i < j){
//1、先从左往右找一个大于基准值的元素
while (i < j && array[i] <= baseValue){
i++;
}
//此时i指向位置要么和j重合,要么就是一个比基准值大的元素
//2、从右往左找一个小于基准值的元素
while (i < j && array[j] >= baseValue){
j--;
}
//此时j指向位置要么和i重合,要么就是一个比基准值小的元素
//3、交换 i 和 j
if( i < j){
swap(array, i, j);
}
}
//循环结束i和j重合,把基准值和ij重合位置进行交换
//此时重合位置的值一定大于基准值
//1、i++ 触发和j重合,i本来指向大于基准值,i和j交换后,j一定是大于基准值的元素,此时i走到j的位置
//2、j--触发了和i重合,此时i指向的是一个大于基准值的元素,(第一个while功能),j--就走到了i指的位置
swap(array, i, right);
return i;
}
private static void swap(int[] array, int i,int j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
public static void main(String[] args) {
int[] a ={9,5,2,7,4,3,8};
quickSort(a);
System.out.print(Arrays.toString(a));
}
}
优化
如果选最后一个元素为基准值,就要先从左往右找,找大于基准值的,再从右往左找,找小于基准值的;
如果选第一个元素为基准值,就要先从右往左找,找小于基准值的,再从左往右找,找大于基准值的。
如果不限定顺序,就无法保证最后一步和基准值交换是合理的。
快速排序的性能受到基准值的影响很大。
如果基准值选的好(基准值接近数组中的中位数),此时左右两个区间比较均衡,此时效率会比较高。
时间复杂度O(NlogN)空间复杂度O(logN)
如果基准值选取不好(基准值接近最大值或者最小值),此时左右两个区间就不平衡,极端情况下,就会出现:
时间复杂度O(N^2)空间复杂度O(N)
1、 优化基准值的选择
随机挑三个数字,取三个数字的中间值作为基准值,将选择的基准值和数组的第一个位置或者最后一个位置进行交换,之后进行和之前正常排序相同的步骤。
2、减少递归次数
当元素个数比较少的时候,就不再进行递归,直接进行插入排序。
3、结合堆排序
当递归深度达到一定程度时,如果此时待排区间的元素数目仍旧较多,可以使用堆排序。
归并排序
非递归
按照下标分组
第一趟分组归并:
[0] [1] 这两个组合并
[2] [3] 这两个组合并
[4] [5] 这两个组合并
[6] [7] 这两个组合并
第二趟分组归并(每组中有两个元素):
[0,1] [2,3] 这两个组合并
[4,5] [6,7] 这两个组合并
第三趟分组归并(每组中有四个元素):
[0,3] [4,7] 这两个组合并
最终组合成的数组就是一个有序的数组
import java.util.Arrays;
public class mergeSort {
public static void mergeSort(int[] array){
//gap代表当前每组的元素个数
for (int gap = 1; gap < array.length; gap *= 2){
for (int i = 0; i < array.length; i+= 2*gap) {
//每循环一次相当于把两个长度为gap的相邻数组进行了合并
//[i,i+gap) [i+gap.i+2*gap)
int left = i;
int mid = i + gap;
int right = i + 2 * gap;
if(mid > array.length){
mid = array.length;
}
if(right > array.length){
right = array.length;
}
merge(array,left,mid,right);
}
}
}
//将两个有序列合并成一个有序序列
private static void merge(int[] array, int left, int mid, int right) {
//当前有两个有序数组
//[left,mid) [mid,right)
int cur1 = left;
int cur2 = mid;
//临时空间要能放下两个数组合并后的结果
int [] output = new int[right - left];
int outputIndex = 0;//当前output数组中插入了几个元素
while (cur1 < mid && cur2 < right){
if(array[cur1] <= array[cur2]){//必须有=好保证是稳定排序
//把cur1位置的元素插入output中
output[outputIndex] = array[cur1];
cur1++;
outputIndex++;
}else {
output[outputIndex] = array[cur2];
cur2++;
outputIndex++;
}
}
while (cur1 < mid ){
output[outputIndex] = array[cur1];
cur1++;
outputIndex++;
}
while (cur2 < right ){
output[outputIndex] = array[cur2];
cur2++;
outputIndex++;
}
//最后一步把数据从临时空间拷贝到原来的数组中
for (int i = 0; i < right - left; i++){
array[left + i] = output[i];
}
}
public static void main(String[] args) {
int[] a ={9,5,2,7,3,6,4,8};
mergeSort(a);
System.out.print(Arrays.toString(a));
}
}