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

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

引言:

今天因为要写论文,所以现在有点迟了,并且此时是北京时间: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点多了,所以就这样吧!我要睡了。

相关文章
|
17天前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》图、查找、排序专题考点(含解析)
408考研——《数据结构》图,查找和排序专题考点选择题汇总(含解析)。
69 29
|
1月前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
37 10
|
1月前
|
搜索推荐 算法 数据处理
【C++数据结构——内排序】希尔排序(头歌实践教学平台习题)【合集】
本文介绍了希尔排序算法的实现及相关知识。主要内容包括: - **任务描述**:实现希尔排序算法。 - **相关知识**: - 排序算法基础概念,如稳定性。 - 插入排序的基本思想和步骤。 - 间隔序列(增量序列)的概念及其在希尔排序中的应用。 - 算法的时间复杂度和空间复杂度分析。 - 代码实现技巧,如循环嵌套和索引计算。 - **测试说明**:提供了测试输入和输出示例,帮助验证代码正确性。 - **我的通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了代码运行的测试结果。 通过这些内容,读者可以全面了解希尔排序的原理和实现方法。
59 10
|
1月前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
42 7
|
3月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
334 9
|
3月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
55 1
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
143 77
|
5天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
1月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
45 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
46 9

热门文章

最新文章