插入排序
概念:
在已经排好序的序列中插入一个数值使其成为新的有序序列
(教材定义:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成)
⚠️教材的定义体现了在原序列上进行的一种排序;
⚠️按照顺序表的方式存储的话,插入排序实际上就采用就地排序【空间复杂度O(1)】;
⚠️每次都要从在已排好的子序列中找到的位置开始整体将数组元素向后移动。
1、直接插入排序
#include <iostream>
using namespace std;
void DirectSort(int *Array,int length);
int main()
{
int Array[11]={0,2,4,1,6,3,7,8,9,5,1};
int length=10;
DirectSort(Array, length);
cout<<"Result:";
for(int i=1;i<=10;i++)
cout<<Array[i]<<" ";
cout<<endl;
return 0;
}
//直接插入排序(递增)
void DirectSort(int *Array,int length)
{
if(Array==nullptr||length==0)
return;
/* for(int i=1;i<length;i++)//待排序元素
{
//比较垃圾的逻辑,而且还有错的地方,不过已经改过来了
int temp = Array[i];//一开始没设置这个变量,直接用Array[i]给Array[j]赋值了,这样一定是错的
for(int j=0;j<i;j++)
{
if(Array[i]<Array[j])
{
for(int k=i;k>j;k--)
Array[k]=Array[k-1];
Array[j]=temp;
}
}
}*/
for(int i=2;i<=length;i++)
{//提高效率,到当前要插入的已经是合理顺序的就只需要比较一次,虽然也比较了,但是肯定是少了啊
if(Array[i]<Array[i-1])
{ Array[0]=Array[i];
//设置一个哨兵结点,Array[0]不存放元素,可以将每次待插入元素存入其中
int j;//直接变向比较边移动
for( j=i-1;Array[0]<Array[j];--j)
Array[j+1]=Array[j];//当退出循环的时候直接就找到了要放置的位置
Array[j+1]=Array[0];
}
}
}
Tips:
- 哨兵结点的使用
- 条件的判断,可以提高效率
- 将移动和比较同时进行,而不是先比较找到位置再进行插入移动
直接插入排序的性能分析
性能 | 分析 |
---|---|
空间效率 | 常数个辅助单元,空间复杂度O(1) |
时间效率 | 最好情况:表中元素已经有序,每次插入一个只需要比较一次,时间复杂度O(n); 最坏情况:顺序完全相反,总的比较次数和移动次数都达到最大O(n2 );平均复杂度O(n2) |
稳定性 | 稳定 |
适用性 | 适用顺序存储和链式存储;链式存储就可以用第一个那个思路,从前往后查找指定元素的位置⚠️大多数排序算法都仅适用于顺序存储的线性表 |
2、折半插入排序
折半插入实际上就是把直接插入中的比较和移动拆成了两步:
1、第一步:
利用了折半查找,要明确折半的范围应该是当前比较元素之前的元素是已经排列好的序列,以便据此设置high和low的值
2、第二步:
用high和low表示位置的时候,high+1/low-1,由于折半插入最后一步的特点
#include <iostream>
using namespace std;
void MiddleInsert(int *Array,int length);
int main ()
{
int Array[11]={0,2,4,1,6,3,7,8,9,5,1};
int length=10;
MiddleInsert(Array, length);
cout<<"Result:";
for(int i=1;i<=10;i++)
cout<<Array[i]<<" ";
cout<<endl;
return 0;
}
void MiddleInsert(int *Array,int length)
{
int i,j,low,high,mid;
for(i=2;i<=length;i++)//注意这里应该有等号,length存储的是实际有效的数组长度,否则最后一个元素就没进入比较序列了
{
Array[0] = Array[i];
low=1;high=i-1;//注意这里的high不是i,是在i之前的已排好的序列中寻找,所以是i-1
//折半查找
while(low<=high)
{
mid=(low+high)/2;
if(Array[0]<Array[mid]) high=mid-1;
else low=mid+1;
}
//元素移动
//从后面开始的,因为都是在待比较元素之前的序列
for(j=i-1;j>high;j--)
{
Array[j+1]=Array[j];
}
Array[high+1]=Array[0];
}
}
折半插入排序性能分析
性能 | 分析 |
---|---|
空间 | O(1) |
时间 | 减少了比较次数,与待排序表的初始状态无关,仅仅取决于表中元素个数n,O(nlog2n);元素移动次数没有改变,它依赖于元素的初始状态;总的时间复杂度O(n2) |
稳定性 | 稳定 |
适用性 | 顺序表,链表no |
希尔排序
1959年,D.L.Shell提出shell排序,又称缩小增量排序。
基本思想:先将待排序表按照希尔增量分割成若干个子表,然后对每个子表进行直接插入排序;按照一定的规律改变希尔增量,然后再次对子表进行直接插入排序,直到希尔增量为1。
⚠️目前尚未求得一个好的增量序列,希尔提出的方法是/2取地板
#include <iostream>
using namespace std;
void ShellSort(int * Array,int length);
int main ()
{
int Array[11]={0,2,4,1,6,3,7,8,9,5,1};
int length=10;
ShellSort(Array,length);
cout<<"Result:";
for(int i=1;i<=10;i++)
cout<<Array[i]<<" ";
cout<<endl;
return 0;
}
//两种方法:一种是传入shell,一种是直接在shellsort中设置好shell
void ShellSort(int *Array,int length)
{
//第一个循环控制shell变量
for(int shell=length/2;shell>=1;shell=shell/2)
{
//第二个循环把第一组都分出来了,然后再往后就是每次都在不同的组内进行第二个第三个。。。元素的插入(虽然都是一个循环下来的)
for(int i=shell+1;i<=length;i++)
{
//和直接插入一样了就是把1换成了shell增量
if(Array[i-shell]>Array[i])
{
Array[0]=Array[i];
int j;
for( j=i-shell;j>0&&Array[j]>Array[0];j=j-shell)
Array[j+shell]=Array[j];
Array[j+shell]=Array[0];
}
}
}
}
shell sort性能分析
性能 | 分析 |
---|---|
空间 | O(1) |
时间 | 分析较困难,涉及数学上尚未解决的难题。当n在某个范围内的时候,约为O(n1.3);最坏情况O(n2) |
稳定性 | 不稳定 |
适用性 | 仅适用于顺序表 |