其他排序
插入排序
核心思想
:设置一个变量来记录当前插入位置的值,如果前一个位置大于该值则将前一个位置的值移入该位置,然后继续与前面元素的值比较,直至该值大于前一个值或者移动到最开头位置,插入该值。
实现方式
例:对如上数组
arr
进行由小到大排序(注:一个元素肯定有序,插入元素之前一定是一个有序序列
)
注:这里橙色箭头仅表示此时要插入的位置,蓝色箭头表示与插入的值比较的位置
首先,我们应该定义一个变量来标记一下即将要插入的值:nValue = arr[1]
;
然后,数组从下标为1的位置开始与前面的元素进行比较,如果此时元素小于前一个元素,则此元素后移,然后继续与nValue
继续与前一个值比较,直至小于或移动到最开头插入。过程如下:
由于arr[0] > nValue
,因此将arr[0]
向后移,即arr[1] = arr[0]
,然后nValue
与arr[0]
的前一个值比较,由于arr[0]
就是第一个元素,因此将nValue
直接插入arr[0]
位置处,然后新插入下一个元素(每插入一个元素,nValue
都要标记新插入的元素)如下
如上,nValue = arr[i](此时i = 2,即nValue = 9)
,将nValue
与前一个值比较,由于nValue > 8
,说明已经有序,此时直接进入下一次循环,如下
如上,nValue = 15
,并且nValue > 9
,已经有序,进入下一次循环,由于29页有序,这里直接跳到1处,如下
如上,
nValue = 1
,由于nValue < 29
,因此29
需要后移,1
与29
的前一个元素15
比较,如下
如上,nValue < 15
,15
向后移动,1
与15
的前一个元素·9
比较,如下,
如上,
nValue < 9
,9
向后移动,1
与9
的前一个元素·8
比较,如下,
如上,
nValue < 8
,8
向后移动,1
与8
的前一个元素·3
比较,如下,
如上,
nValue < 3
,3
向后移动,1
与3
的前一个元素比较,由于3
已经是第一个元素,将nValue
插入数组的第一个位置,并且开始插入新的数据,如下,
如上,插入新的值
nValue = 5
,由于5 < 29
,因此,移动29
,nValue
与29
的前一个值15
比较,如下
如上,由于nValue < 15
,因此,移动15
,nValue
与15
的前一个值9
比较,如下
如上,由于nValue < 9
,因此,移动9
,nValue
与9
的前一个值8
比较,如下
如上,由于
nValue < 8
,因此,移动8
,nValue
与8
的前一个值3
比较,如下
如上,由于
nValue > 3
,因此,插入nValue
,进行下一次循环,如下
…依次按照上面的循环过程,最终可实现插入排序
代码如下
#include <stdio.h>
void InsertSort(int arr[],int nLen)
{
if(arr == NULL || nLen <= 0) return;
int nMark;
int j;
for(int i = 0;i < nLen-1;i++)
{
//标记无序的第一个元素
nMark = arr[i+1];
j = i + 1;
//如果该元素小于前一个,前一个后移
while(nMark <= arr[j] && j >= 0)
{
arr[j] = arr[j-1];
j--;
}
arr[j+1] = nMark;
}
}
void Print(int arr[],int nLen)
{
if(arr == NULL || nLen <= 0) return;
for(int i = 0;i < nLen;i++)
{
printf("%d\t",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {8,5,3,91,123,12345,23,5672,12367,12,56,78,694,12345,567,10,11};
InsertSort(arr,sizeof(arr)/sizeof(arr[0]));
Print(arr,sizeof(arr)/sizeof(arr[0]));
return 0;
}
最好时间复杂度
:来的数组即为有序数组,O(n)
最坏时间复杂度
:倒序数组,O(n²)
平均时间复杂度
:O(n²)
空间复杂度
:O(1)
稳定性
:稳定
适用场合
:数组较为有序时
希尔排序
核心思想
:将数组的元素分组处理,每次折半len分组,比较i 与i+len这两个值得大小,若前者大于后者则交换值,之后继续折半,几次折半后数组变得相对有序,此时再利用插入排序。
通俗点讲就是先将数据分组,分完组之后再进行组内排序
排序过程
:
1、分组
初始时,数组长度nLen = 9;每次分组都在前一次的基础上折半,则第一次分组时
nGap = nLen/2,(nGap 表示分组的间隔,当nGap > 0时退出循环)
分组情况如下:
如上图所示,将原数组分成了四组,第一组元素:8、29、6;第二组元素:3、1;第三组元素:9、5;第四组元素:15、20;
每组之间相互排序,组内排序之后结果如下:
如上图所示,第一次分组排序后,基本上小数在前,大数在后,由于nGap = 4 > 0
,因此继续折半分组,有nGap /= 2
,此时nGap = 2
,分组情况如下:
如上图所示,将原数组分成了二组,第一组元素:6、5、8、9、29;第二组元素:1、15、3、20;每组之间相互排序,组内排序之后结果如下:
由于nGap = 2 > 0
,因此继续折半分组,有nGap /= 2
,此时nGap = 1
,分组情况如下:
如上图所示,当nGap = 1
时,数组只分成了一组,此时排序结果如下:
如上图所示,当nGap = 1
时,就已经排好序,此时再有nGap /= 2
得nGap = 0
,退出循环。
可能我们会有疑问,直接利用插入排序就可以将数组排好序,那么为什么还要进行分组呢?
其实这就是根据插入排序的特点来的,由于插入排序对于小容量,相对有序的数组排序效率更高,但是对数组排序时数组的长度是不会变的,因此利用插入排序时我们总希望数组会相对有序,长度较小,但是实际排序时来的排序数组基本都是无序程度高,基于此,利用希尔排序分组的思想可以将数组容量减小后再组内排序,把数组变得相对有序,从而提高排序效率。
代码如下
#include <stdio.h>
void Print(int arr[],int nLen)
{
if(arr == NULL || nLen <= 0) return;
for(int i = 0;i < nLen;i++)
{
printf("%d\t",arr[i]);
}
printf("\n");
}
void InsertSort(int arr[],int nGroup,int nGap,int nLen)
{
if(arr == NULL || nLen <= 0 || nGap <= 0 || nGroup < 0) return;
int nValue ;//标记要插入的值
int j;//向前遍历的变量
for(int i = nGroup + nGap;i < nLen;i += nGap)
{
nValue = arr[i];
//找出要插入的位置
j = i-nGap;
while(nValue < arr[j] && j >= 0)
{
//向后移动
arr[j+nGap] = arr[j];
j -= nGap;
}
//将值插入
arr[j+nGap] = nValue;
}
}
void ShellSort(int arr[],int nLen)
{
if(arr == NULL || nLen <= 0) return;
//分组
int nGap = nLen/2;
while(nGap > 0)
{
for(int i = 0;i < nGap;i++)
{
//插入排序
InsertSort(arr,i,nGap,nLen);
nGap /= 2;
}
}
}
int main()
{
int arr[] = {45,675,2,89,13,68,4,67,4};
ShellSort(arr,sizeof(arr)/sizeof(*arr));
Print(arr,sizeof(arr)/sizeof(*arr));
return 0;
}
最好时间复杂度
:来的数组即为有序数组,O(n)
最坏时间复杂度
:倒序数组,O(n²)
平均时间复杂度
:O( n1.3)
空间复杂度
:O(log2n)
稳定性
:不稳定
适用场合
:数组量大且较无序