排序之快速排序

来源:互联网 发布:matlab调用c和java 编辑:程序博客网 时间:2024/06/11 22:02

本文以《啊哈!算法》为教材,个人理解整理。

例:将10个数字进行从小到大排序输出。
  输入:6 1 2 7 9 3 4 5 10 8
  输出:1 2 3 4 5 6 7 8 9 10

快速排序首先将最左边的第一个数作为基准数,即6,然后设置两个标志 i 和 j , i 用来从左向右寻找比6大的数, j 用来从右向左寻找比6小的数,当各自找到时,循环暂停,交换这两个数字位置,之后继续循环寻找,直到 i 和 j 在同一个位置碰头,结束本轮循环,将当前 i 和 j 同处位置上的数字与基准数6交换。之后借助递归用相似的方式排列好基准数的左边数列和右边数列。

如例中第一轮循环:
这里写图片描述

首先从右边开始移动 j (为什么从右边开始最后再讨论),寻找到一个比6小的数字,可知是5,则 j 停留在5的位置。之后从左边开始移动 i ,寻找到一个比6大的数字,可知是7,则 i 停留在7的位置。如下:
这里写图片描述

之后交换7和5的位置,数列变为:
这里写图片描述

然后重新开始寻找,从右边开始, j 将停留在4, i 将停留在9,并且交换它们:
这里写图片描述

最后, j 先向左寻找,停留在3,而因为 i 必须小于或者等于 j,因此 i 也只能停留在3,至此 i 和 j 碰头,第一轮循环结束,交换3和基准数6,数列变为:
这里写图片描述

之后采用递归分别对6左边的数列 3 1 2 5 4和6右边的数列9 7 10 8进行同样的操作,最终完成排序,这里引用《啊哈!算法》中的图更直观:

以下是代码,在写quickSort时,一开始我想为什么不直接使用left和right,而是又将它们分别赋值给 i 和 j 之后才使用。所以我忽略了 i 和 j ,直接用left和right,但是很显然我错了,因为写到代码28行时,也就是要将i、j 碰头处和基准数6交换时,我发现我找不到6的位置了。。。才恍然大悟 i 和 j 的用处。

原本书中的代码可以更简单易懂,之所以使用了malloc动态定义数组是因为大学学C时对这个一直懵懵懂懂,借这个机会再巩固巩固。

#include<stdio.h>#include<stdlib.h>int *a;void quickSort(int left, int right){    int i, j, t, p;    if(left > right)        return;    p = a[left]; //基准数     i = left;    j = right; //赋值给i和j保证left和right位置不动     while(i != j)    {        while(i < j && a[j] >= p) //要从右边开始寻找             j--;        while(i < j && a[i] <= p)            i++;        if(i < j) //当i和j没有碰头时         {            t = a[i]; a[i] = a[j]; a[j] = t;        }    }    a[left] = a[i]; a[i] = p; //i和j碰头,交换碰头处数字和基准数     quickSort(left, i-1); //递归基准数左边     quickSort(i+1, right); //递归基准数右边     return;}int main(){    int n, i;    scanf("%d", &n);    a = (int *)malloc(sizeof(int)*n); //为数组a动态分配长度     for(i = 0; i < n; i++)    {        scanf("%d", &a[i]);    }    quickSort(0, n-1);    for(i = 0; i < n; i++)    {        printf("%d ", a[i]);    }    return 0;}

至于为什么要从右边开始移动,百度了一下还没有发现真正解释清楚的内容,不过在知道里找到一个反例,可以帮助理解。

例:3 1 2 5 4

这里基准数为3,如果从左边开始移动则 i 停留在5,而 j 也就不得不停留在5,这时 i 和 j 在5处碰了头,也就意味着要将3和5进行交换,那么数列将变为:5 1 2 3 4

很明显,这样再接下去无论如何结果都是错误的。但是如果按照快速排序从右边开始移动,则 j 停留在2,i 也停留在2, i 和 j 在2处碰了头,交换2和3,数列变为:2 1 3 5 4。显然这样才正确。

为什么会造成这样的情况?一开始我猜测是不是因为现在操作的是从小到大,假如换成从大到小,是不是就应该从左边开始了。于是用3 1 2 5 4实践了下,经过实践发现从大到小也是需要从右边开始的。

然后我把基准数换成了最右边的数,即4,测试下了下发现原来是这个影响了顺序,同样使用3 1 2 5 4,基准数改为4后,从右边就行不通了,而从左边才能够继续下去。至于为什么是这样,我也没想明白,总觉得这会是一个数学题。

0 0