开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
1.分成子问题
2.递归处理子问题
3.子问题合并
基于AcWing,y总的模板进行讲解
1.快速排
1.模板介绍
void quick_sort(int q[], int l, int r)
{
//递归终止
if (l >= r) return;
//l-1,要包含边界,r+1也是
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
//关于为什么采用 do..while
//防止死循环
//如果 q[i]和q[j]都为x,那么交换后 i,j不会进行任何处理,就会进行死循环
//如 1,1,1,1,1 相同数进行while 循环,得到的结果是i,j不会进行移动,使得一致死循环
//如果取q[i]<=x [1,1] 就会导致 数组越界 不会报错,此时q[i]显示成了12784544等大数,并且死循环
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);
//如果要使用i的话
quick_sort(q, l, i-1), quick_sort(q, i, r);
否则 [1,2]会进行死循环,因为左边界因为不满足条件,i就不会进行更新,就会导致死循环
//同样如果x取到了x[r]就会导致右边界出现问题,陷入死循环
并且x=q[l+r+1>>1]
}
2.模板详解
1)参数确定
分治算法 对数组进行一个左右拆分,所以首先需要数组参数,其次是左右区间
void quick_sort(int q[], int l, int r)
2)递归终止
其实这里写==也是可以通过,此函数返回值为void,所以直接return即可
if(l>=r)return;
3)循环条件
问题1:为什么是l-1,r+1
//因为后续使用do...while循环,如果不将左右边界进行向外扩张,那么会导致一开始,左右边界取不到
int i = l - 1, j = r + 1, x = q[l + r >> 1];
问题2:为什么是do...while
while (i < j)
{
//关于为什么采用 do..while
//防止死循环
//如果 q[i]和q[j]都为x,那么交换后 i,j不会进行任何处理,就会进行死循环
//如 1,1,1,1,1 相同数进行while 循环,得到的结果是i,j不会进行移动,那么就导致while一直进行形成死循环
do i ++ ; while (q[i]< x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
问题3:q[i]<x可以取等号吗
do i ++ ; while (q[i]<=x);
do j -- ; while (q[j]>= x);
同上也可以拿相同数组进行测试,1,1,1,1,1
此时上面两个while循环都成功,会一直进行
就会导致i移到了r+1,j移到了-1,就导致数组越界,但是并不会报错,读者可以自己尝试
此时你可以在while循环进行输出q[i],q[j]就会得到一些较大的数 q[i]显示成了12784544,形成死循环,应该是开辟的新空间,具体待解决
4)递归
//递归 进行分段排序
quick_sort(q, l, j), quick_sort(q, j + 1, r);
//如果要使用i的话,那么x取的位置就要进行更改
x=q[l+r+1>>1]
解释:如果是向下取整那么就会遇到x=左边界的情况,就导致i不进行更新
quick_sort(q, l, i-1), quick_sort(q, i, r);
eg:1,3,2,4 如果取到了左边界,i不会更新,j会一直更新到为0
就导致了quick_sort(q,0,-1),quick_sort(q,0,3)//一直循环了
//同样如果以j为分界的话,如果x取到了x[r]就会导致右边界出现问题,陷入死循环
注意事项
循环到最后j<=i,q[j]<=x
do i++; while(q[i] < x)和do j--; while(q[j] > x)不能用q[i] <= x 和 q[j] >= x
假设q[l..r]全相等
则执行完do i++; while(q[i] <= x);之后,i会自增到r+1
然后继续执行q[i] <= x 判断条件,造成数组下标越界(但这貌似不会报错)
并且如果之后的q[i] <= x (此时i > r) 条件也不幸成立,
就会造成一直循环下去(亲身实验),造成内存超限(Memory Limit Exceeded)
2.归并排序
1.模板介绍
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
//归并是先分,再进行排序
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
//将小的先排在前面
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
//然后再判断是否还有剩余的元素,进行填充,因为分到最后小数在前面,所以先进行i<=mid的填充
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
//注意这里的i<=r,是要等于r的,r就是右边界
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
2.模板详解
1)参数确定
2)递归中止
与快排一致,不多赘述
3)递归
//先进行数组的划分,然后再进行汇总
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
4)循环
int k = 0, i = l, j = mid + 1;
//将数组划分为了左右两部分,左边是较小数组,右边是较大数组
while (i <= mid && j <= r)
//判断较小数组的值是否小于较大数组的值,如果是那么就存入较小数
if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
//然后再判断是否还有剩余的元素,进行填充,因为分到最后小数在前面,所以先进行i<=mid的填充
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
5)结束
//注意这里的i<=r,是要等于r的,r就是右边界
//该函数是数组原地进行,没有生成新的数组,所以最后将排序好的数组传回原数组
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
总结
快排和归并都是同一性质的排序,都属于分治算法,只不过前者是先排后分,后者是先分后排再归总,所以读者将分治问题的思想掌握,那么这两种算法就轻而易举