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

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

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

各排序对比


总结:

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

目录
相关文章
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
95 8
|
1月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
109 7
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
33 1
|
2月前
|
算法 搜索推荐 Java
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
基数排序是一种稳定的排序算法,通过将数字按位数切割并分配到不同的桶中,以空间换时间的方式实现快速排序,但占用内存较大,不适合含有负数的数组。
38 0
数据结构与算法学习十三:基数排序,以空间换时间的稳定式排序,速度很快。
|
2月前
|
数据可视化 搜索推荐 Python
Leecode 刷题笔记之可视化六大排序算法:冒泡、快速、归并、插入、选择、桶排序
这篇文章是关于LeetCode刷题笔记,主要介绍了六大排序算法(冒泡、快速、归并、插入、选择、桶排序)的Python实现及其可视化过程。
21 0
|
2月前
|
算法
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
❤️算法笔记❤️-(每日一刷-83、删除排序链表中的重复项)
34 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`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。
下一篇
DataWorks