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)