[英]Why is C quicksort function much slower (tape comparisons, tape swapping) than bubble sort function?
我將為學生實現一個玩具磁帶“大型機”,顯示“快速排序”類功能的快速性(遞歸與否,由於硬件速度慢,並且眾所周知的堆棧反轉技術並不重要) “bubblesort”函數類。 因此,雖然我清楚硬件實現和控制器,但我猜測,快速排序功能在順序,順序和比較距離方面要比其他功能快得多(從中間回放磁帶要快得多)結束,因為倒帶速度不同)。
不幸的是,事實並非如此; 這個簡單的“氣泡”代碼在比較距離,方向和比較和寫入次數方面與“快速排序”功能相比顯示出很大的改進。
所以我有3個問題:
我已經有了“quicksort”功能:
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");
}
我認為問題在於大多數快速排序實現依賴於分區步驟,該分區步驟在要排序的區域的相對端交替進行讀取和寫入。 在隨機訪問模型中,這非常好(所有讀取基本上都是O(1)),但在磁帶上這可能非常昂貴,因為在要排序的范圍的不同端之間來回交換可能需要O( n)磁帶卷軸一直向前和向后滾動的時間。 這通常將O(n)分區步驟轉換為潛在的O(n 2 ),從而支配函數的運行時。 此外,由於進行磁帶搜索所需的時間可能比處理器的頻率慢數千或數百萬倍,因此該O(n 2 )工作具有巨大的常數因子。
另一方面,冒泡排序沒有這個問題,因為它總是比較陣列中的相鄰單元格。 它最多可以在陣列上進行O(n)次通過,因此要求磁帶只能重繞n次。 在冒泡排序中處理邏輯肯定更昂貴 - 比幾乎任何其他O(n 2 )排序更加昂貴 - 但與沒有來回尋找磁帶所節省的時間相比,這沒什么。
簡而言之,快速排序在磁帶上的運行速度應該比冒泡排序慢得多,因為它需要磁帶在執行期間移動更多。 由於磁帶搜索費用昂貴,因此快速排序的自然運行時優勢將在此步驟中消耗掉,並且bubblesort應該看起來更快。
templatetypedef的答案是正確的錢。 bubbleort的訪問不僅極少分散,而且可以就地運行 。 我懷疑它實際上是具有單個,任意慢速磁帶和僅O(1)RAM的機器的最佳排序算法。 [編輯:實際上雞尾酒排序 (bubbleort的雙向版本)應該更好,因為它避免了浪費的倒帶 - 感謝Steve Jessop。]
如果您有4個可用的磁帶驅動器,那么mergesort可以控制它們 。 只有3個磁帶,可以使用更高版本的mergesort 。
QuickSort比冒泡排序更快的原因之一是它可以瞬間移動元素很遠的距離。 如果QuickSort將一個元素向上移動50個元素,然后向下移動20個,向上移動10個,向上移動5個向下移動2個向下移動到它的適當位置,該元素將從它開始的位置移動43個插槽,同時僅移動5次。 冒泡排序會使元素移動43次。 如果移動元素,則一個插槽的成本與將其移動50相同,這是一個重大勝利。 但是,如果移動元素的成本與距離成比例,則QuickSort將移動元素的總距離為87個插槽 - 是冒泡排序的兩倍。
如果一個人處理磁帶驅動器,最佳算法將在很大程度上取決於驅動器的物理工作方式。 例如,在某些驅動器上,唯一的操作是倒帶並准備寫入(有效擦除過程中的磁帶),倒帶並准備讀取,並處理下一個字節(讀取或寫入,具體取決於倒帶模式)。 其他驅動器允許在磁帶上的任何位置隨機訪問和替換各個塊。 有些驅動器僅限於向一個方向讀取。 其他(例如QIC磁帶)有一些軌道在一個方向讀取而一些在另一個方向讀取。 我不知道是否有任何驅動器允許在兩個方向上讀取或寫入相同的數據塊,但這樣的事情至少在理論上是可能的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.