接下来,我来给大家讲解第三种排序算法,即插入排序。
基本介绍
首先,我们来看下插入排序的基本介绍。
插入排序,其属内部排序法,是对于欲排序的元素以插入的方式来找寻该元素的适当位置,以便最终达到排序目的的一种排序算法。
基本思想
简单认识插入排序之后,接下来我就要来给大家详细说说它的排序思想了。
插入排序的基本思想是这样子的,即把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含n-1个元素,然后在排序的过程中每次从无序表中取出第一个元素,取出过后,把它的排序码依次与有序表元素的排序码进行比较,比较过后,再将其插入到有序表中的适当位置,使之成为一个新的有序表。
可能我这样讲,有些同学会觉得比较抽象,不过没关系啊,下面我会给大家看一个插入排序的思路图解,相信大家看完过后自然就会明白我上面讲的插入排序的基本思想了。
说啊,目前有一个数组,它的初始状态是下面这样子的。
大家可以看到,初始状态下我们认为第一个元素17就是一个有序表,因为一个元素不管是按从小到大排,还是按从大到小排,它都是有序的。至于后面的五个元素嘛,那我们就要把它看作是一个无序表了。
接下来,自然就是要来针对该数组进行插入排序了。
首先,我们来看一下第一次插入,根据插入排序的基本思想可知,第一次插入是要将无序表中的第一个元素3插入到有序表中的适当位置去,即有序表元素17的前面。
有同学可能会有疑惑,无序表中的第一个元素3是如何知道要插入到有序表元素17的前面的呢?很简单,3会先跟17比较,很明显,3是要比17小的,因此我们会让17往后面挪一位,挪毕,继续让3与有序表前面的元素进行比较,若发现有序表前面已没有元素了,则便把3插入到17的前面。不难知道,此时,会形成新的有序表(即[3, 17]
)与无序表(即[25, 14, 20, 9]
)。
然后,我们再来看一下第二次插入,第二次插入不用我说,想必大家都已经知道是要将无序表中的第一个元素25插入到有序表中的适当位置去了吧!
同样,还是刚刚那个逻辑,第二次插入我们会先让25跟有序表中最后一个元素比较,很明显25是要比17大的,所以我们要将25给插入到17的后面。
注意,这儿我是按照从小到大的顺序来对待排序数组进行排序的,当然,如果是按照从大到小的顺序来排序的话,那么这一次插入25就要被插入到有序表最前面的那个位置上了。
接着,我们再来看一下第三次插入,第三次插入不用我说,想必大家都已经知道是要将无序表中的第一个元素14插入到有序表中的适当位置去了吧!
紧接着,我们再来看一下第四次插入,同上,第四次插入得将无序表中的第一个元素20插入到有序表中的适当位置去。
最后,我们来看一下第五次插入,很明显,第五次插入是要将无序表中的第一个元素9插入到有序表中的适当位置去。
现在大家应该搞清楚插入排序的基本思想了吧!是不是还是挺简单的啊!至少我认为与冒泡排序的排序思想比起来,插入排序还是更好理解一点的。
不知道大家有没有发现这一点,就是我们待排序数组一共有6个元素,插入排序完其实我们一共得插入5次。为什么是5次呢?因为第一个元素我们认为它就是一个有序表,因此无论怎么排,它都是有序的,而这也就是说我们不需要为它找寻适当位置并进行插入。所以,实际上,我们是要为后面的5个数找寻适当位置并进行插入。
至此,关于插入排序的基本思想,那我就给大家讲解到这里了。至于接下来要干嘛,相信大家也都知道了,无非就是来编写具体代码用以实现插入排序。
代码实现
先来看这样一个案例,说是班级里面有一群小朋友,他们的考试成绩分别是101、34、119和1,现请你对他们的考试成绩按照从小到大的顺序进行排序。
以上案例,相信大家也不难理解其需求,无非就是对一个数组进行插入排序罢了。
废话不多说,下面我们直接开写代码。注意,这里我同样会采用逐步推导的方式来为大家进行讲解,之所以这样做,是因为我觉得这样娓娓道来,大家最后理解起来一定会更加深刻,而且,这也是我的一贯风格。
只是,在正式编写代码之前,我还有一个问题想问一下大家,就是待排序数组目前是总共有4个元素的,我想问大家,插入排序完之后,你觉得一共会经历几次插入?是不是3次啊!对吧!至于原因嘛,我在上面就已经说过了,所以这里我就不再过多赘述了。
言归正传,我们先来看第一次插入,很明显,这一次插入就是要将34给插入到有序表中的适当位置去,而此时有序表中又只有一个元素,即101,所以实际上34是要插入到有序表元素101的前面去的,也就是说,此时我们会得到[34, 101, 119, 1]
这样一个数组。
package com.meimeixia.sort;
import java.lang.reflect.Array;
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1};
insertSort(arr);
}
/**
*
* @param arr:你只要给我传进来一个数组,那我就帮你进行插入排序
*/
public static void insertSort(int[] arr) {
/**
* 第一次插入。
*/
// 首先,我们得定义一个待插入的数,既然是要待插入的数,那有同学知道这个待插入的数到底是哪一个嘛?想都不用想,这个待插入的数就是arr[1],即34,对不!
int insertValue = arr[1];
// 然后,再定义一个待插入数的索引。我不知道大家晓不晓得这儿这个待插入数的索引应该是要等于0的,为什么呢?因为我们上面说过这儿这个待插入数,也即34,它
// 是要跟有序表元素来进行比较的,准确地讲,它是要跟它前面的数(即101)进行比较,而现在待插入数的下标又是1,因此自然而然我们就要在其基础上减1来得到待
// 插入数的索引了,也就是arr[1]前面那个数的下标。
int insertIndex = 1 - 1;
// 接着,为待插入数找到一个适当的插入位置。
// 注意,这儿while循环有两个约束条件,其中第一个约束条件是为了保证待插入数(即34)在跟有序表元素进行比较,进而寻找适当插入位置时,不至于越界;而第二个
// 约束条件则是为了说明待插入数还没有找到适当的插入位置,当然前提是该条件得成立。为什么会这么说呢?你想啊,待插入数在跟有序表元素进行比较时,我们是
// 不是得让它跟有序表中的元素依次进行比较啊,现在待插入数(即34)小于有序表元素(即101),因此我们还得接着让它跟有序表前面的元素进行比较,直至为其找
// 到那个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
// 如果待插入数还没有找到适当的插入位置,那么我们就得让arr[insertIndex](此时它就等于101)后移了,即把位置给腾出来。
arr[insertIndex + 1] = arr[insertIndex]; // 程序如果一旦执行到这里,相信大家应该不难理解此时待排序数组就由{101, 34, 119, 1}变成{101, 101, 119, 1}了吧!
// 当然,有同学可能会说,那待插入数(即34)不就丢了嘛?丢了嘛?没丢,因为我们一开始就将其保存到了insertValue这个变量中,是不!
// 相信大家应该都知道这个地方为什么要让insertIndex减减了吧!因为我们还得接着让待插入数跟有序表前面的元素(即101前面的数)进行比较。
// 当然,有同学可能会说,那如果越界了,又该怎么办呢?很简单,直接退出while循环即可,要知道我们while循环可是有条件约束的。而当退出
// while循环时,我们也就找到了待插入数它所要插入的那个位置,即有序表第一个元素所在的位置,换句话来说,就是下标为insertIndex+1的这
// 个位置。
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。为什么我会说是这个位置呢?
// 道理也很简单,我举个例子,大家看完,就什么都明白了。假如此刻我们待插入的数不是33,而是334,那么很明显程序往下执行,它是不会进入到while
// 循环里面去的 ,因为334并不小于101,聪明的同学看到这里,相信立马就应该知道待插入数它所要插入的那个位置了吧,是不是就直接插在有序表元素
// 101的后面就行了啊,也就是下标为insertIndex+1的这个位置。
//
// 我不知道我举的这个例子,大家都看明白了没有,应该是都看明白了啊,毕竟我讲的都这么详细了,饭都喂到你的嘴边了。但你要实在是理解不了,那也没
// 关系,因为待会我会带着大家一起来Debug一下咱们的代码,等代码Debug完,相信你也就都明白了。
arr[insertIndex + 1] = insertValue;
System.out.println("第1次插入过后,待排序数组的样子:");
System.out.println(Arrays.toString(arr));
}
}
执行以上程序,不难发现第一次插入过后,34确实是被插到了有序表的最前面。
至此,第一次插入的实现代码,咱们也就写完了,从上能看到,咱们写的代码并不算多,反而是注释写了很多很多,之所以我这么用心去写这么多注释,主要是因为这样做可以便于大家理解。当然,我在注释中也说过了,就是你要实在是理解不了,那也没关系,因为接下来我还会带着大家一起来Debug一下咱们的代码,等代码Debug完,相信大家也就都彻底理解了。
下面,我就来给大家打个断点,Debug一下咱们写的代码吧!
首先,在如下这一行代码处打上一个断点。
断点打好之后,接下来我们便要以Debug模式来执行我们写好的程序了。
从上图中大家可以看到,Variables区中的arr
变量其值是{101, 34, 119, 1}
,而这正是我们传递进来的待排序数组,对吧,换句话说,此时此刻该数组还没发生变化。
然后,让程序往下走两步,如下图所示,可以看到此时此刻insertValue
变量的值是34,insertIndex
变量的值是0。
接着,我们就要让待插入数依次与有序表元素进行比较了。由于此时待插入数确实小于有序表元素,难不成34还大于101啊,因此程序往下走,它是会进入到while循环中去的,如下图所示。
上面我们说过,如果待插入数小于有序表元素,那么就说明它还没有找到适当的插入位置,而要是没有找到适当的插入位置的话,那我们就得让有序表元素(此时它是101)后移了,后移之后,待排序数组就变成下面这个样子了。
当然,有同学可能会说,那待插入数(即34)不就丢了嘛?丢了嘛?没丢,因为我们一开始就将其保存到了insertValue
这个变量中,是不!
紧接着,继续让待插入数跟有序表前面的元素(即101前面的数)进行比较,这样,insertIndex
变量就得--
了,--
过后,insertIndex
变量的值就变成-1了,很明显,它并不满足while循环的约束条件,因此程序会跳过while循环直接来到下面这行代码处。
最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,哪个位置呢?就是下标为insertIndex + 1
的这个位置,也就是说待插入数得插到有序表的最前面,即待排序数组下标为0处。待插入完毕之后,待排序数组就会变成下面这个样子了。
整个流程走下来之后,可以看到,经过第一次插入,34确实是被插到了有序表的最前面。
啥都不说了,下面我们接着来写后面几次插入的实现代码。相信不用我说,大家应该都知道后面几次插入的实现代码该怎么写了吧,毕竟第一次插入的实现代码我都给大家写完了,照葫芦画瓢,难道你还不会吗?so easy啊!
废话不多说,请大家看第二次插入的实现代码。
package com.meimeixia.sort;
import java.lang.reflect.Array;
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1};
insertSort(arr);
}
/**
*
* @param arr:你只要给我传进来一个数组,那我就帮你进行插入排序
*/
public static void insertSort(int[] arr) {
/**
* 第一次插入。
*/
// 首先,我们得定义一个待插入的数,既然是要待插入的数,那有同学知道这个待插入的数到底是哪一个嘛?想都不用想,这个待插入的数就是arr[1],即34,对不!
int insertValue = arr[1];
// 然后,再定义一个待插入数的索引。我不知道大家晓不晓得这儿这个待插入数的索引应该是要等于0的,为什么呢?因为我们上面说过这儿这个待插入数,也即34,它
// 是要跟有序表元素来进行比较的,准确地讲,它是要跟它前面的数(即101)进行比较,而现在待插入数的下标又是1,因此自然而然我们就要在其基础上减1来得到待
// 插入数的索引了,也就是arr[1]前面那个数的下标。
int insertIndex = 1 - 1;
// 接着,为待插入数找到一个适当的插入位置。
// 注意,这儿while循环有两个约束条件,其中第一个约束条件是为了保证待插入数(即34)在跟有序表元素进行比较,进而寻找适当插入位置时,不至于越界;而第二个
// 约束条件则是为了说明待插入数还没有找到适当的插入位置,当然前提是该条件得成立。为什么会这么说呢?你想啊,待插入数在跟有序表元素进行比较时,我们是
// 不是得让它跟有序表中的元素依次进行比较啊,现在待插入数(即34)小于有序表元素(即101),因此我们还得接着让它跟有序表前面的元素进行比较,直至为其找
// 到那个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
// 如果待插入数还没有找到适当的插入位置,那么我们就得让arr[insertIndex](此时它就等于101)后移了,即把位置给腾出来。
arr[insertIndex + 1] = arr[insertIndex]; // 程序如果一旦执行到这里,相信大家应该不难理解此时待排序数组就由{101, 34, 119, 1}变成{101, 101, 119, 1}了吧!
// 当然,有同学可能会说,那待插入数(即34)不就丢了嘛?丢了嘛?没丢,因为我们一开始就将其保存到了insertValue这个变量中,是不!
// 相信大家应该都知道这个地方为什么要让insertIndex减减了吧!因为我们还得接着让待插入数跟有序表前面的元素(即101前面的数)进行比较。
// 当然,有同学可能会说,那如果越界了,又该怎么办呢?很简单,直接退出while循环即可,要知道我们while循环可是有条件约束的。而当退出
// while循环时,我们也就找到了待插入数它所要插入的那个位置,即有序表第一个元素所在的位置,换句话来说,就是下标为insertIndex+1的这
// 个位置。
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。为什么我会说是这个位置呢?
// 道理也很简单,我举个例子,大家看完,就什么都明白了。假如此刻我们待插入的数不是33,而是334,那么很明显程序往下执行,它是不会进入到while
// 循环里面去的 ,因为334并不小于101,聪明的同学看到这里,相信立马就应该知道待插入数它所要插入的那个位置了吧,是不是就直接插在有序表元素
// 101的后面就行了啊,也就是下标为insertIndex+1的这个位置。
//
// 我不知道我举的这个例子,大家都看明白了没有,应该是都看明白了啊,毕竟我讲的都这么详细了,饭都喂到你的嘴边了。但你要实在是理解不了,那也没
// 关系,因为待会我会带着大家一起来Debug一下咱们的代码,等代码Debug完,相信你也就都明白了。
arr[insertIndex + 1] = insertValue;
System.out.println("第1次插入过后,待排序数组的样子:");
System.out.println(Arrays.toString(arr));
/**
* 第二次插入。
*/
// 首先,我们还是得把这一次待插入的数确定下来,不用我说,相信大家应该都知道这一次插入是要为arr[2]找寻适当的插入位置吧!
insertValue = arr[2];
// 然后,我们就要让待插入数依次与有序表元素进行比较了,不过在这之前,我们还得明确一件事,就是待插入数得先跟哪一个元素开始比较起,相信不用我说,大家应该都知道是要跟它前面的数(即arr[1])开始比较起吧!换言之,就是有序表最后那个元素,对不!
insertIndex = 2 - 1; // 表示有序表最后那个元素的下标
// 接着,为待插入数找到一个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。
arr[insertIndex + 1] = insertValue;
System.out.println("第2次插入过后,待排序数组的样子:");
System.out.println(Arrays.toString(arr));
}
}
从上可以看到,第二次插入的实现代码,我们就改了一下insertValue
和insertIndex
这俩变量的值,而其他地方都没动,是这样子吧!
照理来说,下面我们就该来执行以上程序了,但在此之前,我还有一点得给大家说明一下,就是本次插入,我们在为待插入数(即119)找寻适当的插入位置时,其实是一下子就找到了,对不,因为待插入数(即119)本身就大于有序表的最后那个元素(即101),所以那可不就得直接插在有序表的最后那个元素的后面了,是这样子的吧!
好了,下面我们来执行以上程序,可以看到经第二次插入后,119确实是被插到了有序表的最后面。
同理,第三次插入的实现代码,是不是也只需要改一下insertValue
和insertIndex
这俩变量的值啊!简单吧!
package com.meimeixia.sort;
import java.lang.reflect.Array;
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1};
insertSort(arr);
}
/**
*
* @param arr:你只要给我传进来一个数组,那我就帮你进行插入排序
*/
public static void insertSort(int[] arr) {
/**
* 第一次插入。
*/
// 首先,我们得定义一个待插入的数,既然是要待插入的数,那有同学知道这个待插入的数到底是哪一个嘛?想都不用想,这个待插入的数就是arr[1],即34,对不!
int insertValue = arr[1];
// 然后,再定义一个待插入数的索引。我不知道大家晓不晓得这儿这个待插入数的索引应该是要等于0的,为什么呢?因为我们上面说过这儿这个待插入数,也即34,它
// 是要跟有序表元素来进行比较的,准确地讲,它是要跟它前面的数(即101)进行比较,而现在待插入数的下标又是1,因此自然而然我们就要在其基础上减1来得到待
// 插入数的索引了,也就是arr[1]前面那个数的下标。
int insertIndex = 1 - 1;
// 接着,为待插入数找到一个适当的插入位置。
// 注意,这儿while循环有两个约束条件,其中第一个约束条件是为了保证待插入数(即34)在跟有序表元素进行比较,进而寻找适当插入位置时,不至于越界;而第二个
// 约束条件则是为了说明待插入数还没有找到适当的插入位置,当然前提是该条件得成立。为什么会这么说呢?你想啊,待插入数在跟有序表元素进行比较时,我们是
// 不是得让它跟有序表中的元素依次进行比较啊,现在待插入数(即34)小于有序表元素(即101),因此我们还得接着让它跟有序表前面的元素进行比较,直至为其找
// 到那个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
// 如果待插入数还没有找到适当的插入位置,那么我们就得让arr[insertIndex](此时它就等于101)后移了,即把位置给腾出来。
arr[insertIndex + 1] = arr[insertIndex]; // 程序如果一旦执行到这里,相信大家应该不难理解此时待排序数组就由{101, 34, 119, 1}变成{101, 101, 119, 1}了吧!
// 当然,有同学可能会说,那待插入数(即34)不就丢了嘛?丢了嘛?没丢,因为我们一开始就将其保存到了insertValue这个变量中,是不!
// 相信大家应该都知道这个地方为什么要让insertIndex减减了吧!因为我们还得接着让待插入数跟有序表前面的元素(即101前面的数)进行比较。
// 当然,有同学可能会说,那如果越界了,又该怎么办呢?很简单,直接退出while循环即可,要知道我们while循环可是有条件约束的。而当退出
// while循环时,我们也就找到了待插入数它所要插入的那个位置,即有序表第一个元素所在的位置,换句话来说,就是下标为insertIndex+1的这
// 个位置。
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。为什么我会说是这个位置呢?
// 道理也很简单,我举个例子,大家看完,就什么都明白了。假如此刻我们待插入的数不是33,而是334,那么很明显程序往下执行,它是不会进入到while
// 循环里面去的 ,因为334并不小于101,聪明的同学看到这里,相信立马就应该知道待插入数它所要插入的那个位置了吧,是不是就直接插在有序表元素
// 101的后面就行了啊,也就是下标为insertIndex+1的这个位置。
//
// 我不知道我举的这个例子,大家都看明白了没有,应该是都看明白了啊,毕竟我讲的都这么详细了,饭都喂到你的嘴边了。但你要实在是理解不了,那也没
// 关系,因为待会我会带着大家一起来Debug一下咱们的代码,等代码Debug完,相信你也就都明白了。
arr[insertIndex + 1] = insertValue;
System.out.println("第1次插入过后,待排序数组的样子:");
System.out.println(Arrays.toString(arr));
/**
* 第二次插入。
*/
// 首先,我们还是得把这一次待插入的数确定下来,不用我说,相信大家应该都知道这一次插入是要为arr[2]找寻适当的插入位置吧!
insertValue = arr[2];
// 然后,我们就要让待插入数依次与有序表元素进行比较了,不过在这之前,我们还得明确一件事,就是待插入数得先跟哪一个元素开始比较起,相信不用我说,大家应该都知道是要跟它前面的数(即arr[1])开始比较起吧!换言之,就是有序表最后那个元素,对不!
insertIndex = 2 - 1; // 表示有序表最后那个元素的下标
// 接着,为待插入数找到一个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。
arr[insertIndex + 1] = insertValue;
System.out.println("第2次插入过后,待排序数组的样子:");
System.out.println(Arrays.toString(arr));
/**
* 第三次插入。
*/
// 首先,我们还是得把这一次待插入的数确定下来,不用我说,相信大家应该都知道这一次插入是要为arr[3]找寻适当的插入位置吧!
insertValue = arr[3];
// 然后,我们就要让待插入数依次与有序表元素进行比较了,不过在这之前,我们还得明确一件事,就是待插入数得先跟哪一个元素开始比较起,相信不用我说,大家应该都知道是要跟它前面的数(即arr[2])开始比较起吧!换言之,就是有序表最后那个元素,对不!
insertIndex = 3 - 1; // 表示有序表最后那个元素的下标
// 接着,为待插入数找到一个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。
arr[insertIndex + 1] = insertValue;
System.out.println("第3次插入过后,待排序数组的样子:");
System.out.println(Arrays.toString(arr));
}
}
下面我们来执行以上程序,可以看到经第三次插入后,1确实是被插到了有序表的最前面。
那有同学就要问了,还有第四次插入吗?没有了,因为我们待排序数组一共就只有3个元素,而第一个元素我们又认为它是一个有序表,因此无论怎么排,它都是有序的,而这也就是说我们不需要为它找寻适当位置并进行插入。所以,实际上,我们是要为后面的3个数找寻适当位置并进行插入,那当然就一共只需要进行3次插入了,对不!
至此,插入排序的逐步推导过程,我也就给大家讲解完毕了。那有了这个推导过程之后,相信接下来大伙应该都知道怎么来处理一下就能将插入排序的完整实现代码写出来了吧!是不是只需要写一个for循环就可搞定啊!你看一下,代码是不是像下面这样写的。
package com.meimeixia.sort;
import java.lang.reflect.Array;
import java.util.Arrays;
public class InsertSort {
public static void main(String[] args) {
int[] arr = {101, 34, 119, 1};
insertSort(arr);
}
/**
*
* @param arr:你只要给我传进来一个数组,那我就帮你进行插入排序
*/
public static void insertSort(int[] arr) {
int insertValue = 0;
int insertIndex = 0;
// 针对如下这个for循环,我还有两点需要向大家说明一下。
// 1. i这个变量刚刚好对应的就是插入的次数,即第几次插入;
// 2. 针对本案例,相信大家应该都知道我们须一共经历3次插入吧!因此,这儿这个i就得小于arr.length了,而不能是小于arr.length - 1哟!
// 如果你要是写成小于arr.length - 1了,那么后果就是待排序数组最后那个元素就将不会参与找寻适当插入位置的这个过程了。切记,切记!
for (int i = 1; i < arr.length; i++) {
// 首先,我们还是得把这一次待插入的数确定下来,不用我说,相信大家应该都知道这一次插入是要为arr[i]找寻适当的插入位置吧!
insertValue = arr[i];
// 然后,我们就要让待插入数依次与有序表元素进行比较了,不过在这之前,我们还得明确一件事,就是待插入数得先跟哪一个元素开始比较起,相信不用我说,大家应该都知道是要跟它前面的数(即arr[i - 1])开始比较起吧!换言之,就是有序表最后那个元素,对不!
insertIndex = i - 1; // 表示有序表最后那个元素的下标
// 接着,为待插入数找到一个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。
arr[insertIndex + 1] = insertValue;
System.out.println("第" + i + "次插入过后,待排序数组的样子:");
System.out.println(Arrays.toString(arr));
}
}
}
代码写完之后,下面我们就来执行一下以上程序吧!如下图所示,可以看到历经3次插入之后,待排序数组已然就是一个有序的了。
为了更进一步验证我们所写的插入排序代码是否正确,下面我们不妨就将待排序数组整得稍微复杂一点之后再进行测试,如这里,我就将待排序数组改成{101, 34, 119, 1, -1, 89}
了。改好之后,执行程序,可以看到总共须历经5次插入,咱们的待排序数组才变得有序。
至此,插入排序的完整实现代码,我们就这样写出来了,应该不难理解吧!
回过头,再看一下之前我们讲解的选择排序,它与插入排序的区别在哪呢?区别是不是对于插入排序而言,我们得将待排序数组的第一个元素看作是一个有序表,而剩余其他元素看作是一个无序表啊,这样看待之后,然后我们就要依次为无序表中的每一个元素找寻适当的插入位置并进行插入了,对不!
速度测试
这里,我们同样也来测试一把插入排序的速度,看看和前面两种排序算法相比较,谁更快一点。
那如何来测试呢?还是像我们在上篇文章中测试选择排序的速度那样,先创建一个8万个随机数的数组,然后再来对该数组进行插入排序,当然我们得分别记录下来排序前和排序后的时间,这样两相比较之后,我们就能知道插入排序到底会花费我们多长时间了。
package com.meimeixia.sort;
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class InsertSort {
public static void main(String[] args) {
// int[] arr = {101, 34, 119, 1, -1, 89};
// insertSort(arr);
// 首先,创建一个拥有8万个随机数的数组
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 想必大家应该都知道这儿会产生一个[0, 8000000)该范围的随机数吧!
}
Date date1 = new Date();
// 大家应该还记得SimpleDateFormat这个玩意吧!是不是用它我们就可以来格式化一把时间啊!就像下面这样,
// 我们是把当前时间以年-月-日 时:分:秒的形式来格式化了一把,然后再进行了一个输出。之所以这样做,是因
// 为待会更方便我直观地给大家呈现出排序前后的时间,这样大家心里就能有一个比较了。
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的时间是:" + date1Str);
// 然后,对以上数组进行插入排序
insertSort(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序后的时间是:" + date2Str);
}
/**
*
* @param arr:你只要给我传进来一个数组,那我就帮你进行插入排序
*/
public static void insertSort(int[] arr) {
int insertValue = 0;
int insertIndex = 0;
// 针对如下这个for循环,我还有两点需要向大家说明一下。
// 1. i这个变量刚刚好对应的就是插入的次数,即第几次插入;
// 2. 针对本案例,相信大家应该都知道我们须一共经历3次插入吧!因此,这儿这个i就得小于arr.length了,而不能是小于arr.length - 1哟!
// 如果你要是写成小于arr.length - 1了,那么后果就是待排序数组最后那个元素就将不会参与找寻适当插入位置的这个过程了。切记,切记!
for (int i = 1; i < arr.length; i++) {
// 首先,我们还是得把这一次待插入的数确定下来,不用我说,相信大家应该都知道这一次插入是要为arr[i]找寻适当的插入位置吧!
insertValue = arr[i];
// 然后,我们就要让待插入数依次与有序表元素进行比较了,不过在这之前,我们还得明确一件事,就是待插入数得先跟哪一个元素开始比较起,相信不用我说,大家应该都知道是要跟它前面的数(即arr[i - 1])开始比较起吧!换言之,就是有序表最后那个元素,对不!
insertIndex = i - 1; // 表示有序表最后那个元素的下标
// 接着,为待插入数找到一个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。
arr[insertIndex + 1] = insertValue;
// 由于没有必要在每一次插入的时候都输出排序好的数组,因此我们还得注销掉如下两句代码。
// System.out.println("第" + i + "次插入过后,待排序数组的样子:");
// System.out.println(Arrays.toString(arr));
}
}
}
注意,由于目前待排序数组共有8万个元素,因此我们也就没必要再在每次插入之后都输出其结果了,毕竟数据量太大了,对不,而且你要真给打印出来,不说输出到控制台的结果没法看,插入排序真正会花费的时间那也体现不出来,要知道每次插入之后的结果输出到控制台就得花费我们一些时间。
下面我们就来执行一下以上程序,看看其结果,如下图所示,可以看到8万个数据进行插入排序只须耗时1秒,对比我们选择排序所须耗时3秒来看,插入排序所耗时间似乎要比选择排序短。
接下来,我们再来执行一下以上程序,看看其结果,如下图所示,可以看到8万个数据进行插入排序现在耗时都不到1秒了。
啥也不说了,咱们再来执行一次以上程序,看看其结果,如下图所示,可以看到8万个数据进行插入排序所耗时间依旧还是0秒。
由此,我觉着现在8万个数据插入排序完大致应该是要耗费我们0~1秒左右的时间,反正我估摸着是差不了多少了,当然,这是在我电脑上运行的结果,可能在你电脑上运行又是另一个结果,嘿嘿🤭!但不管怎么说,插入排序所耗时间确实是要比选择排序短,也即插入排序比选择排序要快,虽说只快了那么一丢丢。
虽说插入排序我是给大家讲解完毕了,但是有一点我还是得给大家说明一下,就是针对{101, 34, 119, 1}
这个待排序数组而言,在第二次插入的时候,不知道大家有没有发现在为待插入数(即119)找寻适当的插入位置时,找到的这个位置恰恰好就是待插入数(即119)实际所在的位置,而这是不是就意味着我们不需要再为其进行插入了啊,对不!因此,接下来我们还得来对以上插入排序代码做一个小小的优化。
那如何优化呢?很简单,在最后阶段为待插入数进行插入时,加一个判断即可,那加怎样一个判断呢?大家看一下,是不是得加这样一个判断,即如果我们为待插入数找到的适当的插入位置恰恰好就是其实际所在的位置,那么说明我们就不需要再为其进行插入了,你想啊,待插入数就在它要插入的位置上,那它还插个毛啊,是不是,反之,则就要进行插入了。
代码优化完毕之后,下面我们肯定是要先来验证一下我们改后的代码是不是没什么错误的了。
那怎么进行验证呢?很简单,先创建一个8个随机数的数组,然后再对其进行插入排序,插入排序过后,我们再来看看它是不是真的变得有序了。
package com.meimeixia.sort;
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class InsertSort {
public static void main(String[] args) {
// int[] arr = {101, 34, 119, 1, -1, 89};
// insertSort(arr);
// 首先,创建一个拥有8个随机数的数组
int[] arr = new int[8];
for (int i = 0; i < 8; i++) {
arr[i] = (int) (Math.random() * 8000000); // 想必大家应该都知道这儿会产生一个[0, 8000000)该范围的随机数吧!
}
Date date1 = new Date();
// 大家应该还记得SimpleDateFormat这个玩意吧!是不是用它我们就可以来格式化一把时间啊!就像下面这样,
// 我们是把当前时间以年-月-日 时:分:秒的形式来格式化了一把,然后再进行了一个输出。之所以这样做,是因
// 为待会更方便我直观地给大家呈现出排序前后的时间,这样大家心里就能有一个比较了。
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的时间是:" + date1Str);
// 然后,对以上数组进行插入排序
insertSort(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序后的时间是:" + date2Str);
System.out.println(Arrays.toString(arr));
}
/**
*
* @param arr:你只要给我传进来一个数组,那我就帮你进行插入排序
*/
public static void insertSort(int[] arr) {
int insertValue = 0;
int insertIndex = 0;
// 针对如下这个for循环,我还有两点需要向大家说明一下。
// 1. i这个变量刚刚好对应的就是插入的次数,即第几次插入;
// 2. 针对本案例,相信大家应该都知道我们须一共经历3次插入吧!因此,这儿这个i就得小于arr.length了,而不能是小于arr.length - 1哟!
// 如果你要是写成小于arr.length - 1了,那么后果就是待排序数组最后那个元素就将不会参与找寻适当插入位置的这个过程了。切记,切记!
for (int i = 1; i < arr.length; i++) {
// 首先,我们还是得把这一次待插入的数确定下来,不用我说,相信大家应该都知道这一次插入是要为arr[i]找寻适当的插入位置吧!
insertValue = arr[i];
// 然后,我们就要让待插入数依次与有序表元素进行比较了,不过在这之前,我们还得明确一件事,就是待插入数得先跟哪一个元素开始比较起,相信不用我说,大家应该都知道是要跟它前面的数(即arr[i - 1])开始比较起吧!换言之,就是有序表最后那个元素,对不!
insertIndex = i - 1; // 表示有序表最后那个元素的下标
// 接着,为待插入数找到一个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。
// 如果我们为待插入数找到的适当的插入位置恰恰好就是其实际所在的位置(即insertIndex + 1 == i),那么是不是就说明我们不需要再为其进行插入了啊,你想啊,待插入数就在它要插入的位置上,那它还插个毛啊,反之,则是不是就要进行插入了啊!
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertValue;
}
// 由于没有必要在每一次插入的时候都输出排序好的数组,因此我们还得注销掉如下两句代码。
// System.out.println("第" + i + "次插入过后,待排序数组的样子:");
// System.out.println(Arrays.toString(arr));
}
}
}
执行以上程序,可以看到最终打印出来的确实是一个有序数组,而且它还是按照从小到大的顺序来排列的,对不!很显然,咱们优化之后的代码没有任何问题。
既然咱们优化之后的代码没有任何问题了,那咱就看看对于8万个随机数的数组,它插入排序完之后,所耗时间会不会有些许变化,简言之,就是速度上会不会再快一点点。
package com.meimeixia.sort;
import java.lang.reflect.Array;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class InsertSort {
public static void main(String[] args) {
// int[] arr = {101, 34, 119, 1, -1, 89};
// insertSort(arr);
// 首先,创建一个拥有8万个随机数的数组
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 8000000); // 想必大家应该都知道这儿会产生一个[0, 8000000)该范围的随机数吧!
}
Date date1 = new Date();
// 大家应该还记得SimpleDateFormat这个玩意吧!是不是用它我们就可以来格式化一把时间啊!就像下面这样,
// 我们是把当前时间以年-月-日 时:分:秒的形式来格式化了一把,然后再进行了一个输出。之所以这样做,是因
// 为待会更方便我直观地给大家呈现出排序前后的时间,这样大家心里就能有一个比较了。
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的时间是:" + date1Str);
// 然后,对以上数组进行插入排序
insertSort(arr);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序后的时间是:" + date2Str);
// System.out.println(Arrays.toString(arr));
}
/**
*
* @param arr:你只要给我传进来一个数组,那我就帮你进行插入排序
*/
public static void insertSort(int[] arr) {
int insertValue = 0;
int insertIndex = 0;
// 针对如下这个for循环,我还有两点需要向大家说明一下。
// 1. i这个变量刚刚好对应的就是插入的次数,即第几次插入;
// 2. 针对本案例,相信大家应该都知道我们须一共经历3次插入吧!因此,这儿这个i就得小于arr.length了,而不能是小于arr.length - 1哟!
// 如果你要是写成小于arr.length - 1了,那么后果就是待排序数组最后那个元素就将不会参与找寻适当插入位置的这个过程了。切记,切记!
for (int i = 1; i < arr.length; i++) {
// 首先,我们还是得把这一次待插入的数确定下来,不用我说,相信大家应该都知道这一次插入是要为arr[i]找寻适当的插入位置吧!
insertValue = arr[i];
// 然后,我们就要让待插入数依次与有序表元素进行比较了,不过在这之前,我们还得明确一件事,就是待插入数得先跟哪一个元素开始比较起,相信不用我说,大家应该都知道是要跟它前面的数(即arr[i - 1])开始比较起吧!换言之,就是有序表最后那个元素,对不!
insertIndex = i - 1; // 表示有序表最后那个元素的下标
// 接着,为待插入数找到一个适当的插入位置。
while (insertIndex >= 0 && insertValue < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
// 最后,当退出while循环时,说明我们已然找到了待插入数它所要插入的那个位置,即下标为insertIndex+1的这个位置。
// 如果我们为待插入数找到的适当的插入位置恰恰好就是其实际所在的位置(即insertIndex + 1 == i),那么是不是就说明我们不需要再为其进行插入了啊,你想啊,待插入数就在它要插入的位置上,那它还插个毛啊,反之,则是不是就要进行插入了啊!
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertValue;
}
// 由于没有必要在每一次插入的时候都输出排序好的数组,因此我们还得注销掉如下两句代码。
// System.out.println("第" + i + "次插入过后,待排序数组的样子:");
// System.out.println(Arrays.toString(arr));
}
}
}
执行以上程序,可以看到8万个数据进行插入排序现在须耗时1秒。
啥也不说了,咱们再来执行一次以上程序,看看其结果,如下图所示,可以看到8万个数据进行插入排序所耗时间依旧还是1秒。
看来,优化之后,速度也没有什么明显的提升嘛!其实,插入排序这儿优不优化都无所谓,因为优化我们也只是加了一个判断,而程序在判断的时候,也是会需要一点时间的。
总之,插入排序这儿你优化也好,不优化也好,都可以,这不是一个大的问题。
最后,还有一点得给大家稍微说一下,就是如果你想按照从大到小的顺序来对待排序数组进行插入排序的话,那么你只需要将下面的这个<
改成>
就可以了,相信大家对此应该都没有任何疑问吧!
至此,关于插入排序的所有内容,我就给大家讲解完毕了!
这里吐槽一下,完整写完这篇文章,可以说是花费了我巨巨巨量的时间,每天写一点点,才写就,真的特别特别的不容易,鉴于此,大家在阅读这篇文章的时候那就一定要认认真真仔仔细细地看了,每一个字每一个字都得看,因为它们都是我绞劲脑汁敲出来的,总之,我希望大家不要枉费了我的这一片苦心,还有我会为好内容,全力以赴!