|
最近在学习排序,学到快速排序的时候,发现快排的优化貌似很多 尝试了一下书上的三数中值分割和小数组的方法,发现效果真是好,但是我又从网上看到了另外两种方法,一种是九数中值(Median-of-nine)和splitend,却发现一些很奇怪的问题 应该说,理论上九数中值的方法应该比三数中值要快(数字全是随机的情况下,前者应该能更好的划分中值)但是我排序一千万数据的时候前者要比后者快50ms以内,但是一个亿数据的时候前者却比后者慢200ms左右(可能存在误差,但不至于每次都是这样吧?),速率上快不了多少,至于参考量的问题我也是选了很久,但是还是存在这个差距 另外splitend应该是最快的,但是我自己写了一下,发现事实上却慢很多,稍微分析了一下应该是因为考虑相等元素的时候splitend是一直往后搜索的,当元素大量重复的时候会退化为0(N^2),还有交换的时候换了两次,可是这个博客说的那个,Arraysort会很快,但是事实上却慢了很多,我看了一下,好像和我写的没什么区别呀,难道是我理解问题?
好了不多说,贴代码:
//split_end.cpp
#include "quick_sort_plug.h"
int main()//快速排序的几种优化
{
ElementType *A = (ElementType *)malloc(sizeof(ElementType)*MAX);
ElementType *B = (ElementType *)malloc(sizeof(ElementType)*MAX);
int i = 1;
time_t c1, c2;
for (A[0] = 1; i < MAX; i++)
{
srand((unsigned)(time(NULL)*i*A[i-1]));
A[i] = rand();
}
CopyArray(A, B);
CopyArray(A, C);
puts("Quicksort_median_of_nine");
c1 = clock();quicksort_median_of_nine(B, 0, MAX - 1);c2 = clock();
printf("%ldms\n\n\n", c2 - c1);
puts("Quicksort_normal");
c1 = clock(); quicksort(A, 0, MAX - 1); c2 = clock();
printf("%ldms\n\n\n", c2 - c1);
free(A); free(B); free(C); free(tmp);
system("pause"); return 0;
}
void quicksort_median_of_nine(ElementType *A, Position left, Position right)
{
if (left >= right) return;//判断递归结束的标志
int len = right - left;
ElementType pivot;
Position i, j, min = (left + right) / 2;
if (len < CUTOFF) { Insertionsort(A, left, len + 1); return; } //如果len小于CUTOFF则为小数组,采用插入排序,记得return
if (len > MCUTOFF)//大数组,median-of-nine
{
Position ln, rn;
ln = SelectPivot_s(A, left, left + OFFSET, left + OFFSET * 2);
rn = SelectPivot_s(A, right - OFFSET * 2, right - OFFSET, right);
min = SelectPivot_s(A, ln, min, rn);
}
pivot = A[SelectPivot(A, left, min, right)];
for (i = left, j = right;;)
{
while (A[++i] < pivot){};
while (A[--j] > pivot){};
if (i < j) swap(&A[i], &A[j]);
else break;
}
swap(&A[i], &A[right]);
quicksort_median_of_nine(A, left, i - 1);
quicksort_median_of_nine(A, i + 1, right);
}
void quicksort(ElementType *A, Position left, Position right)
{
if (right <= left)return;
Position i, j;
ElementType pivot;
if (right - left > CUTOFF)
{
pivot = A[SelectPivot(A, left, (left + right) /2, right)];
for (i = left, j = right;;)
{
while (A[++i] < pivot){};
while (A[--j] > pivot){};
if (i < j) swap(&A[i], &A[j]);
else break;
}
swap(&A[i], &A[right]);
quicksort(A, left, i - 1);
quicksort(A, i + 1, right);
}
else Insertionsort(A, left, right - left + 1);//转插入排序
}
void Insertionsort(ElementType *A, Position left,int len)
{
ElementType tmp;
int i = left, j, sum = i + len;
for (; i < sum; i++)
{
tmp = A[i];
for (j = i; j > 0 && A[j - 1] > tmp; A[j] = A[j - 1], j--);
A[j] = tmp;
}
}
Position SelectPivot(ElementType *A, Position a, Position b, Position c)//三数取中值函数,我们要排序成从小到大的顺序,隐藏枢纽元
{
if (A[a] > A[b]) swap(&A[a], &A[b]);
if (A[a] > A[c]) swap(&A[a], &A[c]);
if (A[b] > A[c]) swap(&A[b], &A[c]);
swap(&A[b], &A[c]);//交换中值点和末尾点(隐藏尾结点)
return c;
}
Position SelectPivot_s(ElementType *A, Position a, Position b, Position c)//三数取中值函数,我们要排序成从小到大的顺序,不隐藏结点
{
return A[a] < A[b] ? (A[b] < A[c] ? b : A[a] < A[c] ? c : a): A[b] > A[c] ? b : A[a] > A[c] ? c : a;
}
void swap(ElementType *A, ElementType *B)
{
ElementType tmp = *A;
*A = *B; *B = tmp;
}
#include <stdio.h> #include <stdlib.h> #include <time.h> typedef int ElementType; typedef int Position; #define MAX 100000000 #define CUTOFF 500 #define MCUTOFF 35000 #define OFFSET 4000 void swap(ElementType *, ElementType *); void quicksort_median_of_nine(ElementType *, Position, Position); void Insertionsort(ElementType *, Position,int); Position SelectPivot(ElementType *, Position, Position, Position); Position SelectPivot_s(ElementType *A, Position a, Position b, Position c); void CopyArray(ElementType *, ElementType *); |
|
|
另外这是我的splitend代码,感觉应该是有一个错误
Position SelectPivot_s(ElementType *A, Position a, Position b, Position c)//三数取中值函数,我们要排序成从小到大的顺序,不隐藏结点
{
return A[a] < A[b] ? (A[b] < A[c] ? b : A[a] < A[c] ? c : a): A[b] > A[c] ? b : A[a] > A[c] ? c : a;
}
void quicksort_split_end(ElementType *A, Position left, Position right)
{
if (left >= right) return;//判断递归结束的标志
int len = right - left;
ElementType pivot;
Position i, j, a, b, min = (left + right) / 2, s1, s2;
if (len < CUTOFF) { Insertionsort(A, left, len + 1); return; } //如果len小于7则为小数组,采用插入排序,记得return
if (len > MCUTOFF)//大数组,median-of-nine
{
Position ln, rn;
ln = SelectPivot_s(A, left, left + OFFSET, left + OFFSET * 2);
rn = SelectPivot_s(A, right - OFFSET * 2, right - OFFSET, right);
min = SelectPivot_s(A, ln, min, rn);
}
pivot = A[SelectPivot(A, left, min, right)];
a = left, b = right - 1, i = left, j = right;
//其中a与b保留相等元素的最高/低位,c和d和快排的两个因子作用一样
for (;;)
{
for (; i < j&&A[++i] <= pivot;) { if (A[i] == pivot){ swap(&A[a++], &A[i]); } }//这里为什么要加i<j因为换元素的时候是不包括相等元素的,可能会冲出限制
for (; i < j&&A[--j] >= pivot;) { if (A[j] == pivot){ swap(&A[b--], &A[j]); } }
//这里虽然多了一步判断,但是总体来讲能让整体数据偏向中心,影响减少
if (i >= j)break;
else swap(&A[i], &A[j]);
}
swap(&A[i], &A[right]);//最后一个值和枢纽元交换,此时j==i
s1 = SelectMin(a - left, i - a);//i千万不要-1,不然会出现-1这种坑爹的情况
Exchange_data(A, left, i - s1, s1);
s2 = SelectMin(right - b - 1, b - j);//right记得-1,不包括被隐藏的枢纽元
Exchange_data(A, j, right - 1 - s2, s2);
quicksort_split_end(A, left, i - 1 - s1);
quicksort_split_end(A, i + s2 + 1, right);
}
int SelectMin(int A , int B)//选择最小的数的函数
{
return A >= B ? B : A;
}
void Exchange_data(ElementType *A, Position a, Position b, Position sum)//交换整批数据
{
for (int i = 0; i < sum; swap(&A[a], &A[b]), i++, a++, b++);
}
|
|
|
因为楼主不太会用srand和rand函数,我猜。
...
unsigned long ulrand(void) {
return (
(((unsigned long)rand()<<24)&0xFF000000ul)
|(((unsigned long)rand()<<12)&0x00FFF000ul)
|(((unsigned long)rand() )&0x00000FFFul));
}
int main()//快速排序的几种优化
{
ElementType *A = (ElementType *)malloc(sizeof(ElementType)*MAX);
ElementType *B = (ElementType *)malloc(sizeof(ElementType)*MAX);
int i = 1;
time_t c1, c2;
srand((unsigned)time(NULL));
for (A[0] = 1; i < MAX; i++)
{
A[i] = ulrand();
}
...
|
|
|
额。。。我对rand和srand的认识仅限于他可以产生随机数。。。
试了一下赵老师的随机数产生法,发现我的快排的速度明显减慢了,归并的速度没有发生多大的变化 但是稍微调整下序列,貌似又回到了原来的样子,看来快排真的很依赖序列啊 现在给我的感觉就是那两个快排的优化方法差不了多少。 |
|
|
赵老师对快排有什么好的优化方法可以介绍一下吗,或者说你对随机数列的排序是怎么处理的? |
|
| 80分 |
搜“B+树”
|
|
看到了,比较了下B+做索引真强。。。。 |
|
|
<1000个元素,冒泡排序
<100000个元素,qsort函数 <10000000个元素,放数据库中,建索引,(B+树排序) ≥10000000个元素,没用到过。 |
|
|
可是冒泡排序不是交换的次数最小是逆序个数吗,插入排序直接就是逆序个数。。直接用插入排序效果不是更好? 对了赵老师我最后再问个问题。。。其实我不懂你为什么要选用那样的随机数列。。。这样写数据会更随机一点吗? |
|
|
rand函数返回值的范围是0..32767
|
|
|
明白了,原来如此 |
|
|
C:\Program Files\Microsoft Visual Studio 10.0\VC\crt\src\rand.c
/***
*rand.c - random number generator
*
* Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
* defines rand(), srand() - random number generator
*
*******************************************************************************/
#include <cruntime.h>
#include <mtdll.h>
#include <stddef.h>
#include <stdlib.h>
/***
*void srand(seed) - seed the random number generator
*
*Purpose:
* Seeds the random number generator with the int given. Adapted from the
* BASIC random number generator.
*
*Entry:
* unsigned seed - seed to seed rand # generator with
*
*Exit:
* None.
*
*Exceptions:
*
*******************************************************************************/
void __cdecl srand (
unsigned int seed
)
{
_getptd()->_holdrand = (unsigned long)seed;
}
/***
*int rand() - returns a random number
*
*Purpose:
* returns a pseudo-random number 0 through 32767.
*
*Entry:
* None.
*
*Exit:
* Returns a pseudo-random number 0 through 32767.
*
*Exceptions:
*
*******************************************************************************/
int __cdecl rand (
void
)
{
_ptiddata ptd = _getptd();
return( ((ptd->_holdrand = ptd->_holdrand * 214013L
+ 2531011L) >> 16) & 0x7fff );
}
|
|
|
顶一下吧,感觉学习了
|
|