手撕排序算法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(范围)

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

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

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

相关文章
|
28天前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
18 1
|
1月前
|
搜索推荐 Java Go
深入了解计数排序算法
深入了解计数排序算法
31 4
|
1月前
|
搜索推荐 Java Go
深入了解快速排序算法
深入了解快速排序算法
28 2
|
28天前
|
算法 定位技术
数据结构与算法学习九:学习递归。递归的经典实例:打印问题、阶乘问题、递归-迷宫问题、八皇后问题
本文详细介绍了递归的概念、重要规则、形式,并展示了递归在解决打印问题、阶乘问题、迷宫问题和八皇后问题等经典实例中的应用。
34 0
|
1月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
1月前
|
算法 Python
Python算法编程:冒泡排序、选择排序、快速排序
Python算法编程:冒泡排序、选择排序、快速排序
|
16天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
1天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
2天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
3天前
|
存储 算法 决策智能
基于免疫算法的TSP问题求解matlab仿真
旅行商问题(TSP)是一个经典的组合优化问题,目标是寻找经过每个城市恰好一次并返回起点的最短回路。本文介绍了一种基于免疫算法(IA)的解决方案,该算法模拟生物免疫系统的运作机制,通过克隆选择、变异和免疫记忆等步骤,有效解决了TSP问题。程序使用MATLAB 2022a版本运行,展示了良好的优化效果。