数据结构之排序【快速排序和归并排序的非递归代码实现及分析】

简介: 数据结构之排序【快速排序和归并排序的非递归代码实现及分析】

引言:

今天因为要写论文,所以现在有点迟了,并且此时是北京时间:2022/12/28/1:41 ,我发现晚睡我真的是专业的,当然睡觉我也是专业的,懂的都懂,现在有点迟加上天大寒,手指不可屈伸,所以我们的引言就这样啦!但是这个位置我还想要记录一下:今天我的搜狗输入法成功进入20万字了,电脑上自带的键盘都要给我敲烂了,我已经能听出来空格键的声音跟以前不一样了,但是还可以用,本来是打算在20万字之时就换一个键盘的,但是……一言难尽,所以暂时我们继续用电脑上的键盘,等我们到40万字的时候再换吧(想象一下,等40万字,可能……)。上篇博客我们讲到了快速排序的递归方法实现,今天我们就讲一下快速排序的非递归实现。


1.快排实现(非递归)

有的人会问,我们不是已经实现了快排吗?快排我已经学会了啊,为什么还要区分递归和非递归呢?

原因就是递归的缺陷,递归是有一定的缺陷的。


递归的缺陷:建立栈帧,效率低(但是这个对于递归基本不算是缺陷) ,在一些极端情况下,栈帧深度(因为调用一个函数就会创建一个栈帧)太深,会导致栈溢出(这个就是递归的真正的大缺陷)


所以我们就要想一些方法来解决这个缺陷,当然解决方法有很多,比如增加栈的空间,使它不会栈溢出,但是这好像是不可能的,所以最好的方法就是不使用递归(当然是在某些特定的情况下不使用)

所以我们就可以想到递归的替换方法,我相信大多数人第一时间都会想到的是用循环来替代递归,但是今天我们不使用循环来替代递归,今天我们使用数据结构中的栈来模拟递归的过程,当然前提是有一个栈,所以我们要先自己实现一个栈。


代码如下:

void Swap(int* child, int* parent)
{
  int tmp = *child;
  *child = *parent;
  *parent = tmp;
}
int PartQuickSort1(int* arr, int left, int right)
{
  int index = GetMidIndex(arr, left, right);
  Swap(&arr[left], &arr[index]);
  int begin = left;
  int end = right;
  int pivot = begin;
  int key = arr[begin];
  while (begin < end)
  {
    while (begin < end && arr[end] >= key)
    {
      end--;
    }
    arr[pivot] = arr[end];
    pivot = end;
    while (begin < end && arr[begin] <= key)
    {
      begin++;
    }
    arr[pivot] = arr[begin];
    pivot = begin;
  }
  pivot = begin;
  arr[pivot] = key;
  return pivot;
}
void QuickSortNonR(int* arr, int n)
{
  //此时因为这个需要有一个栈需要我们去拿以前文件中的栈的实现的代码
  //所以为了方便,我们把这个非递归的快排的实现给拿到外面去
  ST st;
  StackInit(&st);
  //栈实现的原理:栈里面的区间就是需要被单趟排序的
  StackPush(&st, n - 1);//根据栈的原理(后进先出),当我们想要先出我们的左,我们就不应该先入左,而是应该要先入右,此时的右也就是我们的n-1(传参传的是闭区间的话)
  StackPush(&st, 0);//根据栈的原理,并且同理的情况下,这边我们要出的是右,所以我们就应该要入左,也就是入0
  while (!StackEmpty(&st))//只要栈里面有数据就不结束,只有当我的栈里面的数据为空的时候才停止
  {
    //此时如上面两步,我已经把我的左子数组和右子数组给放进去了
    int left = StackTop(&st);//数组放进去之后,我就开始出数组中的元素(并且此时因为先进后出原理,所以此时我们栈中出的第一个元素就是我的要放在arr[0]的元素了)
    StackPopt(&st);//出完元素要删除掉,因为是栈,只有这样我们才可以出剩下的元素
    int right = StackTop(&st);
    StackPopt(&st);
    int keyIndex = PartQuickSort1(arr, left, right);
    //此时有了上面这个挖坑法的代码,我们此时就可以拿到中间的那个key了
    //所以我们此时就把这个数组给分为了左子区间和右子区间
    //[left,keyIndex-1][keyIndex][keyIndex+1,right]
    if (keyIndex + 1 < right)
    {
      //此时就是表示右半区间还存在,元素没有排序完,我们就还需要入栈
      StackPush(&st, right);
      StackPush(&st, keyIndex + 1);
    }
    if (left < keyIndex - 1)//这个表示此时的栈
    {
      //因为上述的代码left是会++的,所以只要left没有走到key的位置处,就是表示此时left中还有元素没有出完
      StackPush(&st, keyIndex-1);
      StackPush(&st, left);
    }
  }
  StackDestory(&st);
}

此时的void QuickSortNonR(int* arr, int n)这个代码中的内容就是不使用递归实现快排的代码,而int PartQuickSort1(int* arr, int left, int right)而这个函数就是一个普通的挖坑法获取key的代码而已,所以重点是void QuickSortNonR(int* arr, int n)这个函数中的内容,但是前提是我们要有一个数据结构中的栈,所以我们此时就还要实现一个栈出来(C语言的不好的地方,假如是C++或者Java就可以不用,因为它们有自己独立的栈可以供我们直接调用),所以关于栈的知识可以看我以前的博客,讲解的都是非常的到位的。


2.归并排序(非递归)

原理如图:

38.png


代码实现:

void MergeSortNonR(int* arr, int n)
{
  //归并的思想,我肯定是要有一个临时数组的
  int* tmp = (int*)malloc(sizeof(int) * n);
  int gap = 1;//表示每一组需要进行归并的数据的个数
  while (gap < n)
  {
    int i;
    for (i = 0; i < n; i += 2 * gap)//由图得到
    {
      //[i,i+gap-1] [i+gap,i+gap*2-1]
        //归并算法的代码实现:
      int begin1 = i;
      int end1 = i + gap - 1;
      int begin2 = i + gap;
      int end2 = i + gap * 2 - 1;
      int index = i;
      //此时因为数据的个数,不一定按照整数倍,所以在划分组的时候就可能导致越界,或者不存在
      //所以需要修正一下
      if (begin2 >= n)
      {
        begin2 = n + 1;
        end2 = n;
      }
      //end2越界也要修正一下
      if (end1 >= n)
      {
        end1 = n - 1;
      }
      //end2越界,需要修正后再归并
      if (end2 >= n)
      {
        end2 = n - 1;
      }
      while (begin1 <= end1 && begin2 <= end2)
      {
        if (arr[begin1] < arr[begin2])
        {
          tmp[index++] = arr[begin1++];
        }
        else
        {
          tmp[index++] = arr[begin2++];
        }
      }
      while (begin1 <= end1)
      {
        tmp[index++] = arr[begin1++];
      }
      while (begin2 <= end2)
      {
        tmp[index++] = arr[begin2++];
      }
    }
    //将新数组中的元素拷贝回原数组
    int j;
    for (j = 0; j < n; j++)
    {
      arr[j] = tmp[j];
    }
    gap *= 2;
  }
  free(tmp);
}


3.测试代码

39.png


时间有点急了,已经快3点多了,所以就这样吧!我要睡了。

相关文章
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
27 1
|
2月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
34 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
2月前
|
存储 搜索推荐 算法
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
【用Java学习数据结构系列】七大排序要悄咪咪的学(直接插入,希尔,归并,选择,堆排,冒泡,快排)以及计数排序(非比较排序)
29 1
|
2月前
|
存储
【数据结构】二叉树链式结构——感受递归的暴力美学
【数据结构】二叉树链式结构——感受递归的暴力美学
|
2月前
|
算法
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
蓝桥杯宝藏排序 | 数据结构 | 快速排序 归并排序
|
2月前
|
算法 定位技术
数据结构与算法学习九:学习递归。递归的经典实例:打印问题、阶乘问题、递归-迷宫问题、八皇后问题
本文详细介绍了递归的概念、重要规则、形式,并展示了递归在解决打印问题、阶乘问题、迷宫问题和八皇后问题等经典实例中的应用。
48 0
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
159 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
26 1
|
18天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
39 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。

热门文章

最新文章