ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

快速排序

2022-08-19 00:30:43  阅读:203  来源: 互联网

标签:do 指向 -- ++ while 排序 快速 指针


1. 快速排序——分治

# 算法原理:

image-20220408191422396

在给定序列找到一个点x使得x左边区间数都小于x,右边区间数都大于x

# 步骤:

  1. 确定分界点
    • 随机,可以是第一个数
  2. 调整区间
    • 使左边都小于分界点,右边都大于分界点
  3. 递归处理左右两段
    • 递归停止的条件if(l >= r) return;即区间里没有数或只有1个数就直接返回

# 如何实现x左边都小于等于x右边都大于等于x?

方法一:暴力法

  1. 开辟两个数组 a[]b[]
  2. 对序列q[l~r]:小于x的放a[] 大于x放b[]
    • if q[i] <= x then q[i]-->a[]
    • if q[i] > x then q[i] -->b[]
  3. a[],b[]依次放回q[]
    • a[],b[]-->q[]

方法二:双指针

image-20220408191559453

  1. i指针指向的数小于x,指针后移
    • q[i]<xi++
  2. 直到i指向的数大于x,开始判断j指向的数
    • q[i]>x, 判断q[j]
  3. j指针指向的数大于x,指针前移
    • q[j]>xj--
  4. 直到j指向的数小于x,交换i,j指向的数
    • q[j]<xswap(q[i], q[j])
  5. 重复上述过程,直到i>j

最终j指针一定在i指针前面,i指向x

因为循环结束的条件是i<j,然后每次循环至少执行1次i++j--,因此在退出循环时,必定满足j + 1 = i,即ji的前面。后文有解析这个问题,点击跳转

  • i 左边的数永远 <=x
  • j 右边的数永远 >=x

i, j 两个指针穿过后,左边的数为<=x,右边的数位>=x,从而分成2个区间。

# 快排模板

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);
}

# 需要斟酌的细节(边界问题)

参考了CSDN上总结的一篇非常Nice的文章

细节一:指针移动的判断不带等号

考虑一个边界问题,为什么移动ij指针的条件是q[i] < xq[j] > x,而不是q[i] <= xq[j] >= x

原因如下

  • 如果选取的x是数组里最大的数,序列中所有的数都满足q[i] <= x,会导致i会一直++发生越界都不会停下来。
  • 如果选取的x是数组里最小的数,同理q[j] >= x恒成立,j会一直--发生越界。

这也是造成快排不稳定的原因,排序算法是否稳定,与时间效率是否稳定无关。稳定是指若源序列中两个值相同的数,排序后这两个数的先后次序不会发生改变。

而快排中当边界点存在重复的数会交换位置。因此快排不稳定。

解决不稳定的方法:把序列中的数改成二元数,Ai改成 <Ai, i>,从而使所有的数都不相同。

细节二:使用do-while在判断前先移动指针

考虑一个边界问题,为什么不能让i = lj = r然后使用while循环代替do-while循环?

探讨一下写成如下形式会有什么问题

while(q[i] < x) i++;
while(q[j] > x) j--;

若数组中存在重复的数字,某一轮可能存在ij都指向重复的数字,并且分界点x也是这个数字,上述两个while语句的判断就会结束循环,此时q[i] = q[j] = x,交换ij指向这个局面仍然不会改变,因此下一轮会重复这个过程,陷入死循环。

因此,要确保每轮下来两个指针都至少会移动一步,保证上一次交换的结果不会再次判断。

思路是,在判断while条件时,先移动指针。用do-while最容易实现这个思想,也可以用while实现:

do i++; while(q[i] < x);
do j--; while(q[j] > x);
//等价于
while(q[++i] < x);
while(q[--j] > x);

同时为了保证每轮下来边界lr都能被判断到,因此初始化要i = l - 1, j = r + 1使指针在数组两端之外。

细节三:区间左半边使[l, j]而不是[l, i]

考虑一个边界问题,q[i]q[j]i == j - 1 时停下来做交换的场景,交换完成之后ij会各自前进(i ++, j --)一步,形成i > j(即i == j + 1)的不合法局面。

这个局面的原因:由于i指针移动时,其走过的数都满足< xj移动时其走过的数都满足> xi, j相遇时,其再继续移动就会穿过对方,进入对方走过的数,因此会停下来等待交换。此时i, j位置就发生了交换, j + 1 = i。并且i, j指向的数不用再交换了,因为前后的数都已经判断过了。

此时,从lj的数都满足q[l, j] <= x。为什么不是< x,因为i走过的数如果需要和j交换,换过来的数可能是= x的。

所以,在这个局面下,满足性质<= x的区间是[l, j]而不是[l, i],因此划分的两个区间是[l, j][j + 1, r]

细节四:关于x的取的位置

考虑一个边界问题,xq[l], q[r], q[l+r>>1], q[l+r+1>>1]会有什么不同的结果?区别在于上取整还是下取整。

当按照q[l+r+1>>1],递归区间[l, j], [j+1, r]时,会有以下边界问题:

image-20220410133230166

最终i = j = 2,跟前面分析的不一样,最后结果也是进入死循环无限递归。

这个细节等二分的细节研究完再补充。

2022.04.10

标签:do,指向,--,++,while,排序,快速,指针
来源: https://www.cnblogs.com/Ethan-Code/p/16600622.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有