复习:
一次使得一个数据有序
插入排序:在无序区间选择第一个数,在有序区间遍历,查找适合的位置插入
选择排序:遍历无序区间,查找最大数的下标
冒泡排序:遍历无序区间,通过两两比较,将最大的数挤到最后
堆排序:
总览:大的选择排序(减治排序,每次选择最大的树放在最后)
利用堆的特性选择最大的数
快速排序(重点):
总览过程:
1.在待排序区间中选择一个基准值(pivot)
2.Partition:遍历整个待排序期间,将比基准值小的(可能包含==)放在左边,
将比基准值大的(可能包含==)放在右边。
3.分治思想
对左右的小区间分别用同样的方式处理
**直到:区间长度==1(有序)或者 长度==0(没有数据)
实现:
public static void quickSort(int[] array) {
quickSortInter(array, 0, array.length - 1);
}
// 待排序区间[left, right]
private static void quickSortInter(int[] a, int left, int right) {
if (left >= right) {
// 直到 长度 <= 1
return;
}
// 1. 选择基准值 array[left]
// 2. 做 partition
int pivotIndex = partition(a, left, right);
// 左边小区间 [left, pivotIndex - 1]
// 右边小区间 [pivotIndex + 1, right]
// 3. 分别对左右小区间按同样方式处理
quickSortInter(a, left, pivotIndex - 1);
quickSortInter(a, pivotIndex + 1, right);
}
如何做 partition:
Hoare法:
过程:
int i=left;
int j=right;
int pivot=a[left]; //基准值
基准值在左,右边先走,即 j++;
当 a[j]<pivot 时,i++;
当 a[i]>pivot 时
交换 a[i] 和 a[j]
当 j 和 i 相遇时,交换 基准值pivot 和 a[i];
此时,在基准值之前的,全部比 pivot 小;在其之后的,全部比 pivot 大;
private static int partition1(int[] a, int left, int right) {
int begin = left;
int end = right;
int pivot = a[left];
// [left, begin) <= pivot
// (end, right] >= pivot
while (begin < end) {
while (begin < end && a[end] >= pivot) {
end--;
}
while (begin < end && a[begin] <= pivot) {
begin++;
}
swap(a, begin, end);
}
swap(a, left, begin);
return begin;
}
private static void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
挖坑法:
基本思路和Hoare 法一致,只是不再进行交换,而是进行赋值(填坑+挖坑)
int begin = left;
int end = right;
int pivot = a[left];
先将 pivot 复制到一边, pivot 位置上值为空;
基准值在左,右边先走;
当 a[end]<pivot 时,将 a[end] 放到 pivot 的位置上;
begin++;
当 a[begin]>pivot 时,将 a[begin] 放到空的位置上;
end++;
当 begin 和 end 相遇时,将基准值放到这个空位上。
private static int partition2(int[] a, int left, int right) {
int begin = left;
int end = right;
int pivot = a[left];
while (begin < end) {
while (begin < end && a[end] >= pivot) {
end--;
}
a[begin] = a[end];
while (begin < end && a[begin] <= pivot) {
begin++;
}
a[end] = a[begin];
}
a[begin] = pivot;
return begin;
}
前后遍历法:
int pivot=a[left];
当 a[i]>pivot 时,i 往后走;
当 a[i]<pivot 时,a[i] 和 a[j] 交换;
i++,d++;
直到 i 走完整个数组
private static int partition3(int[] a, int left, int right) {
// array[left]
// [left + 1, right]
int pivot = a[left];
int d = left + 1;
for (int i = left + 1; i <= right; i++) {
if (a[i] < pivot) {
swap(a, i, d++);
}
}
swap(a, d - 1, left);
return d - 1;
}
partition 不是做快排,只是快排的一个步骤
partition 一定要所有的数据都和基准值做过比较
基准值在左,先走右边
partition 的时间复杂度和空间复杂度:
时间复杂度:O(n)
空间复杂度:O(1)
快速排序的时间复杂度和空间复杂度:
时间复杂度:partition 的时间复杂度 * 二叉树的高度
空间复杂度:partition 的空间复杂度 * 二叉树的高度
二叉树的高度:O(log(n))~O(n)
稳定性:不稳定
退化成单支树和两个因素有关:
1.数组的原顺序
2.基准值的选择
修改选择基准值的办法:
1.选择边上(左或者右)--只有有序/逆序,就是单支树
2.随机选择--不能消除单支树,但减少概率
3.几数取中(三数取中)--消除单支树
array[left], array[mid], array[right] 大小是中间的为基准值
非递归分治(了解):
public static void quickSort(int[] array) {
Stack<Integer> stack = new Stack<>();
stack.push(array.length - 1);
stack.push(0);
while (!stack.isEmpty()) {
int left = stack.pop();
int right = stack.pop();
if (left >= right) {
continue;
}
int pivotIndex = partition(array, left, right);
stack.push(right);
stack.push(pivotIndex + 1);
stack.push(pivotIndex - 1);
stack.push(left);
}
}
总结:
1. 在待排序区间选择一个基准值
选择左边或者右边
随机选取
几数取中法
2. 做 partition,使得小的数在左,大的数在右
1. hoare
2. 挖坑
3. 前后遍历
4. 将基准值相等的也选择出来(了解)
3. 分治处理左右两个小区间,直到小区间数目小于一个阈值,使用插入排序
完!