排序算法
排序算法介绍
排序算法一般是指将有数字大小关系的序列,对其进行操作,得到一次有序的序列关系。
- 内部排序:内部排序算法(只访问内存),外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
- 算法的稳定性:在待排序的文件中,若存在多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,该排序方法是稳定的;若具有相同关键字的记录之间的相对次序发生改变,则称这种排序方法是不稳定的。
稳定与否其实与算法的实现有很大的关系,一个不稳定的算法如果多加一个域进行比较又可以变稳定,因此本文稳定性分析只针对最朴素算法而言,并非改良算法。当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;以下,依次介绍各自排序算法及其c++代码实现。
一、插入排序—直接插入排序(Insertion Sort)
算法过程:每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。
算法复杂度:时间复杂度O(N2),空间复杂度O(1)。性能分析:最佳效率O(N),最糟效率O(N2)与冒泡、选择相同,适用于排序小列表,若列表基本有序,则插入排序比冒泡、选择更有效率。
算法稳定性分析:如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
/*
排序算法1:直接插入排序
将一个待排数据按大小插到已排序数据的适当位置,直到全部插入完毕。
算法复杂度:O(N^2)
*/
void insertion_sort(int num[],int numLen)
{
for(int j=1 ; j<numLen ; j++){
int key = num[j];//取未排序的数
int i=j-1;//排好序数组的末位
while(num[i]>key && i>=0){
//---数组移位
num[i+1] = num[i];//向右移
i--;
}
//---第一个比key小(或相等)的数位置为i,将key插在i+1处
num[i+1]=key;
}
}
int main(){
int num[] = { 11, 33, 55, 22, 77, 88, 66, 22 };
int numLen = sizeof(num) / sizeof(int);
cout << "数组长度:" << numLen << endl;
cout << "排序前:" << endl;
for (int i = 0; i < numLen; i++)
{
cout << num[i] << " ";
}
cout << endl;
insertion_sort(num, numLen);
cout << "排序后:" << endl;
for (int i = 0; i < numLen; i++)
{
cout << num[i] << " ";
}
cout << endl;
system("pause");
return 0;
}
此处的main函数内容,同样适合后续算法,只需替换对应算法名字即可。
二、插入排序—希尔排序(Shell`s Sort)
图中增量取5,3,1。
算法过程:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。一般的初次取序列的一半为增量,以后每次减半,直到增量为1。
算法复杂度:比较次数依赖于增量的选取,大约为O(NlogN)~ O(N1.5),空间复杂度O(1)。
算法分析:插入排序的改进。比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。没有快排快,中等规模下表现好,但是它好在最坏情况和平均情况差不多,快排最坏情况效率很低。任何排序都可以先希尔排序,如果事实证明不够快再改成快排。
①当数据基本有序时直接插入排序所需的比较和移动次数均较少。
②在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
稳定性分析:因为排序过程中元素可能会前后跳跃,所以不稳定。
/*
排序算法2:希尔排序
将相隔一定增量元素按照小组对待,然后组内直接插入排序。当增量逐渐递减到1的时候
说明排序结束,此时所有数组元素为一个小组。
*/
void shellInsert(int num[] , int numLen , int increment)//按照分组插入排序
{
for(int i=increment ; i<numLen ; i++){//遍历分组的无序数组
int key = num[i];
int j=i-increment;//分组的有序数组末位
while(num[j]>key && j>=0){
num[j+increment] = num[j];
j=j-increment;//组内偏移量
}
//---小于等于待插数key的有序数组中第一位为j位
num[j+increment] = key;
}
}
void shellSort(int num[] , int numLen)//增量折半递减
{
int d = numLen/2;
while(d>=1){//直到增量为1排序结束
shellInsert(num,numLen,d);//分组插入排序
d=d/2;//每次增量折半
}
}
三、交换排序—冒泡排序(Bubble Sort)
算法过程:比较相邻的前后二个数据,如果前面数据大于后面的数据,就将二个数据交换;这样对数组的第0个数据到N-1个数据进行一次遍历后,最大的一个数据就“沉”到数组第N-1个位置。重新令N=N-1,重复前面二步直到N变为0。
算法复杂度:O(N^2),空间复杂度O(1)
算法性能分析:最好情况下(正序)只需要遍历一次,不做交换,复杂度O(N)。最坏情况(逆序),需要遍历n(n-1)/2个元素,每个元素需要交换一次(访问3次),故最坏情况下复杂度3n(n-1)/2=O(N2),因此平均时间复杂度O(N2)。
稳定性分析:由于算法只在前面元素大于后面时候才交换,相等情况下不交换,因此算法稳定。
/*
排序算法3:冒泡排序
从[0,N-1]遍历,如果前面的数比后面的数大则交换位置(让大数下沉),经过一轮后最大数沉到N-1处。
下一次迭代从[0.N-2]遍历···直到排序结束
*/
void bubbleSort(int num[],int numLen)
{
for(int i=numLen-1 ; i>=0 ; i--)//遍历的下界不断缩小至0结束排序
{
for(int j=0 ; j<i ; j++){
if(num[j]>num[j+1]){//交换次序
int temp = num[j];
num[j] = num[j+1];
num[j+1] = temp;
}
}
}
}
四、选择排序—简单选择排序(Simple Selection Sort)
算法过程:在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
算法复杂度:时间复杂度O(N2),空间复杂度O(1)。
性能分析:冒泡法的改进,找到最合适的那个位置再交换,只需交换一次就能找到一个数的合适位置,而冒泡排序对一个数下沉或上浮到合适位置需要交换若干次,即数据的移动次数少,但是遍历次数多了。对最好情况(正序),还是需要遍历数据寻找最小值、作比较等等···,因此无论最坏还是最佳情况下,时间复杂度都是O(N2)。
稳定性分析:由于有交换的存在(而不是插入),例2,4(1),4(2)排序后由于交换会变成2,4(2),4(1),因此不稳定。
/*
排序算法4:简单选择排序
从[1,n-1]找最小的数与num[0]比较,如果更小就交换。下一次迭代从[2,n-1]中找最小数与num[1]相比,如果更小就交换···
直到排序结束
*/
void simpleSelectSort(int num[] , int numLen)
{
int index = -1;//最小值下标
for(int i=0 ; i<numLen ; i++)//有序数组末位
{
index = i;
for(int j=i+1 ; j<numLen ; j++){//遍历一遍无序数组
if(num[j] < num[index])
index = j;//找最小值下标
}
if(index != i){//最小值不是有序数组末位,则把它交换无序数组最小值
int temp = num[index];
num[index] = num[i];
num[i] = temp;
}
}
}
五、选择排序—堆排序(Heap Sort)
堆:对一棵完全二叉树,树中任一非叶子结点的关键字均不大于其左右孩子(若存在)结点的关键字称为最小堆,最大堆同理,见下图:
存储堆:可以用数组存储,对于下标从1开始的情况,非叶子节点和它的左右孩子有关系:k(i)左孩子为k(2i)右孩子k(2i+1),可以利用数组下标关系方便的更新数组元素,用来存储堆元素。最后一个非叶子节点是多少呢?画一棵满二叉树可知为n/2向下取整。
更新最大堆:将堆底元素(数组最后一个元素)与堆顶元素交换,然后将堆顶与左右孩子中最小的那个交换,一旦与左孩子交换,可能会破坏左子树的最小堆性质,依次迭代直到没有子树违背这个性质。见下图:
建立堆:通过数组建立堆,由于子树的根节点非常重要,因此可以从最后一个非叶子节点自底向上更新堆,见下图:
算法过程:堆排序正是利用小根堆(或大根堆)来选取当前无序区中关键字小(或最大)的记录实现排序的。我们不妨利用大根堆来排序。每一趟排序的基本操作是:将当前无序区调整为一个大根堆,选取关键字最大的堆顶记录,将它和无序区中的最后一个记录交换。这样,正好和直接选择排序相反,有序区是在原记录区的尾部形成并逐步向前扩大到整个记录区,其实本质上就是选择排序的思想。
算法复杂度:时间复杂度O(NlogN),空间复杂度O(1)(直接用数组原地排序)。
性能分析:每一次筛选过程都需要调用一次updateHeap函数,而每次调用都会导致节点下沉一级,所以updateHeap()开销O(logN),对于createHeap从第一个非叶节点逆推到根节点,每次都调用updateHeap()函数,因此createHeap()函数开销O(NlogN)。对于堆排序,需要一次createHeap时间加上N-1次调整堆updateHeap时间,加在一起还是O(NlogN),由堆排序过程可以知道,本算法不存在最佳或最坏情况,都需要做建堆和调整堆的操作,都是O(NlogN)。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
稳定性分析:不稳定,举例3,27(1),36,27(2)如果先输出堆顶3,然后27(2)放到堆顶,满足性质则会继续输出堆顶27(2),则27(2)先于27(1)输出。
/*
排序算法5:堆排序
维护一个最大堆,用数组作为数据结构
*/
//维护以下标为index非叶节点子树的堆性质
void updateHeap(int num[] , int numLen , int index)
{
int nonLeaf = numLen/2-1;
if(index <= nonLeaf && index>=0)
{//如果是非叶节点一定存在左孩子
int LChild = 2*index+1;//左孩子下标,注意数组下标从0开始
int RChild = 2*index+2;//右孩子下标
int largest = index;//最小值下标
if(num[LChild] > num[largest])
largest = LChild;
if(RChild <= numLen-1 && num[RChild]>num[largest])//右孩子也存在
largest = RChild;
if(largest != index)
{//如果非叶节点不是最大的,互换该节点与最大孩子节点
int temp = num[largest];
num[largest] = num[index];
num[index] = temp;
updateHeap(num,numLen,largest);//维护被破坏堆性质的子树
}
}
}
//由数组建立堆
void createHeap(int num[] , int numLen)
{
int nonLeaf = numLen/2-1;//最后一个非叶节点下标
for (int i=nonLeaf; i>=0; i--)
{
updateHeap(num,numLen,i);//自底向上维护堆
}
}
//堆排序
void heapSort(int num[] , int numLen)
{
createHeap(num,numLen);
//---将堆顶元素与堆底元素互换,然后重新按照[0,numLen-2]建立最小堆
for(int i=numLen-1 ; i>=1 ; i--)
{//最后无序区只剩一个元素排序完成
int temp = num[0];//堆底堆顶互换
num[0] = num[i];
num[i] = temp;
updateHeap(num,i,0);//每次堆节点数量减一,从根维护最大堆
}
}
六、交换排序—快速排序(Quick Sort)
算法过程:是冒泡法的改进,本质上也是交换排序。思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
算法复杂度:最佳情况时间复杂度O(NlogN),最坏情况O(N2),平均时间复杂度为O(NlogN),空间复杂度:操作上只需要O(1)。
算法性能分析:最好情况每次划分为两个等长的部分,需要logN次划分,每次划分后形成的小组内依旧需要对组内元素比较进行下一次划分,整体而言,每次划分都需要比较N次,因此最佳情况为O(NlogN);最坏情况(主元为当前组内最大或最小数字)每次划分只能减少一个元素,此时退化为冒泡(O(N2))。
稳定性分析:不稳定,发生在中枢元素和a[j] 交换的时刻,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱。
/*排序算法6:快速排序依主元划分数组,找到主元所在位置,要求主元左边元素均小于主元,右边元素均大于主元本质上是交换排序。*/ //快速排序一次划分
int Partition(int r[], int first, int end)
{ int i = first;
//初始化
int j = end; int temp;
while (i<j)
{
while (i<j && r[i] <= r[j])
j--;
//右侧扫描
if (i<j)
{
temp = r[i];
//将较小记录交换到前面
r[i] = r[j];
r[j] = temp;
i++;
}
while (i<j && r[i] <= r[j])
i++;
//左侧扫描
if (i<j)
{
temp = r[j];
r[j] = r[i];
r[i] = temp;
//将较大记录交换到后面
j--;
}
}
return i;
//i为轴值记录的最终位置
}
//快速排序
void QuickSort(int r[], int first, int end)
{
if (first<end)
{
//递归结束
int pivot = Partition(r, first, end);
//一次划分
QuickSort(r, first, pivot - 1);
//递归地对左侧子序列进行快速排序
QuickSort(r, pivot + 1, end);
//递归地对右侧子序列进行快速排序
}
}
七. 分治法—归并排序(Merge Sort)
算法过程:归并排序是采用分治法(Divide and Conquer)的一个非常典型的应用,分而治之,不断的将原序列划分,直至两两一组,此时进行排序后再将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
算法复杂度:时间复杂度O(NlogN),空间复杂度O(N)( 辅助数组大小)。
算法性能分析:分治法:总时间=分解时间+解决问题时间+合并时间;
分解时间就是把一个待排序序列分解成两序列,时间为一常数,时间复杂度O(1)。
解决问题时间是两个递归式,把一个规模为N的问题分成两个规模分别为N/2的子问题,时间为2T(N/2)。
合并时间复杂度为O(N)。
总时间T(N)=2T(N/2)+O(N).这个递归式可以用递归树来解,其解是O(NlogN)。此外在最坏、最佳、平均情况下归并排序时间复杂度均为O(NlogN),因为无论序列如何都需要分治。速度仅次于快速排序,适用于总体无序,但各子项相对有序的数列。
稳定性分析:稳定,因为分治的缘故需要分解为两个一组或一个一组,对于一个一组不可能调换顺序,两个一组的情况如果两数相同也不需要调换顺序,因此分解后合并时算法稳定。
/*
排序算法7:归并排序
分治法,将数组划分至两两一组,排序后归并即可
*/
//对数组num[begin,mid]和数组num[mid+1,end]进行归并,结果存放temp中
void mergeArray(int num[],int begin , int mid ,int end , int temp[])
{
int i=begin ,j=mid , m=mid+1 , n=end;
int k=0;//temp起始位从0开始
while(i<=j && m<=n){//只要有一个序列合并完跳出循环
if(num[i] <= num[m])//一定要加等号,相等的元素不会相邻
temp[k++] = num[i++];
else
temp[k++] = num[m++];
}
while(i<=j)//此时m>n即序列num[mid+1,end]合并结束,将num[begin,mid]直接复制到temp中
temp[k++] = num[i++];
while(m<=n)
temp[k++] = num[m++];
//---将temp的数据复制回num
for(int i=0 ; i<k ; i++)//k是temp的有效数据个数
num[begin+i] = temp[i];
}
//分治与归并
void divideAndMerge(int num[] , int begin , int end , int temp[])
{
if(begin >= end)//只留下一个数的时候返回
return;
int mid = begin+(end-begin)/2;
divideAndMerge(num , begin ,mid, temp);
divideAndMerge(num , mid+1 ,end, temp);
mergeArray(num , begin , mid , end , temp);
}
void mergeSort(int num[] , int numLen)
{
int *temp = new int[numLen];//分配临时数组用于存放原数组排序后的数
//共用一个临时数组,此暂存数组也可以在mergeArray中创建
divideAndMerge(num , 0 , numLen-1 , temp);
delete[]temp;
}
快速排序在平均情况下复杂性为O(NlogN),最坏情况下复杂性为O(N2);堆排序和合并排序在最坏情况下复杂性为O(NlogN),因此堆排序和归并排序是渐进最优的比较排序算法。
八、线性时间排序(非基于比较排序)—计数排序(Counting Sort)、基数排序(Radix Sort)、桶排序(Bucket Sort)
无论是冒泡、插入、选择、希尔、堆排、快排、归并,都是基于比较的,这一节的三种排序均不是基于比较的。
8.1 计数排序(Counting Sort)
算法过程:对每一个元素x,确定小于x的元素个数N,就可以把x放置在数组的第N+1位上(数组下标从0开始,也就是下标为N的位置),对于重复元素,对计数数组逆向扫描,输出一次计数器减一直到完全输出。
算法复杂度:时间复杂度O(N+maxVal),空间复杂度O(N+maxVal)。
算法分析:从代码来看,计数排序有5个for循环(memset算是一个),其中三个时间是N,两个时间是maxVal。所以时间复杂度为3N+2maxVal,即O(N+maxVal);空间复杂度为N+maxVal(两个辅助数组)。不管是在最坏还是最佳情况下,都需要作出统计与复制,因此时间复杂度不变。辅助空间在maxVal比较大的时候是非常浪费的,且有限制条件,因为计数数组的下标与数组元素关联,因此数组元素要求非负且在一定范围内。
稳定性分析:稳定,对于重复元素x,由前向后扫描得到小于等于x的元素个数Pos,如果重复元素有2个x1,x2,则按稳定性需求将分别要在num[]中位于Pos-1,Pos二个位置,在“对号入座”时逆向扫描保证第一次访问到x的元素为x2,位置是Pos,并在输出一次后让计数器减一可以保证后访问x的元素x1紧挨着x2放置在Pos-1位置。
/*
排序算法8:计数排序
对于数组中的每个数统计小于它的元素个数n,那么可以直接将它放置在n+1的位置上了。
当然,当有相同元素时需要做适当调整。
*/
void countingSort(int num[] , int numLen ,int maxVal)
{
int *countArray = new int [maxVal+1];
int *temp = new int[numLen];
//---初始化计数数组
for(int i=0 ; i<maxVal+1 ; i++)
countArray[i] = 0;
//---计算数组中num[i]出现的次数
for(int i=0 ; i<numLen ; i++)
(countArray[num[i]])++;
//---累计数组中小于num[i]的数出现次数
for(int i=1 ; i<maxVal+1 ; i++)
countArray[i] += countArray[i-1];
//---对应小于num[i]的数出现次数将数字放在指定位置
for(int i=numLen-1 ; i>=0 ; i--){//从后向前遍历让排序稳定
int pos = countArray[num[i]]--;//小于等于num[i]的数字个数,元素相等时,如果输出一次,计数器减一。
temp[pos-1] = num[i];
}
//--将temp的数据复制回num
for(int i=0 ; i<numLen ; i++)
num[i] = temp[i];
delete []temp;
delete []countArray;
}
8.2 基数排序(Radix Sort)
算法过程:计数排序中当maxVal很大时,时间和空间的开销都会增大(想象序列{8888,1234,9999}计数排序得花多少空间和遍历的时间?)。于是可以把待排序记录由低位到高位分解(如果递归的先比较高位再比较低位会产生许多要保存的临时数据),自低位到高位分别进行计数排序。这样的话分解出来的每一位最大值为9。
算法复杂度:时间复杂度O(N),空间复杂度O(K+N)(由于K<<maxVal,因此所占空间一般比计数排序小得多)。
算法性能分析: 基数排序时间T(n)=d*(2K+3N),其中d是记录值的位数,(2K+3N)是每一趟计数排序时间,由于一位数最大值K不超过9,d的值一般也很小,k、d都可以看成是一个很小的常数,所以时间复杂度O(n)。最坏最佳情况并不改变时间复杂度。
稳定性分析:稳定,由于是基于计数排序,因此若实现的计数排序稳定则本算法也稳定。
/*
排序算法9:基数排序
将一个d位数字分割成d次计数排序,由低位到高位进行计数排序。只需要进行d轮就能排序完成
对一个数(无论高位还是低位)最大值为9,因此计数排序maxVal = 9
*/
//对第d位进行计数排序
void countingSort(int num[] , int numLen , int maxVal , int d)
{
int *countArray = new int[maxVal+1];
int *temp = new int[numLen];
//---初始化
for(int i=0 ; i<maxVal+1 ; i++)
countArray[i] = 0;
//---统计数组每个num[i]第d位出现的次数
for(int i=0 ; i<numLen ; i++){
int dNum =num[i]/(int)pow(10,d-1)%10;
countArray[dNum]++;
}
//---累计数组中小于等于num[i]第d位出现的次数
for(int i=1 ; i<maxVal+1 ; i++){
countArray[i] += countArray[i-1];
}
//---逆向遍历第d位的累计计数数组寻找相应位置
for(int i=numLen-1 ; i>=0 ; i--){
int dNum = num[i]/(int)pow(10,d-1)%10;
int pos = countArray[dNum]--;
temp[pos-1] = num[i];
}
//---将排序完的数组复制回num
for(int i=0 ; i<numLen ; i++)
num[i] = temp[i];
delete []temp;
delete[]countArray;
}
void radixSort(int num[] , int numLen , int dMax)
{
for(int i=1 ; i<=dMax ; i++)//由低位到高位计数排序
countingSort(num , numLen , 9 , i);
}
8.3 桶排序(Bucket Sort)
算法过程:假设输入均匀、独立的分布在区间[0,1),将之划分成M个(本图选10个)相同大小的子区间,称为桶。将N个记录分布到各个桶中去。如果有多于一个记录分到同一个桶中,需要进行桶内排序。最后依次把各个桶中的记录列出来即得到有序序列。
算法复杂度:期望时间复杂度O(N),空间复杂度O(N+M)。
算法性能分析:当元素越均匀、独立的分布在[0,1)空间上,该算法效率越高,一般不会出现很多个数落在同一个桶的情况。桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,最好的时间复杂度达到O(N)。当然桶排序的空间复杂度为O(N+M)(见上图),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。
稳定性分析:稳定,对于两个相同元素,如果掉在同一个桶不会毁坏稳定性,如果掉在不同的桶,则由桶内排序和桶间链表连接决定是稳定的。
/*
排序算法10:桶排序
假定有N个数据输入均匀、独立的分布在[0,1)之间,只需要建立一个大小为M的链表作为桶
将N个数据均匀的扔进桶内,对于桶内不为空的链表进行插入排序,最终输出链接好的节点
*/
void bucketSort(double num[] , int numLen)
{
//---定义链表节点
struct Node{
Node():pNext(NULL){}
Node *pNext;
double data;
};
//---定义一个长为numLen的指针数组,数组内每个元素为Node*类型
Node **bucket = new Node*[numLen];
//---初始化桶标号
for(int i=0 ; i<numLen ; i++){
bucket[i] = new Node;
bucket[i]->data = i;
bucket[i]->pNext = NULL;
}
//---将数组中元素均匀的插入到桶中相应位置。
for(int i=0 ; i<numLen ; i++){
int bucketIndex = (int)num[i]*numLen;//欲扔入的桶下标
Node *newNode = new Node;
newNode->data = num[i];
//---寻找相应位置,将temp插进去
Node *pTemp = bucket[bucketIndex];
while(pTemp->pNext && pTemp->pNext->data<=num[i])//找到下一个元素大于num[i]的位置,在该位置前面插入
pTemp = pTemp->pNext;
newNode->pNext = pTemp->pNext;
pTemp->pNext = newNode;
}
//---按桶标号顺序取出排好序的数据
int k=0;
for(int i=0 ; i<numLen ; i++){
Node *pTemp = bucket[i]->pNext;//非空桶第一个节点
if(pTemp == NULL)//空桶
continue;
while (pTemp)
{
num[k++] = pTemp->data;
pTemp = pTemp->pNext;
}
}
//---释放内存空间
for(int i=0 ; i<numLen ; i++){
Node *pTemp = bucket[i];//表头结点
Node *pHead = pTemp ;
while(pTemp){
pHead = pHead->pNext;//表头后移
delete pTemp;
pTemp = pHead;//再指向表头
}
}
delete []bucket;//最终释放指针数组
}
int main()
{
double bucketTest[]={0.78,0.17,0.39,0.26,0.72,0.94,0.21,0.12,0.23,0.68};
int numLen = sizeof(bucketTest)/sizeof(double);
bucketSort(bucketTest ,numLen);
for(int i=0 ; i<numLen ; i++)
cout<<bucketTest[i]<<" ";
cout<<endl;
return 0;
}
以上是总结的多种排序算法,及对应分析,下面列表总结对比。