笔者在前面两篇文章当中简单介绍了下冒泡排序以及选择排序,这里顺带介绍下插入排序(Insertion-Sort)。
举个栗子(出自《算法导论》第三版)。
一堆无序的扑克牌,从上面抽取第一张,放在手上,然后抽取第二张,比较和第一张的大小。若小于第一张扑克,则放在第一张扑克的前面,否则放在第一张扑克的后面。然后抽取第三张,若小于第一张扑克,在放在最前面;若大于等于第一张扑克且小于等于第二张扑克,则放在两张扑克中间,否则放在第二张扑克后面…以此类推,可以得到一个升序排序的扑克牌序列。
上述过程阐明了插入排序的基本原理。
- 算法从数组的第二个元素开始.
- 每次迭代中,都会将当前位置元素的值与前面元素的值进行比较。若前面的某一元素的值大于当前位置元素的值,则后移之,直到插入点前面的元素都小于当前元素的值,后面的元素都大于等于当前元素的值。
最坏情况下,插入排序的时间复杂度大致为O(n^2)。由于在插入排序中,通常情况下只有当前面的元素大于当前元素的值的时候,前面的元素值在往后移动。因此,插入排序是一种稳定的排序算法。另外,由于在《算法导论》看到过一个提示,大概的意思是
如果要对数组A[1,2,…n]进行排序,需首先对数组A[1,2,…n-1]排序。要对数组A[1,2,…n-1]进行排序,则需要首先对数组A[1,2,…n-2]进行排序…因此,考虑将插入排序的过程采用递归(Recursion)来实现。
插入排序-for循环版本
import com.sun.istack.internal.NotNull;
import java.util.Arrays;
import java.util.Random;
/**
* A demo of {@code InsertionSort}.
*
* @author Mr.K
*/
public class InsertionSort {
public static void main(String[] args) {
int N = 20;
int[] numbers = new int[N];
Random random = new Random();
for (int i = 0; i < N; i++) {
numbers[i] = random.nextInt(2 * N);
}
System.out.println(Arrays.toString(numbers) + "\n");
insertionSort(numbers);
System.out.println("\n" + Arrays.toString(numbers));
}
/**
* Accepts an array, each element is an integer number and sorts the array by algorithm
* {@code InsertionCode}, which starts from the second element, and ends at the last one.
* <ul>
* <li>For each iteration, current element compares with elements ahead.</li>
* <li>if a certain element is greater than current element, that certain element will
* move to the next element until there is no element is greater than current element,
* in the form of key</li>
* <li>At last, current element will be placed in the right place where each element
* ahead is less than or equals to current element, and elements backward is greater
* than current element.</li>
* </ul>
* Since the parameter is in a form of array, which means it's a reference, thus no results
* will be returned.<br><br>
* <p>
* Be aware that, in the bad cases, the cost of time of {@code InsertionSort} is O(n^2),
* which may be a bottleneck for large number of numbers to be sorted.<br><br>
* By the way, {@code InsertionSort} is stable cause the judgement is
* <blockquote>
* numbers[i] > key
* </blockquote>
* If there are some numbers with the same values, only when the number in the sorted
* sequence if greater than the sorting number, exchange will be made. If the judgement
* changes to
* <blockquote>
* numbers[i] >= key
* </blockquote>
* Then, if two number has the same value, they will exchange their position, or index if you
* like. So the order of both number changes, which is different from the original array. In
* that case, {@code InsertionSort} is unstable.
*
* @param numbers numbers to be sorted, in a form array
*/
public static void insertionSort(@NotNull int[] numbers) {
for (int j = 1; j < numbers.length; j++) {
int key = numbers[j];
int i = j - 1;
System.out.println("第" + String.format("%2d", j) + "步, 待排序数字: " +
String.format("%2d", key) + " -> " + Arrays.toString(numbers));
while (i >= 0 && numbers[i] > key) {
numbers[i + 1] = numbers[i];
i--;
}
numbers[i + 1] = key;
}
}
}
插入排序-递归版本
import com.sun.istack.internal.NotNull;
import java.util.Arrays;
import java.util.Random;
/**
* A demo of {@code InsertionSort} by invoking {@code InsertionSort} itself.
*
* @author Mr.K
*/
public class InsertionSortByRecursion {
public static void main(String[] args) {
int N = 20;
int[] numbers = new int[N];
Random random = new Random();
for (int i = 0; i < numbers.length; i++) {
numbers[i] = random.nextInt(2 * N);
}
System.out.println("待排序数组: " + Arrays.toString(numbers) + "\n");
insertionSortByRecursion(numbers, numbers.length - 1);
System.out.println("\n已排序数组: " + Arrays.toString(numbers));
}
/**
* Accepts an array and an index and sorts this array by invoking itself, which is so
* called {@code Recursion}. The index is the condition to terminate the {@code Recursion}.
* When the index goes to 0, which means it's the first element of the specified array,
* the process goes back and starts the process of {@code InsertionSort}.
* <ul>
* <li>When this method is invoked, it will check that whether the index equals to 0.
* If so, then returns and sort the array, in a range of [0, 1] with {@code InsertionSort}.
* And then, sub-array in a range of [0, 2] should be sorted as well until the whole
* array is sorted.</li>
* </ul>
* This method is stable cause only when a certain is greater than current key number, then that
* number will be moved backwards by one step.<br><br>
* The cost of the time of {@code InsertionSort} is O(n^2) in a bad case, which has no differences
* from the version, who uses <em>For-Loop</em> to finish the iterations.
*
* @param numbers specified array to be sorted
* @param index index of the end of current range, which start from 0(inclusive)
*/
public static void insertionSortByRecursion(@NotNull int[] numbers, @NotNull int index) {
if (index == 0) {
return;
} else {
insertionSortByRecursion(numbers, index - 1);
int i = index - 1, key = numbers[index];
System.out.println("第" + String.format("%2d", index) + "步, 待排序数字: " +
String.format("%2d", key) + " -> " + Arrays.toString(numbers));
while (i >= 0 && numbers[i] > key) {
numbers[i + 1] = numbers[i--];
}
numbers[i + 1] = key;
}
}
}
程序运行结果如下所示
待排序数组: [38, 18, 23, 38, 12, 18, 32, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 1步, 待排序数字: 18 -> [38, 18, 23, 38, 12, 18, 32, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 2步, 待排序数字: 23 -> [18, 38, 23, 38, 12, 18, 32, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 3步, 待排序数字: 38 -> [18, 23, 38, 38, 12, 18, 32, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 4步, 待排序数字: 12 -> [18, 23, 38, 38, 12, 18, 32, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 5步, 待排序数字: 18 -> [12, 18, 23, 38, 38, 18, 32, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 6步, 待排序数字: 32 -> [12, 18, 18, 23, 38, 38, 32, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 7步, 待排序数字: 27 -> [12, 18, 18, 23, 32, 38, 38, 27, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 8步, 待排序数字: 6 -> [12, 18, 18, 23, 27, 32, 38, 38, 6, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第 9步, 待排序数字: 7 -> [6, 12, 18, 18, 23, 27, 32, 38, 38, 7, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第10步, 待排序数字: 0 -> [6, 7, 12, 18, 18, 23, 27, 32, 38, 38, 0, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第11步, 待排序数字: 1 -> [0, 6, 7, 12, 18, 18, 23, 27, 32, 38, 38, 1, 38, 2, 34, 15, 9, 0, 5, 27]
第12步, 待排序数字: 38 -> [0, 1, 6, 7, 12, 18, 18, 23, 27, 32, 38, 38, 38, 2, 34, 15, 9, 0, 5, 27]
第13步, 待排序数字: 2 -> [0, 1, 6, 7, 12, 18, 18, 23, 27, 32, 38, 38, 38, 2, 34, 15, 9, 0, 5, 27]
第14步, 待排序数字: 34 -> [0, 1, 2, 6, 7, 12, 18, 18, 23, 27, 32, 38, 38, 38, 34, 15, 9, 0, 5, 27]
第15步, 待排序数字: 15 -> [0, 1, 2, 6, 7, 12, 18, 18, 23, 27, 32, 34, 38, 38, 38, 15, 9, 0, 5, 27]
第16步, 待排序数字: 9 -> [0, 1, 2, 6, 7, 12, 15, 18, 18, 23, 27, 32, 34, 38, 38, 38, 9, 0, 5, 27]
第17步, 待排序数字: 0 -> [0, 1, 2, 6, 7, 9, 12, 15, 18, 18, 23, 27, 32, 34, 38, 38, 38, 0, 5, 27]
第18步, 待排序数字: 5 -> [0, 0, 1, 2, 6, 7, 9, 12, 15, 18, 18, 23, 27, 32, 34, 38, 38, 38, 5, 27]
第19步, 待排序数字: 27 -> [0, 0, 1, 2, 5, 6, 7, 9, 12, 15, 18, 18, 23, 27, 32, 34, 38, 38, 38, 27]
已排序数组: [0, 0, 1, 2, 5, 6, 7, 9, 12, 15, 18, 18, 23, 27, 27, 32, 34, 38, 38, 38]