排序使我们实际开发中最常使用到的几个算法之一,按照如果按照排序过程中依据的原则对内部排序进行分类,则大致上可以分为插入排序、交换排序、选择排序、归并排序等排序方法。让我们首先看看插入排序的算法有哪些,以及他们的具体实现。插入排序的基本排序思想是:逐个考察每个待排序元素,将每一个新元素插入到前面已经排好序的序列中适当的位置上,使得新序列仍然是一个有序序列。在这一类排序中主要介绍三种排序方法:直接插入排序、折半插入排序和希尔排序。
1.直接插入排序
a.算法描述
直接插入排序是一种最简单的插入排序方法,它的基本思想是:仅有一个元素的序列总是有序的,因此,对n个记录的序列,可从第二个元素开始直到第n个元素,逐个向有序序列中执行插入操作,从而得到n个元素按关键字有序的序列。一般来说,在含有j-1 个元素的有序序列中插入一个元素的方法是:从第j-1 个元素开始依次向前搜索应当插入的位置,并且在搜索插入位置的同时可以后移元素,这样当找到适当的插入位置时即可直接插入元素。以关键字序列{ 26 , 53 , 48 , 11 , 13 , 48, 32 , 15}为例,直接插入排序的过程如下所示。
b:算法实现
public void insertSort(int[] r, int low, int high) { for (int i = low + 1; i <= high; i++) if (compare(r[i], r[i - 1])) { // 小于时,需将r[i]插入有序表 int temp = r[i]; r[i] = r[i - 1]; int j = i - 2; for (; j >= low && compare(temp, r[j]); j--) r[j + 1] = r[j]; // 记录后移 r[j + 1] = temp; // 插入到正确位置 } }
【效率分析】
空间效率:仅使用一个辅存单元。
时间效率:假设待排序的元素个数为n,则向有序表中逐个插入记录的操作进行了n-1趟,每趟操作分为比较关键码和移动记录,而比较的次数和移动记录的次数取决于待排序列按关键码的初始排列。直接插入排序的时间复杂度为O(n²)
c:实现举例
StraightInsertionSort.java
package com.test.sort.insertion; public class StraightInsertionSort { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("直接插入排序排序功能实现》》》》"); int[] arr = { 23, 54, 6, 2, 65, 34, 2, 67, 7, 9, 43 }; StraightInsertionSort sort = new StraightInsertionSort(); System.out.println("排序之前序列:"); sort.printArr(arr); sort.insertSort(arr, 0, arr.length - 1); System.out.println("排序之后序列:"); sort.printArr(arr); } public void insertSort(int[] r, int low, int high) { for (int i = low + 1; i <= high; i++) if (compare(r[i], r[i - 1])) { // 小于时,需将r[i]插入有序表 int temp = r[i]; r[i] = r[i - 1]; int j = i - 2; for (; j >= low && compare(temp, r[j]); j--) r[j + 1] = r[j]; // 记录后移 r[j + 1] = temp; // 插入到正确位置 } } public boolean compare(int paramA, int paramB) { if (paramA < paramB) { return true; } else { return false; } } /** * 依次打印出数组元素 */ public void printArr(int[] arr) { if (arr != null) { for (int temp : arr) { System.out.print(temp + " "); } System.out.println(); } } }
d:结果输出
2.希尔排序
a.算法描述
希尔排序又称为“缩小增量排序”,它也是一种属于插入排序类的排序方法,是一种对直接插入排序的改进,但在时间效率上却有较大的改进。从对直接插入排序的分析中知道,虽然直接插入排序的时间复杂度为O(n²),但是在待排序元素序列有序时,其时间复杂度可提高至O(n)。由此可知在待排序元素基本有序时,直接插入排序的效率可以大大提高。从另一方面看,由于直接插入排序方法简单,则在n值较小时效率也较高。希尔排序正是从这两点出发,对直接插入排序进行改进而得到的一种排序方法。
希尔排序的基本思想是:首先将待排序的元素分为多个子序列,使得每个子序列的元素个数相对较少,对各个子序列分别进行直接插入排序,待整个待排序序列“基本有序”后,再对所有元素进行一次直接插入排序。根据上述排序思想,下面我们给出希尔排序的排序过程:
⑴选择一个步长序列t1,t2,…,tk,其中ti>tj(i<j),tk=1;
⑵按步长序列个数k,对待排序元素序列进行k趟排序;
⑶每趟排序,根据对应的步长ti,将待排序列分割成ti个子序列,分别对各子序列进行直接插入排序。当步长因子为1时,所有元素作为一个序列来处理,其长度为n。以关键字序列{ 26 , 53 , 67 , 48 , 57 , 13 , 48, 32 , 60 , 50}为例,假设选择的步长序列为{5 , 3 , 1},则希尔排序的过程如图9-2所示。因为步长序列长度为3,因此对待排序序列一共需要进行3趟排序。首先,第一趟排序中将关键字序列分成5个子序列{26 , 13},{53 , 48},{67 , 32},{48 , 60},{57 , 50},对它们分别进行直接插入排序,结果如图所示。然后,进行第二趟希尔排序,此时步长为3,则将关键字序列分成3个子序列{13 , 48 , 53 , 57},{48, 50 , 67},{32 , 26 , 60},对它们进行直接插入排序后的结果如图所示。最后,对整个序列进行一趟直接插入排序,此时得到一个关键字有序的序列,希尔排序结束。
b.算法实现
public void shellSort(int[] r, int low, int high, int[] delta) { for (int k = 0; k < delta.length; k++) shellInsert(r, low, high, delta[k]); // 一趟步长为delta[k]的直接插入排序 } private void shellInsert(int[] r, int low, int high, int deltaK) { for (int i = low + deltaK; i <= high; i++) if (compare(r[i], r[i - deltaK])) { // 小于时,需将r[i] 插入有序表 int temp = r[i]; int j = i - deltaK; for (; j >= low && compare(temp, r[j]); j = j - deltaK) r[j + deltaK] = r[j]; // 记录后移 [j]; r[j + deltaK] = temp; // 插入到正确位置 } }
【效率分析】
空间效率:仅使用一个辅存单元。
时间效率:假设待排序的元素个数为n,则向有序表中逐个插入记录的操作进行了n-1趟,每趟操作分为比较关键码和移动记录,而比较的次数和移动记录的次数取决于待排序列按关键码的初始排列。直接插入排序的时间复杂度为O(n²)
c.使用示例
HashInsertSort.java
package com.test.sort.insertion; public class HashInsertSort { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("希尔排序功能实现》》》》"); int[] arr = { 23, 54, 6, 2, 65, 34, 2, 67, 7, 9, 43 }; int[] delta = {5,3,1}; HashInsertSort sort = new HashInsertSort(); System.out.println("排序之前序列:"); sort.printArr(arr); sort.shellSort(arr, 0, arr.length - 1,delta); System.out.println("排序之后序列:"); sort.printArr(arr); } public void shellSort(int[] r, int low, int high, int[] delta) { for (int k = 0; k < delta.length; k++) shellInsert(r, low, high, delta[k]); // 一趟步长为delta[k]的直接插入排序 } private void shellInsert(int[] r, int low, int high, int deltaK) { for (int i = low + deltaK; i <= high; i++) if (compare(r[i], r[i - deltaK])) { // 小于时,需将r[i] 插入有序表 int temp = r[i]; int j = i - deltaK; for (; j >= low && compare(temp, r[j]); j = j - deltaK) r[j + deltaK] = r[j]; // 记录后移 [j]; r[j + deltaK] = temp; // 插入到正确位置 } } public boolean compare(int paramA, int paramB) { if (paramA < paramB) { return true; } else { return false; } } /** * 依次打印出数组元素 */ public void printArr(int[] arr) { if (arr != null) { for (int temp : arr) { System.out.print(temp + " "); } System.out.println(); } } }
d.结果输出
3.折半插入排序
a.算法描述
直接插入排序算法简便、容易实现。当待排序元素的数量n很小时,这是一种较好的排序方法,但是通常待排序元素数量n很大,则不宜采用直接插入排序方法,此时需要对直接插入排序进行改进。直接插入排序的基本操作是向有序序列中插入一个元素,插入位置的确定是通过对有序序列中元素按关键字逐个比较得到的。既然是在有序序列中确定插入位置,则可以不断二分有序序列来确定插入位置,即搜索插入位置的方法可以使用折半查找实现。
b.算法实现
public void binInsertSort(int[] r, int low, int high) { for (int i = low + 1; i <= high; i++) { int temp = r[i]; // 保存待插入元素 int hi = i - 1; int lo = low; // 设置初始区间 while (lo <= hi) { // 折半确定插入位置 int mid = (lo + hi) / 2; if (compare(temp, r[mid])) hi = mid - 1; else lo = mid + 1; } for (int j = i - 1; j > hi; j--) r[j + 1] = r[j]; // 移动元素 r[hi + 1] = temp; // 插入元素 }// for }
【效率分析】
空间效率:仅使用一个辅存单元。
时间效率:折半插入排序仅减少了元素的比较次数,但是并没有减少元素的移动次数,折半插入排序的时间复杂度为O(n²)。
c.应用举例
BinaryInsertSort.java
package com.test.sort.insertion; public class BinaryInsertSort { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("折半插入排序排序功能实现》》》》"); int[] arr = { 23, 54, 6, 2, 65, 34, 2, 67, 7, 9, 43 }; BinaryInsertSort sort = new BinaryInsertSort(); System.out.println("排序之前序列:"); sort.printArr(arr); sort.binInsertSort(arr, 0, arr.length - 1); System.out.println("排序之后序列:");; sort.printArr(arr); } public void binInsertSort(int[] r, int low, int high) { for (int i = low + 1; i <= high; i++) { int temp = r[i]; // 保存待插入元素 int hi = i - 1; int lo = low; // 设置初始区间 while (lo <= hi) { // 折半确定插入位置 int mid = (lo + hi) / 2; if (compare(temp, r[mid])) hi = mid - 1; else lo = mid + 1; } for (int j = i - 1; j > hi; j--) r[j + 1] = r[j]; // 移动元素 r[hi + 1] = temp; // 插入元素 }// for } public boolean compare(int paramA, int paramB) { if (paramA < paramB) { return true; } else { return false; } } /** * 依次打印出数组元素 */ public void printArr(int[] arr) { if (arr != null) { for (int temp : arr) { System.out.print(temp + " "); } System.out.println(); } } }
d.结果输出