1,插入排序
基本思想:
这种排序就是充分利用已排好序的元素进行比较和插入
性能分析:
时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
这样遍历整个数组就能完成排序
下面是代码
//插入排序
void InsertSort(int * array,int sz)
{
int end;
int key;
int i=0;
assert(array);
for(i=1;i<sz;i++)//从第二个元素开始遍历
{
end=i-1;//标记待插入节点前一个节点,也就是有序部分的最后一个元素
key=array[i];//标记待插入的元素,因为下面的搬移元素会使该节点消失
while(end>=0&&key<array[end])//条件一:为了不数组越界
//条件二:寻找插入位置
{
array[end+1]=array[end];//搬移元素
end--;//向前走
}
array[end+1]=key;//将待插入的元素插入找到的位置
}
}
2,二分查找版的插入排序
基本思想:
每次的插入都要和有序元素大小进行比较,时间复杂度为O(n),恰好可以用二分查找完成这一步,且时间复杂度为O(log以2为底的N),由此可以写出二分查找版的插入排序
性能分析:
时间复杂度:
最坏情况:每次都在有序序列的起始位置插入,则整个有序序列的元素需要后移,时间复杂度为O(n^2)
最好情况:待排序数组本身就是正序的,每个元素所在位置即为它的插入位置,此时时间复杂度仅为比较时的时间复杂度,为O(log2n)
平均情况:O(n^2)
空间复杂度:O(1)
稳定性:稳定
要注意,插入排序是可以实现排序稳定性的,当两个元素相同时,把带插入元素放在有序元素的后面
//插入排序(二分查找)
void InsertBinSort(int * array,int sz)
{
int i=0;
int key=0;
int mid =0;
int left=0;
int right=0;
assert(array);
for(i=1;i<sz;i++)//从第二个元素开始,将后面的每个元素都做为带插入元素
{
key=array[i];//标记待插入的元素,因为下面的搬移元素会使该节点消失
left=0;//左边界
right=i-1;//右边界
while(left<=right)//这个循环是在寻找插入位置,最后找到的位置就是left
{
mid=left+(right-left)/2;//中间节点
if(key>=array[mid]) //将大于和等于两种情况合并处理,也可满足排序的稳定性
{
left=mid+1;
}
if(key<array[mid])
{
right=mid-1;
}
}
//搬移元素
right=i-1;//要搬移部分的最后一个节点,left是第一个节点
while(left<=right)
{
array[right+1]=array[right];//搬移
right--;
}
array[left]=key;//将待插入的元素插入找到的位置
}
}
3,希尔排序
基本思想:将待排序的序列分为若干组,在每组内进行直接 插入排序,以使整个序列基本有序,然后在对整个序列进行直接插入排序,实质上是一种分组插入。
希尔排序的时间复杂度:O(n*lgn) (lg是log以2为底)
下面是代码
//希尔排序
void ShellSort(int * array,int sz)
{
int i=0;
int gap=3;//数据的间隔
int key=0;
int end=0;
assert(array);
while(gap>0)//每次进行排序的数据间隔都不一样,gap越小,数据越近似有序
{
for(i=gap;i<sz;i++)
{
key=array[i];
end=i-gap;//算出间隔前一个数据下标
while(end>=0&&key<array[end])//条件一:为了不数组越界
//条件二:寻找插入位置
{
array[end+gap]=array[end];
end-=gap;
}
array[end+gap]=key;//将待插入的元素插入找到的位置
}
gap--;//数据间隔递减
}
}
4,选择排序
基本思想:
每一趟遍历都将被排序位置的元素与后面所有元素进行比较,选出来最小的,放入被排序的位置。
第一趟排序比较长度是N,因为从N个元素中找出最小的,第二次就是N-1了,….依次递减比较的长度。
我们选择排序其实就是选择无序中的最小,然后与有序序列的后一个元素交换!
性能分析:
时间复杂度:O(N^2);每个元素都要比较,共有N个元素,N*N
空间 复杂度:O(1)
稳定性:这显然是一个不稳定排序
//选择排序
void SelectSort(int * array,int sz)
{
int tmp=0;
int i=0;
int j=0;
assert(array);
for(;i<sz;i++)
{
for(j=i+1;j<sz;j++)
{
if(array[i]>array[j])
{
tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
}
}
}
5,选择排序(优化版)
基本思想:
通过每趟遍历标记范围内 (范围会不断缩小)最大值和最小值,一趟结束后将 最大值与右边界交换,
最小值与左边界交换;左右边界缩小一个。
性能分析:
时间复杂度:O(N/2*N/2),还是O(N^2),比较的次数少了一半
空间 复杂度:O(1)
稳定性:这显然是一个不稳定排序
注意:使用这种方法是会有一个BUG,分3种情况
情况1:最大值在最小位置处,最小值在普通位置
情况2:最小值在最大位置处,最大值在普通位置
由于我们先交换left和min,那么最大位置就空了,再将最大值赋给最大位置,所以不会产生交换错误
情况3:最大值在最小位置处,最小值在最大位置处
两次重复的交换,会导致本质没有交换,和情况1一样,只不过重新标记后的max在最大位置处,所以不用交换
下面是代码
//选择排序(优化版)
void SelectWellSort(int * array,int sz)
{
int i=0;
int left=0;
int right=sz-1;
int min=0;
int max=0;
int tmp=0;
assert(array);
while(left<right)
{
min=left;
max=right;
for(i=left;i<=right;i++)
{
if(array[i]>array[max])
{
max=i;
}
if(array[i]<array[min])
{
min=i;
}
}
if(min!=left)//相同位置就不用交换了
{
tmp=array[min];
array[min]=array[left];
array[left]=tmp;
}
if(max==left) //这是处理最大值在最小位置,或者最小值在最大位置上的情况
//因为将max最大值搬移到了min最小位,所以重新标记max
{
max=min;
}
if(max!=right)//相同位置就不用交换了
{
tmp=array[max];
array[max]=array[right];
array[right]=tmp;
}
left++;
right--;
}
}
5,快速排序
基本的思想:
对于“”6,3,8,2,9,4,1,5“”这样一个数组,以最后一个元素为基准值(就是比较值),将这个数组调整为“”1,3,4,2,5,9,6,8“”,
就是将比基准值小的数5小的值放在5的左边,将比基准值大的元素放在5的右边。那么这个数组中元素5就排好了,将这个数组划分为“”1,3,4,2“”和“”9,6,8“”。对这两个小数组也进行上面的操作,排好基准值,再进行划分,直到小数组中只有一个元素时退出。
性能分析:
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:不稳定
//快速排序(非递归)
void QuickSort (int *array,int left,int right)//开区间
{
int mid;
assert(array);
if((right-left)>1)//说明有不止一个元素
{
mid =pration3(array,left,right);//得到中间位置,并完成以基准值为中心的简单排序
QuickSort(array,left,mid);//划分后的右小数组
QuickSort(array,mid+1,right);//划分后的左小数组
}
}
进行以基准值为中心的简单排序有3中方式
//找中点函数1(交换法)
int pration1(int *array,int left,int right)
{
int key=array[right-1];
int begin=left;
int end=right-1;
assert(array);
while(begin<end)
{
while((array[begin]<=key)&&(begin<end))//从左向右找比基准值大的,找到出循环
{
begin++;
}
while((array[end]>=key)&&(begin<end))//从右向左找比基准小的,找到出循环
{
end--;
}
if(end>begin)
{
Swap(&array[begin],&array[end]);//只要没有相遇,就交换
}
}
if(begin!=right-1)
{
Swap(&array[begin],&array[right-1]);//最后还要将基准值与交点位置元素进行交换
}
return begin;//返回交点的下标,为作为划分子数组的依据
}
//找中点函数2(挖坑法)
int pration2(int *array,int left,int right)
{
int begin=left;
int end=right-1;
int key=array[right-1];
assert(array);
while(end>begin)
{
while((begin<end)&&(array[begin]<=key))
{
begin++;
}
if(end>begin)
{
array[end]=array[begin];
end--;
}
while((begin<end)&&(array[end])>=key)
{
end--;
}
if(end>begin)
{
array[begin]=array[end];
begin++;
}
}
array[begin]=key;
return begin;
}
//找中点函数3(复杂法)
int pration3(int *array,int left,int right)
{
int key=array[right-1];
int cur=left;//是数组下标,
int per=left-1;//是数组下标
assert(array);
//per停下来时,++per对应的值必定是大于基准值的
while(cur<right) //当cur找到一个比基准值小的值,那么++per就需要与cur进行交换
{ //但是当++per==cur时,不交换,因为这时本身就是有序的
if((array[cur]<key)&&(++per!=cur))
{
Swap(&array[cur],&array[per]);
}
cur++;
}
Swap(&array[++per],&array[right-1]);
return per;
}
6.快速排序(非递归)
要用非递归方式解决快速排序,就要用栈来保存数组的边界。
基本思想:
找基准值,将整个数组划分为两部分,左边全部小于或等于基准值,右边全部大于或等于基准值,再循环式的用这种方法分别划分左右两部分,直到区间 内只有一个元素。
循环式就是将分割的左右边界按先右后左的顺序入栈,然后出栈的时候先取到左边界,就可以先出左边界。
注意:所有区间都是左开右闭
//快速排序(非递归)
void QuickSortNor (int *array, int size)
{
int left,right,div;
StackD str;
StackDInit(&str);
StackDPush(&str,size);//右边界入栈(右开)
StackDPush(&str,0); //左边界入栈(左闭)
while(!StackDEmpty(&str))//栈空了 ,就排完序了
{
left=StackDTop(&str);
StackDPop(&str);
right=StackDTop(&str);
StackDPop(&str);
if(left<right)
{
//找到中间值
div=prationThreePoint1(array,left,right);//(左闭右开)
//先处理左边部分,所以先将右区间入栈
StackDPush(&str,right);
StackDPush(&str,div+1);
StackDPush(&str,div);
StackDPush(&str,left);
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/ed01cac73562297bd356f6a0e67e648d.png)