排序算法系列之快速排序 (5)

一、普通快速排序

  1. 快速排序把原数组划分为2个数组,其中左边第一个数组的元素都比选定的关键字小,右边的第二个数组都比选定的关键字大;
  2. 快速排序采用的是分治的思想:把一个规模大而复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
  3. 快速排序的关键在于:选择边界关键值,使得每个左边的数都小于关键值,每个右边的数都大于关键值,快速排序是选择n个关键值的过程.
  4. 快速排序理解:
  • 在第一轮排序过程中,原数组的全部数据是一个无序数组M,无序数组最左侧元素i,最右侧元素j,想象左右分别为L和R的有序空数组;
  • 选择首位元素值为边界关键字boundKey,然后从无序数组j最右侧依次向左遍历,
  • 如果找到大于边界数值boundKey的值,则j -- (表示右侧有序数组R添加了一个元素,中间无序数组M减少了一个元素)
  • 直到找到一个元素,使得data[j] < boundKey,此时boundKey为data[i = 0] = boundKey的值,两个数据交换 data[j] <==> data[i],这时候,选定的边界关键字到了j的位置,而比boundKey小的值到了data[i]的位置;
  • 一次交换以后,反过来从靠近i的右侧元素i+1位置开始跟 在data[j] 位置的boundKey进行数值比较,如果data[i] 小于BoundKey,则i ++(表示左侧有序数组L添加了一个元素,中间无序数组减少了一个元素)
  • 直到找到data[i] > BoundKey,此时boundKey在位置data[j]上,把data[j] <==>data[i] 互换,这时候,选定的关键字到了i的位置,而比boundKey大的值data[i]已经移动到了data[j]上
  • 左右循环一次以后,i和j都向中间压缩,直到 i = j 的时候,边界关键字 boundKey左边的值data[0] –> data[i] 都比boundKey小,右侧的值 data[j] –> data[n]都比boundKey大,满足要求;
  • 此时boundKey位置已经确定,使用分治的思维,把[0] –> data[i -1] 到data[j +1] –> data[n] 看做两个全新的无序数组,把它们分别进行各自的选择关键值,各自又拆分成新的两个数组进行快速排序
  • 直到子数组的元素subArray.count = 1 的时候,不能进行再拆分,分治结束,把全部子数组按顺序排列起来,就是原数组排序后的结果

image

代码如下:

#region QuickSort快速排序

    /// <summary>
    /// 快速排序
    /// </summary>
    /// <param name="datas"></param>
    public void QuickSort<T>(T[] datas) where T:IComparable<T>
    {
        QRecursionSort(datas, 0, datas.Length - 1);
    }
    // 快速排序内部递归函数 :(无序数组,快排起始位置,快排结束位置)//
    private void QRecursionSort<T>(T[] datas, int iBeginPos, int iEndPos) where T :IComparable<T>
    {
        if (datas == null || iBeginPos >= iEndPos || iEndPos >= datas.Length && iBeginPos < 0) return;
        if (iBeginPos < iEndPos)
        {
            int boundPos = QSplit(ref datas, iBeginPos, iEndPos);
            QRecursionSort(datas, iBeginPos, boundPos - 1);
            QRecursionSort(datas, boundPos + 1, iEndPos);
        }
    }

    /******************************************************** 
    *函数名称:Split 
    *参数说明:datas 无序数组; 
    *          iBegin为datas需要快速排序的起始位置 
    *          iEnd为datas需要快速排序的结束位置 
    *函数返回:分割后的分割数位置 
    *说明:    以iBegin处的数值value作为分割数, 
               使其前半段小于value,后半段大于value 
    *********************************************************/
    private int QSplit <T>(ref T[] datas, int iBegin, int iEnd) where T:IComparable<T>
    {
        T boundData = datas[iBegin];  // 默认选择子数组第一个数作为边界值进行比较
        while (iBegin < iEnd)           // 循环切割数组,使得比边界值小的数放数组左边,比边界值大的数放数组右边
        {
            while (iEnd > iBegin && datas[iEnd].CompareTo(boundData) >= 0 )
            {
                iEnd--;
            }
            if (iEnd != iBegin)
            {
                datas[iBegin] = datas[iEnd]; // 把后方查找到的小与边界值的数据存放在数组首位(边界值之前)
                iBegin++;
                while (iBegin < iEnd && datas[iBegin].CompareTo(boundData) <= 0)
                {
                    iBegin++;
                }
                if (iBegin != iEnd)
                {
                    datas[iEnd] = datas[iBegin];
                    iEnd--;
                }
            }
        }

        datas[iEnd] = boundData;
        return iEnd;
    }
    
    #endregion
    • 快速排序还有个缺点就是不稳定性:
    • 选择关键字很重要,如果刚好原数组是一个几乎排序好的数组,第一个值恰好是最小或者最大的数,那么就会导致拆分的数组左侧为0或者很小,而右侧很大(反之亦然),那么事件复杂度将会导致T(n) = T(1) + T(n-1) + Θn  (其中Θn为最后一次把subArray叠加起来的常数次数),最后导致复杂度为T(n) = O(n)²,那么我们应该怎么解决呢?
    • 解决的关键是关键字,我们可以随机选取关键字,就可能会避免这种问题;
public void QuickSortRandom<T>(T[] datas, int beginPos, int endPos) where T : IComparable<T>
    {
        if (datas == null || beginPos >= endPos || endPos >= datas.Length && beginPos < 0) return;
        if (beginPos < endPos)
        {
            int boundPos = SplitRandom(ref datas,beginPos,endPos);
            QuickSortRandom<T>(datas, beginPos, boundPos - 1);
            QuickSortRandom<T>(datas, boundPos + 1, endPos);
        }
    }

    private int SplitRandom<T>(ref T[] datas, int beginPos, int endPos) where T : IComparable<T>
    {
        int lowest = beginPos;
        int highest = endPos;

        T boundData = datas[lowest];  //随机快速排序关键在于边界锚点的选定

        while (lowest < highest)
        {
            // 如果数据比边界锚点大,右侧(小)R序列+1个元素,highest下表左移,中间无序数组元素-1
            for (; highest > lowest && datas[highest].CompareTo(boundData) >= 0; )
            {
                highest--;
            }
            if (highest != lowest)
            {
                datas[lowest] = datas[highest];
                lowest++;
                // 如果数据比边界锚点小,左侧(小)L序列+1个元素,lowest下表左移,中间无序数组元素+1
                for (; lowest < highest && datas[lowest].CompareTo(boundData) <= 0; )
                {
                    lowest++;
                }
                if (lowest != highest)
                {
                    datas[highest] = datas[lowest];
                    datas[lowest] = boundData;
                    highest--;
                }
            }

        }
        datas[highest] = boundData;
        return highest;
    }
原文地址:https://www.cnblogs.com/liaoguipeng/p/5279879.html