【交换排序】冒泡排序 与 快速排序

简介: 【交换排序】冒泡排序 与 快速排序

1.冒泡排序

假设升序。每次遍历,两两比较,将大的元素向后交换,直到选出最大的元素放在最后,这时已经确定了升序中最后一个元素,然后多次遍历前面无序的元素,每次可以确定一个最大的数,直到排序完成。


动态图解:

代码实现:

//交换函数
void Swap(int* p1, int* p2)
{
  int t = *p1;
  *p1 = *p2;
  *p2 = t;
}
void BubbleSort(int* arr, int n)
{
  for (int i = 0; i < n - 1; i++)
  {
    for (int j = 0; j < n - 1 - i; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        Swap(&arr[j], &arr[j + 1]);
      }
    }
  }
}


如果在排序有序数据时,我们还可以优化一下,提高一下效率,代码如下

void BubbleSort(int* arr, int n)
{
  for (int i = 0; i < n - 1; i++)
  {
        int flag = 1;//假设已经有序
    for (int j = 0; j < n - 1 - i; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        Swap(&arr[j], &arr[j + 1]);
                flag = 0;//发生交换,说明无序
      }
    }
        if(flag == 1)//已经有序,不在继续排序
        {
            break;
        }
  }
}


冒泡排序的特性总结:

  1. 冒泡排序是─种非常容易理解的排序
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:稳定


2.快速排序

2.1 递归实现

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


图示:

代码实现:

假设按照升序对array数组中【left, right】左右都是闭区间中的元素进行排序。

void QuickSort(int* a, int left, int right)
{
  if (left >= right)
  {
    return;
  }
    //按照基准值(中间位置)对array数组的[left, right]区间中的元素进行划分
  int keyi = PartSort(a, left, right);
    //划分成功后以div为边界形成了左右两部分[left, keyi-1]和[key+1,right]
    //递归排 [left, keyi-1]
  QuickSort(a, left, keyi - 1);
    //递归排[key+1,right]
  QuickSort(a, keyi + 1, right);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,大家在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。


将区间按照基准值划分为左右两半部分(PartSort)的常见方式有:


1. hoare 版本


我们先看一下动图,方便理解

巧妙之处:

  1. 左边做key,右边先走;保障了相遇位置的值比key小,或者就是key的位置
  2. 右边做key,左边先走;保障了相遇位置的值比key大,或者就是key的位置


我们这里使用第一种方法:


L和R相遇无非就是两种情况,L遇R和R遇L:


  1. 情况一:L遇R,R是停下来,L在走,R先走,R停下来的位置一定比key小,相遇的位置就是R停下的位置,就一定比key要小
  2. 情况二:R遇L,在相遇这一轮,L就没动,R在移动,跟L相遇,相遇位置就是L的位置,L的位置就是key的位置或者这个位置交换过,换成了比key小的,相遇L位置一定比key小

代码实现:

int PartSort1(int* a, int left, int right)
{
  int keyi = left;
  while (left < right)
  {
    //右边找小 与key相等的数据放在左边右边都可以
    while (left < right && a[right] >= a[keyi])
    {
      right--;
    }
    //左边找大 与key相等的数据放在左边右边都可以
    while (left < right && a[left] <= a[keyi])
    {
      left++;
    }
    Swap(&a[left], &a[right]);
  }
  Swap(&a[keyi], &a[right]);
  return right;
}


2. 挖坑法

left 和 right 中有一个一定是坑位,右边找小,左边找大,找到就将值放入原坑位,该位置成为新坑位。

代码实现:

int PartSort2(int* a, int left, int right)
{
  int hole = left;
  int key = a[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;
}

3. 前后指针版本


  1. 最开始prev和cur相邻的
  2. 当cur遇到比key的大的值以后,他们之间的值都是比key大的值
  3. cur找小,找到小的以后,跟++prev位置的值交换,相当于把大翻滚式往右边推同时把小的换到左边


代码实现:

int PartSort3(int* a, int left, int right)
{
  int keyi = left;
  int prev = left;
  int cur = left + 1;
  while (cur <= right)
  {
    if (a[cur] <= a[keyi]&&++prev!=cur)//自己不与自己交换
    {
      Swap(&a[cur], &a[prev]);
    }
    cur++;
  }
  Swap(&a[prev], &a[keyi]);
  return prev;
}


2.2 快速排序优化

当我们遇到有序数据时,由于我们的key是选的第一个元素,时间复杂度会变成O(N^2)。有两种优化方法:

  1. 三数取中法选key
  2. 随机数选key

我们这里讲一下第一种方法:让三个数作比较,取中间值

三数取中,代码实现:

int GetMidIndex(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//a[left]>a[mid]
  {
    if (a[mid] > a[right])
    {
      return mid;
    }
    else if (a[left] > a[right])
    {
      return right;
    }
    else
    {
      return left;
    }
  }
}


然后我们需要在上面3种划分方式开头都加上下面代码,就可以达到优化的目的

int midi = GetMidIndex(a, left, right);
Swap(&a[midi], &a[left]);


2.3 非递归实现

我们这里使用栈来实现,栈内存放需要划分的区间端点值,利用栈先入后出的特点模拟实现递归

void QuickSortNonR(int* a, int left, int right)
{
  Stack st;
  StackInit(&st);
  //入栈
  StackPush(&st, right);
  StackPush(&st, left);
  while (!StackEmpty(&st))
  {
    left = StackTop(&st);
    StackPop(&st);
    right = StackTop(&st);
    StackPop(&st);
    int keyi = PartSort1(a, left, right);
    //想先处理左边,就先右边区间先入栈
        //以基准值为分割点,形成左右两部分:[left, keyi-1]和[keyi+1,right)
    if(right > keyi + 1)
    {
      StackPush(&st, right);
      StackPush(&st, keyi + 1);
    }
    if (left < keyi - 1)
    {
      StackPush(&st, keyi - 1);
      StackPush(&st, left);
    }
  }
  StackDestroy(&st);
}

当然也可以使用队列来模拟。队列相当于广度优先,二叉树中的层序遍历,栈是深度优先,二叉树的先序遍历。

快速排序的特性总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定


本篇结束!我们下一篇文章来学习排序第四课【归并排序】。

相关文章
|
存储 人工智能 自然语言处理
轻松改造公众号:10分钟实现智能客服自动化!
在阿里云平台上,仅需10分钟即可将微信公众号(订阅号)升级为AI智能客服,提供7x24小时客户支持,显著提升用户体验。方案包括四步:创建大模型问答应用、搭建微信公众号连接流、引入AI智能客服以及增加私有知识库,确保客服能精准回答复杂咨询,助力业务竞争力提升。整个过程简单快捷,在免费试用额度内费用为零。
714 7
轻松改造公众号:10分钟实现智能客服自动化!
|
监控 关系型数据库 MySQL
初体验:数据库监控、管理和可观测性工具(PMM)
Percona Monitoring and Management (PMM) 是一个开源工具,用于监控MySQL、PostgreSQL和MongoDB的性能。它提供实时监控、数据可视化、故障排除和管理功能,支持本地和云端数据库。要安装PMM,首先需安装Docker,然后通过提供的脚本部署PMM服务器和客户端。在MySQL服务器上创建PMM用户后,使用`pmm-admin`命令添加数据库。访问PMM的HTTPS网址(默认用户名和密码为admin)进行配置。本文还包含了安装Docker和PMM的命令行步骤。
初体验:数据库监控、管理和可观测性工具(PMM)
|
安全 物联网 5G
无线网络技术:5G之后的通信革命
【10月更文挑战第16天】本文探讨了5G之后无线网络技术的发展趋势,涵盖5G-A、Wi-Fi 7及未来通信技术展望。5G-A提升了网络速度、时延和连接数,Wi-Fi 7则在性能和可靠性上大幅跃升,未来通信技术将朝向更高速度、更低延迟、更广覆盖方向发展。
|
消息中间件 中间件 Kafka
分布式事务最全详解 ,看这篇就够了!
本文详解分布式事务的一致性及实战解决方案,包括CAP理论、BASE理论及2PC、TCC、消息队列等常见方案,助你深入理解分布式系统的核心技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式事务最全详解 ,看这篇就够了!
ly~
|
域名解析 网络协议 Linux
如何测试 DNS 记录中的反向代理服务器是否生效?
本文介绍了三种测试反向代理服务器配置的方法。首先,通过命令行工具如 `ping`、`nslookup` 和 `dig` 检查域名解析是否指向正确的 IP 地址。其次,利用 Web 浏览器访问域名,验证页面加载正常且请求头信息无误。最后,借助网络抓包工具如 `Wireshark` 和 `tcpdump` 分析数据包,确保请求正确转发并返回预期响应。
ly~
1154 2
|
JavaScript
Vue3搜索框(InputSearch)
这篇文章介绍了如何在Vue 3中创建一个具有搜索、清除、加载状态等多功能的搜索框组件(InputSearch),并提供了组件的配置选项、事件处理和使用示例。
1265 6
Vue3搜索框(InputSearch)
|
Java Android开发
Android 12 自定义底部导航栏
Android 12 自定义底部导航栏
536 4
|
监控 安全 网络协议
深入理解HTTPS及其默认端口
【8月更文挑战第24天】
5683 0
|
数据管理 程序员 人工智能
后台数据管理系统 - 项目架构设计【黑马程序员】
后台数据管理系统 - 项目架构设计【黑马程序员】
555 0
后台数据管理系统 - 项目架构设计【黑马程序员】
|
安全 Unix Linux
第一章 操作系统概述
第一章 操作系统概述
798 0