我正在尝试使用std :: chrono时间计算并使用在[A,B]范围内的随机生成的整数数组来测量Merge Sort和Quick Sort函数的持续时间,该数组的大小从5000到100,000个整数。
我的代码的目的是证明,当改进快速排序中拾取(枢轴)的方法时,快速排序功能最终比合并排序花费的时间更少,这是我选择枢轴的方式正在使用随机索引方法来最小化具有(n ^ 2)复杂度的可能性,但是在某些情况下(我将在下面描述),快速排序最终比合并排序要花更多的时间,我想知道为什么这样做发生。
情况1: 数组中数字的范围很小,这增加了在数组中重复数字的可能性。
情况2: 当我使用像clion这样的本地IDE时,快速排序功能比合并排序要花费更多的时间,但是,像IDEONE.com这样的在线编译器在两种排序算法中都会得到相似的结果(即使生成的整数的范围很小) / p>
这是我在上述情况下得到的结果(第一行是合并排序结果,第二行是快速排序结果):
1-clion可缩小数字范围(-100、600)
2-clion结果的数字范围很广(INT_MIN,INT_MAX)
3-IDEONE结果的数字范围很小(-100、600)
4- IDEONE结果的范围很广(INT_MIN,INT_MAX)
#include <bits/stdc++.h>
#include <chrono>
#include <random>
using namespace std;
mt19937 gen(chrono::steady_clock::now().time_since_epoch().count());
int* generateArray(int size)
{
int* arr = new int[size];
uniform_int_distribution<> distribution(INT_MIN, INT_MAX);
for (int i=0; i < size; ++i)
{
arr[i] = distribution(gen);
}
return arr;
}
void merge(int* leftArr, int nL, int* rightArr, int nR, int* mainArr)
{
int i=0, j=0, k=0;
while (i < nL && j < nR)
{
if (leftArr[i] < rightArr[j]) { mainArr[k++] = leftArr[i++]; }
else { mainArr[k++] = rightArr[j++]; }
}
while (i < nL){ mainArr[k++] = leftArr[i++]; }
while (j < nR){ mainArr[k++] = rightArr[j++]; }
}
void mergeSort (int* mainArray, int arrayLength)
{
if (arrayLength < 2) { return; }
int mid = arrayLength/2;
int* leftArray = new int[mid];
int* rightArray = new int[arrayLength - mid];
for (int i=0; i<mid; ++i) {leftArray[i] = mainArray[i];}
for (int i = mid; i<arrayLength; ++i) {rightArray[i - mid] = mainArray[i];}
mergeSort(leftArray, mid);
mergeSort(rightArray, arrayLength-mid);
merge(leftArray, mid, rightArray, arrayLength-mid, mainArray);
delete[] leftArray;
delete[] rightArray;
}
int partition (int* arr, int left, int right)
{
uniform_int_distribution<> distribution(left, right);
int idx = distribution(gen);
swap(arr[right], arr[idx]);
int pivot = arr[right];
int partitionIndex = left;
for (int i = left; i < right; ++i)
{
if (arr[i] <= pivot)
{
swap(arr[i], arr[partitionIndex]);
partitionIndex++;
}
}
swap(arr[right], arr[partitionIndex]);
return partitionIndex;
}
void quickSort (int* arr, int left, int right)
{
if(left < right)
{
int partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex-1);
quickSort(arr, partitionIndex+1, right);
}
}
int main()
{
vector <long long> mergeDuration;
vector <long long> quickDuration;
for (int i = 5000; i<= 100000; i += 5000)
{
int* arr = generateArray(i);
auto startTime = chrono::high_resolution_clock::now();
quickSort(arr, 0, i - 1);
auto endTime = chrono::high_resolution_clock::now();
long long duration = chrono::duration_cast<chrono::milliseconds>(endTime - startTime).count();
quickDuration.push_back(duration);
delete[] arr;
}
for (int i = 5000; i <= 100000; i += 5000 )
{
int* arr = generateArray(i);
auto startTime = chrono::high_resolution_clock::now();
mergeSort(arr, i);
auto endTime = chrono::high_resolution_clock::now();
long long duration = chrono::duration_cast<chrono::milliseconds>(endTime - startTime).count();
mergeDuration.push_back(duration);
delete[] arr;
}
for (int i = 0; i<mergeDuration.size(); ++i)
{
cout << mergeDuration[i] << " ";
}
cout << endl;
for (int i = 0; i<quickDuration.size(); ++i)
{
cout << quickDuration[i] << " ";
}
}
答案 0 :(得分:4)
众所周知,当输入集包含大量重复项时,Quicksort的性能较差。解决方案是使用Wikipedia中所述的三向分区:
重复的元素
使用上述分区算法(甚至 选择一个好的枢轴值),quicksort表现不佳 包含许多重复元素的输入的性能。的 当所有输入元素都相等时,问题显而易见: 每次递归时,左分区为空(输入值均小于该值) 而不是枢轴),而右分区只减少了一个 元素(枢轴已删除)。因此,该算法需要 二次时间对等值数组进行排序。
要解决此问题(有时称为Dutch national flag problem),可以使用替代的线性时间分区例程 将值分为三个组:值小于 枢轴,值等于枢轴,值大于枢轴。 ... 价值 等于枢轴的点已经被排序,所以只有小于和 大于分区需要进行递归排序。用伪代码, 快速排序算法变为
algorithm quicksort(A, lo, hi) is if lo < hi then p := pivot(A, lo, hi) left, right := partition(A, p, lo, hi) // note: multiple return values quicksort(A, lo, left - 1) quicksort(A, right + 1, hi)
分区算法将索引返回到第一个(“最左侧”)和 到中间分区的最后一个(“最右边”)项目。的每一项 该分区等于p,因此被排序。因此, 分区的项不必包含在对的递归调用中 快速排序。
以下经过修改的quickSort
可提供更好的结果:
pair<int,int> partition(int* arr, int left, int right)
{
int idx = left + (right - left) / 2;
int pivot = arr[idx]; // to be improved to median-of-three
int i = left, j = left, b = right - 1;
while (j <= b) {
auto x = arr[j];
if (x < pivot) {
swap(arr[i], arr[j]);
i++;
j++;
} else if (x > pivot) {
swap(arr[j], arr[b]);
b--;
} else {
j++;
}
}
return { i, j };
}
void quickSort(int* arr, int left, int right)
{
if (left < right)
{
pair<int, int> part = partition(arr, left, right);
quickSort(arr, left, part.first);
quickSort(arr, part.second, right);
}
}
输出:
0 1 2 3 4 5 6 7 8 9 11 11 12 13 14 15 16 19 18 19
0 0 0 1 0 1 1 1 1 1 2 3 2 2 2 2 3 3 3 3
0 1 2 3 4 5 6 6 8 8 9 12 11 12 13 14 16 17 18 19
0 0 1 1 1 2 3 3 3 4 4 4 5 6 5 6 7 7 8 8
因此,现在有很多重复的运行要快得多。
答案 1 :(得分:2)
当数组具有重复值时,为什么快速排序算法的持续时间会增加?
仅当使用Lomuto类型分区方案时,这是正确的,其中重复的值会使拆分变得更糟。
如果使用Hoare分区方案,则当数组具有重复值时,算法持续时间通常会减少,因为拆分更接近于精确拆分为一半的理想情况,改进的拆分可补偿具有内存的典型系统上的额外交换缓存。