初始快速排序
1、问题引入:
2、问题分析:
这本身是一个简单的 绝对值排序 问题,但是使用 选择排序 和 冒泡排序 均出现了超时问题,这是由于数的数量最大达到10^5这时候经过学习,了解了常见排序算法的 时间复杂度 ,这时候初始了都够更快解决问题的 快速排序 法。
3、初始快排:
快速排序是一种基于分治的快速排序,是对冒泡排序的一种改进,是非常重要且应用比较广泛的一种高效率排序算法。
大致步骤 :
- 1.先从数列中取出一个数作为基准数。
- 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
- 3.再对左右区间重复第二步,直到各区间只有一个数。
代码呈现:
//一种快速排序方式
void quickSort(int* arr, int low, int high) {
int i = low; //由i从左向右判断
int j = high; //由j从右向左判断
int temp = arr[low]; //记录第一个基准值。(可以不为第一个)
if (i >= j) //判断边界条件。(判断结束排序递归)
return;
while (i != j) { //判断每一次分块是否完成
while (i < j && arr[j]>=temp) //从右向左找到小于基准值的数后停止
j--;
while (i < j && arr[i] <= temp) //从左向右找到大于基准值的数后停止
i++;
if (i < j)
swap(&arr[i], &arr[j]); //交换两个被找到的数,使大于基准值的都在右边,小于的在左
}
//**关于循环中的( i<j )条件 与 常讨论的左右排序前后问题:由于需要完全分块,即将基准值调到左边,由于选择的是第一个数作为基准值,所以需要与它交换的是小于基准值的值,在哪里停止就尤为重要。
//**先判断从右向左的原因 :若从左向右判断,最后一趟交换会从 i++ 到达 i=j。而此时的j是刚交换完的大于基准值的值。导致最终完成完全分块发生问题。(交换数为大值)
swap(&arr[low], &arr[i]); //在前面的条件判断中,需要保证arr[i]<temp,交换基准值与arr[i],完成完全分块。( <temp ; temp ; >temp )
quickSort(arr, i + 1, high); //递归完成两块的后续分块
quickSort(arr, low, i - 1);
}
重要部分!:
完成完全分块 ( <temp ; temp ; >temp )需要将基准值调到中间,基准值在此代码中选择的是第一个数。
//** 关于循环中加入的( i<j )条件 与 常讨论的左右排序前后问题:由于需要完全分块,即将基准值调到左边,由于选择的是第一个数作为基准值,所以需要与它交换的是小于基准值的值,在哪里停止就尤为重要,(i<j)条件加入使(i=j)时跳出循环。
//** 先判断从右向左的原因 :若从左向右判断,最后一趟交换会从 i++ 到达 i=j。而此时的j是刚交换完的大于基准值的值。导致最终完成完全分块发生问题。(交换数为大值)
//**若基准值选择的是最后一个数,则最后需要交换的为大于基准值的值,所以需要从左向右
在再探快速排序部分将深入解决这一问题。
4、问题解决:
//不同于前一种快排的优化版
# include<stdio.h>
# include<math.h>
int a[100005], n;
void Quicksort(int a[], int l, int r)
{
//1. check boundary.
if (l >= r)
return;
//2. partition
int mid = l + (r - l) / 2, i = l - 1, j = r + 1, x = abs(a[mid]);
while (i < j)
{
do i++; while (abs(a[i]) < x);
do j--; while (abs(a[j]) > x);
if (i < j)
{
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}//每次partition会执行多次while, 但一旦一次while结束, 就完成了一次划分. 左边所有数小于基准数 右边所有数大于基准数.
//3. recurrence.
Quicksort(a, l, j);
Quicksort(a, j + 1, r);
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
Quicksort(a, 0, n - 1);
for (int i = 0; i < n; ++i)
printf("%d ", a[i]);
printf("\n");
return 0;
}
再探快速排序
问题引申:
仔细观察问题解决的快速排序,与之前初识快排部分有较大的不同。它去除了最后的交换步骤。
没有完成完全分块。我们来深入看看优化版的代码。
- 1、基准值的选择。基准值选择了数组的中间数,没有完成完全分块,循环判断最后实现的形式为 ( < temp ; > temp ) temp的位置不确定
- 2、【根本优化】循环结束条件判断。先看先前版本下因为需要分为三块,而使用( i = j )作为最后结束标志——诱发了判断时左右方向不同的不同结果;而优化版本下不使用 ( i = j )为结束标志,最后结束时 i 的位置为 右边分块的最左边 ;j 的位置为左边分块的最右边,( i > j )顺利退出循环,由于最后位置确定,所以从左往右还是从右往左都无关紧要了。
- 3、递归实现。分为两块后可以通过 i 或 j的位置来实现左右分块的递归.
- 4、优化版中do-while运用与while效果类似,无特殊作用
问题的再拓展:
以上版本为 快速排序 的 hoare版本 和 其优化版。还有别的版本和优化后续将会更新;希望大家指出代码和注释的问题(我自己绕的也很晕……