插入类排序:
1.直接插入排序:
//插入排序算法较简单。具体思路为,A[0]为哨兵,A[i]为待插入元素,A[1]~A[i-1]为已排列的有序序列,
//将A[i]从后往前比较插入有序序列中。
void InsertSort(ElemType A[],int n){
int i,j;
for(i=2;i<=n;i++)
if(A[i].key<A[i-1].key){
A[0]=A[i];
for(j=i-1;A[0].key<A[j].key;--j)
A[j+1]=A[j];
A[j+1]=A[0];
}
}
最好情况时间复杂度为O(n),最坏情况时间复杂度为O(n^2),稳定性是稳定。
2.折半查找排序:
//该算法和直接插入算法相似,只是在找插入位置时,将遍历查找改为了折半查找。
void InsertSort(ElemType A[],int n){
int i,j,low,high,mid;
for(i=2;i<=n;i++){
A[0]=A[i];//存储该元素
low=1;high=i-1;
while(low<=high){
mid=(low+high)/2;
if(A[mid].key<A[0].key) high=mid-1;
else low=mid+1;
}
for(j=i-1;j>=high+1;--j) A[j+1]=A[j];
A[high+1]=A[0];
}
}
时间复杂度为O(n^2),稳定性是稳定。
3.希尔排序:
//希尔排序是以d为增量将序列分组再进行排序的算法,每小组排序用的是插入排序。一轮过后,d=d/2,再次
//分组排序,直到d=1,进行最后一轮排序。
void ShellSort(ElemType A[],int n){
int i,j;
for(int d=n/2;d>=1;d=d/2)//控增量
for(i=d+1;i<=n;++i)//控分组
if(A[i].key<A[i-d].key){//这里还是插入排序
A[0]=A[i];
for(j=i-d;j>0&&A[0].key<A[j].key;j-=d)
A[j+d]=A[j];
A[j+d]=A[0];
}
}
希尔排序的时间复杂度约为O(n^1.3),在最坏情况下希尔排序的时间复杂度是O(n^2)。空间复杂度为O(1)。稳定性是不稳定。
交换类排序:
4.冒泡排序:
void BubbleSort(ElemType A[],int n){
for(int i=0;j<n-1;i++){
bool flag=false;
for(int j=n-1;j>i;j++)
if(A[j-1].key>A[j].key){
ElemType temp=A[j-1].key;
A[j-1].key=A[j].key;
A[j].key=temp;
flag=true;
}
if(flag==false) return ;//若没有发生交换,则序列已有序
}
}
冒泡排序的时间复杂度约为O(n),在最坏情况下希尔排序的时间复杂度是O(n^2)。空间复杂度为O(1)。稳定性是稳定的。
5.快速排序:
//快排的思路就是选定一个数(一般为序列第一个数)为枢轴,将序列分为比枢轴小和比枢轴大的两个序列,
//再对两个序列进行快排,从而逐渐实现使序列在分解过程中逐渐有序的目的。
void QuickSort(ElemType A[],int low,int high)
{
if(low<high)
{
int pivotpos=Partition(A,low,high);//选枢轴并按枢轴分为两个序列,返回枢轴位置
QuickSort(A,low,pivotpos-1);//对枢轴前一部分进行划分
QuickSort(A,pivotpos+1,high);//对枢轴后一部分进行划分
}
}
int Partition(ElemType A[],int low,int high)
{
ElemType pivot=A[low];//选第一个元素为枢轴
while(low<high)//逐次对序列元素进行比较,比枢轴大则右移,比枢轴小则左移
{
while(low<high&&A[high]>=pivot)//找到比枢轴小的数则结束循环
--high;
A[low]=A[high];//移到左边序列
while(low<high&&A[low]<=pivot)//找到比枢轴大的数则结束循环
++low;
A[high]=A[low];//移到右边序列
}
A[low]=pivot;//枢轴放中间
return low;//返回枢轴位置
}
时间复杂度,最好情况是O(nlogn),待排序序列越无序,算法效率越高。最坏情况是O(n^2),待排序序列越有序,算法效率越低。快速排序最好的情况是每次取的枢轴都刚好平分整个数组,最坏的情况是每次取的枢轴都在数组的一端。
空间复杂度,最好情况是O(logn),也就是递归树的深度。最坏情况是O(n),递归次数是n-1。
快速排序是不稳定的,因为存在交换关键字。
选择类排序:
6.简单选择排序:
void SelectSort(ElemType A[],int n){
for(int i=0;i<n-1;i++){
min=i;
for(j=i+1;j<n;j++)
if(A[j]<A[min]) min=j;
if(min!=i){
ElemType temp=A[i];
A[i]=A[min];
A[min]=temp;
}
}
}
时间复杂度为O(n^2);稳定性是不稳定的。
7.堆排序:
//堆排序算法过程是,先制造大顶堆,然后通过不断摘取根节点(最大值)并重塑大顶堆的过程,将序列有
//序化。
void HeapSort(ElemType A[],int len)
{
int i;
BuildMaxHeap(A,len);//塑造大顶堆
for(i=len;i>1;i--)
{
swap(A[i],A[1]);//摘取最大值
AdjustDown(A,1,i-1);//重新塑造大顶堆,注意现在的末元素是i-1
}
}
void BuildMaxHeap(ElemType A[],int len)
{
for(int i=len/2;i>0;i--)//从最后一个叶子结点的父节点开始,将树中结点最大值逐步传到根节点
AdjustDown(A,i,len);
}
void AdjustDown(ElemType A[],int k,int len)
{
int i;
A[0]=A[k];//保存该结点数值
for(i=2*k;i<len;i*=2)
{
if(i<len&&A[i]<A[i+1])//选出左右结点中最大的结点
i++;
if(A[0]>=A[i])//A[0]节点大则找到该数值插入的位置
break;
else{//否则将数值大的子结点上移替补父节点,并沿该子结点的路径找A[0]结点该插入的位置
A[k]=A[i];
k=i;
}
}
A[k]=A[0];//找到A[0]数值的位置并插入
}
空间复杂度为O(1);时间复杂度为:建堆部分(O(n))+调堆部分(O(nlogn))=O(nlogn)。堆排序是不稳定的。
8.归并排序:
//归并排序是将序列通过不断二分为最小单位序列,再将它们两两比较合并为有序序列,从而得到最终的有序
//序列的算法。
void MergeSort(ElemType A[],int low,int high)
{
if(low<high)
{
int mid=(low+high)/2;//将序列分为两段
MergeSort(A,low,mid);//使前半段有序
MergeSort(A,mid+1,high);//使后半段有序
Merge(A,low,mid,high);//将有序的前后两段合并
}
}
void Merge(ElemType A[],int low,int mid,int high)//将有序的前后两段序列合并
{
ElemType B[N];
int i,j,k;
for(k=low;k<=high;k++)//复制A到B
B[k]=A[k];
for(i=low,j=mid+1,k=i;i<mid&&j<=high;k++)//i控制前半段元素比较游标,j控制后半段元素比
//较游标,k控制依次写入A的较小元素
{
if(B[i]<=B[j])
A[k]=B[i++];
else
A[k]=B[j++];
}
while(i<=mid)
A[k++]=B[i++];
while(j<=high)
A[k++]=B[j++];
}
归并排序时间复杂度为O(nlogn)。空间复杂度为O(n)。稳定性是稳定的。
9.基数排序(桶排序)
又分为最低位优先(LSD)和最高位优先(MSD)。
最低位优先是,按最低位个位到高位的顺序依次进行分配和收集;如按个位排序分配进入0~9这十个队列中,再依次收集形成新的序列;再按十位排序分配收集,直到最高位,则可得到有序序列。设关键字数是n,关键字的位数是d,进制数是r,则空间复杂度是O(r),时间复杂度是O(d(n+r))。由于队列是先进先出,则稳定性是稳定的。
最高位优先类似,先按最高位进行分配,然后再对队列里n>1的队列进行递归对次高位进行分配,以此类推,处理完收集即可得到有序序列。
外部排序:
由于内存容量无法容纳大量数据,因而要用到多路归并排序,它可以在较小的内存空间里完成大规模的数据排序。
如何得到初始的归并段——置换选择排序:解决了排序段放入内存的问题。这是因为内存相对小的情况下,初始数据段无法全部放入内存中,则可以通过置换选择排序将其输出为数条尽可能长且不等长的有序归并段。归并段尽可能长,则归并段合并次数则相对减少。
如何减少多个归并段的归并次数——最佳归并树:最小的归并次数(I/O次数)。类似于m路的哈夫曼树。N为一次归并所执行的归并段。
如何每次m路归并快速得到最小的关键字——败者树——减少比较次数。败者树类似于大顶堆的塑造和调整。叶子结点存储的各个归并段当前参加比较的记录,内部结点存储的则是左右子树中的失败者。胜利者则一直继续向上比较直到根结点。