您的位置:首页 > 编程语言

【编程珠玑】读书笔记 第十一章 排序

2013-07-15 20:54 295 查看
2013-07-15 20:18:41

本章以排序为例,展示了编程过程中的一般步骤。

主要针对插入排序、快速排序进行了讨论。

之前看过一本书《剑指offer》,感觉写的很好,有具体的实例,对每个题都给出思路以及实例,并对编程中的要点进行说明,代码风格也很好,现在看这本《编程珠玑》,是因为想多了解一些编程以及算法方面的理论。

书快看完了,个人感觉这本书内容还不错,但是表达方式比较晦涩,不太好理解,比如说代码的思路说明不够清楚、编程的风格也比较糟糕,有些术语也不太好理解,比如脚手架(书中的意思是测试框架)、程序验证(书中的意思是证明编程思路的正确性)、算法调优、代码调优等。

本章的内容有些表叔还是很晦涩,排序本来是很简单的,而本章中提到的思路有的却是不好理解(有的效率还比较低),看了很长时间,才看懂。

下面给出根据书中的思路写出的完整代码,以及测试“脚手架”,“脚手架”在此处的测试却是挺好用。后面附上了测试结果,给出了待排序数组为1000,000时每种排序的时间消耗。

从测试结果可以看到,同样的排序,最快的是采用函数QuickSortAdvaced(使用随机枢轴)与QuickSortImproved_2,标准库函数qsort与QuickSortImproved_1差不多,QuickSortBasic也比较快,且比QuickSortImproved_1差不多快一倍;

而插入排序很慢,最快的插入排序大概是最快的快速排序QuickSortAdvaced的200倍;

最慢的插入排序是最快的插入排序的100倍,可见即使是同样的算法,代码的编写对程序的效率也是很重要的,要从代码上进行优化.

QuickSortBasic在划分时,使用的单向划分,在数组元素都相同的极端情况下,需要n-1次划分,而不是log2(n)次,因此时间复杂度恶化为O(n^2);

为了改善这种情况,提出了双向划分,即为下面代码中的QuickSortImproved_1,该算法在数组元素都相同的极端情况下,可将规模减半,因此时间复杂度为O(nlog2(n)),为快速排序的最好性能;

QuickSortImproved_1的双向换分是书中给出的代码,可能是先入为主的原因,之前看到有[b]QuickSortImproved_2中代码的写法,个人感觉[b]QuickSortImproved_1中的写法不是很好理解,且交换次数增加很多,因此有了[b]QuickSortImproved_2,且从运行时间上看,[b]QuickSortImproved_2比[b]QuickSortImproved_1要快1倍还多,若增加输入规模,速度的差别会显著;[/b][/b][/b][/b][/b]

对于QuickSortImproved_2,要注意到,Partion函数主循环while (i < j)内的循环的条件是 while (unsortedArray[j] > unsortedArray[i] && i < j),而非 while (unsortedArray[j] >= unsortedArray[i] && i < j),另外一个循环是while (unsortedArray[j] < unsortedArray[i] && i < j),而非 while (unsortedArray[j] <= unsortedArray[i] && i < j),也就是两个循环都是不包含=的,这样才能保证Partion在数组元素都相同的极端情况下,将规模减半;否则,就会使最坏的情况。

假设输入数组为已经排好序的数组,那么使用上述的算法([b]QuickSortBasic、[b]QuickSortImproved_1、[b]QuickSortImproved_2[/b][/b]),时间性能也是最差的O(n^2),至此,我们已经做了两种极端情况的分析,一种是数组元素都相同,我们通过双向划分解决,但该方法不能解决数组已经排好序的情况,还有没有其他的极端情况?快速排序的复杂度到底决定于哪些因素?怎样才能达到最佳性能?[/b]

在《算法导论》的7.2节指出,快速排序的运行时间与划分是否对称有关,而后者又与选择了哪一个元素来进行划分有关。如果划分是对称的,那么本算法从渐进意义上来讲,就与合并算法一样快;如果划分是不对称的,那么本算法渐进上就和插入算法一样慢。

那么,要想得到对称的划分,使用了随机划分的方法,可以得到近似对称的划分,如QuickSortAdvaced所示。QuickSortAdvaced用函数PartionRandomPivot随机选择枢轴元素,从而得到近似对称的划分。

测试环境:win7 32bit + VS2010

代码:

#include <iostream>
#include <cassert>
#include <time.h>
using namespace std;

typedef int DataType;
const int MaxSize = 100000000;
const int SortCutOff = 50;

//输入合法性检查
void CheckInvalid(int array[],int len)
{
assert(NULL != array && len > 0);
}

//直接插入排序基本版本
void InsertSortBasic(int unsortedArray[],int len)
{
int i;
int j;

CheckInvalid(unsortedArray,len);

for (i = 1;i < len;++i)
{
for (j = i;j >= 0 && unsortedArray[j] < unsortedArray[j - 1];--j)
{
swap(unsortedArray[j],unsortedArray[j - 1]);
}
}
}

//直接插入排序改进,将for循环中的swap函数改为内联的,提高效率
void InsertSortImproved(int unsortedArray[],int len)
{
int i;
int j;
int tmp;

CheckInvalid(unsortedArray,len);

for (i = 1;i < len;++i)
{
for (j = i;j >= 0 && unsortedArray[j] < unsortedArray[j - 1];--j)
{
tmp = unsortedArray[j];
unsortedArray[j] = unsortedArray[j - 1];
unsortedArray[j - 1] = tmp;
}
}
}

//直接插入排序的进一步改进
void InsertSortAdvacaed(int unsortedArray[],int len)
{
int i;
int j;
int t;

CheckInvalid(unsortedArray,len);

for (i = 1;i < len;++i)
{
t = unsortedArray[i];
for (j = i - 1;j >= 0 && unsortedArray[j] > t;--j)
{
unsortedArray[j + 1] = unsortedArray[j];
}
unsortedArray[j + 1] = t;
}
}

//快速排序的基本版本,从一个方向进行划分
void QuickSortBasic(int unsortedArray[],int begin,int end)
{
if (begin >= end)
{
return;
}

int i;
int mid = begin;
int tmp;

for (i = begin + 1;i <= end;++i) //注意终止条件不是i < end
{
if (unsortedArray[i] < unsortedArray[begin])
{
++mid;
tmp = unsortedArray[i];
unsortedArray[i] = unsortedArray[mid];
unsortedArray[mid] = tmp;
}
}

swap(unsortedArray[begin],unsortedArray[mid]);
QuickSortBasic(unsortedArray,begin,mid - 1);
QuickSortBasic(unsortedArray,mid + 1,end);
}

//快速排序的改进,从两个方向划分
void QuickSortImproved_1(int unsortedArray[],int begin,int end)
{
if (begin >= end)
{
return;
}

int i, j;
DataType t;
t = unsortedArray[begin];
i = begin;
j = end + 1;

for (;;)
{
do i++;
while (i <= end && unsortedArray[i] < t);
do j--;
while (unsortedArray[j] > t);
if (i > j)
break;
swap(unsortedArray[i],unsortedArray[j]);
}
swap(unsortedArray[begin],unsortedArray[j]);
QuickSortImproved_1(unsortedArray,begin,j - 1);
QuickSortImproved_1(unsortedArray,j + 1,end);
}

//将a_unsorted的首个元素放到排序后的位置,返回该数据的位置
int Partion(int unsortedArray[],int begin,int end)
{
int i = begin;
int j = end;
int tmp;

while (i < j)
{
while (unsortedArray[j] > unsortedArray[i] && i < j)  //从后向前比较,知道发现元素不大于轴a_unsorted[i]
{
--j;
}

if (i < j)
{
tmp =  unsortedArray[i];    //交换之后,轴变为a_unsorted[j]
unsortedArray[i] = unsortedArray[j];
unsortedArray[j] = tmp;
++i;        //缩小范围
}

while (unsortedArray[i] < unsortedArray[j] && i < j)//从前向后比较,知道发现元素不小于轴a_unsorted[j]
{
++i;
}

if (i < j)
{
tmp = unsortedArray[i];        //交换之后,轴变为a_unsorted[i]
unsortedArray[i] = unsortedArray[j];
unsortedArray[j] = tmp;

--j;     //缩小范围
}
}

return i;
}

//快速排序,更好的写法
void QuickSortImproved_2(int unsortedArray[],int begin,int end)
{
if (begin >= end)
{
return;
}

int mid = Partion(unsortedArray,begin,end);

QuickSortImproved_2(unsortedArray,begin,mid - 1);
QuickSortImproved_2(unsortedArray,mid + 1,end);
}

//产生在[lowBound,upperBound - 1]区间的随机数
int RandomIntGenerate(int lowBound, int upperBound)
{
return (lowBound + (RAND_MAX * rand() + rand()) % (upperBound - lowBound + 1) );
}

//将a_unsorted的首个元素放到排序后的位置,返回该数据的位置
int PartionRandomPivot(int unsortedArray[],int begin,int end)
{
int i = begin;
int j = end;
int tmp;

int pivotIndex =  RandomIntGenerate(begin,end);
swap(unsortedArray[begin],unsortedArray[pivotIndex]);

while (i < j)
{
while (unsortedArray[j] > unsortedArray[i] && i < j)  //从后向前比较,知道发现元素不大于轴a_unsorted[i]
{
--j;
}

if (i < j)
{
tmp =  unsortedArray[i];    //交换之后,轴变为a_unsorted[j]
unsortedArray[i] = unsortedArray[j];
unsortedArray[j] = tmp;
++i;        //缩小范围
}

while (unsortedArray[i] < unsortedArray[j] && i < j)//从前向后比较,知道发现元素不小于轴a_unsorted[j]
{
++i;
}

if (i < j)
{
tmp = unsortedArray[i];        //交换之后,轴变为a_unsorted[i]
unsortedArray[i] = unsortedArray[j];
unsortedArray[j] = tmp;

--j;     //缩小范围
}
}

return i;
}

//快速排序的进一步改进,
//随机选择枢轴元素
//且在数组长度小于一定值(此处为SortCutOff)时,用插入排序
void QuickSortAdvaced(int unsortedArray[],int begin,int end)
{
if (begin >= end)
{
return;
}

if((end - begin + 1) < SortCutOff)
{
InsertSortAdvacaed(unsortedArray,(end - begin + 1));
return;
}

int mid = PartionRandomPivot(unsortedArray,begin,end);

QuickSortImproved_2(unsortedArray,begin,mid - 1);
QuickSortImproved_2(unsortedArray,mid + 1,end);
}

int IntCompare(const void *_p,const void *_q)
{
int *p = (int *) _p;
int *q= (int *) _q;
return (*p - *q);
}

void DisplayArray(int array[],int len)
{
CheckInvalid(array,len);

for (int i = 0;i < len;++i)
{
cout<<array[i]<<"\t";
}
cout<<endl;
}

//测试“脚手架”
void TestDriver()
{
//int unsortedArray[MaxSize];
int *unsortedArray = new int[MaxSize];
size_t programToTest;
size_t lengthOfUnsortedArray;
int MinRandomInt;
int MaxRandomInt;
size_t i;
int timeStart = 0;
double timeCostAverage = 0;

cout<<"the identifier of the program is :"<<endl;
cout<<"InsertSortBasic : 11"<<endl;
cout<<"InsertSortImproved : 12"<<endl;
cout<<"InsertSortAdvacaed : 13"<<endl;
cout<<"QuickSortBasic : 21"<<endl;
cout<<"QuickSortImproved_1 : 22"<<endl;
cout<<"QuickSortImproved_2 : 23"<<endl;
cout<<"QuickSortAdvaced : 24"<<endl;
cout<<endl;

cout<<"please enter the length Of UnsortedArray,MinRandomInt and MaxRandomInt :"<<endl;
cin>>lengthOfUnsortedArray>>MinRandomInt>>MaxRandomInt;
cout<<"please enter the identifier of the program to test (end with ctrl+z): "<<endl;
while (cin>>programToTest)
{
for (i = 0;i < lengthOfUnsortedArray;++i)  //准备待排序数组
{
unsortedArray[i] = RandomIntGenerate(MinRandomInt,MaxRandomInt);
}

/*    cout<<"the unsorted array is :"<<endl;
DisplayArray(unsortedArray,lengthOfUnsortedArray);*/
timeStart = clock();
switch (programToTest)
{
case 11:
cout<<"Test InsertSortBasic..."<<endl;
InsertSortBasic(unsortedArray,lengthOfUnsortedArray);
break;

case 12:
cout<<"Test InsertSortImproved..."<<endl;
InsertSortImproved(unsortedArray,lengthOfUnsortedArray);
break;

case 13:
cout<<"Test InsertSortAdvacaed..."<<endl;
InsertSortAdvacaed(unsortedArray,lengthOfUnsortedArray);
break;
case 21:
cout<<"Test QuickSortBasic..."<<endl;
QuickSortBasic(unsortedArray,0,lengthOfUnsortedArray - 1);
break;
case 22:
cout<<"Test QuickSortImproved_1..."<<endl;
QuickSortImproved_1(unsortedArray,0,lengthOfUnsortedArray - 1);
break;
case 23:
cout<<"Test QuickSortImproved_2..."<<endl;
QuickSortImproved_2(unsortedArray,0,lengthOfUnsortedArray - 1);
break;
case 24:
cout<<"Test QuickSortAdvaced..."<<endl;
QuickSortAdvaced(unsortedArray,0,lengthOfUnsortedArray - 1);
break;
case 25:
cout<<"Test qsort..."<<endl;
qsort(unsortedArray,lengthOfUnsortedArray,sizeof(DataType),IntCompare);
break;
default:
break;
}

timeCostAverage = 1e9 * ( clock() - timeStart ) / ( CLOCKS_PER_SEC * lengthOfUnsortedArray );
cout<<"the average time cost per data is : "<<timeCostAverage<<" ns"<<endl;

for (i = 0;i < lengthOfUnsortedArray - 1;++i)
{
if (unsortedArray[i] > unsortedArray[i + 1])
{
cout<<"sort bug i = "<<i<<endl;
}
}

/*cout<<"the sorted array is :"<<endl;
DisplayArray(unsortedArray,lengthOfUnsortedArray);*/

cout<<endl;
cout<<"please enter the identifier of the program to test (end with ctrl+z): "<<endl;
}

delete [] unsortedArray;
}

//使用脚手架的测试程序
int main(void)
{
TestDriver();
return 0;
}

//不用脚手架的测试程序
//int main(void)
//{
//    int unsortedArray[MaxSize];
//    int len = 0;
//    DataType data;
//
//    cout<<"please enter the data of the array ,end with ctrl+z : "<<endl;
//    while (cin>>data)
//    {
//        unsortedArray[len++] = data;
//    }
//    cout<<"the unsorted array is :"<<endl;
//    DisplayArray(unsortedArray,len);
//
//    /*cout<<"Test InsertSortBasic..."<<endl;
//    InsertSortBasic(unsortedArray,len);
//    cout<<"the sorted array is :"<<endl;
//    DisplayArray(unsortedArray,len);*/
//
//
//    //cout<<"Test InsertSortImproved..."<<endl;
//    //InsertSortImproved(unsortedArray,len);
//    //cout<<"the sorted array is :"<<endl;
//    //DisplayArray(unsortedArray,len);
//
//    /*cout<<"Test InsertSortAdvacaed..."<<endl;
//    InsertSortAdvacaed(unsortedArray,len);
//    cout<<"the sorted array is :"<<endl;
//    DisplayArray(unsortedArray,len);*/
//
//    /*cout<<"Test QuickSortBasic..."<<endl;
//    QuickSortBasic(unsortedArray,0,len - 1);
//    cout<<"the sorted array is :"<<endl;
//    DisplayArray(unsortedArray,len);*/
//
//    /*cout<<"Test QuickSortImproved_1..."<<endl;
//    QuickSortImproved_1(unsortedArray,0,len - 1);
//    cout<<"the sorted array is :"<<endl;
//    DisplayArray(unsortedArray,len);*/
//
//    //cout<<"Test QuickSortImproved_2..."<<endl;
//    //QuickSortImproved_2(unsortedArray,0,len - 1);
//    //cout<<"the sorted array is :"<<endl;
//    //DisplayArray(unsortedArray,len);
//
//    cout<<"Test QuickSortAdvaced..."<<endl;
//    QuickSortAdvaced(unsortedArray,0,len - 1);
//    cout<<"the sorted array is :"<<endl;
//    DisplayArray(unsortedArray,len);
//
//    return 0;
//}


测试结果:

(可以看到,同样的排序,最快的是采用函数QuickSortAdvaced(使用随机枢轴)与QuickSortImproved_2,标准库函数qsort与QuickSortImproved_1差不多,QuickSortBasic也比较快,且比QuickSortImproved_1差不多快一倍;

而插入排序很慢,最快的插入排序大概是最快的快速排序QuickSortAdvaced的200倍;

最慢的插入排序是最快的插入排序的100倍,可见即使是同样的算法,代码的编写对程序的效率也是很重要的,要从代码上进行优化)

the identifier of the program is :
InsertSortBasic : 11
InsertSortImproved : 12
InsertSortAdvacaed : 13
QuickSortBasic : 21
QuickSortImproved_1 : 22
QuickSortImproved_2 : 23
QuickSortAdvaced : 24

please enter the length Of UnsortedArray,MinRandomInt and MaxRandomInt :
100000 -1000000 1000000
please enter the identifier of the program to test (end with ctrl+z):
24
Test QuickSortAdvaced...
the average time cost per data is : 860 ns

please enter the identifier of the program to test (end with ctrl+z):
25
Test qsort...
the average time cost per data is : 2330 ns

please enter the identifier of the program to test (end with ctrl+z):
23
Test QuickSortImproved_2...
the average time cost per data is : 860 ns

please enter the identifier of the program to test (end with ctrl+z):
22
Test QuickSortImproved_1...
the average time cost per data is : 2020 ns

please enter the identifier of the program to test (end with ctrl+z):
21
Test QuickSortBasic...
the average time cost per data is : 1080 ns

please enter the identifier of the program to test (end with ctrl+z):
13
Test InsertSortAdvacaed...
the average time cost per data is : 164740 ns

please enter the identifier of the program to test (end with ctrl+z):
12
Test InsertSortImproved...
the average time cost per data is : 297960 ns

please enter the identifier of the program to test (end with ctrl+z):
11
Test InsertSortBasic...
the average time cost per data is : 3.66578e+006 ns

please enter the identifier of the program to test (end with ctrl+z):
^Z
请按任意键继续. . .
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: