Day2 分治法——快速排序
十大经典算法总结
https://www.cnblogs.com/onepixel/articles/7674659
一、思想:分治,递归
序位,前后位置——下标
元素的值:关键字,数字的大小
-
分治:以一个关键字(key)为基准(basic)分割数据在前后两边,小于等于basic的在basic前,大于等于basic的在后
这个basic可以随便取,通常可以取左边界元素的值
-
多次分割,完成排序(可利用递归)
快速排序,效率很高。
图示说明
(和具体操作一起看,那里的一些结论直接画在图上了)
二、具体操作
(一)思路
Basic,分割后的数组左右边界lr(下标),动的标记i,j(下标)
-
一次分割:
一部分:找到两边“位置”不对的数,进行交换完成分割(先规定元素等于basic时,是“位置正确”。这样会有问题在这里先忽略不详细讨论)做标记l从最左边向右移动,直到 i 所指的数大于basic的数时,说明他位置不对,需要交换,i停止右移。
再从右向左找小于basic,位置不对需要交换的数,即r开始从最右边向左移动,直到j所指的数小于basic停止。
此时,交换i,j当前所指的元素。二部分: 多次进行上述移动&交换操作,遍历到所有元素 完成一次分割。
-
运用递归法,将分割好的两部分各进行上述分割操作。
(二)思考
-
一部分
1.左右移动的循环:
循环条件:什么时候停止左右移动?找到不符合条件的。“停止”不是循环,“左右移动”才是循环,所以左右移动的循环条件是“位置正确”。
2.j移动在i停止移动之后
3.什么时候交换?当j停止移动时。
i移动的循环,j移动的循环,交换,三个按顺序进行,顺序结构。 -
二部分
完成分割需要进行多次“一部分”的整体,又是一个循环。
循环条件:什么时候完成一次分割?要遍历当前边界的所有元素,所以i一定要等于j或者超过j。那么现在考虑i能否超过j。
1)j 后面位置:由于j是从右往左,“位置对”才会移动,所以j后面的一定“位置对”,即大于等于basic。
2)再来考虑j位置:
i 停止右移动,j才会左移动。
i 移动时,一定完成了上一次交换,或者j在边界R处一次没动过。
情况1:若果是完成了上一次交换,那么当前 j 位置一定正确,即大于等于basic。
情况2:若是j在边界R处一次没动过,说明没有和basic比较过,则有位置不对的可能,即小于basic。
3)综上,从j到j后中,只有j 处在情况2时才有可能位置不对(即小于basic)。
4)i右移动时,“位置对”才右移动。
情况1时,i最多移动到i=j;
情况2时,j=r,j小于basic时,i可能加到r+1处。防止出现这种情况while循环条件加上i<r,防止溢出。所以一次分割结束时,i=j。i=j,是一次分割完成的标志。
但“结束”不是循环,i 不断变大最大值是 j ,所以循环条件是 i < j 。 -
一个问题:等于basic是否判断为“位置正确”?
如果i,j移动时等于basic都代表位置正确,那么如数组8 2 1 4 3 0,取左边界元素8为basic,那么i会因为一直“位置”正确向右移动,直到i=j;那么这样一直不会分割。
经过思考,我们会发现,等于basic对于判断 i 是否移动时,应算作位置不正确,停止移动,对于 j 应该算作位置正确,继续移动。 -
递归部分
- 建立分割的这个函数,这个需要在递归中多次用到的函数,可以说他是一个分割函数,一次分割,通常说他是一次“快排”,有人将他命名为quik_sort()
- 形参表我们要找这个函数里面的变量,形参表需要传递的数
1)想一想:每一次递归需要更新的部分?
每一次都只对分割后的数组进行再次分割,其实需要传递新的左右边界
2)再想一想新的边界是什么?
分割后的左边部分,需要更新右边界。
分割后的右边部分,需要更新左边界。
它们其实都在上次分割的分割点(i=j)处,新的右边界是分割点-1,左边界是分割点。
(三)代码
问题代码:
#include <iostream>
using namespace std;
void quick_sort(int q[],int l,int r){
int i,j,basic;
i=l;j=r;basic=q[l];
while(i<j){
while(q[i]<basic&&i<=r)i++;
while(q[j]>=basic&&i<j)j--;
/*防止j左移动多i:当i移动到等于j时由于上面呢个while控制最后一
定会停在j这里,若不加个这个条件,此时j若不满足条件可能左移动越过i,
交换已正确拍的部分*/
swap(q[i],q[j]);
}//完成一次基本分割
quick_sort(q,l,j-1);//左半边再分割
/*一直执行这句到不了下一句为啥??????? */
quick_sort(q,j,r);//右半边再分割
}
int main(){
int q[100],n;
cin>>n;
for(int i=0;i<n;i++)cin>>q[i];
quick_sort(q,0,n-1);
for(int i=0;i<n;i++)cout<<q[i];
return 0;
}
void swap(int a,int b){
int c;
c=a;
a=b;
b=a;
}
改正后代码,依然有问题。
#include <iostream>
using namespace std;
void quick_sort(int q[],int l,int r){
int i,j,basic;
i=l;j=r;basic=q[l];
while(i<j){
while(q[i]<basic&&i<=r)i++;
while(q[j]>=basic&&i<j)j--;
swap(q[i],q[j]);
}//完成一次基本分割
if(l<j-1) quick_sort(q,l,j-1);//左半边再分割
/*解决方案:增加左边界比有边界小这个条件 */
if(j<r)quick_sort(q,j,r);//右半边再分割
/*依然有问题:进行到这里r值不是整体边界了,左边分割时将j-1不断传给r改变了r值了,现在的r值是左半边的有边界了,但是这里需要的是整体的右边界
怎么解决?快排学不会了 */
}
int main(){
int q[100],n;
cin>>n;
for(int i=0;i<n;i++)cin>>q[i];
quick_sort(q,0,n-1);
for(int i=0;i<n;i++)cout<<q[i];
return 0;
}
void swap(int a,int b){
int c;
c=a;
a=b;
b=a;
}
提供的模板方法代码
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);
}
作者:yxc
链接:https://www.acwing.com/blog/content/277/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。