算法手记(7)快速排序

终于到了经典的快排了,作为20世纪科学和工程领域十大算法之一,自60年代发明以来,一直吸引着一批批工程师和科学家对其改进,今天我们就分析快排算法以及它的几种改进方案。

快速排序

概述:
快速排序算法也是基于分治思想的方案,与归并排序不同的是,它是原地排序,同时可以将长度为N的数组排序所需的时间和NlogN成正比,我们已经学习过的算法都无法将这两个优点结合起来。

快速排序流行的原因是因为它实现简单,适用于各种不同的输入数据且在一般应用中比其他算法要快得多,他可能是使用最广泛的算法了。

分析:

快排是一种分治的排序算法。它讲一个数组分成两个子数组,将两部分独立地排序,当两个子数组都有序时整个数组自然就有序啦。在快速排序中,切分(Partition)的位置取决于数组的内容,该算法的关键也在于切分,这个过程需要满足3个条件:

1.对于某个j,a[j]已经排定;

2.a[lo]到a[j-1]中的所有元素都不大于a[j];

3.a[j+1]到a[hi]中的所有元素都不小于a[j];

根据归纳法不难证明他能正确的排序数组:如果左子数组和右子数组都是有序的,那么左子数组,切分元素,右子数组组成的结果数组也一定是有序的。它是一个随机化算法,因为它在排序之前顺序会被打乱,这么做是希望可以预测该算法的性能特征。

这里切分的一般策略是先随意地取a[lo]为切分元素,然后我们分别自左向右扫描直到找到一个大于等于它的元素,再自右向左扫描直到找到一个小于等于它的元素,我们交换这两个元素的位置。如此继续,就可以保证坐指针i的元素不大于切分元素,右指针j的元素不小于切分元素,最后当i和j相遇时,将a[lo]与a[j]交换,然后返回j即可。

实现:

public class Quick
    {
        public static void sort(IComparable[] a)
        {

RandomShuffle.shuffle(a);

            sort(a, 0, a.Length - 1);
        }
        private static void sort(IComparable[] a, int lo, int hi)
        {
            if (hi <= lo) return;
            int j = partition(a, lo, hi);
            sort(a, lo, j - 1);
            sort(a, j + 1, hi);
        }

        private static int partition(IComparable[] a, int lo, int hi)
        {
            int i = lo, j = hi + 1;
            IComparable v = a[lo];
            while (true)
            {
                while (less(a[++i], v)) if (i == hi) break;
                while (less(v, a[--j])) if (i == lo) break;
                if (i >= j) break;
                exch(a, i, j);
            }
            exch(a, lo, j);
            return j;
        }
        private static bool less(IComparable i, IComparable j)
        {
            return i.CompareTo(j) < 0;
        }
        private static void exch(IComparable[] a,int i, int j)
        {
            var temp=a[i];
            a[i] = a[j];
            a[j] = temp;
        }
        public static void test(int size)
        {
            var stopWatch = new Stopwatch(DateTime.Now);
            Data[] data = new Data[size];
            Random rd = new Random();
            for (int i = 0; i < size; i++)
            {
                data[i] = new Data { value = rd.Next(10000000) };
                Console.WriteLine(data[i]);
            }
            Console.WriteLine("After sort:");

            Quick.sort(data);
            for (int i = 0; i < size; i++)
            {
                Console.WriteLine(data[i]);
            }
            stopWatch.elapsedTime();

        }
        public static void Main(string[] args)
        {
            test(100);
        }
    }

注意点:

1.原地切分

如果使用辅助数组,则可以很轻松的实现切分,但是将切分后的数组复制回去的开销可能是我们无法承受的,这会大大降低算法的排序速度。

2.别越界

如果切分元素是数组中最小或最大的元素,我们就需要确保扫描数组别跑出边界,需要实现明确地检测来防止这种情况。

3.保持随机性

数组元素顺序是被打乱过的,它的所有子数组也都是随机排序的,这对预测算法的运行时间很重要,同时也能减小最坏情况发生几率。

4.终止循环

有经验的程序员都明白终止循环需要格外小心,快速排序的切分循环也不例外。一个常见的错误是没有考虑到数组中可能包含和切分元素的值相同的其他元素。

5.处理切分值有重复的情况

这里的实现算法里,左侧扫描最好在遇到大于等于切分值时停下,右侧扫描在遇到小于等于切分值停下。尽管这样可能将一些等值的元素交换,但在某些典型应用中,这可以避免算法的运行时间变为平方级别。

6.终止递归

有经验的程序员在保证递归总是能够结束也是需要小心的。快速排序的一个常见错误就是不能保证将切分元素放入正确的位置,从而导致程序在切分元素正好是最大或最小值时陷入了无限的递归之中。

总结:

数学上对快速排序已经有了详尽的分析,因此我们能够精确说明它的性能。快速排序切分方法的内循环会用一个递增的索引将数组元素和一个定值比较,很难想象排序算法中还有比这个更短小的内循环了。

快速排序的另一个优势在于它的比较次数较少,快速排序最好的情况是每次都能正好将数组对半分,这种情况下正好满足分治递归的Cn=2Cn/2+N公式,即Cn~NlogN。

尽管快速排序有很多优点,但它的基本实现有一个潜在的缺点:在切分不平衡时会极为低效。我们在快排之前将数组随机打乱就是为了防止此种情况,这样可以将糟糕的切分情况可能性降到最低。

时间: 2024-10-05 10:58:37

算法手记(7)快速排序的相关文章

基础算法之排序--快速排序

1 /************************************************************************************** 2 * Function : 快速排序 3 * Create Date : 2014/04/21 4 * Author : NTSK13 5 * Email : [email protected] 6 * Copyright : 欢迎大家和我一起交流学习,转载请保持源文件的完整性. 7 * 任何单位和个人不经本人允许不

算法——基础篇——快速排序

快速排序是一个经常使用的算法,由于每次用的时候,都感觉没有理解清楚,特写一篇文章记录一下. 算法介绍 快速排序有点类似有冒泡排序,冒泡排序从相邻的两个元素比较,小的在左边,大的在右边,这个算法很容易理解.而快速排序它相当于是在一头一尾两边分别排序比较,比较的对象是当前元素值,和一个选定的key值,主题的思想就是通过跟key值比较,把大于key的值放在右边,小于的放在左边这样就完成了一次排序,接着在对key值左边的序列进行同样的操作,右边也是,最后便能将所有的元素给排好序,由于它每次排序,都会分成

iOS算法(一)置快速排序算法

快速排序是当遇到较大数据时,排序快,高效的方法(公司面试时,基本上会被问到...) 该方法的基本思想是: 1.先从数列中取出一个数作为基准数. 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边. 3.再对左右区间重复第二步,直到各区间只有一个数. 简单地理解就是,找一个基准数(待排序的任意数,一般都是选定首元素),把比小于等于基准数的元素放到基准数的左边,把大于基准数的元素放在基准数的右边.排完之后,在把基准数的左边和右边各看成一个整体, 左边:继续选择基准数把小于等

[7] 算法之路 - 快速排序之3轴演算

左轴演算.中轴演算.右轴演算 题目: 快速排序法(quick sort)是目前所公认最快的排序方法之一(视解题的对象而定),虽然快速排序法在最差状况下可以达O(n2),但是在多数的情况下,快速排序法的效率表现是相当不错的. 快速排序 - 算法 1.快速排序法的基本精神是在数列中找出适当的轴心,然后将数列一分为二 2.分别对左边与右边数列进行排序 左轴演算: // 快速排序 - 左轴演算 // 1. 附设两个指针left/right,并设最左端的数为最初枢轴pivot // 2. 从右向左搜索,找

图形化排序算法比较:快速排序、插入排序、选择排序、冒泡排序

图形化排序算法比较:快速排序.插入排序.选择排序.冒泡排序

Javascript算法系列之快速排序(Quicksort)

Javascript算法系列之快速排序(Quicksort) 原文出自: http://www.nczonline.net/blog/2012/11/27/computer-science-in-javascript-quicksort/ https://gist.github.com/paullewis/1981455#file-gistfile1-js 快速排序(Quicksort)是对冒泡排序的一种改进,是一种分而治之算法归并排序的风格 核心的思想就是通过一趟排序将要排序的数据分割成独立的

算法入门之快速排序

快速排序原理: 快速排序先把等待排序的集合打乱顺序,把第一个元素作为基准元素,为第二个元素和最后一个元素分配两个指针i和j,如果a[i]小于基准元素则i++,如果a[j]大于基准元素则j--,这样把大于基准元素的a[i]和小于基准元素的a[j]互换,以此类推,最终把基准元素与a[j]相交换,就得到一个a[j]左侧全部小于a[j],右侧全部大于a[j]的一个近似有序数组,然后按照如上步骤重新寻找每个被a[j]分开的数组中的分隔点,最终得到有序数组. 在通用排序中,一般都会选取快速排序来解决问题.

算法笔记之快速排序

1.1 算法思路-- 该算法在数组中选定一个元素作为主元(一般选第一个),然后以这个主元为参考对象将数组分为两个部分,第一部分都是小于或者等于主元,第二部分都是大于或者等于主元.然后对第一和第二部分递归地使用快速排序算法,直到分到最小的小组为止. 1.2 时间复杂度-- 在最差的情况下,要把n个元素的数组划分,需要n次比较和n次移动.假设用T(n) 来表示使用快速排序算法来排序n个元素的数组所耗费的时间.那么 T(n) = T(n/2)+ T(n/2) +2n 所以,快速排序的时间复杂度应该是

SuperHakce 算法实践之快速排序

1.算法概念. 快速排序(Quicksort)是对冒泡排序的一种改进.由C. A. R. Hoare在1962年提出. 2.算法思想. 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列. 3.实现思路. ①以第一个关键字 K 1 为控制字,将 [K 1 ,K 2 ,-,K n ] 分成两个子区,使左区所有关键字小于等于 K 1 ,右区所有关键字大于

一步一步写算法(之快速排序)

原文:一步一步写算法(之快速排序) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 快速排序是编程中经常使用到的一种排序方法.可是很多朋友对快速排序有畏难情绪,认为快速排序使用到了递归,是一种非常复杂的程序,其实未必如此.只要我们使用好了方法,就可以自己实现快速排序. 首先,我们复习一下,快速排序的基本步骤是什么: 1. 判断输入参数的合法性 2.把数组的第一个数据作为比较的原点,比该数据小的数据排列在左边,比该数据大的数据排列在右边 3