欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

快速排序算法-C语言编程实现及其问题优化

程序员文章站 2022-05-17 20:35:01
...

提示:博主只是一个普普通通的13岁初中生,精力不足,年龄尚小,仍有缺陷,请谅解!也希望大家不吝啬自己的评论,对博主进行指点教育,万分感谢。期待明天会更好!


前言

快速排序算法基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。


提示:以下是本篇文章正文内容

一、快速排序算法是什么?

快速排序(Quicksort)是对冒泡排序算法的一种改进,由 C.A.R.Hoare(Charles Antony Richard Hoare,东尼·霍尔)在 1962 年提出。

我对冒泡排序算法也写过一篇文章进行详细介绍,并说了对原有冒泡排序算法代码的优化方法(注意:这里不是改进)。

传送门:冒泡排序算法-C语言实现及针对性优化详解

二、快速排序算法如何用C语言实现?

1.了解过程

  1. 首先取出一个key,一般取第一个元素
  2. 从后往前遍历,如果数组中的数据小于了key,那么就将从前往后未比较过的第一个位置即fisrt位置替换为该数据
  3. 然后从前往后遍历,如果数组中的数据大于了key,那么就将从后往前的第一个比较过数据位置替换
  4. 直到左右两边的位置重合,说明key就找到了正确的位置,每次循环就能找到一个数的正确位置
  5. 然后将key左右两边的数据分为两组,递归调用自己。

2.核心函数(含注释)

多说无益,说再多理论不去真刀真枪地实践,那也只能是纸上谈兵。

因为代码会有不同的版本,不同版本又会有不同优化。这里只取一种进行展示。代码内含注释!
代码如下(示例):

void sort(int *a, int left, int right)
{
    if(left >= right)/*如果左边索引大于或者等于右边的索引就代表已经整理完成一个组了*/
    {
        return ;
    }
    int i = left;
    int j = right;
    int key = a[left];
     
    while(i < j)                               /*控制在当组内寻找一遍*/
    {
        while(i < j && key <= a[j])
        /*而寻找结束的条件就是,1,找到一个小于或者大于key的数(大于或小于取决于你想升
        序还是降序)2,没有符合条件1的,并且i与j的大小没有反转*/ 
        {
            j--;/*向前寻找*/
        }
         
        swap(a,i,j);
                /*交换a[i]和a[j]的数值*/
        /*找到一个这样的数后就把它赋给前面的被拿走的i的值(如果第一次循环且key是
        a[left],那么就是给key)*/
         
        while(i < j && key >= a[i])
        /*这是i在当组内向前寻找,同上,不过注意与key的大小关系停止循环和上面相反,
        因为排序思想是把数往两边扔,所以左右两边的数大小与key的关系相反*/
        {
            i++;
        }
         
        swap(a,i,j);
    }
     
    a[i] = key;/*当在当组内找完一遍以后就把中间数key回归*/
    sort(a, left, i - 1);/*最后用同样的方式对分出来的左边的小组进行同上的做法*/
    sort(a, i + 1, right);/*用同样的方式对分出来的右边的小组进行同上的做法*/
                       /*当然最后可能会出现很多分左右,直到每一组的i = j 为止*/
}

3.完整代码(缺陷版)

代码如下(示例):代码附有注释

#include <stdio.h>
void Swap(int *, int *);  //函数声明, 交换两个变量的值
void QuickSort(int *, int, int);  //函数声明, 快速排序
int main(void)
{
    int i;  //循环变量
    int a[] = {900, 2, -58, 3, 34, 5, 76, 7, 32, 4, 43, 9, 1, 56, 8,-70, 635, -234, 532, 543, 2500};
    QuickSort(a, 0, 20);  /*引用起来很简单, 0为第一个元素的下标, 20为最后一个元素的下标*/
    printf("最终排序结果为:\n");
    for (i=0; i<21; ++i)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}
void Swap(int *p, int *q)
{
    int buf;
    buf = *p;
    *p = *q;
    *q = buf;
    return;
}
void QuickSort(int *a, int low, int high)
{
    int i = low;
    int j = high;
    int key = a[low];
    if (low >= high)  //如果low >= high说明排序结束了
    {
        return ;
    }
    while (low < high)  //该while循环结束一次表示比较了一轮
    {
        while (low < high && key <= a[high])
        {
            --high;  //向前寻找
        }
        if (key > a[high])
        {
            Swap(&a[low], &a[high]);
            ++low;
        }
        while (low < high && key >= a[low])
        {
            ++low;  //向后寻找
        }
        if (key < a[low])
        {
            Swap(&a[low], &a[high]);
            --high;
        }
    }
    QuickSort(a, i, low-1);  //用同样的方式对分出来的左边的部分进行同上的做法
    QuickSort(a, low+1, j);  //用同样的方式对分出来的右边的部分进行同上的做法
}

程序实际上还可以进行优化。

在快速排序算法中,每轮比较有且仅有一个 key 值。但是 请注意!敲黑板,划重点!key 值的位置是不断变化的,只有比较完一轮后 key 值的位置才固定。

在上面这个程序中每次执行 swap 时实际上交换的是 key 和 a[low] 或 key 和 a[high],而 key 的位置每次都是不一样的。既然 key 的位置是“动来动去”的,那么就不必将它赋到数组中了。因等最后一轮比较结束后它的位置“不动”了,再将它赋到数组中。

Do you understand?


4.优化版本(含注释)

# include <stdio.h>
void QuickSort(int *, int, int);  /*现在只需要定义一个函数, 不用交换还省了一个函数, 减少了代码量*/
int main(void)
{
    int i;  //循环变量
    int a[] = {900, 2, -58, 3, 34, 5, 76, 7, 32, 4, 43, 9, 1, 56, 8,-70, 635, -234, 532, 543, 2500};
    QuickSort(a, 0, 20);  /*引用起来很简单, 0为第一个元素的下标, 20为最后一个元素的下标*/
    printf("最终排序结果为:\n");
    for (i=0; i<21; ++i)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
    return 0;
}
void QuickSort(int *a, int low, int high)
{
    int i = low;
    int j = high;
    int key = a[low];
    if (low >= high)  //如果low >= high说明排序结束了
    {
        return ;
    }
    while (low < high)  //该while循环结束一次表示比较了一轮
    {
        while (low < high && key <= a[high])
        {
            --high;  //向前寻找
        }
        if (key > a[high])
        {
            a[low] = a[high];  //直接赋值, 不用交换
            ++low;
        }
        while (low < high && key >= a[low])
        {
            ++low;  //向后寻找
        }
        if (key < a[low])
        {
            a[high] = a[low];  //直接赋值, 不用交换
            --high;
        }
    }
    a[low] = key;  //查找完一轮后key值归位, 不用比较一次就互换一次。此时key值将序列分成左右两部分
    QuickSort(a, i, low-1);  //用同样的方式对分出来的左边的部分进行同上的做法
    QuickSort(a, low+1, j);  //用同样的方式对分出来的右边的部分进行同上的做法
}

是不是代码少了?是不是更简单明了?爽不爽? 咳咳……

Last:注意事项

快速排序也有一个问题,就是递归深度的问题。

调用函数要消耗资源,递归需要系统堆栈,所以递归的空间消耗要比非递归的空间消耗大很多。如果递归太深的话会导致堆栈溢出,系统会“撑”不住。快速排序递归的深度取决于基准数的选择。

如果基准数的一边有几万个数的话则系统直接就崩溃了。所以需要对递归深度进行优化。怎么优化呢?就是任意取三个数,一般是取序列的第一个数、中间数和最后一个数,然后选择这三个数中大小排在中间的那个数作为基准数,这样起码能确保获取的基准数不是两个极端。

一个简单的判断函数就可以了。

#include <stdio.h>

//求三个数的中间数
int main()
{
    int a=1,b=2,c=3;//随便取三个数
    if((b<=a && a<=c) ||(c<=a && a<=b))
        printf("%d\n",a);
    else if((a<=b && b<=c)||(c<=b && b<=a))
        printf("%d\n",b);
    else if((a<=c && c<=b) ||(b<=c && c<=a))
        printf("%d\n",c);
    return 0}

总结

快速排序实际上是冒泡排序的一种改进,是冒泡排序的升级版。这种改进就体现在根据分割序列的基准数,跨越式地进行交换。正是由于这种跨越式,使得元素每次移动的间距变大了,而不像冒泡排序那样一格一格地“爬”。快速排序是一次跨多格,所以总的移动次数就比冒泡排序少很多。

快速排序一般用于大数据排序,即数据的个数很多的时候(不是指数的值很大)。如果是小规模的排序,就用其他的几种简单排序方式就行了,不必使用快速排序。