快速排序的作用
用于排序
快速排序大致思路
先来了解一下快速排序的大致思路。(由小到大排序)
首先我们设定我们需要排序的数组如下,最左边是 l 下标,最右边是 r 下标,中间是我们的若干元素。
接着我们随便找一个值,记为x, 这个值是 数组[l, r]区间内的一个值,
接着我们需要将数组分为两个区间,其中左边 区间的值都<= x,右边区间的值都是 >= x 的。
这时我们再将我们划分出来的两个区域的每个区域再次执行该操作。
也就是在每个区域内 再次找个值 然后再次划分成两个区域
然后一直划分下去,这样最终数组就会有序,这里会运用到递归的思想。
快速排序代码实现
带着代码分析会更清晰一点。
接下来我们写一个快速排序函数
快速排序只需要将数组排序,所以不需要用到返回值,所以返回值类型为void,参数需要排序的数组和需要排序的区间首和尾下标。
根据刚才的思路,我们需要一个值,记为x,然后将数组划分为两个区域,左半边 <= x, 右半边>= x。
这个x 可以是数组当中的 任意一个值,不过这里最好是区间中点的值,至于为什么,会在最后的易错点分析当中解释。
接下来就是最关键的一步,我们要 尽全力的将 <= x 的值 放在左边,>= x的值放在右边。
这个时候我们可以用到双指针做法:
首先需要重新定义两个下标,分别指向区间的两边
我们假设此时数组有这些值
其中,按照刚才我们定义的x ,这里x应该是 5,是这个数组中间下标的值。
然后,我们需要分别移动 i 和 j ,使得 i下标的值 >=x,j下标的值 <= x,然后交换这两个元素。
接着循环重复此操作
我们接着来看代码。
这里的i 和 j 就是 区间的两边。
接着我们需要分别移动 i 和 j 来使得 i下标的值 >=x,j下标的值 <= x。
这里我们用到了 do while 循环,由于 i 上来就会自增1次,所以也解释了为什么 刚开始 i = l - 1 而不是 l ,同理 r 也是。
找到了之后,然后将他们交换。
然后我们需要重复这个过程。
这里的循环条件是 i < j,也就是说当 i 和 j 相遇或 穿过时 这个时候 j 左边的所有值都是 <= x的(包含 j ),j 的右边的值都是 >= x的(不包含j)。
所以我们此时就通过 j 可以将数组的区间分为两个部分 一个 是 [ l, j ] [ j+1, r ]。(当然也可以通过 i 来划分,就是[ l, i-1 ],[ i, r ])
接着我们将这两个区域 再次进入这个函数,也就是递归。
当然,递归不可能无线递归下去,所以我们需要在开头加上递归的结束条件。
这个递归的结束条件的意思是,如果区间内有一个元素或者没有元素时,就直接结束该函数。
下面是快速排序的模版:
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l-1,j = r + 1,x = q[(l+r) >> 1];
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) swap(q[i], q[j]);如果没有交换函数,就手写一下
}
quick_sort(q, l, j);
quick_sort(q, j+1, r);
}
快速排序易错点分析
易错点1:取 x 的值 老老实实 的取数组中间下标的值,不要乱取边界的值。( x = q[(l + r) >> 1] )
因为容易造成无限划分
用 下标为j 划分时 x 不能取 q[r],用下标为 i 划分时 x 不能取 q[l];
比如 当 x = q[r]; 那么 j 就可能取到 r(比如 此时r 下标的元素都已经 <= x),最后在递归的时候就是 quick_sort(q, l, r); 和 quick_sort(q, r + 1, r);
此时就会无限递归下去。
同理 当 x = q[l] 时,如果此时用 i 来划分([l, i - 1] 和 [i, r] 来递归) ,那么递归就有可能 是 quick_sort(q, l, l - 1); 和 quick_sort(q, l, r):,这也会导致无限递归下去
易错点2: 下面while循环中的判断不能是 q[i] <= x 和 q[j] >= x;
do i++; while (q[i] < x);
do j--; while (q[j] > x);
比如 如果我们 数组中所有元素都是 等于 x ,那么这个 i 就会一直自增到 r + 1 ,就会越界,要是越界之后 条件还成立的话,那么就会一直循环下去,总之会导致出现错误。
易错点3:用 j下标 划分区域的时候,划分的是 [l, j-1]和 [j, r]。用 i 下标划分区域的时候,划分的是 [ l, i ] 和 [ i+1, r ]。
当我们循环停止的时候,要么 j 和 i 下标是一个值,要么 j 就在 i 的左边。
此时,j 右边的元素(不包含j)一定是 >= x ,i 左边的元素(不包含i)一定是 <= x 的。
这时 j 下标 是 <= x ,i 下标是 >= x的,所以不能以 [l, j-1] 和 [j, r] 划分 快排要求 右边这个区间是 >= x 的,而里面的 j 是 <= x的,所以不满足条件。同理i 也是
易错点4:下面的 while循环的判断不要写成 i <= j,这个在某种特殊的情况下也会造成无限划分
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) swap(q[i], q[j]);如果没有交换函数,就手写一下
}
易错点5:下面的do while 循环不能写成while循环
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) swap(q[i], q[j]);如果没有交换函数,就手写一下
}
do while 相较于while循环 每次都必定会 自增1,如果 i下标和 j下标的值都是 x的话 那么就会卡死。
完