快速排序,归并排序导致栈溢出如何解决?(非递归算法)

简介: 上期我们讲完了排序算法下,不知道小伙伴们有没有发现一个问题,快速排序和归并排序我们都是用递归来实现的,可能有小伙伴会问,如果说数据量很多话,栈区空间会不会不够用呢?这期我们就来解决使用递归实现的排序导致栈溢出如何解决?

🍊 1、栈溢出原因和递归的基本认识

🍏 2、快速排序(非递归实现)

🍍 3、归并排序(非递归实现)

🍊 1、栈溢出原因和递归的基本认识

我们先简单来了解下操作系统进程地址空间分布结构:

栈区:用于存放地址、临时变量等;                                                                

堆区:程序运行期间动态分配所使用的场景;

静态区:存放全局变量和静态变量,具体还分为 .bss段和.data段;

              .bss段:存放未初始化的和初始化为0的全局变量或者静态变量;

             .data段:初始化不为0的全局变量或者静态变量;

常量区:存放常量(比如比变量名字,非0的初始化值,const常量,字符串等),只读;

代码区:存放代码的位置,只读;

我们再来看这样的一串代码运行的结果:

这是一个累加求和的递归函数,当我们发现累加求和到1000递归仍然能正常执行,我们接着改为10000看看是否还能正常运行?

 

我们可以看到,当数值达到10000的时候程序已经崩了,并不会显示任何错误,当我们进入调试可以发现报错显示栈溢出,那为什么会造成栈溢出呢,我们接着往下看!

递归的基本认识:

递归本质也是函数调用,是函数调用,本质就要形成和释放栈帧

调用函数是有成本的,这个成本就体现在形成和释放栈帧上:时间+空间

所以,递归就是不断形成栈帧的过程

内存和CPU的资源是有限的,也就决定了,合理的递归是绝对不能无限递归下去,如果递归调用深度太深,这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出!

既然使用递归极端情况下会出现栈溢出的问题,那么我们就用非递归的方式来实现快速排序和归并排序!

🍏 2、快速排序(非递归实现)

快速排序非递归实现思想:

首先我们可以借助数据结构的栈来完成,遵循栈的后进先出,我们可以先入右再入左,然后使用我们上一期讲的三个方法中的其中一个方法,这里我们选择挖坑法,使用挖坑法我们可以看作成两个区间也就是: [left, keyIndex - 1] 和 [keyIndex + 1, right],如果区间存在我们接着入栈,如此循环直到栈为空,则排序结束!

图解见下:

代码实现如下:

//挖坑法 - 升序
int PartSort(int* a, int left, int right)
{
  int begin = left;
  int end = right;
  int key = a[begin];
  int pivot = begin;
  while (begin < end)
  {
    while (begin < end && a[end] >= key)
    {
      --end;
    }
    a[pivot] = a[end];
    pivot = end;
    while (begin < end && a[begin] <= key)
    {
      ++begin;
    }
    a[pivot] = a[begin];
    pivot = begin;
  }
  pivot = begin;//当begin与end相遇,随便把begin和end的值给pivot
  a[pivot] = key;
  return pivot;
}
void QuickSortNonR(int* a, int n)
{
  Stack st;
  StackInit(&st);//初始化栈
  StackPush(&st, n - 1);//入数组最后一个元素下标
  StackPush(&st, 0);//入数组第一个元素下标
  while (!StackEmpty(&st))//当栈不为空我们就进入循环
  {
    int left = StackTop(&st);//取出栈顶元素给left
    StackPop(&st);//出栈 - 删除栈顶元素
    int right = StackTop(&st);
    StackPop(&st);
    int keyIndex =  PartSort(a, left, right);//使用挖坑法区间排序
    //[left, keyIndex - 1] keyIndex [keyIndex + 1, right]  -  分成子区间
    if (keyIndex + 1 < right)//因栈后进先出的特性,所以先入右区间
    {
      StackPush(&st, right);
      StackPush(&st, keyIndex + 1);
    }
    if (left < keyIndex - 1)
    {
      StackPush(&st, keyIndex - 1);
      StackPush(&st, left);
    }
  }
  StackDestory(&st);//销毁栈
}

🍍 3、归并排序(非递归实现)

归并排序非递归实现思想:

上期我们知道归并需要开辟一个数组,并且使用分治的算法来实现归并排序,而非递归版本我们的思路也是差不多,先让他们一个一个归并,然后两个两个归并,再接着四个四个一起归并,具体图解见下:

代码实现如下:

void MergeSortNonR(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc:");
    return;
  }
  int gap = 1;//gap为每组数据的个数,每次翻倍
  while (gap < n)
  {
    for (int i = 0; i < n; i += 2 * gap)
    {
      //可以看成 [i, i + gap - 1]  [i + gap, i + 2 * gap - 1]
      int begin1 = i, end1 = i + gap - 1;
      int begin2 = i + gap, end2 = i + 2 * gap - 1;
      //归并过程中右半区间有可能不存在!
      if (begin2 >= n)
        break;
      //归并过程中右半区间越界了,就修正下
      if (end2 >= n)
      {
        end2 = n - 1;
      }
      int index = i;
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (a[begin1] < a[begin2])
        {
          tmp[index++] = a[begin1++];
        }
        else
        {
          tmp[index++] = a[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[index++] = a[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[index++] = a[begin2++];
      }
      //拷贝进去
      for (int j = i; j <= end2; ++j)
      {
        a[j] = tmp[j];
      }
    }
    gap *= 2;
  }
  free(tmp);
}

本期到这里就结束了,相信你们已经对非递归快速排序和归并排序已经很了解了,非递归这两个在校招中经常会考,加油把!

相关文章
|
3月前
|
算法
【算法】栈
栈相关算法题,供参考,附有链接地址及板书
|
4月前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
159 61
|
4月前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
94 4
|
4月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
5月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
215 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
5月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
99 1
|
5月前
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
89 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
|
5月前
|
算法
数据结构与算法二:栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式
这篇文章讲解了栈的基本概念及其应用,并详细介绍了中缀表达式转换为后缀表达式的算法和实现步骤。
106 3
|
5月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
5月前
|
存储 算法 搜索推荐
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
算法进阶之路:Python 归并排序深度剖析,让数据排序变得艺术起来!
106 0

热门文章

最新文章