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

    Introsort(内观排序)

    2013-09-28 23:59 by v.la, 51 阅读, 0 评论, 收藏编辑

    .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;
    }
    }

    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;
    }


    复制代码
            //分治法:三数取中分割法
            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;
            }
    复制代码

    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;
    }


    复制代码
    //判断区间长度少于等于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;
    }
    复制代码

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

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

     if (depthLimit == 0) {
         HeapSort(array, low, height, desc);
         return;
     }
         depthLimit--;
  • 相关阅读:
    watir简单使用
    一些简单的Linux网络配置命令
    Watir识别HTML元素的方法及watir api补充
    web系统测试
    测试方法
    内存泄漏检测工具
    跟我一起学Oracle 11g【10】Oracle 中的那些函数
    限制textarea 文本框的长度(收集了几个办法)
    跟我一起学Oracle 11g【9】SQL 基础学习[嵌套查询]
    通过程序启用/禁用 网络连接(提供4种思路 你值得拥有)
  • 原文地址:https://www.cnblogs.com/davidshi/p/3346942.html
Copyright © 2011-2022 走看看