筆者在前面兩篇文章當中簡單介紹了下冒泡排序以及選擇排序,這裏順帶介紹下插入排序(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]