再次推荐《算法导论》这本书,在我的上次的随笔中有下载链接。哈哈。真正支持还是需要买一下纸版。呵呵。
7. 不过在编程的时候还是要注意细节的,例如我不能每次都来算一下比他小的个数。呵呵,思路就这样了。奉上源代码: #include
int CountSort(int* pData, int nLen) {
int* pCout = NULL; //保存记数数据的指针 pCout = (int*)malloc(sizeof(int) * nLen); //申请空间 //初始化记数为0
for (int i = 0; i < nLen; ++i) {
pCout[i] = 0; }
//记录排序记数。在排序的值相应记数加1。 for (int i = 0; i < nLen; ++i) {
++pCout[pData[i]]; //增 }
//确定不比该位置大的数据个数。 for (int i = 1; i < nLen; ++i) {
pCout[i] += pCout[i - 1]; //不比他大的数据个数为他的个数加上前一个的记数。 }
int* pSort = NULL; //保存排序结果的指针 pSort = (int*)malloc(sizeof(int) * nLen); //申请空间
for (int i = 0; i < nLen; ++i) {
//把数据放在指定位置。因为pCout[pData[i]]的值就是不比他大数据的个数。 //为什么要先减一,因为pCout[pData[i]]保存的是不比他大数据的个数中包括了 //他自己,我的下标是从零开始的!所以要先减一。
--pCout[pData[i]]; //因为有相同数据的可能,所以要把该位置数据个数减一。
pSort[pCout[pData[i]]] = pData[i]; }
//排序结束,复制到原来数组中。 for (int i = 0; i < nLen; ++i) {
pData[i] = pSort[i]; }
//最后要注意释放申请的空间。 free(pCout); free(pSort); return 1; }
int main() {
int nData[10] = {8,6,3,6,5,8,3,5,1,0}; CountSort(nData, 10); for (int i = 0; i < 10; ++i) {
printf(\ }
printf(\
system(\ return 0; }
快速排序 (一) 实现
快速排序的实现基于分治法,具体分为三个步骤。假设待排序的序列为L[m..n]。 分解:序列L[m .. n]被划分成两个可能为空的子序列L[m .. pivot-1]和L[pivot+1 .. n],使L[m .. pivot-1]的每个元素均小于或等于L[pivot],同时L[pivot+1.. n]的每个元素均大于L[pivot]。其中L[pivot]称为这一趟分割中的主元(也称为枢轴、支点)。
解决:通过递归调用快速排序,对子序列L[m .. pivot-1]和L[pivot+1 .. r]排序。
合并:由于两个子序列是就地排序的,所以对它们的合并不需要操作,整个序列L[m .. n]已排好序。 (二)概述
快速排序(Quick Sort)是一种有效的排序算法。虽然算法在最坏的情况下运行时间为O(n^2),但由于平均运行时间为O(nlogn),并且在内存使用、程序实现复杂性上表现优秀,尤其是对快速排序算法进行随机化的可能,使得快速排序在一般情况下是最实用的排序方法之一。
快速排序被认为是当前最优秀的内部排序方法。 (三)性质 内部排序
快速排序是一种内部排序方法。也就是说快速排序的排序对象是读入内存的数据。 比较排序
快速排序确定元素位置的方法基于元素之间关键字大小的比较。
所有基于比较方法的排序方法的时间下界不会低于O(nlgn)。这个结论的具体证明,请参考有关算法的书籍,例如《算法导论》(第一版)第8章(第二版在第七章QuickSort)。 在理想情况下,能严格地达到O(nlgn)的下界。一般情况下,快速排序与随机化快速排序的平均情况性能都达到了O(nlgn)。 不稳定性
快速排序是一种不稳定的排序方法。简单地说,元素a1, a2的关键字有a1.key=a2.key,则不稳定的排序方法不能保证a1, a2在排序后维持原来的位置先后关系。 原地排序
在排序的具体操作过程中,除去程序运行实现的空间消费(例如递归栈),快速排序算法只需消耗确定数量的空间(即S(1),常数级空间)。
这个性质的意义,在于在内存空间受到限制的系统(例如MCU)中,快速排序也能够很好地工作。 (四)时空复杂度
快速排序每次将待排序数组分为两个部分,在理想状况下,每一次都将待排序数组划分成等长两个部分,则需要logn次划分。
而在最坏情况下,即数组已经有序或大致有序的情况下,每次划分只能减少一个元素,快速排序将不幸退化为冒泡排序,所以快速排序时间复杂度下界为O(nlogn),最坏情况为O(n^2)。在实际应用中,快速排序的平均时间复杂度为O(nlogn)。
快速排序在对序列的操作过程中只需花费常数级的空间。空间复杂度S(1)。 但需要注意递归栈上需要花费最少logn 最多n的空间。 (五)随机化算法
快速排序的最坏情况基于每次划分对主元的选择。基本的快速排序选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法是随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。一位前辈做出了一个精辟的总结:“随机化快速排序可以满足一个人一辈子的人品需求。”
随机化快速排序的唯一缺点在于,一旦输入数据中有很多的相同数据,随机化的效果将直接减弱。对于极限情况,即对于n个相同的数排序,随机化快速排序的时间复杂度将毫无疑问的降低到O(n^2)。 (六)减少递归栈使用的优化
快速排序的实现需要消耗递归栈的空间,而大多数情况下都会通过使用系统递归栈来完成递归求解。在元素数量较大时,对系统栈的频繁存取会影响到排序的效率。
一种常见的办法是设置一个阈值,在每次递归求解中,如果元素总数不足这个阈值,则放弃快速排序,调用一个简单的排序过程完成该子序列的排序。这样的方法减少了对系统递归栈的频繁存取,节省了时间的消费。 一般的经验表明,阈值取一个较小的值,排序算法采用选择、插入等紧凑、简洁的排序。一个可以参考的具体方案:阈值T=10,排序算法用选择排序。 阈值不要太大,否则省下的存取系统栈的时间,将会被简单排序算法较多的时间花费所抵消。
另一个可以参考的方法,是自行建栈模拟递归过程。但实际经验表明,收效明显不如设置阈值。 (七)C例程
以下是C语言权威《The C Programming Language》中的例程,在这个例程中,对于数组v的left到right号元素以递增顺序排序。 //Qsort.c by Tydus. #include
int arr[] = {14,10,11,5,6,15,0,15,16,14,0,8,17,15,7,19,17,1,18,7}; /* swap函数:交换v[k]与v[j]的值 */ inline void swap(int v[], int k, int j) {
int temp; temp = v[k]; v[k] = v[j]; v[j] = temp; }
void qsort(int v[], int left, int right) {
int j, last;
if (left >= right) /* 若数组包含的元素个数少于两个 */ return; /* 则不执行任何操作 */
swap(v, left, (left + right)/2); /* 将划分子集的元素移动到V[0] */ last=left; /* 用last记录中比关键字小间的最右位置*/ for (j = left+1; j <= right; j++) /* 划分子集 */ {
if (v[j] < v[left]) {
swap(v, last++, j); }
} /*小小。。。。关键字大大大大*/ qsort(v, left, last-1); qsort(v, last+1, right); }
void main() { int j;
qsort(arr, 0, 19); for(j=0; j<=19; j++) {
printf(\ }
printf(\ }
(八)消除递归的快速排序
传统的快速排序是递归的,这就会受到递归栈深度的限制。比如在一台普通的PC上,当待排序元素达到10^6以上时,传统的递归快排会导致栈溢出异常,或者一个莫名其妙的错误结果。所以,对于巨大的数据规模,将快速排序消除递归是十分必要的。而消除递归,
又将带来巨大的性能提升,把系统级的消耗降到最低。
消除递归的方法,就是模拟栈操作。但是从代码可以看出,这种模拟的消耗几乎可以忽略不计。因此消除递归的快排的效率是有保障的。
(虽然下面的代码没有使用随机化,但经过测试,它是目前所有快排编写方法中,效率最高,速度最快的!)
#define MAXARRAY 10000
#define PUSH(A,B) {sl[sp]=A;sr[sp]=B;sp++;} #define POP(A,B) {sp--;A=sl[sp];B=sr[sp];} void quicksort(int a[],int l,int r){
static int sl[MAXARRAY], sr[MAXARRAY], sp; int i,j,p,t; sp=0;
PUSH(l,r); while(sp){ POP(l,r);
i=l;j=r;p=a[(i+j)/2]; while(i<=j){ while(a[i]
p)j--; if(i<=j){
t=a[i];a[i]=a[j];a[j]=t; i++;j--; } }
if(l (九)C++例程 以下是一个用C++编写的快速排序程序。虽然C标准库中提供了快速排序,但作为快速排序的介绍,原理程序的代码更加有助于对快速排序运行过程的分析。 在这个例程中,对于数组x的0~n-1号元素的排序,初始调用为:quicksort(x, 0, n-1); int quicksort_partition(int L[], int Lbb, int Ubb) { //随机化 int iRndPivID; srand(unsigned(time(0))); iRndPivID = (rand() % (Ubb - Lbb + 1)) + Lbb; swap(L[iRndPivID], L[Ubb]); //快排 int iPivValue; int i; int iPivPos; iPivValue = L[Ubb];