排序算法(2) 快速排序——快排原理以及快排函数qsort

2023-11-20

上次我们分享了一个基本排序方法———冒泡排序的使用,今天我们来分享第二种排序方法:

快速排序

快速排序,我们简称快排。我们先来回顾一下上次的冒泡排序,冒泡排序就是在一个序列里,两两比较并根据大小关系进行换位处理,经过多次从头到尾的比较,从而实现整个序列的排序。这个排序方法可行,并且好像并没有什么局限性,那为什么我们还需要快速排序?那是因为冒泡排序在时间上很耗时,也就是他的时间复杂度很大。我们知道,程序最重要的一个因素就是运行时间,时间极其重要,相信大家也在日常刷题的时候,遇到“时间超限”的报错。

把我们来看一下冒泡排序,他的时间复杂度是O(N^2)假如我 们的计算机每秒钟可以运行 10 亿次,那么对 1 亿个数进行排序,冒泡排序则需要 1 千万秒,达到 115 天之久,是不是很吓人?

所以,为了解决时间问题,我们就有了快速排序,顾名思义,快速排序是一种效率很高的排序方法,让我们一起来康康。

首先我们通过实例来分析快速排序的原理。

假设我们现在对“6 1 2 7 9 3 4 5 10 8”这 10 个数进行排序。首先在这个序列中随 便找一个数作为基准数(不要被这个这就是一个用来参照的数,待会儿你就知 道它用来做啥了)为了方便,就让第一个数 6 作为基准数吧。
接下来,需要将这个序列中所有比基准数大的数放在 6 的右边,比基准数小的数放在 6 的左边,得到类似下面这种排列
3 1 2 5 4 6 9 7 10 8
那我们现在想一下,怎么从一开始排序变成我们现在这个排序呢?有没有什么方法呢?
在初始状态下,数字 6 在序列的第 1 位。我们的目标是将 6 挪到序列中间的某个位置, 假设这个位置是 k。现在就需要寻找这个 k,并且以第 k 位为分界点,左边的数都小于等于 6, 右边的数都大于等于 6。当然,我们利用上一节的冒泡排序是可以做到这一点的,但是,仔细想一想,我们总是相邻两两之间进行比较,是不是很麻烦?为什么不能跳着比较呢?是的,跳着比较,我们的思路就来了。
分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从 右往左找一个小于 6 的数,再从左往右找一个大于 6 的数,然后交换它们。这里可以用两个 变量 i j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵 “”和 “哨兵  ”  j  ”。刚开始的时候让哨兵 i 指向序列的最左边(即 i=1),指向数字 6。让哨兵 j 指向序 列的最右边(即 j=10),指向数字 8。
图示:
首先哨兵 j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵 j 先出动,
这一点非常重要(请自己想一想为什么)。哨兵 j 一步一步地向左挪动(即 j++),直到找到
一个小于 6 的数停下来。接下来哨兵 i 再一步一步向右挪动(即 i++),直到找到一个大于 6
的数停下来。最后哨兵 j 停在了数字 5 面前,哨兵 i 停在了数字 7 面前。

 

现在交换哨兵 i 和哨兵 j 所指向的元素的值。交换之后的序列如下。
6 1 2 5 9 3 4 7 10 8

 然后我们剩下的也是这么操作,就可以实现我们所需要的了。只是需要注意一点:

到最后  i  和  j  相遇之后,即在本实例中的 3 的位置,因为我们要把基准数放在中间,所以最后要实现3和6的交换哦

那么根据这个思想,当以6位基准数完成一次排序之后,我们就可以在6的左边和右边分别找基准数,在两边分别进行快速排序,最后就可以完成对整个序列的排序。

整个过程图示: 

快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候
设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全
部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进
行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当
然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和
冒泡排序是一样的,都是 O(N^2),它的平均时间复杂度为 O (NlogN)。
我们知道原理之后,我们来写一写实现代码吧:
#include <stdio.h> 
int a[101],n;//定义全局变量,这两个变量需要在子函数中使用 
void quicksort(int left,int right) 
{ 
 int i,j,t,temp; 
 if(left>right) 
 return; 
 
 temp=a[left]; //temp中存的就是基准数 
 i=left; 
 j=right; 
 while(i!=j) 
 { 
 //顺序很重要,要先从右往左找 
 while(a[j]>=temp && i<j) 
 j--; 
 //再从左往右找 
 while(a[i]<=temp && i<j) 
 i++; 
 //交换两个数在数组中的位置 
 if(i<j)//当哨兵i和哨兵j没有相遇时
 { 
 t=a[i]; 
 a[i]=a[j]; 
 a[j]=t; 
 } 
 } 
 //最终将基准数归位
 a[left]=a[i]; 
 a[i]=temp; 
 
 quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程 
 quicksort(i+1,right);//继续处理右边的,这里是一个递归的过程 
} 
int main() 
{ 
 int i,j,t; 
 //读入数据 
 scanf("%d",&n); 
 for(i=1;i<=n;i++) 
 scanf("%d",&a[i]); 
 quicksort(1,n); //快速排序调用 
 
 //输出排序后的结果 
 for(i=1;i<=n;i++) 
 printf("%d ",a[i]); 
 getchar();getchar(); 
 return 0; 
}

这个是我们自己写快排的代码,其实在C和C++的库函数里面,有一个自带的快排函数qsort

这个函数的头文件:

#include <stdlib.h>

这个函数的书写格式,函数声明是这样:

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

声明里面的参数的分别是:

base -- 指向要排序的数组的第一个元素的指针。

nitems -- 由 base 指向的数组中元素的个数。

size -- 数组中每个元素的大小,以字节为单位。

compar -- 用来比较两个元素的函数

这几个参数里面,唯独最后一个参数不好理解。最后一个参数,是要传给他一个比较函数,比较函数是什么?就是比较两个数大小的函数;从哪里找这个函数?自己写!!

是的,最后那个参数其实就是一个自己写的比较函数。

为了方便大家理解,我们举个例子来使用一下。

#include <stdio.h>      /* printf */
#include <stdlib.h>     /* qsort */

int values[] = { 40, 10, 100, 90, 20, 25 };// 自定义的一个序列

int compare (const void * a, const void * b)
{
  return ( *(int*)a - *(int*)b );
}// 这个就是我们自己写的比较函数,用于比较a,b大小.如果a小于b,则返回值为负数(< 0),也即a会排在b的前面。同理,若a大于b,则a会排在b的后面。所以,这里的qsort()为从小到大即升序排序。如果需要从小到大,那么就返回b-a即可

int main ()
{
  int n;
  qsort (values, 6, sizeof(int), compare);
//调用qsort函数 values 所需要排序的序列的首地址;6 代表序列有6个元素;sizeof(int)因为是个int数组,所以每个元素的大小是sizeof(int);最后compare就是我们自己的比较函数
  for (n=0; n<6; n++)
     printf ("%d ",values[n]);
  return 0;
}

这个就是qsort函数的实例,不是很理解的可以评论区留言,然后自己多上手试试,这个函数还是比较好用的,这么使用不比我们自己写一个快速排序或者冒泡排序来的快?!

如果在这个基础上,运动动态分配是可以实现一个排序程序的(就是任一序列进行排序),大家也可以上手试一试,不是很难。

好滴,今天的分享到此结束,对了,大伙儿圣诞节快乐哦!!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

排序算法(2) 快速排序——快排原理以及快排函数qsort 的相关文章

随机推荐

  • qt 开发遇到的坑

    1 QString的toString 和toWString 引起的win32位release 下std string的析构崩溃 代码 QString qs std string str qs toStdString const wchar
  • Linux NFS说明,配置及故障分析

    一 NFS服务简介 NFS 是Network File System的缩写 即网络文件系统 一种使用于分散式文件系统的协定 由Sun公司开发 于1984年向外公布 功能是通过网络让不同的机器 不同的操作系统能够彼此分享个别的数据 让应用程序
  • MATLAB:figure的用法

    figure的定义 figure 创建图窗窗口 可以理解为创建一个有画板的窗口 我们在这块画板上绘制 plot 曲线等 figure主要是创建图窗窗口或者切换图窗窗口 figure n 查找到n存在时 将当前窗口切换成n 不存在时创建标识为
  • Java的String类、Object类、包装类

    1 String类 1 1 String类的两种实例化方式 1 直接赋值 String str hello 2 使用构造方法new的形式赋值 String str new String hello 1 2 String类定义的字符串的比较
  • Eclipse安装SVN插件

    http subclipse tigris org servlets ProjectProcess jsessionid A870EAC9A292637E167F9719F6399F60 pageID p4wYuA Installation
  • Binary Tree on Plane【费用流】

    题目链接 CF 277 E 题意翻译 给你平面上 n 个点 2 n 400 要求用这些点组成一个二叉树 每个节点的儿子节点不超过两个 定义每条边的权值为两个点之间的欧几里得距离 求一个权值和最小的二叉树 并输出这个权值 其中 点 i 可以成
  • Qt导出库接口类无法connect信号

    问题 动态库中的接口类内部有信号 但是在主程序中无法connect 链接时报错 undefined symbol xxx staticMetaObject 解决 在接口类前加导出标记 参考动态库的隐式调用 一般接口类如果不需要继承实现的话
  • 2013年9月22日---2013年10月5日(每天1小时,共15小时,还有5050小时)

    之所以写每天1小时 是因为这段时间浮躁了 面临培训 过于兴奋 另外一个是假期闹得 不过 每天1小时肯定有 为了这1万小时的计算不浮夸 宁可少一些
  • 一个boot.oat crash问题的分析

    最近遇到一个手机重启的问题 日志如下 05 18 13 42 55 553 I AEE AED 10514 pid 1734 tid 1788 name android ui gt gt gt system server lt lt lt
  • 了解数据的发展历程--大数据简史

    数据技术的发展历史就是人类追求美好生活过程最真实的写照 大数据分析的历史与未来展望 最早的数字不是阿拉伯人发明的 数字的起源如同文字起源一样古老 结绳记事 易九家言 中记载 事大 大结其绳 事小 小结其绳 之多少 随物众寡 即根据事件的性质
  • 2048游戏矩阵操作算法

    题目 1 手指向一个方向滑动 所有格子会向那个方向运动 2 相同数字的两个格子 相撞时数字会相加 输入描述 1 输入为一个3 3的矩阵 2 接下来输入一个1 4的数字 1表示向上滑动 2表示向下划动 3表示向左滑动 4表示向右滑动 输出描述
  • Ireport 报表设计部分填坑记录 基于Ireport 4.5.1

    Ireport 报表设计 基于Ireport 4 5 1 Ireport 换行遇到分页时 一行会被拆分为两行 断行 方式一 面板直接修改 点击detail栏的空白处 修改其 Split Type属性值为 Prevent 如果方式一 无法修改
  • 内存取证CTF-Memlabs靶场6

    1 挑战说明 我们从情报局收到了这个内存转储 他们说这个证据可能包含黑帮大卫本杰明的一些秘密 这个内存转储是从本周早些时候被 FBI 逮捕的他的一名员工那里获取的 你的工作是通过内存转储 看看你是否能找出一些东西 FBI还表示 大卫通过互联
  • 【AndroidStudio】按钮基本操作(普通按钮、图片按钮、单选按钮设置)(单击事件监听器触发对话框和页面跳转)

    普通按钮 普通按钮xml设置
  • [NCTF2019]Fake XML cookbook

    NCTF2019 Fake XML cookbook 日常刷题 打开题目嗯 一开始我的脑子里想到的是禁止自娱自乐 狗头 哈哈 第一想法就是试一下admin 别问为什么 web狗的自觉 果然没这么容易 抓包 将提交的数据放到了doLogin
  • vue router在同界面使用 this.$router跳转路由,mounted不再调用

    1 在vue中 刷新数据常用的办法是 this r o u t e r g o 0 或 者 t h i s router go 0 或者this router go 0 或者this router push path 在最近一次的使用时 发
  • 第三章 总线

    一 系统总线概念 系统总线是计算机内部各个组件之间传输数据和控制信息的通信线路 连接中央处理器 内存 输入输出设备 扩展插槽等各个组件 是计算机系统中最重要的硬件组成部分之一 具有数据传输 控制信号传输和总线协议等功能 系统总线的性能对计算
  • 登录即代表您同意 用户服务协议

    UILabel remindLabel if remindLabel remindLabel UILabel alloc init NSDictionary attributes NSFontAttributeName UIFont sys
  • 一文搞懂Mybatis原理

    文章目录 一 快速入门 二 查询流程分析 2 1首先通过ClassLoader读取配置文件生成输入流 2 2建造者模式加载配置创建SQLSessionFactory 2 2 1SQLSessionFactoryBuilder builder
  • 排序算法(2) 快速排序——快排原理以及快排函数qsort

    上次我们分享了一个基本排序方法 冒泡排序的使用 今天我们来分享第二种排序方法 快速排序 快速排序 我们简称快排 我们先来回顾一下上次的冒泡排序 冒泡排序就是在一个序列里 两两比较并根据大小关系进行换位处理 经过多次从头到尾的比较 从而实现整