1.基本概念
主关键字唯一区分不同数据,否则为次关键字。主关键字排序具有唯一性,次关键字否。若相同次关键字的元素排序可能发生交换顺序,则称算法不稳定。常用排序算法优劣衡量指标:
❶时间性能好。即较少的关键字比较次数和元素移动次数。
❷空间性能好。即辅助缓存小。
❸稳定性。即相同次关键字元素,排序前后相对位置恒定
2.排序方法汇总
2.1直接插入(二分法)排序
❶原理:从第2个元素开始,假设前面的元素已经排序完毕,每次将后序一个元素插入至前面合适位置。 ❷特点:稳定。适用基本有序或者排序或者元素个数少。 ❸伪代码:
viod insertSort (int data[],int n) int temp; int i,j; for i=1:n-1 temp=data[i]; j=i-1; while(j>-1 &&temp <A[j]) A[j+1]=A[j]; j--; A[j+1]=temp; end
❹二分法直接插入。先寻找合适的插入位置。继而移动元素,然后插入。减少了比较次数,移动次数未变。
template<typename T, unsigned int n> void insertSort(T (&data)[n]){ int temp, mid; int low, high; int i, j; for (i = 1; i < n; i++){ temp = data[i]; if (data[0]>data[i]) low = 0; else if (data[i] < data[i - 1]){ low = 0; high = i - 1; while (low<high){//寻找low,使得倒数第一个data[low]>temp mid = (low + high) / 2; if (data[mid]>temp) high = mid; else low = mid + 1; } } else low = i; for (j = i - 1; j >= low; j--)//数据向后移动 data[j + 1] = data[j]; data[low] = temp;// 插入数值 } }
2.2 希尔排序:
❶原理:又称最小增量排序。希尔排序先将元素分为若干不重叠小组。每个小组分别直接排序。小组个数逐渐减小。最后在一个组排序完成。希尔排序基于直接排序在基本有序元素列的高效率考虑。 ❷特点:不稳定。 ❸伪代码:
void shellSort(int data,int n,int d[],int m) int span,temp; int i,j,k,key; for(i=0;i<m;i++)//m个跨度,d[m]为步长,最后一个增量为1 { span=d[m]; for(k=0;k<span;k++)//至多span个分组,data[0]......data[span-1]分别为span个分组的起始位置。 for(j=k+span;j<n;j+=span)//每个分组使用插入排序。 temp=data[j]; key=j-span; while(key>=0&&temp>data[key]) data[key+span]=data[key]; key-=span; data[key+span]=temp; }
分析:shell排序时间性能优于直接选择排序。
1)初始时候步长比较大,每组元素少,排序很快。
2)后来基本有序,使用直接插入的时间也较快。
2.3直接选择排序
❶原理:选取最小的元素作为第一个元素,继而寻找第2大的元素作为第2个元素。继而依次.... ❷特点:不稳定。 ❸伪代码:
viod insertSort (int data[],int n) int min; int temp; for(int i=0;i<n-1;i++) min=i; for(int j=i+1;j<n;j++)//寻找i---n-1最小元素位置 if(data[min]>data[j]) min=j; if(min!=i)//将最小元素放在比较序列的最前面 temp=data[i]; data[i]=data[min]; data[min]=temp;
2.4堆排序
❶堆:以大跟堆为例,满足3个性质。根节点最大;从大根堆至任何叶子节点的路径,非递增有序;任意非空左右子树均为大根堆。 ❷原理:调整堆 ❸特点:不稳定。不适宜元素少或者排列有序的情况,适合元素多且乱序情况。堆不稳定因为需要每次最后一个元素和堆顶元素交换。
void adjustHeap(int A[],int n,int k)//调整堆,已知A[k+1]----A[n-1]已经为堆。调整根节点为A[k]的堆 { int j,temp; bool flag=false; temp=A[k];//暂存根节点值 j=2*k+1; while(j<n&&!flag) { if(j<n-1&&A[j+1]>A[j])//j指向左右节点的关键字最大 j++; if(temp>A[j]) flag=true; else { A[k]=A[j]; k=j;//更新k j=2*k+1;//更新j } } A[k]=temp;//根节点赋值给当前子节点 } void heapSort(int A[],int n) { int temp; for(int i=(n-2)/2;i>=0;i--)//从非叶子节点开始,从后到前调整建立堆 adjustHeap(A,n,i); for(int i=n;i>1;i--)//将根节点(最大值)与最后一个叶子节点的值交换,i-表示长度 { temp=A[0]; A[0]=A[i-1]; A[i-1]=temp; adjustHeap(A,i-1,0);//调整长度序列为i-1。 } }
2.5冒泡排序
❶原理:两两比较大小,将较大的元素像右移动。因此先是最大元素移动至列尾,继而是次最大.......... ❷特点:比直接插入和直接排序移动次数多。是最慢的一种方法。 ❸伪代码:
void bubbleSort(int data[],int n) { int temp=0; int flag=true; for(int i=1;i<n&&flag;i++) flag=false; for (int j=0;j<n-i;j++) if(data[j]>data[j+1]) { flag=true;//代表该次循环中发生过交换。倘若没有发生,则不需要下一次循环。 temp=data[j]; data[j]=data[j+1]; data[j+1]=temp; } }
2.6快速排序
❶原理:又称为划分排序。选取基本元素,将小于基本元素向前移动。大于基本元素的向后移动。最后将基准元素左右2边分别作为子区间递归调用。 ❷特点:需要附加栈空间。不稳定(涉及到元素交换)。平均速度最快。可以比较居中元素,前后元素。取三者中居中的作为基本元素,并将其移动至队前列。辅助空间O(log2n). ❸伪代码:
void quickResort(int data[], int low, int high) { int i = low; int j = high; int temp = data[i];//选取第一个元素为中间元素 while (i < j){ while (i<j&&data[j] >= temp) j--; data[i] = data[j]; while (i<j&&data[j] < temp) i++; data[j] = data[i]; } data[i] = temp; if (low<i - 1) //左区间不止一个元素 quickResort(data, low, i - 1); if (i + 1<high) //右区间不止一个元素 quickResort(data, i + 1, high); }
2.7归并排序
❶原理:将2个有序序列合并为一个有序序列。从2个元素合并一直进行到最后。 ❷特点:稳定。由于需要将2个数组合并,因此需要辅助数组。空间复杂的相对较高。 ❸伪代码:
mergeSort(int data[],int n,int low,int high){ mid=(low+high)/2; if(mid-low>0) mergeSort(data,n,low,mid); if(high-mid>1) mergeSort(data,n,mid+1,high); int i=low,j=mid+1,k=0; int* temp=new int[high-low+1]; while(i<=mid&&j<=high) if(data[i]<data[j]) temp[k++]=data[i++]; else temp[k++]=data[j++]; while(i<=mid) temp[k++]=data[i++]; while(j<=high) temp[k++]=data[j++]; k=low; while(k<=high) data[k]=temp[k] k++; }
2.8计数排序
1.设n个数,范围为n。则时间复杂度为O(m+n),且需要辅助内存O(n),为稳定排序。一般而言,计数排序以空间换性能,且一般适用于正数数组排序。当且仅当n不是很大时,计数排序十分有效。
2.思想:不是基于比较的排序:
1)统计数组中每个值为i的元素出现的次数,存入数组counter[i]
2)循环遍历counter[i],使得counter[i]=counter[i]+counter[i-1]。目的是统计待排序数组中,小于等于i的元素个数
3)从后向前遍历data[j], 则输出的数组中sort[--counter[data[j]]]=data[j]。注意从后向前遍历data[]保证了计数排序的稳定性。
void countSort(int data[], int n,int sorted[],int n){ int max=data[1] for(int i=1;i<n;i++) if(max<data[i]) max=data[i]; int * count=(int*)malloc((max+1)*sizeof(int)); if(!count) exit(1) for(int i=0;i<n;i++) count[data[i]]++; for(int i=1;i<=max;i++) count[i]=count[i]+count[i-1]; for(int i=n-1;i>-1;i--) sorted[--count[data[i]]]=data[i]; free(count); }
2.9 桶排序
思想:通过映射函数将n个数映射至m个桶中(比如划分区间),在单独对每个桶的数进行单独排序比如快速排序。时间复杂度为O(n+n*log(n/m)),最好为O(n)此时每个桶只有一个数,但是空间很大,最坏为log(nlogn),此时所有的数都被分到一个桶里面。
桶排序的稳定性取决于单个桶的排序算法。若是快速排序则不稳定。
计数排序是桶排序的特例,此时映射函数未f(x)=x,桶的个数为max+1
2.10基数排序
一种适用于关键值为整形的高效排序法。设数字为m位d进制数(如1234为4位10进制数)。则定义d个桶。对于总数为n的d进制位的数。最好最差平均时间均为O(m*n),辅助存储方面,若采用链式队列,则为O(n);考虑顺序队列,因为要考虑最差情况,则O(n*d),此时被n个数进入同一个队列,实现需要分配d*n个辅助存储单元.
思想:先排最低位进桶,再排次地位进桶,依次按序号进桶。
桶使用队列存储,保证先进先出。为稳定算法。
//m位d进制 void radixSort(int data[],int length,int m,const int d){ queue<int>* q=new queue<int>[d]; int power; for(int i=0;i<m;i++){ if(i==0) power=1; else power=power*d; int k=0; for(int j=0;j<length;j++){ k=data[j]/power-(data[j]/(power*d))*d; q[k].push(data[j]); } k=0; for(int j=0;j<d;j++){ while(!q[j].empty()){ data[k++]=q[j].front(); q[j].pop(); } } } delete []q; }
3.排序方法比较
❶从时间角度。 平均。直接插入,直接选择,冒泡均为O(n.^2)。堆,归并,快速为O (nlogn)。希尔排序基于2者之间。 最好。直接插入,冒泡和直接插入为O(n)最好。快速,归并,堆次之为 O (nlogn)。 最差。堆和归并仍然为 O (nlogn),其它为O(n.^2)
❷从空间的角度。 归并最差,需要O(n)。快速为O(logn)(辅助栈空间)。其它均为O(1)。
❸从稳定角度 只要直接插入,归并,冒泡,基数,计数 排序稳定。
❹总结: 数目多且随机。内存容许要求稳定可以用归并;内存不容许或者不要求稳定,可以用堆排序。直接插入和冒泡在最好的情况下性能最好。当n比较大且随机分布时,快速排序效果好。当n很大时且关键字很小,使用基数排序。
❺.std::sort封装了快速排序算法,因此是不稳定的,如果要使用稳定排序,可以用std:stable_sort
另外,二叉树排序法的时间复杂度,最坏为O(n.^2),平均为O(nlogn).空间复杂度为O(n)