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

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

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

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

相关文章
|
18天前
|
算法
算法系列--递归(一)--与链表有关(上)
算法系列--递归(一)--与链表有关
27 0
|
4天前
|
算法
数据结构与算法-递归思想
数据结构与算法-递归思想
6 0
|
19天前
|
搜索推荐 C语言 C++
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
|
21天前
|
存储 算法 搜索推荐
【数据结构与算法】归并排序(详解:递归与非递归的归并排序 | 赠:冒泡排序和选择排序)
【数据结构与算法】归并排序(详解:递归与非递归的归并排序 | 赠:冒泡排序和选择排序)
|
21天前
|
算法 数据处理 C语言
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)
|
23天前
|
搜索推荐 算法 索引
【排序算法】深入解析快速排序(霍尔法&&三指针法&&挖坑法&&优化随机选key&&中位数法&&小区间法&&非递归版本)
【排序算法】深入解析快速排序(霍尔法&&三指针法&&挖坑法&&优化随机选key&&中位数法&&小区间法&&非递归版本)
|
28天前
|
搜索推荐 Java
Java基础(快速排序算法)
Java基础(快速排序算法)
24 4
|
10天前
|
机器学习/深度学习 人工智能 算法
基于DCT和扩频的音频水印嵌入提取算法matlab仿真
本文介绍了结合DCT和扩频技术的音频水印算法,用于在不降低音质的情况下嵌入版权信息。在matlab2022a中实现,算法利用DCT进行频域处理,通过扩频增强水印的隐蔽性和抗攻击性。核心程序展示了水印的嵌入与提取过程,包括DCT变换、水印扩频及反变换步骤。该方法有效且专业,未来研究将侧重于提高实用性和安全性。
|
25天前
|
机器学习/深度学习 算法
【MATLAB】GA_BP神经网络时序预测算法
【MATLAB】GA_BP神经网络时序预测算法
35 8
|
1天前
|
算法 TensorFlow 算法框架/工具
基于直方图的图像阈值计算和分割算法FPGA实现,包含tb测试文件和MATLAB辅助验证
这是一个关于图像处理的算法实现摘要,主要包括四部分:展示了四张算法运行的效果图;提到了使用的软件版本为VIVADO 2019.2和matlab 2022a;介绍了算法理论,即基于直方图的图像阈值分割,通过灰度直方图分布选取阈值来区分图像区域;并提供了部分Verilog代码,该代码读取图像数据,进行处理,并输出结果到&quot;result.txt&quot;以供MATLAB显示图像分割效果。