手撕排序算法3:优化版冒泡排序和快速排序的三种方法(包括三数取中,小区间优化,递归版本)(上)

简介: 手撕排序算法3:优化版冒泡排序和快速排序的三种方法(包括三数取中,小区间优化,递归版本)(上)

一.冒泡排序

1.算法思想

冒泡排序,顾名思义,就是在每一趟中将最大的数沉到水底,也就是将最大的数移动到末尾位置,一共进行n-1次

冒泡排序的整体思想是:

1.左右两两比较,大的向后移动,小的向前移动

2.每一趟都会使当前所比较过的元素中最大的那个移动到最后位置

3.一共n-1趟即可,因为对于一个数组来说,如果已经确定好了n-1个数字,又因为一个数组只有n个数字,所以剩下的那一个数字的位置也就唯一确定了

2.代码实现

void BubbleSort(int* a, int n)
{
  int i = 0;
  for (i = 0; i < n - 1; i++)//一共冒泡n-1趟(i控制趟数)
  {
    int flag = 1;//假设有序
    int j = 0;
    for (j = 0; j < n - 1 - i; j++)//(j控制每一趟比较的次数)
      //第一趟比较n-1次,第二趟比较n-2次(因为第二趟就不需要比较最后一个数字了,因为最后一个数字已经是最大的了)....,归纳为n-1-i次
    {
      if (a[j] > a[j + 1])
      {
        Swap(&a[j], &a[j + 1]);
        flag = 0;//发生交换,改为无序
      }
    }
    if (flag == 1)//有序,所以无需再进行排序,直接break
    {
      break;
    }
  }
}

这是冒泡排序的一种优化版本,使用flag标志数组是否已经有序,如果有序,则跳出循环,排序成功.

3.冒泡排序的时间复杂度与稳定性

冒泡排序的时间复杂度是O(N^2),具有稳定性

至于为什么冒泡排序具有稳定性呢?

是因为冒泡排序在每一趟的比较过程中只有当左边元素大于右边元素时才会发生交换,而当左边元素等于右边元素时并不会发生交换,所以冒泡排序具有稳定性

4.冒泡排序,插入排序,选择排序的优劣介绍

插入排序比冒泡排序好,冒泡排序比选择排序好

前面提到过,当数组是有序或者部分有序时,插入排序有奇效(时间复杂度能达到O(N)!!!)

可是我们刚才进行的冒泡排序的优化也可以减少一些不必要的比较啊,那么它们两个到底谁更好呢?

下面给大家举个例子.

总结:优化版本冒泡排序

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

最好排序:O(N)

最差:O(N^2)

1.跟直接插入排序相比来说,直接插入更好

因为:

插入排序的任意性更强,对有序,接近有序,局部有序的适用性更强,所以说直接插入排序是在O(N^2)中的排序算法中非常好的一个算法了

2.很直接选择排序相比来说,冒泡排序更好,因为直接选择排序无论数组是否有序,都进行N^2次,而冒泡排序针对有序或部分有序有一些优化,避免了一些没有任何必要的比较

3.但是数组无序的情况下,冒泡排序还是效率比较差的

二.快速排序

1.算法思想

快速排序是一个非常强大的排序算法,能够很快地解决大多数排序问题,但是要理解起来不是很容易,我们先来看一下快排的单步思想

那么我们该如何实现让6的左边都比6小,6的右边都比6大呢?

下面我们介绍一下"挖坑"法,pivot:英文单词"坑"

我们来看这几张图片

这就是单趟快排的示意图.

2.单趟快排的代码实现

接下来,我们先写一下单趟快排的代码

void QuickSort(int* a, int n)
{
  int begin = 0;
  int end = n - 1;
  int pivot = begin;
  int key = a[begin];
  while (begin < end)
  {
    //左边有坑,右边找小,放到左边
    while (a[end] >= key && begin < end)
    {
      end--;
    }
    if (begin < end)
    {
      //小的放到左边的坑里,自己形成了新的坑位
      a[pivot] = a[end];
      pivot = end;
    }
    //右边有坑,左边找小,放到右边
    while (a[begin] <= key && begin < end)
    {
      begin++;
    }
    if (begin < end)
    {
      //大的放到右边的坑里,自己形成了新的坑位
      a[pivot] = a[begin];
      pivot = begin;
    }
  }
  pivot = begin;
  a[pivot] = key;
}

可见,单趟排序的时间复杂度是O(N),因为begin和end要相遇,且每轮循环只能有一个"指针"在变动,这里所说的"指针"不是C语言所说的指针,而是一种标记而已

3.快排整体思想和代码实现

这时,我们成功让6的左边都小于6,6的右边都大于6,

此时我们想:

如果能让6的左边变为有序,右边也变为有序,那么不就整体有序了吗?

所以在这里我们呢考虑使用分治的思想,也就是递归的方法,也就是缩短区间(大事化小,将一个大问题拆解为若干个子问题,然后逐个击破)

因为当区间缩小到只剩一个值的时候,它一定有序

下面我们来看一张图片

我们发现,只需要对第一次分割出来的左区间和右区间分别进行多次挖坑法,就可以实现整体有序,

其实我们发现,这个快排的递归思想就像是二叉树的深度优先遍历.

下面我们用代码实现一下,如果感觉理解不了下面代码中的这种递归的方式,请仔细看一下上面这张图片,上面把递归调用的整个过程划分的很详细

void QuickSort(int* a, int left,int right)//这里为了进行函数递归,把形参改为左右"指针",方便进行递归
{
  if (left >= right)//当区间长度小于等于1时,无需进行排序了,如果不加这一条,函数将进入死递归
  {
    return;
  }
  int begin = left;
  int end = right;
  int pivot = begin;
  int key = a[begin];
  while (begin < end)
  {
    //左边有坑,右边找小,放到左边
    while (a[end] >= key && begin < end)
    {
      end--;
    }
    if (begin < end)
    {
      //小的放到左边的坑里,自己形成了新的坑位
      a[pivot] = a[end];
      pivot = end;
    }
    //右边有坑,左边找小,放到右边
    while (a[begin] <= key && begin < end)
    {
      begin++;
    }
    if (begin < end)
    {
      //大的放到右边的坑里,自己形成了新的坑位
      a[pivot] = a[begin];
      pivot = begin;
    }
  }
  pivot = begin;
  a[pivot] = key;
  //这里我们将[left,right]这个区间分为这么三个部分
  //[left,pivot-1]  pivot [pivot+1,right]
  QuickSort(a, left, pivot - 1);
  QuickSort(a, pivot + 1, right);
  //快排的递归思想就像是二叉树的深度优先遍历.
  //根 左区间 右区间 根 左区间 右区间 根 左区间 右区间 根 左区间 右区间 .........,
  //单个区间就像是叶子节点
}

上面就是快排的基础代码(还未经过优化的)

相关文章
|
1月前
|
搜索推荐 C语言
【排序算法】快速排序升级版--三路快排详解 + 实现(c语言)
本文介绍了快速排序的升级版——三路快排。传统快速排序在处理大量相同元素时效率较低,而三路快排通过将数组分为三部分(小于、等于、大于基准值)来优化这一问题。文章详细讲解了三路快排的实现步骤,并提供了完整的代码示例。
57 4
|
24天前
|
搜索推荐 Python
利用Python内置函数实现的冒泡排序算法
在上述代码中,`bubble_sort` 函数接受一个列表 `arr` 作为输入。通过两层循环,外层循环控制排序的轮数,内层循环用于比较相邻的元素并进行交换。如果前一个元素大于后一个元素,就将它们交换位置。
125 67
|
24天前
|
存储 搜索推荐 Python
用 Python 实现快速排序算法。
快速排序的平均时间复杂度为$O(nlogn)$,空间复杂度为$O(logn)$。它在大多数情况下表现良好,但在某些特殊情况下可能会退化为最坏情况,时间复杂度为$O(n^2)$。你可以根据实际需求对代码进行调整和修改,或者尝试使用其他优化策略来提高快速排序的性能
116 61
|
1月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
50 2
|
2月前
|
搜索推荐
冒泡排序算法
【10月更文挑战第19天】冒泡排序是一种基础的排序算法,虽然在实际应用中可能不是最优的选择,但对于理解排序算法的基本原理和过程具有重要意义。
|
2月前
|
算法 搜索推荐 Shell
数据结构与算法学习十二:希尔排序、快速排序(递归、好理解)、归并排序(递归、难理解)
这篇文章介绍了希尔排序、快速排序和归并排序三种排序算法的基本概念、实现思路、代码实现及其测试结果。
33 1
|
2月前
|
搜索推荐 C语言
排序算法--冒泡排序
排序算法--冒泡排序
21 0
|
18天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
24天前
|
机器学习/深度学习 算法 Serverless
基于WOA-SVM的乳腺癌数据分类识别算法matlab仿真,对比BP神经网络和SVM
本项目利用鲸鱼优化算法(WOA)优化支持向量机(SVM)参数,针对乳腺癌早期诊断问题,通过MATLAB 2022a实现。核心代码包括参数初始化、目标函数计算、位置更新等步骤,并附有详细中文注释及操作视频。实验结果显示,WOA-SVM在提高分类精度和泛化能力方面表现出色,为乳腺癌的早期诊断提供了有效的技术支持。
|
4天前
|
供应链 算法 调度
排队算法的matlab仿真,带GUI界面
该程序使用MATLAB 2022A版本实现排队算法的仿真,并带有GUI界面。程序支持单队列单服务台、单队列多服务台和多队列多服务台三种排队方式。核心函数`func_mms2`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。