我有以下Quicksort总是选择子序列的第一个元素作为其轴心:
void qqsort(int array[], int start, int end) {
int i = start; // index of left-to-right scan
int k = end; // index of right-to-left scan
if (end - start >= 1) { // check that there are at least two elements to sort
int pivot = array[start]; // set the pivot as the first element in the partition
while (k > i) { // while the scan indices from left and right have not met,
while (array[i] <= pivot && i <= end && k > i) // from the left, look for the first element greater than the pivot
i++;
while (array[k] > pivot && k >= start && k >= i) // from the right, look for the first element not greater than the pivot
k--;
if (k > i) // if the left seekindex is still smaller than the right index, swap the corresponding elements
swap(array, i, k);
}
swap(array, start, k); // after the indices have crossed, swap the last element in the left partition with the pivot
qqsort(array, start, k - 1); // quicksort the left partition
qqsort(array, k + 1, end); // quicksort the right partition
} else { // if there is only one element in the partition, do not do any sorting
return;
}
}
现在您可以看到,此算法始终将第一个元素作为支点:int pivot = array[start];
我想修改这个算法,使它总是使用最后一个元素而不是子序列的第一个元素,因为我想分析两个实现的物理运行时间。
我尝试将行int pivot = array[start];
更改为int pivot = array[end];
,但算法输出了未排序的序列:
//Changes: int pivot = array[end];
unsorted: {5 4 3 2 1}
*sorted*: {1 2 5 4 3}
为了测试另一个数据透视表,我也尝试使用子序列的center元素,但算法仍然失败:
//Changes: int pivot = array[(start + end) / 2];
unsorted: {5 3 4 2 1}
*sorted*: {3 2 4 1 5}
有人可以帮我正确理解这个算法并告诉我成功实现这个实现需要做出哪些更改总是选择子序列的最后一个元素作为支点?
答案 0 :(得分:6)
问题的原因
问题是你使用int k = end;
。将pivot元素作为数组中的第一个元素时,使用int i = start;
是很好的,因为循环中的检查将略过它(array[i] <= pivot
)。但是,当您使用最后一个元素作为数据透视表时,k将停止在结束索引上,并将数据透视切换到分区左半部分的位置。你已经遇到了麻烦,因为你的枢轴很可能位于左侧分区内而不是边界处。
解决方案
要解决此问题,您需要在使用最右边的元素作为轴时设置int k = end - 1;
。您还需要更改用于将枢轴交换到左右分区之间边界的线条:
swap(array, i, end);
qqsort(array, start, i - 1);
qqsort(array, i + 1, end);
你必须使用i来实现这一点,因为我将最终在右边分区的最左边的元素(然后可以与最右边元素中的枢轴交换,它将保留顺序)。最后,您需要在减少k的while中将k >= i
更改为k > i
,否则数组[-1]索引错误的变化很小。之前不可能发生这种情况,因为此时我总是至少等于i + 1。
应该这样做。
<强>旁注:强>
这是一个写得不好的快速入口,我不建议学习。它有一些无关紧要的,不必要的比较以及一些我不会浪费时间列出的其他错误。我建议使用Sedgewick和Bentley的this presentation快速排序。
答案 1 :(得分:2)
我没有测试过,但无论如何要检查它:
此
// after the indices have crossed,
// swap the last element in the left partition with the pivot
swap(array, start, k);
可能应该是
swap(array, end, i);
或类似的东西,如果我们选择end
作为支点。
编辑:这是一个有趣的分区算法,但它不是标准。
嗯, pivot 是在分区的逻辑中修复的 该算法将第一个元素视为Head,将其余元素视为要分区的Body 分区完成后,作为最后一步,将头(枢轴)与左分区部分的 last 元素交换,以保持排序。
我想在不改变算法的情况下使用不同的支点的唯一方法是:
...
if (end - start >= 1) {
// Swap the 1st element (Head) with the pivot
swap(array, start, pivot_index);
int pivot = array[start];
...
答案 2 :(得分:1)
第一个提示:如果数据是随机的,平均而言,您选择哪个值作为数据透视表无关紧要。实际上提高枢轴“质量”的唯一方法是采用更多(例如3个)指数并使用具有中值的那个指数。
第二个提示:如果更改数据透视值,则还需要更改数据透视索引。这没有明确命名,但array[start]
在一个点被交换到已排序子序列的“中间”。您需要相应地修改此行。如果你取一个不在子序列边缘的索引,你需要在迭代之前先将它交换到边缘。
第三个提示:您提供的代码被过度评论。您应该能够真正理解这种实现。
答案 3 :(得分:0)
放一个
swap(array, start, end)
在初始化数据
之前int pivot = array[start]
答案 4 :(得分:0)
#include <time.h>
#include <stdlib.h>
#include<iostream>
#include<fstream>
using namespace std;
int counter=0;
void disp(int *a,int n)
{
for(int i=0;i<n;i++)
cout<<a[i]<<" ";
cout<<endl;
}
void swap(int a[],int p,int q)
{
int temp;
temp=a[p];
a[p]=a[q];
a[q]=temp;
}
int partition(int a[], int p, int start, int end)
{
swap(a,p,start);// to swap the pivot with the first element of the partition
counter+=end-start; // instead of (end-start+1)
int i=start+1;
for(int j=start+1 ; j<=end ; j++)
{
if(a[j]<a[start])
{
swap(a,j,i);
i++;
}
}
swap(a,start,i-1); // not swap(a,p,i-1) because p and start were already swaped..... this was the earlier mistake comitted
return i-1; // returning the adress of pivot
}
void quicksort(int a[],int start,int end)
{
if(start>=end)
return;
int p=end; // here we are choosing last element of the sub array as pivot
// here p is the index of the array where pivot is chosen randomly
int index=partition(a,p,start,end);
quicksort(a,start,index-1);
quicksort(a,index+1,end);
}
int main()
{
ifstream fin("data.txt");
int count=0;
int array[100000];
while(fin>>array[count])
{
count++;
}
quicksort(array,0,count-1);
/*
int a[]={32,56,34,45,23,54,78};
int n=sizeof(a)/sizeof(int);
disp(a,n);
quicksort(a,0,n-1);
disp(a,n);*/
cout<<endl<<counter;
return 0;
}
答案 5 :(得分:0)
如果你开始监视从数组的第一个元素到最后一个元素的每个元素 - 1,在每次递归时保持最后一个元素作为枢轴,那么你将得到完全 O(nlogn)的答案强>时间。
#include<stdio.h>
void quicksort(int [], int, int);
int main()
{
int n, i = 0, a[20];
scanf("%d", &n);
while(i < n)
scanf("%d", &a[i++]);
quicksort(a, 0, n - 1);
i = 0;
while(i < n)
printf("%d", a[i++]);
}
void quicksort(int a[], int p, int r)
{
int i, j, x, temp;
if(p < r)
{
i = p;
x = a[r];
for(j = p; j < r; j++)
{
if(a[j] <= x)
{
if(a[j] <a[i])
{
temp = a[j];
a[j] = a[i];
a[i] = temp;
}
i++;
}
else
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
if(x != i)
{
temp = a[r];
a[r] = a[i];
a[i] = temp;
}
quicksort(a, p, i - 1);
quicksort(a, i + 1, r);
}
}