直接选择排序,堆排序,冒泡排序,递归快速排序、hoare版本、挖坑法、lomuto前后指针,非递归快速排序

目录

一、选择排序

1、直接选择排序

2、堆排序

二、交换排序

1.冒泡排序

2、递归快速排序

2.1、hoare版本

2.2、挖坑法

2.3、lomuto前后指针

3、非递归快速排序

一、选择排序

每⼀次从待排序的数据元素中选出最⼩(或最⼤)的⼀个元素,存放在序列的起始位置,直到全部待 排序的数据元素排完 。

1、直接选择排序

在元素集合 arrayi--arrayn-1 中选择关键码最⼤(⼩)的数据元素

若它不是这组元素中的最后⼀个(第⼀个)元素,则将它与这组元素中的最后⼀个(第⼀个)元素交换

在剩余的 arrayi--arrayn-2(arrayi+1--arrayn-1) 集合中,重复上述步 骤,直到集合剩余 1 个元素

复制代码
void SelectSort(int* a, int n)
{
 int begin = 0;
 end = n - 1;
 while (begin < end)
 {
  int mini = begin, maxi = begin;
  for (int i = begin; i <= end; i++)
  {
   if (a[i] > a[maxi])
   {
    maxi = i;
   }
   if (a[i] < a[mini])
   {
    mini = i;
   }
  }
  if (begin == maxi)
  {
   maxi = mini;
  }
  swap(&a[mini], &a[begin]);
  swap(&a[maxi], &a[end]);
  ++begin;
  --end;
 }
}

直接选择排序思考⾮常好理解,但是效率不是很好。实际中很少使⽤

时间复杂度: O(n ^ 2) --->这是最坏的情况,一般是达不到N ^ 2 的情况

空间复杂度: O(1)

2、堆排序

堆排序(Heapsort)是指利⽤堆积树(堆)这种数据结构所设计的⼀种排序算法,它是选择排序的⼀ 种。它是通过堆来进⾏选择数据。需要注意的是排升序要建⼤堆,排降序建⼩堆。

复制代码
void HeapSort(int * arr, int n)
{
 //建堆 - 向下调整算法建堆
 for(int i = (n-1-1) / 2;i >= 0;i--)
 {
  AdjustDown(arr,i,n);
 }
 //堆排序
 int end = n -1;
 while(end > 0)
 {
  Swap(&arr[0],&arr[end]);
  AdjustDown(arr,0,end);
  end--;
 }
}

时间复杂度:O(nlogn)

二、交换排序

所谓交换,就是根据序列中两个记录键值的⽐较结果来对换这两个记录在序列中的位置

交换排序的特点是:将键值较⼤的记录向序列的尾部移动,键值较⼩的记录向序列的前部移动

1.冒泡排序

冒泡排序是⼀种最基础的交换排序。之所以叫做 冒泡排序,因为每⼀个元素都可以像⼩⽓泡⼀样,根据⾃⾝⼤⼩⼀点⼀点向数组的⼀侧移动。

复制代码
void BubbleSort(int* a, int n)
{
 int exchange = 0;
 for (int i = 0; i < n; i++)
 {
  for (int j = 0; j <n-i-1 ; j++)
  {
   if (a[j] > a[j + 1])
   {
    exchange = 1;
    swap(&a[j], &a[j + 1]);
   }
  }
  if (exchange == 0)
  {
  break;
  }
 }
}

时间复杂度: O(n ^ 2)

空间复杂度: O(1)

2、递归快速排序

快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法,其基本思想为:任取待排序元素 序列中的某元素作为基准值,按照该排序码将待排序集合分割成两⼦序列,左⼦序列中所有元素均⼩ 于基准值,右⼦序列中所有元素均⼤于基准值,然后最左右⼦序列重复该过程,直到所有元素都排列 在相应位置上为⽌。

主框架:

复制代码
void QuickSort(int* a, int left, int right)
{
 if (left >= right) 
 {
  return;
 }
 //_QuickSort⽤于按照基准值将区间[left,right)中的元素进⾏划分
 int meet = _QuickSort(a, left, right);
 QuickSort(a, left, meet - 1);
 QuickSort(a, meet + 1, right);
}

2.1、hoare版本

1)创建左右指针,确定基准值

2)从右向左找出⽐基准值⼩的数据,从左向右找出⽐基准值⼤的数据,

当left<=right找到之后,左右指针数据交换,进⼊下次循环

当left>right,key和right数据交换

问题1:为什么跳出循环后right位置的值⼀定不⼤于key?

当 left > right 时,即right⾛到left的左侧,⽽left扫描过的数据均不⼤于key,因此right此时指 向的数据⼀定不⼤于key

问题2:为什么left 和 right指定的数据和key值相等时也要交换?

相等的值参与交换确实有⼀些额外消耗。实际还有各种复杂的场景,假设数组中的数据⼤量重复时, ⽆法进⾏有效的分割排序。

当数组中数据全部为重复的数据或本身就是有序的数组,函数找基准值递归了n次,快速排序的时间复杂度会提高为O(N ^ 2),所以一定不能相等

复制代码
int _QuickSort(int* a, int left, int right)
{
 int begin = left;
 int end = right;
 int keyi = left;
 ++left;
 while (left <= right)
 {
  // right从右往左找基准值⼩的
  while (left <= right && a[right] > a[keyi])
  {
   --right;
  }
  // left从左往右找基准值大的
  while (left <= right && a[left] < a[keyi])
  {
   ++left;
  }
  if (left <= right)
  {
   swap(&a[left++], &a[right--]);
  }
 }
 swap(&a[keyi], &a[right]);
 return right;
}

时间复杂度:O(nlogn)

2.2、挖坑法

创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新 的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结 束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

复制代码
int _QuickSort(int* a, int left, int right)
{
 int mid = a[left];
 int hole = left;
 int key = a[hole];
 while (left < right)
 {
  while (left < right && a[right] >= key)
  {
   --right;
  }
  a[hole] = a[right];
  hole = right;
  while (left < right && a[left] <= key)
  {
   ++left;
  }
  a[hole] = a[left];
  hole = left;
 }
 a[hole] = key;
 return hole;
}

2.3、lomuto前后指针

创建前后指针,从左往右找⽐基准值⼩的进⾏交换,使得⼩的都排在基准值的左边。

初始时,prev指针指向序列开头, cur指针指向prev指针的后一个位置

创建两个变量prev,cur,cur从左往右找比基准值要小的数据,prev和cur交换

cur探路,找比基准值要小的数据

cur找到了比基准值小的数据:++prev,prev和cur交换,cur++

cur未找到比基准值小的数据:cur++

复制代码
int _QuickSort(int* a, int left, int right)
{
 int prev = left;
 cur = left + 1;
 int key = left;
 while (cur <= right)
 {
  if (a[cur] < a[key] && ++prev != cur)
  {
   swap(&a[cur], &a[prev]);
  }
  ++cur;
 }
 swap(&a[key], &a[prev]);
 return prev;
}

时间复杂度: O(nlogn)

空间复杂度: O(logn)

3、非递归快速排序

⾮递归版本的快速排序需要借助数据结构:栈

复制代码
-----默认可以使用栈和栈相关算法-----
void QuickSortNonR(int* a, int left, int right)
{
 ST st;
 STInit(&st);
 STPush(&st, right);
 STPush(&st, left);
 while (!STEmpty(&st))
 {
  //取栈顶两次
  int begin = STTop(&st);
  STPop(&st);
  int end = STTop(&st);
  STPop(&st);
  // 单趟 - [begin,end]找基准值
  int keyi = begin;
  int prev = begin;
  int cur = begin + 1;
  while (cur <= end)
  {
   if (a[cur] < a[keyi] && ++prev != cur)
   Swap(&a[prev], &a[cur]);
   ++cur;
  }
  Swap(&a[keyi], &a[prev]);
  keyi = prev;
  // begin keyi end
  // 左序列:[begin, keyi-1] 右序列:keyi [keyi+1, end]
  if (keyi + 1 < end)
  {
   STPush(&st, end);
   STPush(&st, keyi + 1);
  }
  if (begin < keyi-1)
  {
   STPush(&st, keyi-1);
   STPush(&st, begin);
  }
 }
 STDestroy(&st);
}
相关推荐
SomeOtherTime12 分钟前
Rust集合(AI回答)
开发语言·后端·rust
曦樂~30 分钟前
【Web Development】03JavaScript 基础和事件
开发语言·前端·javascript
郝学胜-神的一滴35 分钟前
LangChain Skill 实现(渐进披露 On-Demand Skill,LangChain 官方 Deep Agent Skill 模式)
开发语言·python·langchain·numpy
俊俊几年的40 分钟前
动态规划(Dynamic Programming)详解:从入门到 LeetCode Hot 100
linux·c++·算法·leetcode·嵌入式·动态规划·c
程序员Query1 小时前
day01-认识Go语言 + golang环境的安装
开发语言·后端·golang
小雉系统10 小时前
BcoreOS安全通信架构:FsSession 抗算法后门安全设计
算法·安全·架构
§ꦿCFོ༉11 小时前
嵌入式八股文—C语言入门学习(四)
c语言·开发语言·学习
投图匠11 小时前
十种不同风格的 CTP接口 框架
开发语言·python