我要为学生实现一个玩具磁带“大型机”,显示“快速排序”类功能的快速性(递归与否,由于硬件速度慢而无关紧要,以及众所周知的堆栈反转技术) )与“bubblesort”函数类相比。因此,虽然我清楚硬件实现和控制器,但我猜测,快速排序功能在顺序,顺序和比较距离方面要比其他功能快得多(从中间回放磁带要快得多)结束,因为倒带速度不同)。
不幸的是,事实并非如此;与比较距离,方向和比较和写入次数方面的“快速排序”功能相比,这个简单的“泡沫”代码显示出了很大的改进。
所以我有3个问题:
我已经有了“快速排序”功能:
void quicksort(float *a, long l, long r, const compare_function& compare)
{
long i=l, j=r, temp, m=(l+r)/2;
if (l == r) return;
if (l == r-1)
{
if (compare(a, l, r))
{
swap(a, l, r);
}
return;
}
if (l < r-1)
{
while (1)
{
i = l;
j = r;
while (i < m && !compare(a, i, m)) i++;
while (m < j && !compare(a, m, j)) j--;
if (i >= j)
{
break;
}
swap(a, i, j);
}
if (l < m) quicksort(a, l, m, compare);
if (m < r) quicksort(a, m, r, compare);
return;
}
}
我自己实现了“bubblesort”功能:
void bubblesort(float *a, long l, long r, const compare_function& compare)
{
long i, j, k;
if (l == r)
{
return;
}
if (l == r-1)
{
if (compare(a, l, r))
{
swap(a, l, r);
}
return;
}
if (l < r-1)
{
while(l < r)
{
i = l;
j = l;
while (i < r)
{
i++;
if (!compare(a, j, i))
{
continue;
}
j = i;
}
if (l < j)
{
swap(a, l, j);
}
l++;
i = r;
k = r;
while(l < i)
{
i--;
if (!compare(a, i, k))
{
continue;
}
k = i;
}
if (k < r)
{
swap(a, k, r);
}
r--;
}
return;
}
}
我在测试示例代码中使用了这些排序函数,如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <conio.h>
long swap_count;
long compare_count;
typedef long (*compare_function)(float *, long, long );
typedef void (*sort_function)(float *, long , long , const compare_function& );
void init(float *, long );
void print(float *, long );
void sort(float *, long, const sort_function& );
void swap(float *a, long l, long r);
long less(float *a, long l, long r);
long greater(float *a, long l, long r);
void bubblesort(float *, long , long , const compare_function& );
void quicksort(float *, long , long , const compare_function& );
void main()
{
int n;
printf("n=");
scanf("%d",&n);
printf("\r\n");
long i;
float *a = (float *)malloc(n*n*sizeof(float));
sort(a, n, &bubblesort);
print(a, n);
sort(a, n, &quicksort);
print(a, n);
free(a);
}
long less(float *a, long l, long r)
{
compare_count++;
return *(a+l) < *(a+r) ? 1 : 0;
}
long greater(float *a, long l, long r)
{
compare_count++;
return *(a+l) > *(a+r) ? 1 : 0;
}
void swap(float *a, long l, long r)
{
swap_count++;
float temp;
temp = *(a+l);
*(a+l) = *(a+r);
*(a+r) = temp;
}
float tg(float x)
{
return tan(x);
}
float ctg(float x)
{
return 1.0/tan(x);
}
void init(float *m,long n)
{
long i,j;
for (i = 0; i < n; i++)
{
for (j=0; j< n; j++)
{
m[i + j*n] = tg(0.2*(i+1)) + ctg(0.3*(j+1));
}
}
}
void print(float *m, long n)
{
long i, j;
for(i = 0; i < n; i++)
{
for(j = 0; j < n; j++)
{
printf(" %5.1f", m[i + j*n]);
}
printf("\r\n");
}
printf("\r\n");
}
void sort(float *a, long n, const sort_function& sort)
{
long i, sort_compare = 0, sort_swap = 0;
init(a,n);
for(i = 0; i < n*n; i+=n)
{
if (fmod (i / n, 2) == 0)
{
compare_count = 0;
swap_count = 0;
sort(a, i, i+n-1, &less);
if (swap_count == 0)
{
compare_count = 0;
sort(a, i, i+n-1, &greater);
}
sort_compare += compare_count;
sort_swap += swap_count;
}
}
printf("compare=%ld\r\n", sort_compare);
printf("swap=%ld\r\n", sort_swap);
printf("\r\n");
}
答案 0 :(得分:32)
我认为问题在于大多数快速排序实现依赖于分区步骤,该分区步骤在要排序的区域的相对两端交替进行读取和写入。在随机访问模型中,这非常好(所有读取基本上都是O(1)),但在磁带上这可能非常昂贵,因为在要排序的范围的不同端之间来回交换可能需要O( n)磁带卷轴一直向前和向后滚动的时间。这通常将O(n)分区步骤转换为可能为O(n 2 )的东西,从而支配函数的运行时。此外,由于进行磁带搜索所需的时间可能比处理器频率慢几千或几百万倍,因此这种O(n 2 )工作具有巨大的常数因子。
另一方面,冒泡排序没有这个问题,因为它总是比较数组中的相邻单元格。它最多可以在阵列上进行O(n)次通过,因此要求磁带只能重绕n次。在冒泡排序中处理逻辑肯定更昂贵 - 比几乎任何其他O(n 2 )排序更加昂贵 - 但与没有来回寻找磁带所节省的时间相比,这没什么。
简而言之,快速排序在磁带上运行速度应该比冒泡排序慢得多,因为它需要磁带在执行期间移动更多。由于磁带搜索费用昂贵,因此快速排序的自然运行时优势会在此步骤中消失,而且bubbleort应该看起来更快。
答案 1 :(得分:11)
templatetypedef's answer是对的钱。 bubbleort的访问不仅极少分散,而且就地。我怀疑它实际上是最佳排序算法,用于具有单个,任意慢的磁带和仅O(1)RAM的机器。 [编辑:实际上cocktail sort(bubbleort的双向版本)应该更好,因为它避免了浪费的倒带 - 感谢Steve Jessop。]
如果您有4个磁带机,那么mergesort rules the roost。只有3个磁带,可以使用a fancier version of mergesort。
答案 2 :(得分:1)
QuickSort比冒泡排序更快的原因之一是它可以瞬间移动元素很远的距离。如果QuickSort将一个元素向上移动50个元素,然后向下移动20个,向上移动10个,向上移动5个向下移动2个向下移动到它的适当位置,该元素将从它开始的位置移动43个插槽,同时仅移动5次。冒泡排序会使元素移动43次。如果移动元素,则一个插槽的成本与将其移动50相同,这是一个重大胜利。但是,如果移动元素的成本与距离成正比,则QuickSort将移动元素的总距离为87个插槽 - 是冒泡排序的两倍。
如果一个人遇到磁带驱动器问题,最佳算法将在很大程度上取决于驱动器的物理工作方式。例如,在某些驱动器上,唯一的操作是倒带并准备写入(有效擦除过程中的磁带),倒带并准备读取,并处理下一个字节(读取或写入,具体取决于倒带模式)。其他驱动器允许在磁带上的任何位置随机访问和替换各个块。有些驱动器仅限于向一个方向读取。其他(例如QIC磁带)具有在一个方向上读取的一些轨道和在另一个方向上读取的一些轨道。我不知道是否有任何驱动器允许在两个方向上读取或写入相同的数据块,但这样的事情至少在理论上是可行的。