【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序

简介: 【初阶数据结构】打破递归束缚:掌握非递归版快速排序与归并排序

一、非递归实现快速排序

void QuickSortNonR(int* a, int begin, int end)
{
  ST s;
  STInit(&s);
  STPush(&s, end);
  STPush(&s, begin);
  while (!STEmpty(&s))
  {
    int left = STTOP(&s);//先拿出来
    STPop(&s);
    int right= STTOP(&s);//后拿出来
    STPop(&s);
        //单躺排序,一次调整,得到中间keyi值,划分填入
    int keyi = PartSort2(a, left, right);   
    //[left,keyi-1]keyi[keyi+1,right]
    
    if (left < keyi - 1)
    {
      STPush(&s, keyi - 1);
      STPush(&s, left);
    }
        
    if (keyi + 1 < right)
    {
      STPush(&s, right);
      STPush(&s, keyi+1);
    }
  }
  STDestroy(&s);
}

过程解析:非递归实现快速排序也是需要通过快速排序思想来走的,基本思想是以某数值为基准值,不断将待排序集合分割成两组子序列,采用前序遍历的方法根 左子树 右子树,对于递归的过程中我们知道左子树会演变为新的根,也会分为新根 新左子树 新右子树,然后我们将采用栈来模拟递归的过程,由于栈的特点是后进先出合前序遍历的特性。这里后进代表着左子树,新出代表着该左子树为根演变新左子树和新右子树的过程。然后这里需要范围去定义根(整体范围)、左子树(左边范围)、右子树(右边范围)。这里左子树会不断分为新的左子树和右子树,也意味着产生新的范围,一般来说先取左边(在上)再取右边(在下),对应着右边先压栈,左边再压栈。

单躺排序结束会返回中间位置下标keyi帮整体划分为两部分,不断重复该过程。当条件不满足时,说明暂时不需要继续分为左子树和右子树,可能出现其他两种结果。第一种相等,说明只有一个数据;第二种大于。说明不存在数据,不需要压栈,等到栈为空结束排序。

二、非递归实现归并排序

由于快速排序采用是前序遍历满足栈相关数据结构的特性,然后归并排序属于后序排序因此不是通过使用栈区模拟非递归实现归并排序。如果采用栈去模拟实现非递归归并排序,由于归并排序不像快速排序不是出栈既排序,而是等到栈为空,开始归并排序,然而没有归并操作这种做法。

基本思路:整个序列分为以gap为子序列,结束条件为gap < n(gap = n - 1 表示归并最后一次),将两个有序子序列,比较大小尾插到新数组中,(子序列中只有一个数据时,默认是有序的)。对于两个有序子序归并在一起,形成一个新的有序子序列,当形成完新的子序列,复制到原数组中,不断重复该操作。

解决办法:对此应当设置变量gap为归并每组数据个数,首先gap设为1,以二的幂次方增长。

int begin1=i,end1=i+gap-1;
int begin2=i+gap,end2=i+2*gap-1;
[begin1,end1][begin2,end2]归并

注意点:这里gap是二的幂次方增长,对于两个子序列匹配时,规定数量是固定的,可能会出现越界访问,所以需要注意end1,begin2和end2的值。

对于end2只需要将修改为临界值就行,而对于end1和begin2则不参与进程。完成一趟for循环,需要拷贝到原数组中,这样子的话不参与进程的序列,将被随机值替代.

if (end1 >= n || begin2 >= n)  break;
if (end2 >= n)  end2 = n - 1;
void MergeSortNonR(int* a, int n)
{
    int* tmp = (int*)malloc(n * sizeof(int));
    if (tmp == NULL)
    {
        perror("malloc fail!!!");
        return;
    }
    int gap = 1;
    while (gap < n)
    {
        printf("gap:%2d->", gap);
        for (int i = 0; i < n; i += 2 * gap)//调正部分是跳过两组子序列,去到新的两组子序列中
        {
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + 2 * gap - 1;
            //printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);
            if (end1 >= n || begin2 >= n)
            {
                break;
            }
            if (end2 >= n)
            {
                end2 = n - 1;
            }
            printf("[%2d,%2d][%2d, %2d] ", begin1, end1, begin2, end2);
      
            //上面是传值
            int j = begin1;
      
            //下面是插入,这里j下标很有讲究
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (a[begin1] < a[begin2])
                {
                    tmp[j++] = a[begin1++];
                }
                else
                {
                    tmp[j++] = a[begin2++];
                }
            }//还有部分数据没有放入新数组中
            while (begin1 <= end1)
            {
                tmp[j++] = a[begin1++];
            }
            while (begin2 <= end2)
            {
                tmp[j++] = a[begin2++];
            }
            memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));//可能是右边归并
        }
        printf("\n");
        gap *= 2;
    } 
    free(tmp);
}

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二初阶数据结构笔记,希望对你在学习初阶数据结构中有所帮助!


相关文章
|
11月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
234 59
|
6月前
|
算法 搜索推荐
快速排序-数据结构与算法
快速排序(Quick Sort)是一种基于分治法的高效排序算法。其核心思想是通过选择基准(pivot),将数组划分为左右两部分,使得左侧元素均小于基准,右侧元素均大于基准,然后递归地对左右两部分进行排序。时间复杂度平均为 O(n log n),最坏情况下为 O(n²)(如数组已有序)。空间复杂度为 O(1),属于原地排序,但稳定性不佳。 实现步骤包括编写 `partition` 核心逻辑、递归调用的 `quickSort` 和辅助函数 `swap`。优化方法有随机化基准和三数取中法,以减少最坏情况的发生。
367 13
|
9月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
9月前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
219 10
|
9月前
|
搜索推荐 C++
【C++数据结构——内排序】快速排序(头歌实践教学平台习题)【合集】
快速排序是一种高效的排序算法,基于分治策略。它的主要思想是通过选择一个基准元素(pivot),将数组划分成两部分。一部分的元素都小于等于基准元素,另一部分的元素都大于等于基准元素。然后对这两部分分别进行排序,最终使整个数组有序。(第一行是元素个数,第二行是待排序的原始关键字数据。本关任务:实现快速排序算法。开始你的任务吧,祝你成功!
207 7
|
12月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
328 1
|
12月前
|
算法 定位技术
数据结构与算法学习九:学习递归。递归的经典实例:打印问题、阶乘问题、递归-迷宫问题、八皇后问题
本文详细介绍了递归的概念、重要规则、形式,并展示了递归在解决打印问题、阶乘问题、迷宫问题和八皇后问题等经典实例中的应用。
246 0
|
4月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
65 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
9月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
352 77

热门文章

最新文章