代码改变世界

算法与数据结构——排序(九)快速排序

2012-11-18 17:51  左眼微笑右眼泪  阅读(511)  评论(0编辑  收藏  举报

      终于轮到我们的终极算法了,快速排序顾名思义,速度肯定是很快的。它的基本思想是:把一个待排序列分隔成两个独立的序列,其中一个序列中的数比关键字小,另一个序列中的数比关键字大,然后对这两个序列再进行排序,最后使整个序列有序。

     这个算法里面有两个关键点,一是怎样选择这个关键字?二是怎样把一个序列分成两个子序列,使一个子序列比关键字小,另一个子序列比关键字大?

     我们首先来看第二个问题,怎样用程序实现,在这里面我们假设关键字就是待排序列的第一个数。代码如下:

private int Partition(List<int > sortList,int low,int high)
{
    int pivotkey = sortList[low];
 
    while (low < high)
    {
        //1.1从高位向低位循环,找出一个数,比pivotkey小的,把它与pivotkey交换,此时pivotkey的值是sortList[low]
        while (low < high && sortList[high] >= pivotkey)
        {
            high--;
        }
        Swap(sortList,low,high);
        //1.2从低位向高位循环,找出一个数,比pivotkey大的,把它与pivotkey交换,如果上面的while循环有交换,那么此时pivotkey的值是sortList[high]
        while (low < high && sortList[low] <= pivotkey)
        {
            low++;
        }
        Swap(sortList, low, high);
    }
    return low;//把pivotkey数的下标返回
}
这个算法比较经典,要仔细的理解一下,里面用到的swap方法就是交换两个数,这里就不详细叙述。我个人开始做的时候,没想到这个算法,自己实现了一个比较笨的,但理解起来相对比较容易,代码如下:
private int MyPartition(List<int> sortList, int low, int high)
{
    int position;
    int pivotkey = sortList[low];//默认pivotkey的值是sortList[low]
 
    List<int> tempList = new List<int>();
    //1.1 循环遍历整个序列,把比pivotkey小的值加入到tempList中去
    for (int i = low; i <= high; i++)
    {
        if (sortList[i] < pivotkey)
        {
            tempList.Add(sortList[i]);
        }
    }
    //1.2 把pivotkey的值加入到tempList中去,此时tempList中的数都是比pivotkey的值小的数
    tempList.Add(pivotkey);
    //1.3记录下pivotkey值的位置,这个值需要返回去
    position = tempList.Count - 1 + low;
 
    //1.4 循环遍历整个序列,把比pivotkey大的值加入到tempList中去
    for (int i = low; i <= high; i++)
    {
        if (sortList[i] > pivotkey)
        {
            tempList.Add(sortList[i]);
        }
    }
 
    //1.5此时tempList中的数已经分成了两个序列了,从0到position的数都比pivotkey小,后面的都比pivotkey大
    //把tempList中的数重新放入到sortList中去
    int k = 0;
    for (int i = low; i <= high; i++)
    {
        sortList[i] = tempList[k++];
    }
    return position;
}

    下面来看循环调用的方法:

private void QSort(List<int> sortList, int low, int high)
{
    if (low < high)
    {
        int pivotkey = MyPartition(sortList, low, high);
        QSort(sortList, low, pivotkey - 1);
        QSort(sortList, pivotkey + 1, high);
    }
}

     首先是产生一个关键字,然后把把序列分成两个序列,low到pivotkey-1的数都比关键字小,pivotkey+1到high的数都比关键字大,然后递归对这两个序列又进行排序,最后整个序列就是有序的了。

     真正调用的方法是:

public List<int> QuickSort(List<int> sortList)
{
    QSort(sortList, 0, sortList.Count - 1);
    return sortList;
}

     快速排序在最优的情况下时间复杂度为O(nLogn)。关键字的选取对快速排序性能有比较重要的影响。从上面的代码中可以知道,我们把数组的第一数当作关键字,但是如果这个数是最小的一个数,那么快速排序的时间复杂度就为O(n^n)了,现在就加到第一个问题上,怎样选择关键字。

关键字的选取,有随机选取法,三数取中法,九数取中法。随机选取就是随机产生一个数,把这个数当作关键字,这种办法效果也不是很好,另外一种就是三数取中,也就是在待排序列中的前面,中间,后面各取一个数,把这三个数进行排序,把它们中间的一个数当作关键字,这种方法选择出来的关键字,比随机产生的关键字要好的多。但是如果数据量非常多,那么三数取中效果也不好,这时就又有了九数取中了,九数取中其实就是做三次取样,也就是分别做三次三数取中,把每次的中间的数拿出来进行排序,最后取一次中,把这个数当作关键字。这样的取法比三数取中要更好一些,当然对于关键字的选取,还有其他一些方法,有兴趣的可以在网上查询相关资料。

     除了关键字的选择,上面的算法还有没有地方可以进行优化呢,肯定是有的。

     我们在选取关键字的时候,在不断的进行交换,使一边的数比关键字小,另一边比关键字大,不管是大的一边还是小的一边,里面的数字都不要求是有序的,所以在这个过程中其实有很多交换是不必要的,我们可以对产生关键字的函数进行改进,代码如下(有注释的地方是改进的地方):

private int PartitionNew(List<int> sortList, int low, int high)
{
    int pivotkey = sortList[low];
 
    int temp = pivotkey;//用temp记录pivotkey的值
    while (low < high)
    {
        while (low < high && sortList[high] >= pivotkey)
        {
            high--;
        }
        sortList[low] = sortList[high];//采用替换操作,而不是交换操作
        while (low < high && sortList[low] <= pivotkey)
        {
            low++;
        }
        sortList[high] = sortList[low];//采用替换操作,而不是交换操作
    }
    sortList[low] = temp;//最后把pivotkey的值赋给sortList[low]
    return low;
}

     还有一种改进的就是减少递归的次数,可以把QSort方法改为:

private void QSortNew(List<int> sortList, int low, int high)
{
    while (low < high)//把if改成while
    {
        int pivotkey = MyPartition(sortList, low, high);
        QSort(sortList, low, pivotkey - 1);
        low = pivotkey + 1;//这里low=pivotkey+1,再循环调用一次,相当于 QSort(sortList, pivotkey + 1, high);
    }
}
除了这些外,还有一些需要知道的,快速排序在对大数据量排序时会有很好的效率,但是在对很小的数据排序时,就有些大材小用了,所以要视情况来使用,有时别人使用时,加一个判断,如果数据个数小于7就使用插入排序,如果大于7就用快速排序,有人也说这个临界点为50。所以要根据实际情况来决定使用什么方法。