内部排序
内部排序要点
排序相关的知识
1.排序的作用
将一个数据元素(或记录)的任意序列,重新排列陈一个按照关键字有序的序列。
2.排序方法的稳定性
如果序列中有两个关键字相等的记录,排序前后两个记录的相对位置不变,则称该排序方法是稳定的,否则就是不稳定的。
- 内部排序与外部排序
内部排序指的是待排序记录存放在计算机随机存储器中进行的排序过程。外部排序则指的是待排序的数量很大,计算机内存不能一次容纳全部记录,在排序过程中还需要访问外存的排序过程。
- 内部排序按照排序方法的原理来分类
插入排序,交换排序,选择排序,归并排序以及计数排序。
5.内部排序按照排序方法的工作量(时间复杂度)来分类
a.简单的排序方法,时间复杂度为O(n2);b.先进的排序方法,时间复杂度为O(nlogn);c.基数排序,时间复杂度为O(d*n)。
详细介绍几种内部排序
待排序的数据类型为顺序表:
typedef struct{
int num[MAX];
int length;
}SqList;//顺序表结构体
插入排序
包括直接插入排序、希尔排序、折半插入排序以及其他插入排序
直接插入排序
直接插入排序是一种最简单的排序方法。
1.基本操作:
将一个记录操作插入到已经排好序的有序表中,得到新的、记录数+1的新的有序表直至记录全部插入。
2.算法代码展示(C语言):
//直接插入排序 L.num[0]作为哨兵
void InsertSort(SqList L){
int i,j,m;
for(i=1;i<L.length;++i){
if(L.num[i-1]>L.num[i]){
m=L.num[i];
for(j=i-1;j>=0&&m<L.num[j];--j){
L.num[j+1]=L.num[j];
}
L.num[j+1]=m;
}
}
shownum(L);//将排序后的结果打印出来
}
3.特点:
算法简洁,容易实现,具有稳定性,适合待排序记录数较小的情况,空间上只需要一个记录的辅助空间即空间复杂度为O(1),时间上时间复杂度为O(n2)。
希尔排序
又称“缩小增量排序”
1.基本操作:
先将整个待排序记录分成若干子序列(相隔某个增量的记录组成一个子序列)分别进行直接插入排序,待整个序列中的记录“基本有序”之后,再对全体进行一次直接插入排序。
2.算法代码展示(C语言):
//希尔排序
void ShellSort(SqList L){
int dk=L.length/2;
int i,j,m;
while(dk>=1){
//ShellInsert(L,dk);
for(i=dk;i<L.length;++i){
if(L.num[i]<L.num[i-dk]){
m=L.num[i];
for(j=i-dk;j>=0&&m<L.num[j];j-=dk){
L.num[j+dk]=L.num[j];
}
L.num[j+dk]=m;
}
}
dk=dk/2;
}
shownum(L);//打印排序结果
}
3.特点:
在时间效率上有很大改进,时间复杂度比直接插入排序低,具有不稳定性,近似为O(n3/2),空间复杂度为O(1),如何选取增量dk对于排序的效率极为重要(应该使得增量序列值互质,并且最后一个增量值必须为1)。
折半插入排序
折半插入排序在在直接插入排序的查找部分做了改进。在待排序序列记录数n很大时,可以考虑使用折半插入排序。
1.基本操作:
在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为num[low],末元素设置为num[high],则轮比较时将待插入元素与num[m],其中m=(low+high)/2相比较,如果比参考元素小,则选择num[low]到num[m-1]为新的插入区域(即high=m-1),否则选择num[m+1]到num[high]为新的插入区域(即low=m+1),如此直至low<=high不成立,即将此位置之后所有元素后移一位,并将新元素插入num[high+1]。
2.算法代码展示(C语言):
//折半插入排序
void BInsertSort(SqList L){
int low,high,i,j,m,n;
for(i=1;i<L.length;++i){
n=L.num[i];
low=0,high=i-1;
while(low<=high){
m=(low+high)/2;
if(n<L.num[m]) high=m-1;
else low=m+1;
}
for(j=i-1;j>=high+1;--j){
L.num[j+1]=L.num[j];
}
L.num[j+1]=n;
}
shownum(L);//打印排序结果
}
3.特点:
折半插入排序具有稳定性,速度比直接插入排序算法快,但记录移动的次数没有变。从空间上看,所需附加存储空间与直接插入排序相同,空间复杂度为O(1),从时间上看,减少了关键字之间的比较次数,时间复杂度仍为O(n2)。
其他插入排序
2-路插入排序:减少了排序过程中移动记录的次数,但是需要n个记录的辅助空间,空间复杂度为O(n),时间复杂度为O(n2)。
表插入排序:将一个记录插入到已经排好序的有序表中,求得一有序链表,只能对其进行顺序查找,而不能进行随机查找。
交换排序
包括冒泡排序与快速排序
冒泡排序
1.基本操作:
第一趟排序是先比较第一、二个记录的关键字,若逆序则交换二者,然后接着比较第二、三个记录的关键字,直至所有的关键字比较完毕,第一趟冒泡排序的结果是关键字最大的记录数落到最后一个位置。然后进行第二趟冒泡排序使得次大的记录数落到倒数第二个位置…判断冒泡排序结束的条件应该是在该趟排序过程中没有交换记录的操作。
2.算法代码展示(C语言):
//冒泡排序
void BubbleSort(SqList L){
int i,j,swap;
for(i=0;i<L.length-1;++i){
for(j=0;j<L.length-i-1;++j){
if(L.num[j]>L.num[j+1]){
swap=L.num[j];
L.num[j]=L.num[j+1];
L.num[j+1]=swap;
}
}
}
shownum(L);//打印排序结果
}
3.特点:
若初始序列是正序,则只需要进行一次排序,排序过程中进行n-1次关键字间的比较,且不移动记录;若初始序列是反序,则需要进行n-1次排序,需要进行n(n-1)/2次比较,并作等数量级的记录移动。
时间复杂度为O(n2),空间复杂度为O(1),具有稳定性。
快速排序
对冒泡排序进行了改进。
1.基本操作:
通过一趟排序将待排记录分割为独立的两部分,其中一部分的关键字均比另一部分的关键字小,可分别对两部分的记录进行排序,使得整个序列有序。
2.算法代码展示(C语言):
//递归实现快速排序 待改进
int QuickPartition(SqList L,int low,int high){
int m,key;
m=L.num[low],key=L.num[low];
while(low<high){
while(low<high&&L.num[high]>=m) {--high;}
L.num[low]=L.num[high];
while(low<high&&L.num[low]<=m) {++low;}
L.num[high]=L.num[low];
}
L.num[low]=key;
return low;
}
void QSort(SqList L,int low,int high){
int temp;
if(low<high){
temp=QuickPartition(L,low,high);
QSort(L,low,temp-1);
QSort(L,temp+1,high);
}
}
void QuickSort(SqList L){
QSort(L,1,L.length);
shownum(L);//打印排序结果
}
3.特点:
从时间上看,其平均性能优于其他的排序方法,时间复杂度为O(nlogn),从空间上看,快速排序需要一个栈空间来实现递归,空间复杂度为O(logn)。
选择排序
包括*简单选择排序、其他选择排序
基本思想是:每一趟在n-i+1个记录中选取关键字最小的记录作为有序序列中的第i个记录。
简单选择排序
1.基本操作:
一趟简单选择排序操作为:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换。
2.算法代码展示(C语言):
//简单选择排序
void SelectSort(SqList L){
int i,j,min,temp;//m为i与L.length之间最小的记录值
for(i=0;i<L.length-1;i++){
min=i;
for(j=i+1;j<L.length;j++){
if(L.num[j]<L.num[min]){
min=j;
}
}
if(i!=min){
temp=L.num[i];
L.num[i]=L.num[min];
L.num[min]=temp;
}
}
shownum(L); //打印排序结果
}
3.特点:
简单选择排序过程中,需要移动记录的次数最小值为0,最大值为3(n-1),但是需要进行关键字之间的比较次数总是为n(n-1)/2。其空间复杂度为O(1),时间复杂度为O(n2),具有稳定性。
其他选择排序
1.树形选择排序
首先对n个记录的关键字进行两两比较,然后在[n/2]个较小者中再进行两两比较,如此重复下去,直至选出最小的关键字记录为止。
时间复杂度为O(nlogn),但是空间复杂度较大。
2. 堆排序
只需要一个记录大小的辅助空间,每个待排序的记录仅占有一个存储空间。即堆排序的时间复杂度为O(nlogn),空间复杂度为O(1),具有不稳定性。
(算法代码待更新)
归并排序
归并指的是将两个或者两个以上的有序表合成一个新的有序表。利用归并的思想容易实现排序。
2-路归并排序
1.基本操作:
假设初始序列含有n个记录,则可以看成是n个有序的长度为1的子序列,然后两两合并,得到[n/2]个长度为2或1的有序子序列,再两两归并…如此重复下去,直到最终得到的是一个长度为n的有序子序列。
其核心操作是将一维数组中前后相邻的两个有序序列归并为一个有序序列。
2.算法代码展示(C语言):
//归并排序
void MergeSort(SqList L){
int num[MAX];
int i,m,n,len;
for(len=1;len<L.length;len=len<<1){
int resCount=0;
int groupCount=ceil(L.length*1.0/len);
int mergeCount=groupCount>>1;
for(i=0;i<mergeCount;i++){
int left=(i*len)<<1;
int leftside=left+len;
int right=leftside;
int rightside=(L.length<leftside+len)?L.length:right+len;
while(left<leftside&&right<rightside){
if(L.num[left]<=L.num[right])
num[resCount++]=L.num[left++];
else
num[resCount++]=L.num[right++];
}
while(left<leftside)
num[resCount++]=L.num[left++];
while(right<rightside)
num[resCount++]=L.num[right++];
for(left=i<<1;left<rightside;left++)
L.num[left]=num[left];
}
}
shownum(L);//打印排序结果
}
3.特点:
归并排序具有稳定性,空间复杂度为O(n),时间复杂度为O(nlogn)。
基数排序
基数排序的借助于多关键字排序的思想对单逻辑关键字进行排序(分配与收集),而不需要进行记录关键字之间的比较,具有稳定性。