对于一个已经有序的数据序列,要求在这个已经排好的数据序列中插入一个数,但要求插入后此数据序列仍然有序,这个时候就要用到插入排序。插入排序的基本操作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、元素个数加 1 的有序数组,算法适用于少量数据的排序,时间复杂度为O(n^2)
,是稳定的排序方法。
插入算法把要排序的数组分成两部分:第一部分包含了这个数组的所有元素,但将最后一个元素除外(让数组多一个空间才有插入的位置),而第二部分就只包含这一个元素(即待插入元素),在第一部分排序完成后,再将这个最后元素插入到已排好序的第一部分中。根据插入排序算法的具体实现方法,又可以将其分为:直接插入排序、二分插入排序(又称折半插入排序)、希尔排序(又称缩小增量排序)和链表插入排序。
例如,已知待排序的一组纪录是:
60,71,49,11,24,3,66
假设在排序过程中,前 3 个纪录已按关键码值递增的次序重新排列,构成一个有序序列:
49,60,71
将待排序纪录中的第 4 个纪录(即 11)插入上述有序序列,以得到一个新的含 4 个纪录的有序序列。首先,应找到 11 的插入位置,再进行插入。可以将 11 放入数组的第一个单元arr[0]
中,这个单元称为监视哨,然后从 71 起从右到左查找,11 小于 71,将 71 右移一个位置,11 小于 60,又将 60 右移一个位置,11 小于 49,又再将 49 右移一个位置,这时再将 11 与arr[0]
的值比较,11 ≥ arr[0]
,它的插入位置就是arr[1]
。假设 11 大于第一个值arr[1]
。它的插入位置应该在arr[1]
和arr[2]
之间,由于 60 已经右移了,留出来的位置正好留给 11,后面的纪录依照同样的方法逐个插入到该有序序列中。若纪录总数为n
,则需要进行n-1
趟排序才能完成。以下给出两种 Java 语言的实现代码。
public class InsertSort {
public static void main(String[] args) {
int[] arr = {3, -9, 1, 0, -1, 2, -3};
System.out.println("Before sort:");
printArray(arr);
insertSort(arr);
System.out.println("After sort:");
printArray(arr);
}
private static int[] insertSort(int[] arr) {
// 判断数组是否为空或者数组元素个数为 1
if (arr == null || arr.length < 2) {
return arr;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
} else {
break;
}
}
}
return arr;
}
private static void printArray(int[] arr) {
System.out.print("{");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
if (i < arr.length - 1) {
System.out.print(", ");
}
}
System.out.println("}");
}
}
public class InsertSort {
public static void main(String[] args) {
int[] arr = {3, 2, 1, 0, -1, -2, -3};
System.out.println("Before sort:");
printArray(arr);
insertSort(arr);
System.out.println("After sort:");
printArray(arr);
}
private static void insertSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
int currentValue = arr[i];
// 设置监视哨
int position = i;
for (int j = i - 1; j >= 0; j--) {
if (arr[j] > currentValue) {
arr[j + 1] = arr[j];
// 指针下移
position = position - 1;
} else {
break;
}
}
arr[position] = currentValue;
}
}
private static void printArray(int[] arr) {
System.out.print("{");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
if (i < arr.length - 1) {
System.out.print(", ");
}
}
System.out.println("}");
}
}
二分插入排序是对插入排序算法的一种改进,由于排序算法过程中,就是不断的依次将元素插入前面已排好序的序列中,而且前半部分为已排好序的数列,这样我们可以不用按顺序依次寻找插入点,可以采用折半查找的方法来加快寻找插入点的速度。
具体操作:在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为 arr[low],末元素设置为 arr[high],则轮比较时将待插入元素与 arr[m],其中 m=(low+high)/2 相比较,如果比参考元素小,则选择 arr[low] 到 arr[m-1] 为新的插入区域(即 high=m-1),否则选择 arr[m+1] 到 arr[high] 为新的插入区域(即 low=m+1),如此直至 low <= high 不成立,即将此位置之后所有元素后移一位,并将新元素插入 arr[high+1]。以下给出 Java 语言的实现代码
public class InsertSort {
public static void main(String[] args) {
int[] arr = {3, 2, 1, 0, -1, -2, -3};
System.out.println("Before sort:");
printArray(arr);
binaryInsertSort(arr);
System.out.println("After sort:");
printArray(arr);
}
private static void binaryInsertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int low = 0;
int high = i - 1;
while (low <= high) { //折半排序核心算法
int mid = (low + high) / 2;
if (temp < arr[mid]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
for (int j = i; j >= low + 1; j--) {
arr[j] = arr[j - 1];
}
arr[low] = temp;
}
}
public static void printArray(int[] arr) { //打印数组的工具方法
System.out.print("{");
for (int i = 0; i < arr.length; i++) { //循环打印数组中的元素
System.out.print(arr[i]);
if (i < arr.length - 1) {
System.out.print(", ");
}
}
System.out.println("}");
}
}
希尔排序是插入排序的一种,也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定的排序算法。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。该方法的实质是一种分组插入方法,比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换,其因 DL.Shell 于 1959 年提出而得名。
**基本思想:**先取一个小于 n 的整数 d1 作为第一个增量(一般初次取序列的一半为增量,以后每次减半,直到增量为 1),把文件的全部记录分组,所有距离为 d1 的倍数的记录放在同一个组中,先在各组内进行直接插入排序,然后取第二个增量 d2 < d1,重复上述的分组和排序,直至所取的增量 dt=1(dt < d(t-1) … < d2 < d1),即所有记录放在同一组中进行直接插入排序为止。以下给出 Java 语言的实现代码
public class ShellSort {
public static void main(String[] args) {
int[] arr = {3, 2, 1, 0, -1, -2, -3};
System.out.println("Before sort:");
printArray(arr);
shellSort(arr); //调用shellSort方法
System.out.println("After sort:");
printArray(arr);
}
private static void shellSort(int[] arr) {
int d = arr.length;
while (true) { //希尔排序核心算法
d = d / 2; //设置初始增量为数组长度的一半
for (int x = 0; x < d; x++) {
for (int i = x + d; i < arr.length; i = i + d) {
int temp = arr[i];
int j;
for (j = i - d; j >= 0 && arr[j] > temp; j = j - d) {
arr[j + d] = arr[j];
}
arr[j + d] = temp;
}
}
if (d == 1) { //增量为1时,排序完成
break;
}
}
}
private static void printArray(int[] arr) { //打印数组的工具方法
System.out.print("{");
for (int i = 0; i < arr.length; i++) { //循环打印数组中的元素
System.out.print(arr[i]);
if (i < arr.length - 1) {
System.out.print(", ");
}
}
System.out.println("}");
}
}
对于链表而言,要依次从待排序的链表中取出一个节点插入到已经排好序的链表中,也就是说,在单链表插入排序的过程中,原链表会截断成两部分,一部分是原链表中已经排好序的节点,另一部分是原链表中未排序的节点,这样就需要在排序的过程中设置一个当前节点,指向原链表未排序部分的第一个节点。单链表插入排序和数组插入排序的不同:数组插入排序是从排好序的部分的最后一个节点往前找,找到第一个比它小的数,然后插到其后面;而单链表只能从前往后遍历,找到第一个比当前节点大的值结束,因此在遍历已经排好序的链表部分的时候,需要两个指针,一个指针用于往前遍历(该指针假设为遍历指针),一个指针用于记录遍历指针指向的当前节点的前一个节点(该指针假设为遍历指针),这样当遍历指针找到第一个比待插入节点的值大的节点的时候,就可以将待插入节点插入到记录指针的后面。(之所以使用两个指针,是因为单链表不能反指)。
链表插入排序分两种情况,一种是当前节点的值比已经排好序的尾节点的值大,则直接将当前节点挂在已排序的节点即可;一种是当前节点值比已经排好序的尾节点的值小,则需将已排好序的链表部分从头到尾遍历,找到第一个比当前节点值大的节点,插入到其前面即可。因为可能待插入的节点可能在第一个节点的前面,因此另外创建一个头结点,指向已经排好序的链表的第一个节点。这样可以每次插入新的节点的时候,将上面所提到的记录节点初始化为新创建的头结点,这样便于在第一个节点前面插入新节点。
链表插入排序的 Java 代码具体实现如下,即在创建 LinkInsertSort 类之前,需要先实现 Element 和 StaticLinkedList 这两个类,以便于操作:
public class Element {
private int key = 0;
private int link = 0;
private String info = null;
public Element(int key, int link, String info){
this.key = key;
this.link = link;
this.info = info;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public int getLink() {
return link;
}
public void setLink(int link) {
this.link = link;
}
}
public class StaticLinkedList {
private Element [] element= null;
private int maxSize = 0;
private int currentSize = 0;
public StaticLinkedList(int size){
this.maxSize = size;
this.currentSize = 0;
this.element = new Element[size];
}
public Element getNextElement(int index){
return getElement()[index];
}
public int getMaxSize() {
return maxSize;
}
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
public int getCurrentSize() {
return currentSize;
}
public void setCurrentSize(int currentSize) {
this.currentSize = currentSize;
}
public Element[] getElement() {
return element;
}
public void setElement(Element[] element) {
this.element = element;
}
}
public class LinkInsertSort {
public static void main(String[] args) {
int[] source = {10, 2, 23, 12, 18, 12, 17, 5, 32, 24, 41, 9};
System.out.println("source:");
linkInsertSort(source);
}
public static void linkInsertSort(int[] source) {
StaticLinkedList linkList = new StaticLinkedList(source.length + 1);
linkList.getElement()[0] = new Element(Integer.MAX_VALUE, 1, "Head"); //head node
for (int i = 1; i < linkList.getMaxSize(); i++) { //initial
if (i == 12) {
linkList.getElement()[i] = new Element(source[i - 1], 0, "");
} else {
linkList.getElement()[i] = new Element(source[i - 1], i + 1, "");
}
linkList.setCurrentSize(i + 1);
}
int next = linkList.getElement()[0].getLink();
int count = 1;
while (count < linkList.getCurrentSize()) {
Element element = linkList.getNextElement(next);
System.out.print(element.getKey() + ",");
next = element.getLink();
count++;
}
System.out.println("");
int pre = 0, current = 0;
long startTime = System.currentTimeMillis();
for (int i = 1; i < linkList.getCurrentSize(); i++) {
pre = 0;
current = linkList.getElement()[0].getLink();
while (linkList.getNextElement(current).getKey() <= linkList.getElement()[i].getKey()) {
pre = current;
current = linkList.getElement([current].getLink();
}
linkList.getElement()[i].setLink(current);
linkList.getElement()[pre].setLink(i);
}
long endTime = System.currentTimeMillis();
System.out.println("runtime:" + (endTime - startTime) + "ms");
next = linkList.getElement()[0].getLink();
count = 1;
while (count < linkList.getCurrentSize()) {
Element element = linkList.getNextElement(next);
System.out.print(element.getKey() + ",");
next = element.getLink();
count++;
}
System.out.println("");
}
}