手撕各种排序(中)

简介: 手撕各种排序(中)

手撕各种排序(上):https://developer.aliyun.com/article/1389400

🌙选择排序

      基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。咱们看看图解:


 


这里我们看一个动态的图解:

 


上代码:

//选择排序测试
void SelectSort(int* a, int n)
{
  //初始化第一个元素 和 最后一个元素
  int begin = 0;
  int end = n - 1;
  while (begin < end)
  {
    //选出最大值和最小值
    int mini = begin, maxi = begin;
    for (int i = begin + 1; i <= end; i++)
    {
      if (a[i] > a[maxi])
      {
        maxi = i;
      }
      if (a[i] < a[mini])
      {
        mini = i;
      }
    }
    //最小值和最初的元素交换
    Swap(&a[begin], &a[mini]);
    //如果max被换走就需要重新赋值
    if (maxi == begin)
    {
      maxi = mini;
    }
    //最大值和最末的元素交换
    Swap(&a[end], &a[maxi]);
    ++begin;
    --end;
  }
}

注意事项:

💦1.这里先找到最小值和最大值,然后交换到最前面和最后面,一次进行。

💦2. 如果最大值被交换后,需要从新赋值。

时间复杂度:O(N²)

空间复杂度:O(1)

稳定性:不稳定

复杂性:简单

🌙堆排序

      堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。咱们看看图解:



再看看动态的图解:



上代码:

//堆排序测试
//实现向下调整建大堆
void AdjustDown(int* a, int n, int parent)
{
  //孩子和父亲的关系 
  int child = parent * 2 + 1;
  while (child < n)
  {
    //找出小的那个孩子
    if (child + 1 < n && a[child + 1] < a[child])
    {
      ++child;
    }
    //实现向下调整元素
    if (a[child] < a[parent])
    {
      //元素交换
      Swap(&a[child], &a[parent]);
      // 继续往下调整
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
//堆排序测试
void HeapSort(int* a, int n)
{
  //向下调整建堆
  for (int i = (n - 1 - 1) / 2; i >= 0; i--)
  {
    //实现向下调整建大堆
    AdjustDown(a, n, i);
  }
  int end = n - 1;
  while (end > 0)
  {
    //元素交换
    Swap(&a[0], &a[end]);
    //实现向下调整建大堆
    AdjustDown(a, end, 0);
    --end;
  }
}

注意事项:

💦1.首先我们需要建大堆(以在二叉树讲解咯),交换,再建大堆

💦2. 每交换一个元素都需要再建一次大堆。

时间复杂度:O(NlogN)

空间复杂度:O(1)

稳定性:不稳定

复杂性:复杂

🌙快排

       在快排这个板块中我将讲述四个方法,希小伙伴都能掌握。

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

💫Hoare方法

      在动态图中,key称为基准值,默认把基准值定义在数组的首元素上,其次为了加快遍历的速度,需要用到两个变量分别去对应数组的首尾部分,L处在数列的首部,它需要从左往右寻找比Keyi位置的值大的,遇到后就停下来,没遇到就一直走;R处在数列的尾部,它需要从右往左去寻找比keyi位置的值小的,也是遇到后就停下来,没遇到就一直走。

       当L与R都遇到停下来后开始互换位置,然后继续遍历,直到L==R时,双方都不会再走了,因为它们走到了同一个位置,这个位置被称为它俩的相遇点,之后便需要将keyi位置的值与相遇点的值互换位置,这样keyi位置的值就放到了中间,成为了数组的中心——基准值,它意味着将数组分为两个子序列,左序列全是小于Keyi的值,右序列全是大于Keyi的值,这样便可以利用递归,一层一层再对左右两个子序列进行相同的步骤——将一个大的无序序列转化为多个小的序列,从而进行排序,最终排列为完全有序的序列。

 



上代码:

//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
  //中间值
  int mid = (left + right) / 2;
  //三个数比较找出中间值
  if (a[left] < a[mid])
  {
    if (a[mid] < a[right])
    {
      return mid;
    }
    else if (a[left] > a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
  else
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if (a[left] < a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}
// 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
int PartSort1(int* a, int left, int right)
{
  //找中间值(相对)
  int midi = GetMidi(a, left, right);
  //把最左边(相对)跟中间值(相对)换
  Swap(&a[left], &a[midi]);
  //定位最左边(相对)
  int keyi = left;
  while (left < right)
  {
    //找小
    while (left < right && a[right] >= a[keyi])
    {
      --right;
    }
    //找大
    while (left < right && a[left] <= a[keyi])
    {
      ++left;
    }
    //左右交换
    Swap(&a[left], &a[right]);
  }
  //此时左边走到中间了,开始交换
  Swap(&a[keyi], &a[left]);
  //返回
  return left;
};

注意事项:

💦1.首先先找到中间值。

💦2.再和最左边元素互换

💦3.左边找比(中间值)大的数,右边找(中间值)小的数,然后左右值交换。

💦4.此时左边走到中间了,开始交换

💦5.上述进行循环,知道排序完成

💫挖坑法

大家就看一下下面图解:



//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
  //中间值
  int mid = (left + right) / 2;
  //三个数比较找出中间值
  if (a[left] < a[mid])
  {
    if (a[mid] < a[right])
    {
      return mid;
    }
    else if (a[left] > a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
  else
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if (a[left] < a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}
//挖坑法
int PartSort2(int* a, int left, int right)
{
  //找中间值(相对)
  int midi = GetMidi(a, left, right);
  //把最左边(相对)跟中间值(相对)换
  Swap(&a[left], &a[midi]);
  //定位最左边(相对)
  int key = a[left];
  //保存key值以后,左边形成第一个坑
  int hole = left;
  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;
}

💫前后指针

      通过创建两个指针,prev指针指向数组序列首元素位置,cur指向prev的下一个位置,cur通过遍历去寻找比key基准值小的,若找到了,还得看prev的下一个位置是否为cur所处的位置,若prev的下一个位置确实为cur所处位置,则将cur指向的值与prev指向的值进行互换,若prev的下一个位置不是cur所处位置,则cur继续往后遍历,直到cur遍历结束,最后要将key基准值与prev指向的值进行互换,最终确认基准值处于数组序列的中间位置。



//三数取中 (找出中间值)
int GetMidi(int* a, int left, int right)
{
  //中间值
  int mid = (left + right) / 2;
  //三个数比较找出中间值
  if (a[left] < a[mid])
  {
    if (a[mid] < a[right])
    {
      return mid;
    }
    else if (a[left] > a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
  else
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if (a[left] < a[right])
    {
      return left;
    }
    else
    {
      return right;
    }
  }
}
//前后指针
int PartSort3(int* a, int left, int right)
{
  //找中间值(相对)
  int midi = GetMidi(a, left, right);
  //把最左边(相对)跟中间值(相对)换
  Swap(&a[left], &a[midi]);
  //定义指针开头
  int prev = left;
  //定义指针开头后一个
  int cur = prev + 1;
  int keyi = left;
  while (cur <= right)
  {
    if (a[cur] < a[keyi] && ++prev != cur)
    {
      Swap(&a[prev], &a[cur]);
    }
    ++cur;
  }
  //交换
  Swap(&a[prev], &a[keyi]);
  return prev;
}

💫快排非递归

1.初始化我们的栈。

2.入栈把我们的开始的left==0  right==n-1;

3.进入我们的循环体循环体进入的条件是判断栈中是否还有数值如果有数值的化什么其中的数值对应的范围还是没有序的就需要出栈(这个时候就需要进行出栈(注意栈的数值的左右范围的对应))进行我们的挖坑排序(对于挖坑我们应该把key返回出来通过key的数值进行我们再一次的入栈操作同时我们的范围)。

3.1[left   key-1] 和[key+1   right]   这样的范围满足条件才能继续push  之后pop进行我们的排序;

4如果说我们的循环体结束了的话我们的数组就一定有序!



前面已经讲述了栈,这里就不再实现栈了

//快排非递归(采用栈的形式)(先进后出)
void QuickSortNonR(int* a, int begin, int end)
{
  //定义
  ST st;
  //初始化
  STInit(&st);
  //添加元素
  STPush(&st, end);
  STPush(&st, begin);
  while (!STEmpty(&st))
  {
    //剥离元素  让left取先入但是后出的元素(区间的左边)
    int left = STTop(&st);
    STPop(&st);
    //让right取栈顶元素(是我们后入的区间的右边)
    int right = STTop(&st);
    STPop(&st);
    // 右边找小,左边找大,交换,把左边的值跟a[keyi]交换
    int keyi = PartSort1(a, left, right);
    //分割成段 
    // [lefy,keyi-1] keyi [keyi+1, right]
    if (keyi + 1 < right)
    {
      //添加元素
      STPush(&st, right);
      STPush(&st, keyi + 1);
    }
    if (left < keyi - 1)
    {
      //添加元素
      STPush(&st, keyi - 1);
      STPush(&st, left);
    }
  }
  //销毁
  STDestroy(&st);
}


手撕各种排序(下):https://developer.aliyun.com/article/1389402

目录
相关文章
|
算法 C语言 开发者
C语言手撕实战代码_单链表
本文档详细介绍了使用C语言实现单链表的各种基本操作和经典算法。内容涵盖单链表的构建、插入、查找、合并及特殊操作,如头插法和尾插法构建单链表、插入元素、查找倒数第m个节点、合并两个有序链表等。每部分均配有详细的代码示例和注释,帮助读者更好地理解和掌握单链表的编程技巧。此外,还提供了判断子链、查找公共后缀等进阶题目,适合初学者和有一定基础的开发者学习参考。
136 2
|
7月前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
7月前
|
传感器 人工智能 数据可视化
数字孪生高效赋能,打造水利新质生产力
数字孪生水利运用云计算、大数据、AI、实景三维等技术,实现江河水库等水利工程的可视化展示与智能化模拟。通过三维可视化和实时数据映射,平台提供智能感知、分析、预测和预演功能,支持监测预警、调度优化及灾害预防,助力提升水利管理水平,保障水安全。
|
8月前
|
JavaScript 前端开发 数据安全/隐私保护
npm账户需要登录问题npm error probably out of date. To correct this please try logging in again with优雅草央千澈解决方案
npm账户需要登录问题npm error probably out of date. To correct this please try logging in again with优雅草央千澈解决方案
312 0
npm账户需要登录问题npm error probably out of date. To correct this please try logging in again with优雅草央千澈解决方案
|
存储 Java 索引
Java ArrayList操作指南:如何移除并返回第一个元素
通过上述方法,你可以方便地从Java的 `ArrayList` 中移除并返回第一个元素。这种操作在日常编程中非常常见,是处理列表时的基本技能之一。希望这篇指南能帮助你更好地理解和运用Java的 `ArrayList`。
151 4
|
机器学习/深度学习 人工智能 自然语言处理
|
芯片 内存技术
NUC980 添加XT25BF256BWSIG spi-nor flash
NUC980 添加XT25BF256BWSIG spi-nor flash
289 2
面试必考: 手撕代码系列(一)
面试必考: 手撕代码系列(一)
325 0
|
关系型数据库 MySQL Linux
Linux下mysql添加用户并授权数据库权限
Linux下mysql添加用户并授权数据库权限
734 0
|
消息中间件 缓存 监控
小记 | 一周上线百万级高并发系统
本文是鱼皮在腾讯实习期间,从零开始一周紧急上线百万高并发系统的相关经验、思路及感悟,分享给大家。花 5 分钟阅读本文,你将收获:1. 加深对实际工作环境、工作状态的了解2. 学习高并发系...
806 0