快速排序

1.序言

快速排序网上有很多优秀的博客,此篇文章也有参考,但对于我来说,看过很多次,总觉得这个算法还是别人的,没有成为自己的,本着遇到问题多问几个为什么,我经过思考,发现主要问题在于,如何实现将数据分割成独立的两部分?为什么要进行从后往前遍历寻找小值?为什又要从前往后寻找大值?

2.问题描述

参见 冒泡排序 问题描述

3.问题分析

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

4.前提条件

a.此处我们选择使用标准C语言。

b.此处选择的哨兵元素为数组第一个元素。

c.此处假设我们需要排序的n个数均为整型。

5.实现步骤

a. 数据存储,采用整型数组,用int array[]表示,并且长度用int length表示。

b.分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小。

    <1>既然要分开,就得有参考数据,选取数组的第一个元素为哨兵元素,用 int value = array[0];

    <2>要比较哪个元素比标准大,或者小,就需要循环遍历数组,假设数据的开始位置为int start = 0, 结束位置为 int end = length - 1,则<1>中的哨兵元素一般化为value = array[start],循环条件自然是 start < end。

    <3>完成一次分割

    我们目前打算依赖和哨兵元素比较来获取这个元素所处的分组,但如何在不产生额外空间复杂度的情况下,即直接利用在原数组上操作,来存储分割后的两组数据。

    现在我们假设数据array前边放置比哨兵元素小的数据,后边放置比哨兵元素大的元素,而且哨兵元素已经被value进行标记,所以目前原数组中哨兵元素是一个空位置,可以进行覆盖赋值。

    这个位置,依据假设,要么放置的是比哨兵小的元素,要么放置哨兵元素本身(数组中哨兵元素最小),目前我们需要找到比哨兵元素小的元素,怎么找呢。当然是循环遍历数组,循环条件依然是start  < end,那是从前往后循环,还是从后往前循环呢?当然是从后往前,通过end--的方式,因为这样可以保证数组后边剩下的元素都是比哨兵元素大的元素,而采用从前往后循环,无法保证数组前边的元素都是比哨兵小的元素。循环结束的条件,就是找到end所指向的元素比哨兵元素小,然后将此值赋值给start所在位置。

    此时空余的元素是end所在的位置,目前end所在后边都是比哨兵元素大的元素,start左侧都是比哨兵元素小的元素,而start与end之间还是没有比较过的,所以end这里需要填充的要么是比哨兵元素大的,或者是哨兵元素本身,同样面临如何循环?这次是从前往后,通过start++的方式实现,同样从后往前,无法达到预期效果。循环结束条件,就是找到start所指向的元素比哨兵元素大,然后将此值赋值给end所在的位置。

    最后不要忘了放置哨兵元素呦。即array[start] = value。

    <4>递归实现

    递归结束条件,当array数组元素长度为1,即 length < 1时。

    递归赋值,前后为两个数组,前边为array,长度为start;后边为array[start + 1],长度为length - start - 1。

6.代码

#include<stdio.h>
 
// 打印数组
 void print_array(int *array, int length)
 {
     int index = 0;
     printf("array:\n");
     for(; index < length; index++){
         printf(" %d,", *(array+index));
     }   
     printf("\n\n");
 }
 
 void quickSort(int array[], int length)
 {
     int start = 0;
     int end = length-1;
     int value = array[start];// 得到哨兵元素
 
     if (1 > length) return;// 递归出口
 
     while(start < end){// 以哨兵元素为标准,分成大于它和小于它的两列元素
 
         while(start < end){// 从数组尾部往前循环得到小于哨兵元素的一个元素
             if ( array[end--] < value ){
                 array[start++] = array[++end];
                 break;
             }   
         }   
 
         while( start < end ){// 从数组头部往后循环得到大于哨兵元素的一个元素
             if( array[start++] > value){
                 array[end--] = array[--start];
                 break;
             }   
         }   
     }   
 
     array[start] = value;// 放置哨兵元素
     printf("\nstart:%d, end:%d\n", start, end);// 这个是测试下start和end是否一样
     quickSort(array, start);// 递归排序小于哨兵元素的那一列元素
     quickSort(array + start + 1, length - start - 1);// 递归排序大于哨兵元素的那一列
 }
 
 
 int main(void)
 {
     int array[12] = {1,11,12,4,2,6,9,0,3,7,8,2};
     print_array(array, 12);// 开始前打印下
     quickSort(array, 12);// 快速排序
     print_array(array, 12);// 排序后打印下
     return 0;
 }

7.运行结果


8.时间复杂度

    a.递归方程的一般形式为:

    

    b.最优时间复杂度:

    快速排序最优的情况就是每一次取到的元素都刚好平分整个数组,此时时间复杂度公式为:

        T[n] = 2T[n/2] + f(n)

    下面来利用迭代法,计算最优情况下快排时间复杂度:

    

     c.最差情况下时间复杂度

     最差情况就是每次取到的数组就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次只排好一个元素的顺序),冒泡排序的时间复杂度为 T[n] = n * (n - 1) = n^2 + n;即快排最差情况下的时间复杂度为O(n^2)。

     d.平均时间复杂度

     快排的平均时间复杂度也是: O(nlogn)

9.空间复杂度

    由于我们用的是就地排序,所以快排本身的空间是O(1),就是个常数级,而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据。

    最优的情况下空间复杂度为 O(logn),每一次都平分数组的情况。

    最差的情况下空间复杂度为 O(n),退化为冒泡排序的情况。

10.参考

    a.排序算法之 快速排序 及其时间复杂度和空间复杂度

    b.白话经典算法系列之六 快速排序 快速搞定

    c.递归算法的时间复杂度终结篇

    d.《算法竞赛入门经典》



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值