快速排序(非递归)

简介: 快速排序(非递归)

 前面的三个版本的快速排序都是以递归的方式写的,但是我们都知道,递归虽好,但是递归的深度是不易太深的,因为栈区的内存是有限的,递归深度太深必然会栈溢出,导致程序崩溃,所以,我们有必要学会如何把快排的递归改为非递归,接下来就跟大家聊一聊快排的非递归该如何实现。


       从前面的几种快排递归的写法(hoare版本,挖坑法,前后指针法)中能看出,它们每完成一趟快速排序之后都会返回一个keyi的位置,a[keyi]的左边的值都比a[keyi]小(或者等于),右边的值都比a[keyi]的大。然后下一趟快排就是以keyi为分界线,分成左右两边的子区间再进行同样的快排操作的。也就是说这里的排序能一直递归下去的重点是递归的时候能够把数组不断地划分为更小的区间,由此可知想要用非递归的方式模拟实现递归排序的重点是每一趟快排结束得到keyi,keyi对数组的分割的两个子区间需要被保存下来,等到下一趟进行排序的时候就可以用保存好的子区间作为子数组排序的边界了,这样一来就能完成快排的非递归。


       那怎么保存排序过程中产生的子区间呢?毫无疑问,这里最适合用的是栈存储子区间。其实仔细思考一下你会发现,这个的思想是不是跟二叉树的前序遍历的思想是一样的,先排好一个数,再排好这个数的左区间,然后再排好这个数的右区间,最后使整个数组有序,而二叉树的前序遍历就是dfs(深度遍历)的类型,dfs类型最适合用栈数据结构辅助解题,所以这里用栈来存储是最适合的。


       那我们如何用栈保存数组排序时产生的子区间呢?


大概思路是:刚开始时,先把数组的左右下标入栈,然后开始取栈内的数据作为排序子数组的区间值,那么每走一趟快排都会返回一个keyi,如果keyi-1比begin大,那证明这个区间里面至少还有两个值,那就把begin和keyi-1入栈,否则就证明只有一个值或者没有值了,不需要入栈;如果keyi+1小于end,那就把这keyi+1和end入栈,否则不入,等到下一次循环的时候就在栈中取出两个数,这两个数就是需要排序的子区间,进行排序,以此类推,直到最后栈中没有了数就证明数组已经排完了。


       参考代码及注释:

void Swap(int* a, int* b)
{
  int tmp = *a;
  *a = *b;
  *b = tmp;
}
void PrintArr(int* a, int n)
{
  int i = 0;
  for (i = 0; i < n; i++)
  {
    printf("%d ", a[i]);
  }
  printf("\n");
}
//三数取中
int GetMidNumi(int* a, int left, int right)
{
  int mid = (left + right) / 2;
  if (a[left] > a[mid])
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    //来到这里证明a[mid]最小,那么a[left]和a[right]
    //谁小谁就是中间的那个数
    else if (a[left] < a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
  else//a[left]<=a[mid]
  {
    if (a[mid] < a[right])
    {
      return mid;
    }
    //来到这里证明a[mid]最大,那么a[left]和a[right]
    //谁小谁就是中间的那个数
    else if (a[left] < a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}
int PartSort3(int* a, int left, int right)
{
  //三数取中
  int mid = GetMidNumi(a, left, right);
  //如果left就是取到的数,那么也就没有必要交换了
  if (mid != left)
  {
    Swap(&a[left], &a[mid]);
  }
  int keyi = left;
  int prev = left;
  int cur = prev + 1;
  while (cur <= right)//由于这里是左闭右闭区间,所以需要取等号
  {
    //这里是逻辑与操作,只有第一个条件成立,第二个条件才会参与判断
    //所以后面的++prev是在a[cur]<a[keyi]的条件下再走的,如果++prev
    //后prev==cur,证明它们的位置是一样的,也就没有必要交换了,只有等
    //prev和cur错开才需要交换
    if (a[cur] < a[keyi] && (++prev != cur))
    {
      Swap(&a[cur], &a[prev]);
    }
    //无论上面是否需要交换,cur都需要++
    cur++;
  }
  //最后prev的位置就是a[keyi]的最终的位置,交换prev和keyi对应的值
  Swap(&a[prev], &a[keyi]);
  //交换后a[prev]的值比前面的值大,比后面的值小,这里就是分界点,返回这个prev做递归的边界
  return prev;
}
void QuickSortNonR(int* a, int left, int right)
{
  //定义一个栈
  stack<int> st;
  //先把大数组的需要排序的区间push到栈中
  //这里是先push区间的左边,在push区间的右边
  //所以拿的时候先拿到的是区间的右边,再拿到区间的左边
  st.push(left);
  st.push(right);
  while (!st.empty())
  {
    //由于是先如区间的左边,再入区间的右边
    //所以拿的时候先拿右边,再拿左边
    int end = st.top();
    st.pop();
    int begin = st.top();
    st.pop();
    //用前后指针法完成每一趟快排
    //如果对这里的一趟快排的细节
    //有疑问,可以转移到上一篇文章
    int keyi = PartSort3(a, begin, end);
    //如果返回的keyi-1>begin,说明小区间至少还有两个
    //元素,需要入栈,等循环回去继续排序
    if (begin < keyi - 1)
    {
      st.push(begin);
      st.push(keyi - 1);
    }
    //如果返回的keyi+1<end,说明小区间至少还有两个
    //元素,需要入栈,等循环回去继续排序
    if (keyi + 1 < end)
    {
      st.push(keyi + 1);
      st.push(end);
    }
  }
  //当栈为空时,说明全部元素都已经有序,那么数组也就有序了
}
int main()
{
  int a[] = { 6,6,6,6,1,2,7,9,3,4,5,10,8 };
  int sz = sizeof(a) / sizeof(a[0]);
  QuickSortNonR(a, 0, sz - 1);
  PrintArr(a, sz);
  return 0;
}
相关文章
|
弹性计算 运维 安全
阿里云服务器2核2G99元一年,续费同享99元方法介绍(新手用户参考)
阿里云经济型e实例2核2G 3M固定带宽 40G ESSD Entry云盘,价格为99元一年,另外续费也享99元的优惠价格,最多可99元一年续费3次,本文为大家介绍具体续费流程及注意事项。
阿里云服务器2核2G99元一年,续费同享99元方法介绍(新手用户参考)
|
10月前
|
存储 机器学习/深度学习 算法
基于A星算法的无人机三维路径规划算法研究(Mattlab代码实现)
基于A星算法的无人机三维路径规划算法研究(Mattlab代码实现)
564 1
|
机器学习/深度学习 数据采集 自然语言处理
魔搭社区模型速递(6.2-6.7)
魔搭ModelScope本期社区进展:1910个模型,183个数据集,47个创新应用,5 篇内容
1419 11
魔搭社区模型速递(6.2-6.7)
|
Linux 数据库 iOS开发
CrossOver 25.1.0 for macOS & Linux - 领先的 Wine 解决方案
CrossOver 25.1.0 for macOS & Linux - 领先的 Wine 解决方案
772 0
|
存储 Ubuntu Docker
Docker从入门到精通:Docker pull命令学习
了解Docker镜像下载方法!使用`docker pull`命令从[Docker Hub](https://hub.docker.com/)获取镜像。基本语法是`docker pull NAME[:TAG]`。例如,拉取Python最新镜像的命令是`docker pull python`或`docker pull python:latest`。可选参数包括`-a`(拉取所有标签)和`--quiet`(只显示进度条)。拉取后,用`docker images`检查镜像是否成功存储。开始你的容器化之旅吧!
|
JSON 人工智能 算法
探索LLM推理全阶段的JSON格式输出限制方法
文章详细讨论了如何确保大型语言模型(LLMs)输出结构化的JSON格式,这对于提高数据处理的自动化程度和系统的互操作性至关重要。
3797 52
|
机器学习/深度学习 人工智能 自然语言处理
AI第三极之争:生成式人工智能(GAI)认证如何重塑青年竞争力与时代担当
本文探讨了AI时代青年面临的机遇与挑战,强调跨学科知识、创新思维及伦理意识的重要性,并提出GAI认证对提升青年就业竞争力的意义。通过加强教育衔接、校企合作及政策支持,助力青年在AI领域成长,成为推动社会进步的重要力量。
|
缓存 C语言
glibc函数malloc的工作原理
glibc函数malloc的工作原理
359 0
|
人工智能 搜索推荐 数据挖掘
AI教育的评估方法有哪些?
【6月更文挑战第2天】AI教育的评估方法有哪些?
926 2
|
传感器 芯片
【STM32基础 CubeMX】从0带你点灯
【STM32基础 CubeMX】从0带你点灯
887 0
【STM32基础 CubeMX】从0带你点灯