编者最近在找实习,很担心大公司的笔试能不能过呢。。。趁这个机会复习下插入排序,看看到底是个啥东西。
简介
插入排序,顾名思义,就是不断的向前面已拍好序的数组中插入一个数值, 并使得此数组任然有序。实际上,插入排序有很多种实现方法,例如直接插入排序、折半插入排序、表插入排序、希尔排序。
一、直接插入排序
从第2个元素开始扫描输入数组,即从第2个元素开始向前面的数组中插入。这样,每一次插入后,前面的数组都能够保证有序。
举个例子
例如对数组[256, 0 , 3 , 9 , 11, 7]。
第一次排序是将下标为1的元素0插入前面的数组[256]中,得到第一次排序后的数组为[0, 256 , 3 , 9 , 11, 7]
第二次排序是将下标为2的元素3插入前面的数组[0,256]中,得到第一次排序后的数组为[0, 3 ,256 , 9 , 11, 7]。
……
第n次排序 | 排序后的数组元素 |
---|---|
0 | 256, 0 , 3 , 9 , 11, 7 |
1 | 0 ,256, 3 , 9 , 11 , 7 |
2 | 0 , 3 ,256, 9 , 11 , 7 |
3 | 0 , 3 , 9 ,256, 11 , 7 |
4 | 0 , 3 , 9 , 11 ,256, 7 |
5 | 0 , 3 , 9 , 11 , 7 ,256 |
特点
直接插入排序是一种时间复杂度为O(n2)的算法,虽然说时间复杂度比较高,但是它的优点在于稳定,即原数组中数值相等的几个元素之间的相对位置在经过这种排序后不会被改变。
二、折半插入排序
直接插入排序中,在向前插入元素时,需要将此元素定位,其采取的方法是遍历,很明显,这种遍历方法使得直接插入排序的时间复杂度升高。所以,我们在定位插入元素时,可以采用折半查找的方法(也可以叫二分搜索),采用这样的方法,可以将定位时的时间复杂度降至O(nlog2n),而因为移动次数没有发生变化,所以总的时间复杂度任为O(n2)
三、表插入排序
直接插入排序和折半插入排序有一个明显的缺点,就是每次插入都要可能移动大量数据,为了解决这一问题,我们将存储结构进行改变:
class Node{
int data;//当前节点数据的大小
int next;//下一节点的数组下标
}
也就是说,我们每次插入最多只需要改变2个节点的next值,极大地减少了移动数据时对时间的消耗。
举例
例如对数组[33,20,44,5,7,0]进行排序,过程如下:
第0次插入:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Key值 | Integer.MAX_VALUE | 33 | 20 | 44 | 5 | 7 | 0 |
Next值 | 0 | - | - | - | - | - | - |
这里下标0的节点是我们自定义的一个头结点,本例中为升序排序,故其值取Integer.MAX_VALUE,Next值默认为0,表格中Next为”-“的部分表示其值为空,用-1表示。
第1次插入:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Key值 | Integer.MAX_VALUE | 33 | 20 | 44 | 5 | 7 | 0 |
Next值 | 1 | 0 | - | - | - | - | - |
第2次插入:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Key值 | Integer.MAX_VALUE | 33 | 20 | 44 | 5 | 7 | 0 |
Next值 | 2 | 0 | 1 | - | - | - | - |
第3次插入:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Key值 | Integer.MAX_VALUE | 33 | 20 | 44 | 5 | 7 | 0 |
Next值 | 2 | 3 | 1 | 0 | - | - | - |
第4次插入:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Key值 | Integer.MAX_VALUE | 33 | 20 | 44 | 5 | 7 | 0 |
Next值 | 4 | 3 | 1 | 0 | 2 | - | - |
第5次插入:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Key值 | Integer.MAX_VALUE | 33 | 20 | 44 | 5 | 7 | 0 |
Next值 | 4 | 3 | 1 | 0 | 5 | 2 | - |
第6次插入:
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
Key值 | Integer.MAX_VALUE | 33 | 20 | 44 | 5 | 7 | 0 |
Next值 | 6 | 3 | 1 | 0 | 5 | 2 | 4 |
所以,最后得到的排序后的序列是[0,5,7,20,33,44]
四、希尔排序
由于直接插入排序在数组基本有序的情况下效率很高,所以我们考虑使用一种算法,先对数组进行初步排序,然后再对整个数组进行直接插入排序,这样的时间复杂度可能会小一些。这种方法就是希尔排序法。
过程:设定一些步长di,对于每一个步长,我们从数组中取相互间隔为di的元素,并进行直接插入排序。
举例
对数组[39,80,76,41,13,29,50,78,30,11,100,7,41,86 ]进行希尔排序。
分别取步长为5、3 、1.
第一趟排序(di=5)
组数 | 分组元素 |
---|---|
1 | 39 ,29,100 |
2 | 80 ,50 , 7 |
3 | 76 ,78 , 41 |
4 | 41 ,30 , 86 |
5 | 13 ,11 |
排序后,数组变为:
[29 7 41 30 11 39 50 76 41 13 100 80 78 86]
第二趟排序(di=3)
组数 | 分组元素 |
---|---|
1 | 29 ,30 ,50 , 13 , 78 |
2 | 7 , 11 ,76 , 100, 86 |
3 | 41 ,39 ,41 , 80 |
排序后,数组变为:
[13 7 39 29 11 41 30 76 41 50 86 80 78 100 ]
第三趟排序(di=1)
组数 | 分组元素 |
---|---|
1 | 13 , 7 , 39 , 29 , 11 , 41 , 30 , 76 , 41 , 50 , 86 , 80 , 78 , 100 |
排序后,结果得出,为[7 11 13 29 30 39 41 41 50 76 78 80 86 100 ]
特点
值得注意的是,希尔排序是一种不稳定的排序算法,但是由于减少了元素移动的次数,所以比直接插入排序要快一些,而且由于其最坏情况下和平均情况下的执行效率差距不大,所以,成为了大家首选的排序算法(而不是快排)。
附主要代码:
1.直接插入排序
class DirectInsertionSortTool{
public static void sort(int[] nums){
for(int i = 1 ; i < nums.length ; i ++){
/*升序排序*/
if(nums[i-1] > nums[i]){
//只要将比key大的值后移就行
int key_index = i - 1;
for(int j = key_index ; j >= 0 ; j --){
int temp = nums[i];//保存插入的值
if((j > 0 && nums[j-1] <= nums[i] && nums[j] > nums[i]) || (j == 0 && nums[j] > nums[i])){
//将j至(i-1)间的值往后移
for(int k = key_index ; k >= j ; k --)
nums[k+1] = nums[k];
nums[j] = temp;
}
}
}
}
}
}
2.表插入排序
class TableInsertionSortTool{
public static void sort(Node[] node){
if(node == null || node.length <= 2)
return ;
Node currNode ;//当前向前插入的节点
Node tempNode ;
node[0].next = 1;
node[1].next = 0;
for(int i = 2 ; i < node.length; i ++){
//i表示插入元素的下标
currNode = node[i];//要插入的节点
tempNode = node[0];
boolean isInserted = false;
if(node[node[0].next].data > currNode.data){
currNode.next = node[0].next;
node[0].next = i;
}else{
for(int j = 0 ; j < i ; j ++){
// System.out.println("j="+j);
//查找i-1次
if(tempNode.data <= currNode.data && node[tempNode.next].data > currNode.data){
// System.out.println(currNode.data+" isInserted");
currNode.next = tempNode.next;
tempNode.next = i;
isInserted = true;
break;
}
tempNode = node[tempNode.next];
}
// if(!isInserted)
// tempNode.next = i;
}
}
}
}
3.希尔排序
class ShellSortTool{
public static void shellSort(int[] nums){
int[] steps = new int[]{5,3,1};
for(int i = 0 ; i < steps.length ; i ++){
shellInsert(nums, steps[i]);
}
}
//以k作为步长排序
public static void shellInsert(int[] nums,int k){
//i为排序的组数
for(int i = 0 ; i < k ; i ++){
//对每一组i,i+k,i+2k...排序
for(int j = i + k; j < nums.length ; j = j + k){
//向前插入//nums[j]是目标数
int insertIndex = j ;//默认插在最后
int temp = nums[j];//保存将要插入的值
//遍历找到插入位置
for(int m = i ; m <= j - k ; m = m + k){
if(m == i && nums[m] > nums[j]){
insertIndex = m;
break;
}else if(nums[m] <= nums[j] && nums[m+k] > nums[j]){
insertIndex = m + k;
break;
}
}
//向后移动,腾出位置
for(int m = j - k; m >= insertIndex ; m = m - k){
nums[m+k] = nums[m];
}
nums[insertIndex] = temp;
}
}
System.out.println("\nsteps = "+k+":");
for(int num:nums)
System.out.print(num+" ");
System.out.println();
}
}