zoukankan      html  css  js  c++  java
  • 排序算法系列之快速排序 (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;
        }
  • 相关阅读:
    Oracle 经典语法(三)
    String.format() 方法的用处
    window 官网下载系统
    微信小程序在wxml中调用自定义函数
    前后端分离 poi使用
    微信分享
    微信支付 (jsapi 方式)
    tomcat配置多个ssl证书
    netty websocket集群下遇到的问题
    CompletableFuture 简介和使用
  • 原文地址:https://www.cnblogs.com/liaoguipeng/p/5279879.html
Copyright © 2011-2022 走看看