各位看官们好,接下来鄙人想与大家分享的实现被称为六大排序之一的插入排序。其实关于这六大排序在我们最开始就已经接触过了。我们在最开始学习c语言的时候,我们要学习到其中之一的冒泡排序。虽然现在看起来冒泡排序确实是没有太大的实际效果,但是对于我们入门却还是有很大的帮助的。而我们下来介绍的插入排序这是比排序好很多的一个优质排序方法。那废话少说,们来看看插入排排序是如何实现的,并且在实现插入排序后,再看看比插入排序额还厉害的希尔排序
插入排序思路
对于插入排序的话其实还算是比较好理解的。我想大家应该都玩过扑克牌吧。斗地主大家都知道,我们平常的习惯都是按大小排序。这个其实就是插入排序。大家可以想一下,我们如果以扑克牌为例,我们利用冒泡排序的话,每一个位置就要对比一下,这很麻烦,很耗时间的。但是如果我用插入排序的话,我们以第一个数为例,一次向后对比,如果他比接下来对比的数都大的话,那么我就先把战船一直比较比他更大的数,那么我就把它插入在前面。意思就是相当我们暂时把整个数组看着是有序的。那这个数一直对比,一直到遇到比他大的我在插入。在没遇见之前,我就一直想把它存起来。如果都他是最大的话,那么遇到最后一个循环,那么就结束。
我想我这么说可能是比较难以理解的,大家可以看一下下面这张动图来理解一下。
这样大家可能会理解起来比较简单一些。而且如果大家对数据机构有一点了解的话,就知道插入排序的时间复杂度最坏也才 O(n^2)。那么接下来我们就来尝试一下如何把插入排序写出来。
插入排序实现
那么我们想想如何实现插入排序呢?我们先从第一趟开始,比如说我们就先走第一趟,以下标为零开始的与下标为一对比,如果他比第一个大的话,那么这一趟就结束,我们就只循环一次。下一次循环我们就循环两次,以下交换后的下标再次循环。如果比前面的数小的话,那么就结束循环。
void InsertSort(int* arr, int n)
{
int end = 0;
//待插入的元素
int tem = arr[end + 1];
//单趟排
while (end >= 0)
{
//比插入的数大就向后移
if (tem < arr[end])
{
arr[end + 1] = arr[end];
end--;
}
//比插入的数小,跳出循环
else
{
break;
}
}
arr[end + 1] = tem;
}
这就是插入排序的单次排序。那么我们肯定不是循环一次就能将整个数组变为有序的。那么我就要多循环几次。我们可以看到我们代码最开始是以end开始的。我们把and设为0,那么它就是下标为0与下标为一对比,如果不能将它设为1,那么它就是与下面为1和下面为2对比。那我们就可以再写一个循环,将单次循环放进这个循环里面,End随循环次数增加,但是总体次数要小于我们传入的n小于1。因为我们自己设的要对比的下一个数就是end加一。如果我们这样最开始就是因为小于的话,那么会突出一个随机值。那么这样我们代码就是有问题了。
void InsertSort(int* arr, int n)
{
for (int i = 0; i < n - 1; ++i)
{
//记录有序序列最后一个元素的下标
int end = i;
//待插入的元素
int tem = arr[end + 1];
//单趟排
while (end >= 0)
{
//比插入的数大就向后移
if (tem < arr[end])
{
arr[end + 1] = arr[end];
end--;
}
//比插入的数小,跳出循环
else
{
break;
}
}
//tem放到比插入的数小的数的后面
arr[end + 1] = tem;//为什么写在外面,这是因为防止我们后面有一个最小的数,如果写在里面的话,那么end会减到-1.那么就跳出循环了,无法交换。所以写在外面是防止这样的情况
//代码执行到此位置有两种情况:
//1.待插入元素找到应插入位置(break跳出循环到此)
//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)
}
}
这是我设的一个插入排序的一个示例。当然这些里面的势力还算比较简单的,还不足以体现插入排序的优势。但是大家知道冒泡排序是我们六大排序中速度最慢的。我们学会了插入排序,后面如果对希尔排序,如果不了解的话,插入排序也算是一个较好的排序方法。
补充:我想大家可能会对代码中的有一个有疑问,就是为什么插入的数大于的话后移,后要end减减嘞。这个是因为我们排序就是下标为1的与下标为0的对比然后交换。是从后向前交换,与冒泡排序的从前往后交换。所以我们第一次是只交换一次。并且大家需要注意的是我在上面还的代码中的,为什么将arr[end + 1] = tem;写在外面。
希尔排序思路
对于希尔排序来说啊,这其实超过了我们普通人或一般学霸的认知了。其实虽然我们有想可能会想过对一些很混乱的数组我们先给他排简单排一下,然后再进行排序。但是我们简单排序,怎么给他简单排序了?而且反正都要排序的。那不是浪费更多的时间吗?反正这是我个人是这么想的。他也正是因为我们都能想到可以给它初排序一下。但是我们初排序是如何排的呢?我们却并没有想过。但是希尔他就想过了。他想将一个数组用gap(一个数)来个开。就好比是下面这个样子。
然后我们以两个节点来对比进行交换。而且我们再将移动到下一个下标。并且也以gap隔离来进行节点交换,这样就完成了我们的初排序。大家可以想象这样是不是就形成了一个相对比较有序的数组?
但是大家也知道,我们这个开始想的也就是初排序。我们还需要一个实际排序呀。那么就要用我们上面写的插入排序了。我们插入排序是与下一个进行对比,那我们希尔排序中间隔了gap后再进行对比交换的。那么不知道大家是否要联想到希尔排序与插入排序为基础来实现写的?
好了,我们初入排序写了,那么我们接下来该怎么操作呢?我们每一次单趟的gap是确定的。那么如果我们这样第二堂的gap设为第一堂gap的3分之一。那么我们是不是就会比较细一点了?但是我们想想是不是有时候有些时候被3初了会为零,不会为1。那么我们就没有再具体走一遍,是吧?所以我们在除以下之后再加一,那么我们是不是就能保证最后一次为1了。这样我们是不是就最后将整个排序实现了。
希儿排序实现
那么上面我们居然写了大概思路。我们还是想插入排序一样,先写单趟如何实现。
void ShellSort(int* arr, int size)
{
int gap = 3;
int i = 0;
for (i = 0; i < size - gap; i++) //从0遍历到size-gap-1
{//为什么是小于size-gap。因为当我们size本来就大于数组个数一个,减去gap后的数就是临界点了,再加一个gap的话就超界了。大家可以想想。
int end = i;//是不是就是插入排序啊
int temp = arr[end + gap];
while (end >= 0)
{
if (arr[end] > temp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = temp; //以 end+gap 作为插入位置
}
}
不知道大家看到这个是我觉得很熟悉啊,是不是?像我们上面写的插入排序?就是将插入排序中的一改为了gap。是不是就实现了我们说的gap节点,然后进行对比交换了。 那么我们实现了单次排序之后,我们下一次排序。我们在上面说过gap是会变的。一直到为1就成为最后的插入排序,这个大家了解吧。那么就简单了,我们只需要再写一个循环,在循环的时候这样每一次gap进去之后进行改变。
void xixi(int* arr, int size)
{
int gap = size;
while (gap > 1)
{
gap = gap / 3 + 1;//没进行一次gap后,gap进行改变
int i = 0;
for ( i = 0; i < size - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (arr[end]>tmp)
{
arr[end + gap] = arr[end];
end-=gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
这样大家应该能了解了吧。希尔排序简单一点,也就是说我们先以gap为跨点进行一次大的排序,然后再依次减少,依次减少。这样原本乱序的数组就成为了相对有序的数组。最后我们就直接用插入排序,那样是不是就解决了这个问题了?
所以大家只需要理解,我们先出了gap为1以前的都叫预处理。为1之后就是正式的排列了。 当然希尔排序其实是有一点困难的,大家需要多理解一点多看一下,其实相对还是比较简单的,只需要理解之后大家都能写出来的。
总结
好了,以上就是本篇博客想与大家分享的两个排序方法的。大家可以先着重了解插入排序,因为希尔排序是以插入排序为基础而推进发展的。当然如果大家对希尔还是不太了解的话,插入排序现在对目前来说还是很有用的,先了解他的话也是足够了的。好了,本篇博客肯定还有很多不足的地方,希望大家能在评论区留言,方便鄙人来更改。