首页 > 其他分享 >快速排序

快速排序

时间:2022-08-19 00:22:29浏览次数:75  
标签: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,排序,快速,指针
From: https://www.cnblogs.com/Ethan-Code/p/16600622.html

相关文章

  • c语言中使用冒泡排序法对数组进行排序
     001、#include<stdio.h>#defineNUMBER5voidpsort(intx[],intn){inti,j;for(i=0;i<n-1;i++)......
  • Pytest系列(1-1)-快速入门
    前言目前有两种纯测试的测试框架,pytest和unittestunittest应该是广为人知,而且也是老框架了,很多人都用来做自动化,无论是UI还是接口pytest是基于unittest开发的另一款更......
  • 归并排序
    归并排序整体上是递归,左边排好序+右边排好序+merge让整体有序让其整体有序的过程里用来排外序方法利用master公式来求解时间复杂度当然可以用非递归实现例:......
  • go 接口 实现sort排序接口 进行自定义排序
    packagemainimport("fmt""math/rand""sort")//学生结构体typeStudentstruct{NamestringIdstringAgeint}typeStudentA......
  • VR模拟看车系统帮助商家实现与消费者快速结合-深圳华锐视点
    随着5G网络、大数据、人工智能、VR/AR等技术新基建的日趋完善,VR全景技术的应用领域也更加广泛,正式迎来了爆发元年!为进一步提振区域汽车消费经济,助力汽车企业和相关配......
  • 如何快速上手AIRIOT?
    AIRIOT物联网低代码平台,快速构建稳定可靠的物联网系统,丰富的功能库及组件库,具备低成本、高效率、易操作,可扩展等特点,节省物联网项目实施时间及人力成本,支持二次开发。 ......
  • hbase使用juicefs对象存储测试环境快速部署
    相关技术链接:juicefs官方部署参考文档移动云使用JuiceFS支持ApacheHBase增效降本的探索如何让HBase更快、更稳、更省钱0前言什么是juicefs详情请看官网介......
  • centos快速搭建nfs共享
    一、nfs服务器端01.安装nfs服务yum-yinstallnfs-utils02.创建存储目录mkdir-p/data/2haohr_backup03.设置共享配置#vim/etc/exports/data/2haohr_backup......
  • Filter_概述和快速入门
    Filter_概述 生活中的过滤器:净水器,空气净化器web中的过滤器:当访问服务器的资源时过滤器可以将请求拦截下来完成一些特殊的功能过滤器的作用一般用于完成通用的操作......
  • 小白快速在cenos7系统搭建mongodb数据库及compass远程连接
    前言:本人的cenos系统是在腾讯云部署的云服务器,为个人网站提供服务,这里说明一下安装数据库遇到的问题和折腾记录。远程连接云服务器:这一步使用本地系统的可以跳过。之前......