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

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

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

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

相关文章
|
1月前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
57 4
|
23天前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
115 61
|
1月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
50 2
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
33 1
|
2月前
|
算法 定位技术
数据结构与算法学习九:学习递归。递归的经典实例:打印问题、阶乘问题、递归-迷宫问题、八皇后问题
本文详细介绍了递归的概念、重要规则、形式,并展示了递归在解决打印问题、阶乘问题、迷宫问题和八皇后问题等经典实例中的应用。
53 0
|
2月前
|
存储 搜索推荐 算法
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
【排序算法(二)】——冒泡排序、快速排序和归并排序—>深层解析
|
17天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
23天前
|
机器学习/深度学习 算法 Serverless
基于WOA-SVM的乳腺癌数据分类识别算法matlab仿真,对比BP神经网络和SVM
本项目利用鲸鱼优化算法(WOA)优化支持向量机(SVM)参数,针对乳腺癌早期诊断问题,通过MATLAB 2022a实现。核心代码包括参数初始化、目标函数计算、位置更新等步骤,并附有详细中文注释及操作视频。实验结果显示,WOA-SVM在提高分类精度和泛化能力方面表现出色,为乳腺癌的早期诊断提供了有效的技术支持。
|
3天前
|
供应链 算法 调度
排队算法的matlab仿真,带GUI界面
该程序使用MATLAB 2022A版本实现排队算法的仿真,并带有GUI界面。程序支持单队列单服务台、单队列多服务台和多队列多服务台三种排队方式。核心函数`func_mms2`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。
|
11天前
|
存储 算法
基于HMM隐马尔可夫模型的金融数据预测算法matlab仿真
本项目基于HMM模型实现金融数据预测,包括模型训练与预测两部分。在MATLAB2022A上运行,通过计算状态转移和观测概率预测未来值,并绘制了预测值、真实值及预测误差的对比图。HMM模型适用于金融市场的时间序列分析,能够有效捕捉隐藏状态及其转换规律,为金融预测提供有力工具。