熵最优快速排序
Entropy Optimal Quick Sort
我在我的算法 class 中做排序练习,我们需要实现各种排序算法并根据我们教授提供的输入测试它们。
我有以下快速排序实现,它是熵最优的,这意味着当大量元素序列相等时,它可能比 NlogN 边界更快。我已经完成的实现可以在这个 post 下方找到(按照评论中的建议删除了 pastebin link)
在 运行 上,我发现它比 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;
}
你有两个不同的问题,这确实需要两个不同的问题。不过我会为你回答一个。
哦,以后请不要用 link 来编码,如果那个 link 死了怎么办?那你的问题就没用了
崩溃的问题是几乎所有的编译器都将局部变量(包括数组)放在栈上,而且栈是有限的。例如,在 Windows 上,进程的默认堆栈只有一兆字节。
有了两个这样的数组,每个数组都有 1000000 个条目,您将有 8 兆字节(这恰好是默认堆栈-size for Linux processes),当然加上函数调用堆栈帧和所有其他局部变量和参数等的 space。这超出了(或 way 超出)可用堆栈,你将有 未定义的行为 和可能的崩溃。
Microsoft 的 std::sort 使用 introsort。维基 link:
http://en.wikipedia.org/wiki/Introsort
如果嵌套达到某个限制,Introsort 会从快速排序切换到堆排序,这主要是出于性能原因,因为快速排序变慢的一个指标是过多的嵌套,但它也有防止过度嵌套的附带好处 运行 线程出栈 space.
代码有两个问题。
A) 您正在创建一个可能很大的堆栈分配数组。一旦堆栈大小溢出,下一页可能是从未映射到随机堆内存的任何内容。
B) 我注意到的另一件奇怪的事情是你在每次调用分区时初始化一个 RNG(也可能很昂贵),这会浪费每个分区点的堆栈 space。
我在我的算法 class 中做排序练习,我们需要实现各种排序算法并根据我们教授提供的输入测试它们。
我有以下快速排序实现,它是熵最优的,这意味着当大量元素序列相等时,它可能比 NlogN 边界更快。我已经完成的实现可以在这个 post 下方找到(按照评论中的建议删除了 pastebin link)
在 运行 上,我发现它比 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;
}
你有两个不同的问题,这确实需要两个不同的问题。不过我会为你回答一个。
哦,以后请不要用 link 来编码,如果那个 link 死了怎么办?那你的问题就没用了
崩溃的问题是几乎所有的编译器都将局部变量(包括数组)放在栈上,而且栈是有限的。例如,在 Windows 上,进程的默认堆栈只有一兆字节。
有了两个这样的数组,每个数组都有 1000000 个条目,您将有 8 兆字节(这恰好是默认堆栈-size for Linux processes),当然加上函数调用堆栈帧和所有其他局部变量和参数等的 space。这超出了(或 way 超出)可用堆栈,你将有 未定义的行为 和可能的崩溃。
Microsoft 的 std::sort 使用 introsort。维基 link:
http://en.wikipedia.org/wiki/Introsort
如果嵌套达到某个限制,Introsort 会从快速排序切换到堆排序,这主要是出于性能原因,因为快速排序变慢的一个指标是过多的嵌套,但它也有防止过度嵌套的附带好处 运行 线程出栈 space.
代码有两个问题。
A) 您正在创建一个可能很大的堆栈分配数组。一旦堆栈大小溢出,下一页可能是从未映射到随机堆内存的任何内容。
B) 我注意到的另一件奇怪的事情是你在每次调用分区时初始化一个 RNG(也可能很昂贵),这会浪费每个分区点的堆栈 space。