手撕排序算法5:快速排序非递归版本和计数排序

简介: 手撕排序算法5:快速排序非递归版本和计数排序

一.算法剖析

1.非递归版本的设计

递归版本的缺陷:

1.效率低(早期),目前效率并不低

2.极端情况下(当递归的深度太深时)会导致栈溢出

递归深度太深,程序是没错的,但是栈的空间不够用

递归改非递归

1.直接改循环(简单)

2.借助数据结构栈模拟递归过程(复杂一点)

非递归的效率并没有递归的效率的很大的提升,只不过是防止了栈溢出

在这里我们使用数据结构中的栈来帮助我们实现非递归,改循环的话非常困难

因为C语言没有标准模板库,所以我们必须自己实现栈

栈写好之后,所以我们只需要知道怎么用就行,所以在这里只把头文件给大家了

Stack.h
typedef int STDataType;
typedef struct Stack
{
  STDataType* a;
  int top;
  int capacity;
}ST;
//初始化栈
void StackInit(ST* ps);
//销毁栈
void StackDestroy(ST* ps);
//入栈(压栈)
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//取栈顶元素
STDataType StackTop(ST* ps);
//求栈中数据个数
int StackSize(ST* ps);
//判断栈是否为空
bool StackEmpty(ST* ps);

2.在这里栈需要存什么,怎么利用

1.因为我们在这里使用数据结构的栈去替代函数栈帧来解决问题

那么我们就要让数据结构栈实现函数栈帧的功能

2.函数栈帧是用来存放局部变量的,所以我们要用栈来存放对应的局部变量

3.那么在这里的函数栈帧所存放的局部变量是什么呢?

大家可以看一下这张图片

下面我们来看一下栈的存放过程

二.代码实现

void print(int a[], int sz)
{
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", a[i]);
  }
}
int PartSort(int* a, int left, int right)//前后指针法
{
  int index = GetMidIndex(a, left, right);//三数取中优化版本
  Swap(&a[left], &a[index]);
  int keyi = left;
  int prev = left;
  int cur = left + 1;
  while (cur <= right)
  {
    if (a[cur] < a[keyi])
    {
      ++prev;
      Swap(&a[cur], &a[prev]);
    }
    ++cur;
  }
  Swap(&a[keyi], &a[prev]);
  return prev;
}
void QuickSortNonR(int* a, int n)
{
  ST st;
  StackInit(&st);
  StackPush(&st, n - 1);//栈是后进先出,要想后续先拿区间左侧,就要先入区间右侧,这里先入区间右侧
  StackPush(&st, 0);
  while (!StackEmpty(&st))
  {
    int left = StackTop(&st);//取区间左侧
    StackPop(&st);
    int right = StackTop(&st);
    StackPop(&st);
    int KeyIndex = 0;
    KeyIndex = PartSort(a, left, right);
    //[left,KeyIndex-1] KeyIndex [KeyIndex+1,right] 
    if (KeyIndex + 1 < right)//当右区间长度为1时不许入栈,因为已经长度为1的区间本来就是有序的
    {
      StackPush(&st, right);
      StackPush(&st, KeyIndex + 1);
    }
    if (KeyIndex - 1 > left)
    {
      StackPush(&st, KeyIndex - 1);
      StackPush(&st, left);
    }
  }
  StackDestroy(&st);
}

从这里我们就可以看出用数据结构栈去模拟递归实现也类似于递归实现

也就是说它本质上还是递归的思路和实际的操作步骤

唯一不同的就是递归是将局部变量开辟在栈帧上.

而数据结构的栈是在堆上开辟的

这个非递归也可以通过队列来实现,只不过这个题用队列来实现的话,过程就不想递归了,而是一层一层地去进行的那趟排序,类似于二叉树的层序遍历

而用栈模拟跟递归一样都类似于二叉树的先序遍历

非递归的效率并没有递归的效率的很大的提升,只不过是防止了栈溢出

稳定性

快速排序不具有稳定性

因为我们一般只会左边找大,右边找小再去交换,相等的数我们根本就不管

三.计数排序(属于非比较排序)

1.基本思想

1.利用数组的下标来映射,来统计每个数出现的次数

数组大小为原数据当中最大的值+1,

因为要能够映射最大的那个元素

要统计完以后使用次数就可以排序了

<<剑指offer>>中出现过

但是如果我们遇到的数据是这样的呢?

100 101 102 101 109 105

我们发现,原数组只有6个数,但是最大值缺为109,

那么我们如果申请出一个能容纳110个整数的数组的话是不是就有些大材小用了,

所以我们称上面的映射叫做绝对映射

而我们最好使用下面介绍的相对映射

与绝对映射的不同点在于:

1.我们只需要申请一个数组,这个数组的大小为max-min+1即可

2.绝对映射的下标位置是num,而相对映射的下标位置是num-min

3.绝对映射是还原下标,而相对映射是还原下标+min

下面我们来实现一下代码(相对映射的版本)

2.代码实现

void CountSort2(int* a, int n)
{
  int max = a[0];
  int min = a[0];
  int i = 0;
  for (i = 0; i < n; i++)
  {
    if (a[i] > max)
    {
      max = a[i];
    }
    if (a[i] < min)
    {
      min = a[i];
    }
  }
  int range = max - min + 1;//用range为相对映射
  int* tmp = (int*)malloc(sizeof(int) * range);
  if (tmp == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  //1.遍历初始化为0
  //for (i = 0; i <= max; i++)
  //{
  //  tmp[i] = 0;
  //}
  //2.用memset来初始化为0
  memset(tmp, 0, sizeof(int) * range);
  //统计次数
  for (i = 0; i < n; i++)
  {
    tmp[a[i] - min]++;//-min为相对映射
  }
  //开始排序
  int j = 0;
  for (i = 0; i <= max; i++)
  {
    int count = tmp[i];
    int num = i;
    while (count--)
    {
      a[j] = num + min;//+min为相对映射
      j++;
    }
  }
}

3.时间复杂度

时间复杂度:O(N+范围)

说明它适用于范围集中一组的整形数据排序

空间复杂度:O(范围)

不过计数排序只适用于整数排序

思想很巧,但是适用范围具有局限性

以上就是手撕排序算法的快排非递归版本和计数排序的讲解,希望能对大家有所帮助

相关文章
|
3月前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
81 4
|
3月前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
152 61
|
3月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
81 2
|
4月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
81 1
|
4月前
|
算法 定位技术
数据结构与算法学习九:学习递归。递归的经典实例:打印问题、阶乘问题、递归-迷宫问题、八皇后问题
本文详细介绍了递归的概念、重要规则、形式,并展示了递归在解决打印问题、阶乘问题、迷宫问题和八皇后问题等经典实例中的应用。
72 0
|
4月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
2天前
|
算法 数据安全/隐私保护 计算机视觉
基于FPGA的图像双线性插值算法verilog实现,包括tb测试文件和MATLAB辅助验证
本项目展示了256×256图像通过双线性插值放大至512×512的效果,无水印展示。使用Matlab 2022a和Vivado 2019.2开发,提供完整代码及详细中文注释、操作视频。核心程序实现图像缩放,并在Matlab中验证效果。双线性插值算法通过FPGA高效实现图像缩放,确保质量。
|
1月前
|
算法 数据安全/隐私保护 计算机视觉
基于Retinex算法的图像去雾matlab仿真
本项目展示了基于Retinex算法的图像去雾技术。完整程序运行效果无水印,使用Matlab2022a开发。核心代码包含详细中文注释和操作步骤视频。Retinex理论由Edwin Land提出,旨在分离图像的光照和反射分量,增强图像对比度、颜色和细节,尤其在雾天条件下表现优异,有效解决图像去雾问题。
|
1月前
|
算法 数据可视化 安全
基于DWA优化算法的机器人路径规划matlab仿真
本项目基于DWA优化算法实现机器人路径规划的MATLAB仿真,适用于动态环境下的自主导航。使用MATLAB2022A版本运行,展示路径规划和预测结果。核心代码通过散点图和轨迹图可视化路径点及预测路径。DWA算法通过定义速度空间、采样候选动作并评估其优劣(目标方向性、障碍物距离、速度一致性),实时调整机器人运动参数,确保安全避障并接近目标。
147 68
|
1月前
|
算法 数据安全/隐私保护
室内障碍物射线追踪算法matlab模拟仿真
### 简介 本项目展示了室内障碍物射线追踪算法在无线通信中的应用。通过Matlab 2022a实现,包含完整程序运行效果(无水印),支持增加发射点和室内墙壁设置。核心代码配有详细中文注释及操作视频。该算法基于几何光学原理,模拟信号在复杂室内环境中的传播路径与强度,涵盖场景建模、射线发射、传播及接收点场强计算等步骤,为无线网络规划提供重要依据。