此文章转载
在公司实习了,由于公司的性质,以后的工作中用到算法&数据结构的知识可能会很少,很想把以前学的数据结构&算法的知识系统的回忆一下,但由于时间的原因,再加上我一直都很喜欢排序算法的思想。近期主要就排序这个问题做一个系统的介绍:冒泡排序,简单选择排序,直接插入排序,希尔排序,快速排序,归并排序,堆排序,基数排序。
排序的稳定性:假设 ,在排序前的序列中第i记录领先于第j个记录。如果排序后第i记录仍领先于第j个记录,则称所用的排序算法是稳定的;反之,所用的排序的算法是不稳定的。
对于内排序来说,算法的性能主要受3个方面影响:时间性能(衡量排序算法好坏的最重要的标志);辅助空间(程序在执行过程中需要的额外辅助空间);算法的复杂度(算法本省的复杂度,比如堆排序相对就比较复杂)。
交换两个数据主要有三种方法,下面算法实现:
1、冒泡排序
1、思想描述:一种交换排序,两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
2、算法复杂度及稳定性:
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定性算法
3、算法实现:
/**************************************************************/
/********************冒泡排序算法******************************/
void BubbleSort(int array[],int length)
{
int i,j;
for(i=0;i<length-1;i++)
{
for(j=length-2;j>=i;j--)//从后朝前比较,将较小的放置前面
{
if(array[j]>array[j+1])
swap(array,j,j+1);
}
}
}
上述算法能否进一步改进呢,我们发现对于已经有序的序列来说,外部仍然需要进行length-1次,显然这个时候不需要再继续后面的循环。为了实现这种想法,可以增加一个标志位来标记数组是否已经有序。
改进算法:
void BubbleSortImprove(int array[],int length)
{
int i,j;
boolflag=true;
for(i=0;i<length-1&&flag;i++)
{
flag=false;//初始化为false
for(j=length-2;j>=i;j--)//从后朝前比较,将较小的放置前面
{
if(array[j]>array[j+1])
{
swap(array,j,j+1);
flag=true; //此时发生数据交换,序列不是有序序列,标记为true
}
}
}
}
2、简单选择排序
1、算法思想描述:通过 次的关键字比较,从n-i+1个记录中选出关键字最小的记录,并和第 个记录交换。
2、算法复杂度及稳定性:
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定性算法
3、算法的具体实现:
/**************************************************************/
/********************简单选择排序******************************/
void SelectSort(int array[],int length)
{
inti,j,min;
for(i=0;i<length-1;i++)
{
min=i; //记录length-i最小元素
for(j=i+1;j<length;j++)
{
if(array[j]<array[min])
min=j;
}
if(i!=min)
swap(array,i,min);
}
}
3、直接插入排序
1、算法思想:假设前 个记录已经有序,将第 个记录插入到已经已经排好序的有序表中,从而得到一个有 个记录的新的有序表。
2、算法复杂度及稳定性
平均时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定性算法
3、算法实现:
/**************************************************************/
/********************直接插入排序******************************/
void InsertSort(int array[],int length)
{
inti,j,temp;
for(i=1;i<length;i++)
{
if(array[i]<array[i-1])//需要将array[i]插入到有序数组中
{
temp=array[i];
for(j=i;j>0&&temp<array[j-1];j--)
{
array[j]=array[j-1];//记录后移
}
array[j]=temp;//将array[i]插入到正确位置
}
}
}
4、希尔排序
1、算法思想:实质上就是直接插入排序的改进,先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。
2、算法复杂度及稳定性
平均时间复杂度:O(nlogn)-O(n^2)
空间复杂度:O(1)
稳定性:不稳定性算法
3、算法实现
/**************************************************************/
/********************希尔排序******************************/
void ShellSort(int array[],int length)
{
inti,j,temp,gap;
for(gap=length/2;gap>0;gap=gap/2)//增量序列
{
for(i=gap;i<length;i++)
{
if(array[i]<array[i-gap])//将array[i]插入到增量子序列中
{
temp=array[i];
for(j=i;j>0&&temp<array[j-gap];j=j-gap)
{
array[j]=array[j-gap];
}
array[j]=temp;
}
}
}
}
5、堆排序
1、算法思想:将待排序的序列构造成一个大顶堆。此时整个序列的最大值就是堆顶的根节点。将其移走(就是讲其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的 个序列重新构造堆,这样就会得到n个元素中的次大元素,如此反复,最终就会得到一个有序序列了。
2、算法复杂性及稳定性
平均时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定性算法
3、算法的实现:
/**************************************************************/
/********************堆排序******************************/
void HeapAdjust(int array[],int local,int length)
{
inti,temp;
temp=array[local];
for(i=2*local+1;i<length;i=2*local+1)//从该节点的两个孩子节点开始
{
if((i<length-1)&&array[i]<array[i+1])//比较local的两个孩子的大小,保留较大的下标
i++;
if(temp>=array[i])//应插入位置
break;
array[local]=array[i];
local=i;
}
array[local]=temp;//插入
}
void HeapSort(int array[],int length)//堆排序
{
int i;
for(i=length/2-1;i>=0;i--)//从第一个非叶子节点开始自上而下的调整每一个子树
{
HeapAdjust(array,i,length);
}
for(i=length-1;i>=0;i--)
{
swap(array,0,i); //将堆顶记录和当前未排序的子序列的最后一个记录交换
HeapAdjust(array,0,i);//将剩下的记录重新调整为大顶堆
}
}
6、归并排序
1、算法思想:归并排序就是利用归并的思想,假设初始含有n个记录,把其看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到 个长度为1或2的有序子序列;然后在两两合并……,如此重复,直至得到一个长度为n的有序序列为止,成为2-路归并排序。
2、算法复杂度及稳定性:
平均时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定性算法
3、算法实现:
/**************************************************************/
/********************归并排序的递归形式************************/
void Merge(int array[],int tm[],int start,int mid,intend)//将有序的array[start...mid]和array[mid+1...end]合并为有序的tm[start...end]
{
int i,j,k;
k=start;
i=start;
j=mid+1;
while(i<=mid&&j<=end)//将array中的记录有小到大归并入tm中
{
if(array[i]<=array[j])
tm[k++]=array[i++];
else
tm[k++]=array[j++];
}
if(i<=mid)//将剩余的array[i...mid]复制到tm中
{
while(i<=mid)
tm[k++]=array[i++];
}
if(j<=end)//将剩余的array[j...end]复制到tm中
{
while(j<=end)
tm[k++]=array[j++];
}
}
void MSort(int array[],int tm[],int start,int end)
{
inttm2[MAXSIZE+1];
if(start==end)//如果就一个记录可以直接归并
{
tm[start]=array[end];
}
else
{
intmid=(start+end)/2;
MSort(array,tm2,start,mid);//首先将array[start...mid]归并为一个有序数组,并放在tm2[start..mid]中
MSort(array,tm2,mid+1,end);//然后将array[mid+1...end]归并为一个有序数组,并放在tm2[mid+1...end]中
Merge(tm2,tm,start,mid,end);//最后将tm2[start...mid]和tm2[mid+1...end]合并成一个有序记录,并放在tm[start...end]中
}
}
void MergeSort(int array[],int length)
{
MSort(array,array,0,length-1);//归并排序的递归形式
}
7、快速排序
1、算法思想:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字记录均比另一部分的关键字小,然后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
2、算法复杂度及稳定性分析:
平均时间复杂度:O(nlogn)
空间复杂度:O(logn)-O(n)
稳定性:不稳定性算法:
3、算法实现:
/**************************************************************/
/***********************快速排序******************************/
int Partition(int array[],int low, int high)//交换顺序表array中字表的记录,使枢轴到位,并返回其所在的位置
{
intmid=low+(high-low)/2;//计算数组中间的元素的下标
if(array[low]>array[high])//交换左端和右端数据,保证左端数据较小
swap(array,low,high);
if(array[high]<array[mid])//交换右端和中间数据,保证中间数据较小
swap(array,mid,high);
if(array[low]>array[mid])//交换左端和中间数据,保证左端数据较小
swap(array,low,mid);
intpivot=array[low];//用第一个记录作为枢轴的记录
while(low<high)
{
while(low<high&&array[high]>=pivot)
high--;
array[low]=array[high];//直接替换,不需要交换,将比枢轴小的记录放到低端
while(low<high&&array[low]<=pivot)
low++;
array[high]=array[low];//直接替换,不需要交换,将比枢轴大的记录放到高端
}
array[low]=pivot;
returnlow;//返回枢轴的位置
}
void QSort(int array[],int low,int high)
{
if(low<high)
{
intpartition=Partition(array,low,high);//将array[low...high]一分为二,算出枢轴的位置
QSort(array,low,partition-1);
QSort(array,partition+1,high);
}
}
void QuictSort(int array[],int length)
{
QSort(array,0,length-1);
}
改进主要有优化枢轴记录的选取,一种取中间,随机选取;用替换替代交换;对于记录较少的可以直接使用直接插入排序;尾递归的方法缩减堆栈的深度。
void QSort1(int array[],int low,int high)//尾递归
{
if(low<high)
{
intpartition=Partition(array,low,high);//将array[low...high]一分为二,算出枢轴的位置
QSort1(array,low,partition-1);
low=partition+1;//尾递归
}
}
8、基数排序
1、算法思想:n个记录的关键字进行排序,每个关键字看成一个d元组: ,分为两种方式:LSD(由数值的最右边(低位)开始)和MSD(由数值的最左边(高位)开始)。只讲述LSD,排序时先按 的值,从小到大将记录分到r(称为基数)个盒子中,一次手机;然后在按 的值继续。
2、算法复杂度及稳定性分析:
平均时间复杂度:O(d(n+rd))
空间复杂度:O(rd)
稳定性:稳定性算法
基数排序可参考:http://blog.csdn.net/cjf_iceking/article/details/7943609
八种常见内部排序算法总结回顾
本文将常见的8种内部排序算法做一个总结,排序的重要性不言而喻,主要从算法思想,算法复杂度及稳定性(排序稳定对于某些特殊需求来说是不言而喻的),算法的具体实现(VC++6.0下C语言实现)。下面用一个表格就这八种算法做一个汇总:
简单排序:冒泡排序,简单选择排序,直接插入排序(1)从算法的性质上来看,八种算法分为两类:
改进算法排序:希尔排序,堆排序,归并排序,快速排序,基数排序。
(2)从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏的情况下的时间性能不如堆排序和归并排序。但对于后两者而言,在n较大时,归并所需时间较省,但所需辅助存储量最多。
(3)基数排序最适用于n值很大而关键字较小的序列。而对于基本有序的序列,冒泡和插入性能较佳。
(4)从方法的稳定性来比较,基数排序是稳定的,几种简单排序算法均是稳定性排序算法,而对于改进的算法,比如希尔排序,快速排序,堆排序均是不稳定排序。
综上所述:对于所有的排序算法,没有哪种算法是最优的,在实用是要根据不同的情况适当选择,甚至可以多种方法综合实用(比如快速排序可以和直接插入排序(对于基本有序的序列性能较佳))。