外部排序:待排序的记录数量很大,以至内存不能一次全部容纳,在排序过程中还需对外存进行访问。
内部排序:待排序记录存放在计算机随机存储器中进行的排序过程。内部排序大致分为五类:插入排序,交换排序,选择排序,归并排序和计数排序。
常用基于比较的排序算法:冒泡排序、插入排序、选择排序、希尔排序、归并排序、快速排序
非比较的排序算法:计数排序、桶排序、基数排序
1.直接插入排序
基本思想:对于未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入,直至完成排序。选择第一个数据作为已经有序的序列。
void InsertSort(int a[],int n){
for(int i=1;i=0&&a[j]>tmp;j--){
a[j+1]=a[j]; //将大的元素后移
}
a[j+1]=tmp; //放置到正确有序的位置
}
}
时间复杂度为O(N^2)
2.折半插入排序
基本思想:在直接插入的基础上将关键字之间的比较改进为二分查找的比较,减少比较次数,但移动记录次数不变。
void BInsertSort(int a[],int n){
for(int i=1;itmp)
right=mid-1;
else
left=mid+1;
}
for(int j=i-1;j>=right+1;j--) //将a[right+1,i-1]的元素全部后移
a[j+1]=a[j];
a[right+1]=tmp; //插入到相应正确的位置
}
}
时间复杂度仍为O(N^2)
3.二路插入排序
上面折半插入减少了元素间比较的次数,二路插入在此基础上继续改进,使用辅助空间一个循环数组来达到减少元素移动的次数。
构建一个相同大小的循环数组b,该数组分为两路,一路是有序序列b[first,last],另一路是空的,根据情况选择向哪一路插入:
若a[i]
若a[i]>=b[last],变化last=last+1,b[last]=a[i],插入到第二路中。
若b[first]<=a[i]
可以看出,在插入到第二路的情况下是不需要移动元素的,提高了效率。
void tworoad_InsertSort(int a[],int n){
int first=0,last=0;
int *b=(int*)malloc(sizeof(int)*n); //申请辅助空间
b[0]=a[0]; //选择第一个作为有序的基准数
for(int i=1;i=b[last]){
last++;
b[last]=tmp;
}
else{ //否则二分插入到第一路(选取区间左闭右开),这个时候需要进行元素的移动
int left=first,right=last,mid,d;
while(left!=right){
d=(right-left+n)%n;
mid=(left+d/2)%n;
if(tmp
以上三个插入排序在序列的直观上来看都是每次迭代会使序列前面有序区的序列长度加1,后面无序区长度减1外不发生变化。
4.希尔排序
基本思想:希尔排序又称为缩小增量排序,它的关键是从待排序的n个元素中选取一个增量值p,将序列划分为p个子序列,每个序列中存放的是间隔为p的元素,对每个子序列进行直接插入排序使其有序。然后p缩小,继续按此进行划分和排序,直至p==1时对整个序列进行最后一次排序,得到最终的有序序列。
关于增量值p的取法有很多种,最初shell提出的是p=n/2向下取整,p=p/2向下取整直至p==1,还有其他许多高效的增量p的取法。希尔排序是一种不稳定的排序算法。
void ShellSort(int a[],int n){
int p=n/2; //初始化增量值
while(p>=1){ //循环,最后一次排序时增量值为1,就是对全体序列进行插入排序,但是由于之前的工作,使得此次的排序快了很多
for(int i=p;i=0&&a[j]>tmp;j-=p)
a[j+p]=a[j];
a[j+p]=tmp;
}
p=p/2; //增量缩小
}
}
平均时间复杂度为O(N^1.3),最好为O(N),最坏为O(N^2)。
1.冒泡排序
最基本的交换排序
void bubbleSort(int arr[], int n){
for(int i=0;ia[j+1]){
int tmp=a[j];
a[j]=a[j+1];
a[j+1]=tmp;
flag=0;
}
}
if(flag) break; //如果flag==1则说明没有发生交换,序列已经有序了
}
}
平均情况O(N^2),最好O(N),最坏O(N^2),是一种稳定的排序。从序列上直观的看就是每次迭代都会将最大的元素移到后面的有序区。
2.快速排序(三种算法实现)
我们知道在最好的情况下,基准元素将待排序列区域一分为二,左边的元素全部小于基准数,右边的全部大于,对左右子序列递归调用快速排序。但是如果序列基本有序,那么快排就会大大退化。因此选取最好的基准元素都是尽可能的将序列平均一分为二,通常使用三数取中法(在序列首尾中间三个位置选取中值数作为基准元素)
int getmid(int a[],int l,int r){ //快排优化————三数取中法
int mid=l+(r-l)/2;
if(a[l]<=a[r]){
if(a[l]>=a[mid])
return l;
else if(a[r]<=a[mid])
return r;
else
return mid;
}
else{
if(a[l]<=a[mid])
return l;
else if(a[r]>=a[mid])
return r;
else
return mid;
}
}
①挖坑法
基本思想:先选择一个基准数(一般选择第一个元素)拿出来,序列中空出它的位置;对该序列从后向前寻找找到小于基准数的元素,将这个元素搬出填入空出的位置,该元素的位置成为新的空位置;然后在序列从前向后寻找大于基准数的元素,将其搬出填入到空位置中,继续从后向前寻找......;直至前后两头相遇,将基准数填入该位置中。
void quick_sort(int s[],int l,int r){
if(l=tmp) //从后向前找小于tmp的数
j--;
if(i
②交换法
基本思想:与挖坑填充的思路不同在于使用的是交换方法,当数组元素很多时使用挖坑法比较高效,不需要开临时交换变量。
如果使用非递归的形式,可以将递归改为栈操作!将l,i-1和i+1,r压栈,直至栈为空!
void QiockSort(vector &v, int left, int right)
{
if (left >= right)
return;
stack s;
s.push(left);
s.push(right);
while (!s.empty())
{
int right = s.top(); //注意压进去的时候为先左后右,取出时相反!先右后左!
s.pop();
int left = s.top();
s.pop();
if (left < right)
{
int boundary = partition(v, left, right);
// 左区间
s.push(left);
s.push(boundary-1);
// 右区间
s.push(boundary + 1);
s.push(right);
}
}
}
使用递归的快排会因为递归产生空间消耗,最好情况下空间复杂度为O(logN),最坏情况空间复杂度为O(N)
平均复杂度O(NlogN),最好情况O(NlogN),最坏情况在序列正序或者逆序时为O(N^2),是一种不稳定的排序算法。
1.简单选择排序
基本思想:从第1个元素开始,在其后选择最小的元素与它交换位置,经过n-1次选择交换后序列有序,时间复杂度O(N^2)。从序列上直观看是每次迭代使序列前面的有序区加1,后面无序区长度减1但是因为交换会发生变化!
void SelectSort(int a[],int n){
int i,j,k;
for(i=0;ia[j])
k=j;
}
if(k!=i){
int tmp=a[k];
a[k]=a[i];
a[i]=tmp;
}
}
}
2.堆排序
基本思想:利用堆的性质:i为父节点则下标2*i+1,2*i+2分别为其左右子节点下标。利用大根堆的性质,每次选取堆顶节点(即堆的最大元素)与堆的末尾元素交换,然后重新调整堆成为大根堆,大根堆元素个数减1,末尾有序区元素个数加1。继续将堆顶节点与堆末尾交换重复进行操作,直至堆的元素只剩下一个(剩下的这一个一定是整个序列的最小值),此时完成排序。
void HeapDown(int a[],int p,int n){ //堆的调整函数,如果不符就将其调整为大根堆
int tmp=a[p];
int c=p*2+1; //获取左孩子下标(如果数组下标从1开始则为c=2*p)
while(ca[c])
c++; //如果右孩子大,则就用右孩子比较
if(tmp>=a[c]) //不需要调整
break;
a[p]=a[c]; //进行调整,同时再看孩子节点是否要调
p=c;
c=2*p+1;
}
a[p]=tmp; //将不合适的堆顶元素调到合适的位置
}
void HeapSort(int a[],int n){
for(int i=n/2;i>=0;i--){ //自下而上初始化构建大根堆
HeapDown(a,i,n);
}
for(int i=n-1;i>=1;i--){ //操作n-1次,每次操作都要重新调整一下堆
int tmp=a[i];
a[i]=a[0];
a[0]=tmp;
HeapDown(a,0,i);
}
}
建堆的时间复杂度为O(N),N-1次取顶元素调整为O(N*logN)
故堆排序的最好最坏的时间复杂度都是O(NlogN),是一种不稳定的排序算法。
特点是不额外使用辅助空间,适合排列相对有序的序列。
当想得到一个序列中第k个最小的元素之前的部分排序序列,最好采用堆排序。
1.递归的归并排序
基本思想:将一个无序序列不断二分,直至二分到每一部分只有一个元素时,将这两个部分通过合并函数进行合并为一个有序的序列。随着递归的不断执行,最后一次合并就是将原序列左右两部分有序的序列合并为一个,得到有序序列。
将序列分为小数列需要logN次,每次都需要进行一次合并操作为O(N),故归并排序的时间复杂度为O(NlogN),在最好最坏的情况下均是如此。并且注意到归并排序的特点是每次都在相邻的数据之间进行操作。因此从序列上直观的看就是每次迭代使得在某一步长下的相邻元素有序。
数组下标从1开始:
void merge(int a[], int first, int last, int tmp[])
{
int mid=first+(last-first)/2;
int i=first,m=mid; //借助辅助空间tmp进行合并,在这里统一使用一个辅助空间,如果是每次调用都要现new一个临时空间的话会比较费时
int j=mid+1,n =last;
int k = 1;
while (i <= m && j <= n)
{
if (a[i] <= a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while (i <= m)
tmp[k++] = a[i++];
while (j <= n)
tmp[k++] = a[j++];
for (i = 1; i < k; i++) //将合并好的序列传回a中
a[first + i-1] = tmp[i];
}
void MergeSort(int a[],int first,int last,int tmp[]){ //first、last是首尾元素的位置
if(first
2.非递归的归并排序
基本思想是将相邻的元素通过合并函数合并为一个有序序列,从相邻步长为1开始,步长以2*k逐步增加1、2、4、8......直至最后步长增至序列长度的一半,那么整个序列可看作被划分为两个相邻的“元素”,对其合并得到有序序列。
void merge(int a[], int first, int mid, int last, int tmp[])
{
int i=first,m=mid; //合并函数与递归相同,都是借助辅助空间tmp进行合并
int j=mid+1,n =last;
int k = 1;
while (i <= m && j <= n)
{
if (a[i] <= a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while (i <= m)
tmp[k++] = a[i++];
while (j <= n)
tmp[k++] = a[j++];
for (i = 1; i < k; i++)
a[first+i-1] = tmp[i];
}
void MergeSort_interate(int a[],int n){
int tmp[100005];
for(int k=1;k
在N值很大的情况下,使用归并排序比堆排序要快一些,但是归并排序的辅助空间是最大的。
基本思想:假设待排序的数组a中共有N个整数,并且已知数组a中数据的范围[0, MAX)。在桶排序时,创建容量为MAX的桶数组r,并将桶数组元素都初始化为0;将容量为MAX的桶数组中的每一个单元都看作一个"桶"。在排序时,逐个遍历数组a,将数组a的值,作为"桶数组"的下标。当a中数据被读取时,就将桶的值加1。牺牲空间换取时间。
#include
#include
#include
void bucketSort(int a[],int n,int max){
int bucket[max];
memset(bucket,0,max*sizeof(int));
for(int i=0;i0){
a[j++]=i;
bucket[i]--;
}
}
}
int main(){
int a[10001];
int n;scanf("%d",&n);
int max=-1;
for(int i=0;imax)
max=a[i];
}
bucketSort(a,n,max+1); //注意开的空间数目要比最大值大1
for(int i=0;i
基本思想:RadixSort是桶排序的扩展。将整数按位数切割成不同的数字,然后按每个位数分别比较(这是一种多关键字排序的思想)。从最低位开始依次进行排序,这样从低到高按位数排序完成后,数列就变为一个有序数列。
因此选取关键字排序的次序也有不同的方法,如果从最高位开始排则为最高位优先(MSD),如果从最低为开始则为最低位优先(LSD),是一种稳定的排序算法。其时间复杂度为:O(d(n+k)),n是待排序序列的规模,d是待排序列最大的位数,k是每位数的范围,这也是一种空间换时间的算法。因此,它适用于n值很大但是关键字较小的排序。