熵最优快速排序

Posted

技术标签:

【中文标题】熵最优快速排序【英文标题】:Entropy Optimal Quick Sort 【发布时间】:2015-09-11 07:09:04 【问题描述】:

我在算法课上做了一个排序练习,我们需要实现各种排序算法,并根据教授提供的输入来测试它们。

我有以下快速排序实现,这是熵最优的,这意味着当大量元素相等时,它可能比 NlogN 边界更快。我所做的实现可以在这篇文章下面找到(删除了 cmets 中建议的 pastebin 链接)

在运行它时,我发现它比 std::sort 算法慢(我明白这只是 NlogN 常数的差异)边界,但结果我错过了大的时间限制输入序列。

此外,当输入大小为 1000000 时,std::sort 能够排序,但我的算法给了我一个分段错误。有人可以看看这个,如果我做错了什么,请告诉我。提前致谢。

#include <algorithm>
#include <iostream>
#include <iterator>
#include <random>
#include <utility>

struct Sort 
public:
        enum class SortAlg  selection = 0, insertion, shell, merge, mergeBU, quick, heap ;
        template <typename T, int N>
        static void sort(T (&arr) [N], SortAlg alg) 
                SortArray<T,N> sortM (arr);
                switch (alg) 
                        case SortAlg::quick:
                                sortM.quicksort(); break;
                        default:
                                sortM.quicksort();
                ;
        
private:
        template <typename T, int N>
        class SortArray 
        public:
                SortArray(T (&a) [N]) : arr(a) 
                void quicksort();
        private:
                void qsort(int lo, int hi);
                std::pair<int, int> partition(int lo, int hi);
                T (&arr) [N];
        ;
;


template <typename T, int N>
void Sort::SortArray<T, N>::quicksort()
        qsort(0, N-1);


template <typename T, int N>
void Sort::SortArray<T, N>::qsort(int lo, int hi)
if (lo >= hi) return;
        std::pair<int, int> part = partition(lo, hi);
        qsort(lo, part.first);
        qsort (part.second, hi);


//This partitions the algorithm into 3 ranges
//1st range - elements less than the partition element
//2nd range - elements equal to the partition element
//3rd range - elements greater than the partition element
//it returns a pair (a,b) where[a+1, b-1] represents the
//equal range which will be left out of subsequent sorts and
//the next set of sorting will be on [lo,a] and [b,hi]
template <typename T, int N>
std::pair<int, int> Sort::SortArray<T, N>::partition(int lo, int hi)
        static int count = 0;
        std::random_device rd;
        std::mt19937_64 gen(rd());
        std::uniform_int_distribution<int> dis;
        int elem = lo + (dis(gen) % (hi-lo+1)); //position of element around which paritioning happens
        using std::swap;
        swap(arr[lo], arr[elem]);
        int val = arr[lo];
        //after the while loop below completes
        //the range of elements [lo, eqind1-1] and  [eqind2+1, hi] will all be equal to arr[lo]
        //the range of elements [eqind1, gt] will all be less than arr[lo]
        //the range of elements [lt, eqind2] will all be greated than arr[lo]
        int lt = lo+1, gt = hi, eqind1 = lo, eqind2 = hi;
        while (true)
                while(lt <= gt && arr[lt] <= val) 
                        if (arr[lt] == val)
                                if(lt == eqind1 + 1)
                                        ++eqind1;
                                else
                                        swap(arr[lt], arr[++eqind1]);
                        
                        ++lt;
                
                while(gt >= lt && arr[gt] >= val) 
                        if(arr[gt] == val)
                                if(gt == eqind2)
                                        --eqind2;
                                else
                                        swap(arr[gt], arr[eqind2--]);
                        
                        --gt;
                ;
                if(lt >= gt) break;
                swap(arr[lt], arr[gt]); ++lt; --gt;
        ;
        swap(arr[lo], arr[gt]);
        if (eqind1!=lo)
                    //there are some elements equal to arr[lo] in the first eqind1-1 places
                    //move the elements which are less than arr[lo] to the beginning
                for (int i = 1; i<lt-eqind1; i++)
                        arr[lo+i] = arr[lo + eqind1+i];
        
        if (eqind2!=hi)
                    //there are some elements which are equal to arr[lo] in the last eqind2-1 places
                    //move the elements which are greater than arr[lo] towards the end of the array
                for(int i = hi; i>gt; i--)
                        arr[i] = arr[i-hi+eqind2];
            
        //calculate the number of elements equal to arr[lo] and fill them up in between
        //the elements less than and greater than arr[lo]
        int numequals = eqind1 - lo + hi - eqind2 + 1;
        if(numequals != 1)
                for(int i = 0; i < numequals; i++)
                        arr[lo+lt-eqind1+i-1] = val;
        
        //calculate the range of elements that are less than and greater than arr[lo]
        //and return them back to qsort
        int lorange = lo + lt-eqind1-2;
        int hirange = lo + lt - eqind1 - 1 + numequals;
        return lorange, hirange;


int main() 
        std::random_device rd;
        std::mt19937_64 gen(rd());
        std::uniform_int_distribution<int> dis;
        constexpr int size = 100000;
        int arr[size], arr1[size];
        for (int i = 0; i < size; i++)
                arr[i] = dis(gen)%9;
                arr1[i] = arr[i];;  
        
        std::sort(std::begin(arr1), std::end(arr1));
        std::cout << "Standard sort finished" << std::endl;
        Sort::sort(arr, Sort::SortAlg::quick);
        std::cout << "Custom sort finished" << std::endl;
        int i =0;
        int countDiffer = 0;
        for (; i <size; ++i)
                if (arr[i] != arr1[i])
                        countDiffer++;
                
        
        if (i == size) std::cout << "Sorted" << std::endl;
        else std::cout << "Not sorted and differ in " << countDiffer
                       << " places" << std::endl;   

【问题讨论】:

您正在使用一个包含 1.000.000 个整数(每个 4 字节)的数组,它要求大空间 (4GB) 的连续内存 - 可能分配失败。试试std::deque 吧? @Alex:4MB?如果值相等,可能是实现中的一个错误,它会不断递归? 嗨,Alex,分配不是问题,因为如果是这样,那么即使是对 std::sort 的调用也会失败......我正在运行它的机器上的堆栈大小是8 MB 应该绰绰有余....这肯定是我的实现问题,我需要你们的帮助... 我注意到的另一件奇怪的事情是,每次调用分区时都会初始化一个 RNG(也可能很昂贵)。 没有必要在每个partition 上创建一个新的随机数生成器,使用% 会破坏一致性。使用uniform_int_distribution&lt;int&gt; dis(lo, hi); int elem = dis(gen);(并且只创建一个RNG。) 【参考方案1】:

代码有两个问题。

A) 您正在创建一个可能很大的堆栈分配数组。一旦堆栈大小溢出,下一页可能是从未映射到随机堆内存的任何内容。

B) 我注意到的另一件奇怪的事情是,每次调用分区时都会初始化一个 RNG(也可能很昂贵),这会浪费每个分区点的堆栈空间。

【讨论】:

谢谢...将 RNG 移出解决了两个问题,即堆栈溢出和运行时间比 std::sort 算法慢很多... 那里还有模板膨胀 我们将只在 3 组输入上运行此代码,即 int 数组(小型、中型和大型)...因此将为每个成员函数生成 3 个实例,这是我可以忍受......因为这是一个算法类,所以编写生产安全代码并不重要......而且模板是由教授提供的,我们只是在扩展它【参考方案2】:

你有两个不同的问题,这确实应该保证两个不同的问题。不过我会为你回答一个。

哦,以后请不要有代码链接,如果链接失效了怎么办?那你的问题就没用了。


崩溃的问题是几乎所有编译器都将局部变量(包括数组)放在堆栈上,并且堆栈是有限的。例如,在 Windows 上,进程的默认堆栈只有一个兆字节。

使用两个这样的数组,每个数组包含 1000000 个条目,您将拥有 8 兆字节(这恰好是 Linux 进程的默认堆栈大小),加上当然,函数调用堆栈帧和所有其他局部变量和参数等的空间。这超出(或方式)可用堆栈,您将有未定义的行为 和可能的崩溃。

【讨论】:

谢谢 Joachim ...我明白...但问题是即使我只分配一个包含 1000000 个条目的数组并在其上调用我的排序算法,我也会遇到分段错误。... .它适用于少于100000个元素的数组......我做了一些检查以查看元素大小加倍的测试用例的算法运行时间,并发现该算法是NlgN......但它仍然比std慢::sort 算法...我还能做些什么来提高速度...现在我们可以忽略分段错误问题... @user3493289 它甚至适用于例如999999 个元素?我不信。您应该做的是告诉我们您使用的是 Windows、Linux、OSX 还是其他操作系统,因为这样我们就可以知道堆栈大小是多少,然后我们可以告诉您您的最大大小数组可以。【参考方案3】:

Microsoft 的 std::sort 使用 introsort。维基链接:

http://en.wikipedia.org/wiki/Introsort

如果嵌套达到某个限制,则 Introsort 会从快速排序切换到堆排序,这主要是出于性能原因,因为快速排序会变慢的一个指标是过度嵌套,但它还有一个副作用,即防止过度嵌套运行线程堆栈空间不足。

【讨论】:

以上是关于熵最优快速排序的主要内容,如果未能解决你的问题,请参考以下文章

快速排序的最优和最差比较次数

快速排序堆排序归并排序比较

JavaScript实现快速排序

JavaScript实现快速排序

算法排序3:优化版快速排序非递归实现快速排序

算法排序3:优化版快速排序非递归实现快速排序