【排序算法 下】带你手撕常见排序 (冒泡,快排,归并排序) (动图详解)

简介: 【排序算法 下】带你手撕常见排序 (冒泡,快排,归并排序) (动图详解)

95cfa829fcc324393942dd2d9642970b_060bad25320648609051cb9a6d622b8f.png

“只要有花可开,就不允许生命与黯淡为伍。”


前言:

承接上篇,继续带大家手撕常见排序算法,这次讲剩余的两类:交换排序和归并排序。

注:

这篇博客属于数据结构内容,建议学习完C语言的小伙伴食用~


目录

🐨Part1:交换排序

1.冒泡排序

1.1思想

1.2实现

2.快速排序

2.1Hoare经典版

2.2挖坑版

🐸Part2:归并排序

归并排序

思想

实现

🦄最后总结



Part1:交换排序


1.冒泡排序


1.1思想


冒泡排序相比大家都很熟悉了,这是学习C语言过程当中的第一个排序算法

它的思想就是一趟一趟走,每一次都找到最大的数字,放到靠后的位置,就像水底冒泡一样,越往上气泡越大。


动图:

e63d98f7905fb6eed71bc39dae7d4a7b_f827f785ea1d41fab1d7cff816cacd2c.gif


1.2实现


//冒泡排序
void BubbleSort(int* a, int n)
{
  for (int j = 0; j < n; j++)
  {
    for (int i = 1; i < n-j; i++)
    {
      if (a[i - 1] > a[i])
      {
        Swap(&a[i - 1], &a[i]);
      }
    }
  }
}


特征分析:

像选择排序,思想易理解,也好实现,适合教学;

时间复杂度:最好:O(N^2)  最坏:O(N^2);

空间复杂度:O(1);

稳定性:稳如老狗


2.快速排序


在这里,讲快速排序三种常见的实现方法:Hoare经典版本,挖坑版本,前后指针版本。


2.1Hoare经典版


因为这个算法是霍尔大佬发明的,所以以他的名字命名,这是最经典的快速排序:

7cadf39db9427643ab7a8d5ce3e8cd17_01bbce6e05cc421383e1721b3dfb5753.gif


思想:

先找一个参考值 key,定义左右两端的下标/指针 R , L ,向内靠拢;

注意:左边做key,右边先走,可使相遇点比key小;

R 找比 key 值小的位置,L 找比 key 值大的位置;

都找到后,R L 对应位置的数字交换;

R L 相遇,相遇位置对应的数字与 key 交换。


这样就保证了数组左侧数字都小于 key,右侧数字都大于 key 。

到这里还没完呢,可不能保证左右侧数字都是有序的,所以还要对左右两侧进行相同操作,没错,可以用递归

b7d60f99cef4393304121e1499448e32_2badff8f1f2640ac968545c0e7de41c6.png

有点类似与二叉树的分治。

代码实现:

//快排(Hoare)
void QuickSort1(int* a, int left, int right)
{
  if (left >= right)
    return;
  int begin = left, end = right;
  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]);
  keyi = left;
  //递归
  QuickSort1(a, begin, keyi - 1);
  QuickSort1(a, keyi + 1, end);
}


特性分析:  

快速排序的综合性能和适用场景都比较好,大多数库里的排序都是快排;

时间复杂度:最好:O(N*logN)  最坏:O(N^2)  平均取O(N*logN);

空间复杂度:O(logN)   (递归创建函数栈帧)

稳定性:不稳定

以下另外两种版本与此版本的性能相同,便不再进行此特性分析


2.2挖坑版


这个方式的本质思想是没有变化的,就是把 key 值另外保存起来,形成一个坑位,在数据交换的过程中不断更新坑位:

c8350faf17fd2289f2d417f8b9c679d6_7aa24e08c4384c2d8c889f0225a58b82.gif

这种思路下,就不必让 R 优先走了,也可以让 L 优先走,因为最终都能保证相遇点比 key 小,图示默认让 R 优先走;


代码实现:

//快排(挖坑法)
void QuickSort2(int* a, int left, int right)
{
  if (left >= right)
    return;
  int begin = left, end = right;
  int key = a[left];
  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;
  //递归
  QuickSort2(a, begin, hole - 1);
  QuickSort2(a, hole + 1, end);
}


特征同霍尔版本  


2.3前后指针版


这个版本是最方便,最好理解的;

定义 curprev 两个下标,对应位置的值与 key 比较:

d8095c58bdb894d1dc51dce15897aaec_b4c457a8aa78485a94c149fdc9a837e0.gif

无论比较的值是大是小,cur 都要++。


代码实现:

//部分快排(前后指针法)
int PartSort3(int* a, int left, int right)
{
  int keyi = left;
  int key = a[left];
  int prev = left;
  int cur = left + 1;
  while (cur <= right)
  {
    if (a[cur] < key && ++prev != cur) // 保证 prev 在 cur 的左侧
      Swap(&a[cur], &a[prev]);
    cur++;
  }
  Swap(&a[keyi], &a[prev]);//与下标为keyi的交换
  keyi = prev;
  return keyi;
}

 

特征同霍尔版本


Part2:归并排序


归并排序


思想


所谓归并排序,就是采用分治法,将已有的有序子序列合并,得到完全有序的序列,以此类推,最终并得到的就是一个有序的数组。

90bd5dc53f8fb1c109e48d7206e47eb2_1485665f874343e682bcb7cfd2fcbeff.gif


实现


实现过程中,我们需要一块辅助空间来帮助归并,最先归并好的结果在那块辅助空间当中,最后再把结果拷贝到原数组空间中。

void _MergeSort(int* a, int begin, int end, int* tmp)
{
  if (begin >= end)
    return;
    // 中分
  int mid = (begin + end) / 2;
  //[begin,mid] [mid+1,end]
    // 递归
  _MergeSort(a, begin, mid, tmp);
  _MergeSort(a, mid+1, end, tmp);
  //拷贝
  int begin1 = begin,begin2 = mid + 1;
  int end1 = mid,end2 = end;
  int i = begin;                            
  while (begin1 <= end1 && begin2 <= end2)
  {
    if (a[begin1] < a[begin2])
    {
      tmp[i++] = a[begin1++];
    }
    else
    {
      tmp[i++] = a[begin2++];
    }
  }
  while (begin1 <= end1)
  {
    tmp[i++] = a[begin1++];
  }
  while (begin2 <= end2)
  {
    tmp[i++] = a[begin2++];
  }
  memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}
//归并排序
void MergeSort(int* a, int n)
{
  int* tmp = (int*)malloc(sizeof(int) * n);
  if (tmp == NULL)
  {
    perror("malloc fail");
    return;
  }
    // 这里不能自己递归自己,否则会不断开辟空间
  _MergeSort(a, 0, n - 1, tmp);
  free(tmp);
}


特征分析:

归并排序效率不低,但需要开辟一块大小为N的空间,当数据量过大时,可以考虑在磁盘中排序。

时间复杂度:最好:O(N*logN)   最坏:O(N*logN)

空间复杂度:O(N)

稳定性:稳定


最后总结


屏幕截图 2023-04-16 190134.png

各排序对比


总结:

到这里,其实排序还并没有完结,我在后期会更新快速排序的优化以及快速排序和归并的非递归实现方式,有点小期待呢~

目录
相关文章
|
4天前
|
算法 前端开发 搜索推荐
前端算法之归并排序
前端算法之归并排序
9 0
|
1天前
|
算法
常见的算法排序(2)
常见的算法排序(2)
9 3
|
1天前
|
算法 搜索推荐 索引
数据结构与算法 排序(下)
数据结构与算法 排序(下)
8 1
|
1天前
|
缓存 算法 搜索推荐
数据结构与算法 排序(上)
数据结构与算法 排序(上)
8 0
|
3天前
|
算法 调度
【问题探讨】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究
【问题探讨】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究
|
5天前
|
搜索推荐 C语言
【C语言/数据结构】排序(归并排序|计数排序|排序算法复杂度)
【C语言/数据结构】排序(归并排序|计数排序|排序算法复杂度)
10 0
|
1天前
|
存储 算法 数据可视化
基于harris角点和RANSAC算法的图像拼接matlab仿真
本文介绍了使用MATLAB2022a进行图像拼接的流程,涉及Harris角点检测和RANSAC算法。Harris角点检测寻找图像中局部曲率变化显著的点,RANSAC则用于排除噪声和异常点,找到最佳匹配。核心程序包括自定义的Harris角点计算函数,RANSAC参数设置,以及匹配点的可视化和仿射变换矩阵计算,最终生成全景图像。
|
1天前
|
算法 Serverless
m基于遗传优化的LDPC码NMS译码算法最优归一化参数计算和误码率matlab仿真
MATLAB 2022a仿真实现了遗传优化的归一化最小和(NMS)译码算法,应用于低密度奇偶校验(LDPC)码。结果显示了遗传优化的迭代过程和误码率对比。遗传算法通过选择、交叉和变异操作寻找最佳归一化因子,以提升NMS译码性能。核心程序包括迭代优化、目标函数计算及性能绘图。最终,展示了SNR与误码率的关系,并保存了关键数据。
11 1
|
2天前
|
算法 调度
考虑需求响应的微网优化调度模型【粒子群算法】【matlab】
考虑需求响应的微网优化调度模型【粒子群算法】【matlab】
|
2天前
|
运维 算法
基于改进遗传算法的配电网故障定位(matlab代码)
基于改进遗传算法的配电网故障定位(matlab代码)