1、直接插入排序
******、核心思想
插入排序通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入 ,如此重复,直至完成序列排序。
******、算法步骤
1、从序列第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,设为待插入元素,在已经排序的元素序列中从后向前扫描,如果该元素(已排 序)大于待插入元素,将该元素移到下一位置。
3、重复步骤2,直到找到已排序的元素小于或者等于待排序元素的位置,此时待排序元素大于当前元素不满足条件,退出循序,插入元素
4、重复2,3步骤,完成排序。
******、算法实现
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#define endl '\n'
using namespace std;
int * InsertSort(int *p,int n){
int i,j;
for(i = 2;i < n; i++){ //从下标2开始,位置0为空,用于存放当前比较的值,位置1为一个数,没有可比性所以从2开始
if(p[i] < p[i-1]){
p[0] = p[i]; //位置0做比较位置
for(j = i - 1;p[0] < p[j] ; j-- ) //记录后移
p[j+1] = p[j];
p[j+1] = p[0]; //插入到空缺出来的位置
}
}
return p;
}
main(){
cout<<"\n请随便输入要执行插入排序的树:";
int elem;
cin>>elem;
int i = 1;
int sort[20];
while(elem != -1){
sort[i] = elem;
i++;
cin>>elem;
}
int *address = InsertSort(sort,i);
int n = 1;
cout<<"\n直接插入排序结果如下:";
while(n < i){
cout<<address[n]<<" ";
n ++;
}
}
******、算法分析
上述算法是将线性表中的第一个位置,也就是下标为0的地方设置成为了哨兵,用于存放待排序元素,这样就不用检查是否 j >= 0了,比较方便!当p【0】< p【0】不满足条件的时候就出退出移动循环语句!
时间复杂度:
1、顺序递增排列时,只需比较(n-1)次,插入排序时间复杂度为O(n);
2、逆序排序时,总的关键字比较次数大约是 n*n/2 次,以及记录移动的次数大约是n*n/2 次;
若待排序序列中出现各种可能排列的概率相同,则可以直接取上述最好情况和最坏情况的平均情况!
所以在平均情况下,直接插入排序关键字的比较次数和记录移动的次数均约是 n*n /4;
所以该算法的时间复杂度是 O(n*n)
所以该算法的最坏时间复杂度和平均时间复杂度也都是 o(n*n)
但是该算法的最好时间复杂度是 o (n),元素递增排列的情况!
空间复杂度:
需要p[0]来保存待排序元素,所以空间复杂度是 O(1)
******、算法特点
稳定排序
算法简单,而且容易实现
适用于链式存储结构,只是在单链表上无需移动记录,只需要修改相应的指针
更适合初始记录基本有序(正序)的情况,当初始记录无序,n较大的时候,此算法的时间复杂度较高,不宜采用
2、折半插入排序
******、算法目的
在直接插入排序中,在已经排好序的序列中采用的是顺序查找
找到待排序元素的插入位置,所以折半插入排序就是在已经排好序列中采用折半查找
直接确定元素的插入位置,然后将待插入位置的当前元素全部后移一位,直接将待排序元素插入到该位置即可!
******、算法实现
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#define endl '\n'
using namespace std;
int * InsertSort(int *p,int n){
int i,j,temp;
for(i = 1;i < n; i++){
if(p[i] < p[i-1]){
temp = p[i]; //保存当前的较小值
int low = 0, high = i - 1,mid; //在[0,i-1]的有序区间内使用折半查找
while(low <= high){ //当low = high的时候,mid和low以及high指向同一位置,不论哪种情况都有 low > high,所以插入点必是 high + 1
mid = (low + high)/2;
if (p[mid] > temp) high = mid - 1; //插入点在前一子表,
else low = mid + 1; //插入点在后一子表,当p[mid] = temp,为了算法的稳定性,应该在后一子表中查找
}
for(j = i - 1;j >= high + 1 ; j-- ) //记录后移
p[j+1] = p[j];
p[high+1] = temp; //插入到空缺出来的位置 high + 1
}
}
return p;
}
main(){
cout<<"\n请随便输入要执行插入排序的树:";
int elem;
cin>>elem;
int i = 0;
int sort[20];
while(elem != -1){
sort[i] = elem;
i++;
cin>>elem;
}
int *address = InsertSort(sort,i);
int n = 0;
cout<<"\n折半插入排序结果打印如下:";
while(n < i){
cout<<address[n]<<" ";
n ++;
}
}
******、算法分析
时间复杂度:
从时间上来看,折半查找比顺序查找要快,所以就平均性能来看,折半查找要优于直接插入排序!
在平均情况下,折半插入排序仅减少了关键字的比较次数,而记录移动的次数保持不变!
因此折半插入排序的时间复杂度仍是 O(n*n)
空间复杂度:
折半插入排序和直接插入排序相同,只需要一个辅助变量 temp来记录当前待排序的元素,所以该算法的时间复杂度仍是 O(1)
所以该算法的最坏和平均时间复杂度都是 o(n*n)
但是该算法的最好时间复杂度是o(n*log2n)(n个数,每个数字进行至少log2N次比较),所以总共需要进行 n * log2N
******、算法特点
稳定排序!
因为要进行折半查找,所以要具有随机存取的特性,所以只能使用顺序结构,而不能使用链式结构!
适合初始记录无序,n较大的时候,因为采用的是折半查找,所以能减少关键字的比较次数!
3、希尔排序
******、算法简介
希尔排序,又叫“缩小增量排序”
,也是插入排序的一种,确切的说叫“分组插入排序”
。直接插入排序,当待排序的记录个数较少且待排序序列的关键字基本有序时,效率较高。希尔排序基于以上两点,从“减少记录个数”
和“序列基本有序”
两个方面队直接插入排序进行改进!
******、算法步骤
希尔排序分组不是简单地“逐段分割”,而是将相隔某个“增量”
的记录分成一组!
(1)、第一趟取增量d1(d1 < n),所有间隔为d1的分在同一组,在各个组中进行直接插入排序!
(2)、第二趟取增量d2(d2<d1),重复上面的分组排序
(3)、依次类推,直到所取的增量di = 1(di<di-1<…<d1),所有的记录都在同一组,进行直接插入排序为止
******、算法实现
希尔排序的算法实现可以直接在直接插入算法上修改,直接插入排序可以看成一趟增量是1的希尔排序,将直接插入排序中增量 i 改写成 i = dk 或者 i = di[k],而不是 i = 1
int * ShellInsertSort(int *p,int dk,int n){//对数组p做一趟增量是dk的直接插入排序
int i,j,temp;
for(i =dk;i < n; i++){ //dk指向第m个子表的第二个元素
if(p[i] < p[i-dk]){ //该子表内进行插入排序
temp = p[i]; //保存当前的较小值
for(j = i - dk;j >= 0 && temp < p[j] ; j -= dk ) //依次将temp(保存的是当前下标 i 中的值)中的值与下标 i之前的值进行比较,p[j] > temp 的元素依次后移一位,直到 j < 0 或者 p[j] < temp 退出循环
p[j+dk] = p[j];
p[j+dk] = temp;
}
}
return p;
}
为了方便实现希尔排序算法,直接将多次增量排序与一趟增量排序写到一个函数中,方便函数调用直接打印结果!
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#define endl '\n'
using namespace std;
int * ShellInsertSort(int *p,int di[],int n){ //对数组p做一趟增量是dk的直接插入排序
for(int k = 0; k < 3; k++){ //一趟增量为di[k]的希尔排序
int i,j,temp;
for(i = di[k];i < n; i++){ //dk指向第m个子表的第二个元素
if(p[i] < p[i-di[k]]){ //该子表内进行插入排序
temp = p[i]; //保存当前的较小值
for(j = i - di[k];j >= 0 && temp < p[j] ; j -= di[k] ) //依次将temp(保存的是当前下标 i 中的值)中的值与下标 i之前的值进行比较,p[j] > temp 的元素依次后移一位,直到 j < 0 或者 p[j] < temp 退出循环
p[j+di[k]] = p[j];
p[j+di[k]] = temp;
}
}
}
return p;
}
main(){
cout<<"\n请随便输入要执行希尔排序的数:";
int elem;
cin>>elem;
int i = 0;
int sort[20];
while(elem != -1){
sort[i] = elem;
i++;
cin>>elem;
}
int t[3] = {5,3,1},*address;
address = ShellInsertSort(sort,t,i);
cout<<"\n----------希尔排序结果打印如下----------!\n";
int n = 0;
cout<<"\n";
while(n < i){
cout<<address[n]<<" ";
n ++;
}
}
******、算法分析
时间复杂度难以确定,与递增序列di有关!
空间复杂度:需要一个辅助变量 temp来记录当前待排序的元素,所以该算法的时间复杂度仍是 O(1)
时间复杂度是o (n 的1.3次方)
******、算法特点
(1)、记录跳跃式地移动,导致算法是不稳定的的!
(2)、只能用于顺序结构,而不能用于链式存储(因为需要指定位置进行排序)
(3)、增量序列有多种取法,但是应该使增量序列中的值di没有公因子,并且最后一个增量值必须是1
(4)、记录比价的总次数和移动次数都要比直接插入排序树要少,n越大,这种效果越明显。所以适合 n 越大并且初始记录无序的情况!
******、算法实例