zoukankan      html  css  js  c++  java
  • Introsort(内观排序)

    .NET 4.5 这个版本的Array.Sort更改了STL的内观排序算法,那相对于快速排序内观排序到底有什么优化过的呢?

    根据维基百科所说,这个排序算法首先从快速排序开始,当递归深度超过一定深度(深度为排序元素数量的对数值)后转为堆排序。

    采用这个方法,Introsort既能在常规数据集上实现快速排序的高性能,又能在最坏情况下仍保持 O(N log N) 的时间复杂度。

    由于这两种算法都属于比较排序算法,所以Introsort也是一个比较排序算法。

    按我的理解可以说是快速排序+插入排序+堆排序的混合方式;

    优化过的快速排序:

    private static void IntroSort<T>(T[] array, int low, int height,
        int depthLimit, IComparer<T> comparer, bool desc = false) {
        while (height > low) {
            int partitionSize = height - low + 1;
    
            //判断区间长度少于等于16时候不再使用快速排序提升效率
            if (partitionSize <= IntrosortSizeThreshold) {
                if (partitionSize == 1) {
                    return;
                }
                if (partitionSize == 2) {
                    //长度为2的时候直接左右尝试交换
                    if (desc) {
                        SwapIfLessthan(array, low, height, comparer);
                    } else {
                        SwapIfGreater(array, low, height, comparer);
                    }
                    return;
                }
                if (partitionSize == 3) {
                    //长度为3时候,三数取中分割法
                    if (desc) {
                        SwapMed3ByLessthan(array, low, height, height - 1, comparer);
                    } else {
                        SwapMed3(array, low, height, height - 1, comparer);
                    }
                    return;
                }
    
                //使用插入排序算法
                InsertionSort(array, low, height, desc);
                return;
            }
            if (depthLimit == 0) {
                HeapSort(array, low, height, desc);
                return;
            }
            depthLimit--;
            //pivotpos划分后的基准记录的位置
            //对R[low..high]做划分
            int pivotpos = PickPivotAndPartition(array, low, height, comparer, desc);
            //对右区间递归排序
            IntroSort(array, pivotpos + 1, height, depthLimit, comparer, desc);
            //对左区间递归排序 因为有个while所以不需要递归,相当于QuickSort(array,low,pivotpos-1);
            height = pivotpos - 1;
        }
    }
    View Code

    1.利用基于三中值分区的中枢值来做快排

            //分治法:三数取中分割法
            private static int PickPivotAndPartition<T>(T[] keys, int lo, int hi,
                IComparer<T> comparer, bool desc) {
                //用区间中位记录作为基准
                int median =  GetMedian(lo, hi);
    
                //采取keys[lo],keys[median],keys[hi]三者之中的那个第二大的元素为主元时
                //便能尽最大限度保证快速排序算法不会出现O(N^2)的最坏情况
                if (desc) {
                    SwapMed3ByLessthan(keys, lo, hi, median, comparer);
                } else {
                    SwapMed3(keys, lo, hi, median, comparer);
                }
    
                //基准
                T pivot = keys[median];
    
                //注意:hi-1是因为上面的三数取中算法已经做了低位和高位比较,
                //所以这里获取hi-1(前一个比较),下面的高位实际是高位前一个位置
    
                //尝试中间和高位交换
                Swap(keys, median, hi - 1);
    
                int left = lo;
                //这里意义是为了下面--right使用,
                //如果数组是5开始递减应该是3开始,
                //因为Swap(keys, median, hi - 1);
                //已经做了比较减少一位
                int right = hi - 1;
    
                //从区间两端交替向中间扫描,直至left=right为止
                while (left < right) {
                    if (desc) {
                        //left左边的元素大于pivot,right右边的元素都小于pivot
                        //线性时间的原地划分,只扫描数组一次就能完成
    
                        //使用++left为了跳过第一位,因为上面已经坐了三数取中
                        //从左向右扫描实,左边的元素比基准大,遇到小于pivot时候停止
                        while (comparer.Compare(keys[++left], pivot) > 0) ;
    
                        //从右向左扫描,右边的元素比基准小,遇到大于pivot时候停止
                        while (comparer.Compare(keys[--right], pivot) < 0) ;
                    } else {
                        //left左边的元素小于pivot,right右边的元素都大于pivot
                        //线性时间的原地划分,只扫描数组一次就能完成
    
                        //使用++left为了跳过第一位,因为上面已经坐了三数取中
                        //从左向右扫描实,左边的元素比基准小,遇到大于pivot时候停止
                        while (comparer.Compare(keys[++left], pivot) < 0) ;
    
                        //从右向左扫描,右边的元素比基准大,遇到小于pivot时候停止
                        while (comparer.Compare(keys[--right], pivot) > 0) ;
                    }
    
                    //左右碰撞退出扫描,准备下次递归
                    if (left >= right) break;
    
                    //进行交换
                    Swap(keys, left, right);
                }
    
                //pivot 上面做了Swap(keys, median, hi - 1); 
                //中位和高位比较,所以这里需要基准位置left和高位(hi - 1 同上)交换,
                //因为上面循环的--right已经跳过比较。
                Swap(keys, left, hi - 1);
    
                //基准记录已被最后定位
                return left;
            }
    View Code

    2.设定一个使用切分时数组长度的最小值,如果小于这个值,就使用插入排序(这个最小值根据经验给定,一般设定为16)

    //判断区间长度少于等于16时候不再使用快读排序提升效率
    if (partitionSize <= IntrosortSizeThreshold) {
        if (partitionSize == 1) {
            return;
        }
        if (partitionSize == 2) {
            //长度为2的时候直接左右尝试交换
            if (desc) {
                SwapIfLessthan(array, low, height, comparer);
            } else {
                SwapIfGreater(array, low, height, comparer);
            }
            return;
        }
        if (partitionSize == 3) {
            //长度为3时候,三数取中分割法
            if (desc) {
                SwapMed3ByLessthan(array, low, height, height - 1, comparer);
            } else {
                SwapMed3(array, low, height, height - 1, comparer);
            }
            return;
        }
    
        //使用插入排序算法
        InsertionSort(array, low, height, desc);
        return;
    }
    View Code

    3.监视快排的递归深度,以确保高效的处理。如果快排递归深度超过log(n)级,那么内观排序切换到堆排序

     if (depthLimit == 0) {
         HeapSort(array, low, height, desc);
         return;
     }
         depthLimit--;
    View Code

    源代码下载

    作者:JohnWu
    出处:http://www.cnblogs.com/johnwu/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接
    如有问题,可以通过v.la@Live.cn 联系我,非常感谢。

  • 相关阅读:
    51 Nod 1068 Bash游戏v3
    51 Nod Bash 游戏v2
    51 Nod 1073 约瑟夫环
    UVA 12063 Zeros and ones 一道需要好好体会的好题
    51 Nod 1161 Partial sums
    2018中国大学生程序设计竞赛
    UVA 11971 Polygon
    UVA 10900 So do you want to be a 2^n-aire?
    UVA 11346 Possibility
    python with as 的用法
  • 原文地址:https://www.cnblogs.com/johnwu/p/3344935.html
Copyright © 2011-2022 走看看