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

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

快速排序

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

把我们来看一下冒泡排序,他的时间复杂度是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。
图示:
排序算法(2) 快速排序——快排原理以及快排函数qsort_第1张图片
首先哨兵 j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵 j 先出动,
这一点非常重要(请自己想一想为什么)。哨兵 j 一步一步地向左挪动(即 j++),直到找到
一个小于 6 的数停下来。接下来哨兵 i 再一步一步向右挪动(即 i++),直到找到一个大于 6
的数停下来。最后哨兵 j 停在了数字 5 面前,哨兵 i 停在了数字 7 面前。
排序算法(2) 快速排序——快排原理以及快排函数qsort_第2张图片

 排序算法(2) 快速排序——快排原理以及快排函数qsort_第3张图片

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

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

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

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

整个过程图示: 

排序算法(2) 快速排序——快排原理以及快排函数qsort_第4张图片

快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候
设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全
部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进
行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当
然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和
冒泡排序是一样的,都是 O(N^2),它的平均时间复杂度为 O (NlogN)。
我们知道原理之后,我们来写一写实现代码吧:
#include  
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

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

这个函数的头文件:

#include

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

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

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

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

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

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

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

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

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

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

#include       /* printf */
#include      /* 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函数的实例,不是很理解的可以评论区留言,然后自己多上手试试,这个函数还是比较好用的,这么使用不比我们自己写一个快速排序或者冒泡排序来的快?!

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

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

你可能感兴趣的:(c的入坑计划,排序算法,数据结构,c语言,c++,算法)