初级排序算法
一、排序算法的辅助方法代码示例
public class SortUtil {
// 对元素进行比较
public static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
// 将元素交换位置
public static void exch(Comparable[] arr, int i, int j) {
Comparable t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// 展示数组
public static void show(Comparable[] arr) {
for (Comparable comparable : arr) {
System.out.println(comparable + "");
}
}
// 判断数组是否有序
public static boolean isSorted(Comparable[] arr) {
for (int i = 1; i < arr.length; i++) {
if (less(arr[i], arr[i - 1])) {
return false;
}
}
return true;
}
}
二、选择排序算法
(一)、选择排序算法的概念
选择排序就是重复“从待排序的数据中寻找最小值,将其与序列最左边的数字进行交换。”这一操作的算法。在序列中寻找最小值时使用的是线性查找。
选择排序的内循环知识再比较当前元素与目前已知的最小元素(以及将当前索引加1和检查是否代码越界)。交换元素的代码写再内循环之外,每次交换都能排定一个元素,因此交换的总次数是N。
所以算法的时间效率取决于比较的次数。
选择排序有两个很鲜明的特点:
1、运行时间与输入无关。为了找出最小的元素而扫描一遍数组并不能为下一遍扫描提供扫描信息。这种性质在某些情况下是缺点,因为使用选择排序的人可能会惊讶的发现,一个已经有序的数组或是主键全部相等的数组和一个元素随机排列的数组所用的排序时间是一样长。其他的算法会更善于利用输入的初始状态。
2、数据移动是最少的。每次交换都会改变两个数组元素的值,因此选择排序用了N次交换——交换次数和数组的大小是线性关系。(其他大部分的增长数量级都是线性对数或是平方级别)
(二)、选择排序算法的流程图
该流程图内容引用自《我的第一本算法书》
(三)、选择排序算法的代码示例
public class Selection {
public static void sort(Comparable[] arr) {
// 数组长度
int n = arr.length;
for (int i = 0; i < n; i++) {
// 最小元素的索引
int min = i;
for (int j = i + 1; j < n; j++) {
if (SortUtil.less(arr[j], arr[min])) {
min = j;
}
SortUtil.exch(arr, i, min);
}
}
}
}
(四)、选择排序算法的测试用例
public class SelectionTest {
public static void main(String[] args) {
// 空数组测试
emptyTest();
// 无序数组测试
disorderTest();
// 有序数组测试
orderTest();
}
// 空数组
private static void emptyTest() {
String[] arr = new String[0];
Selection.sort(arr);
}
// 无序数组
private static void disorderTest() {
String[] arr = new String[]{
"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"
};
Selection.sort(arr);
}
// 有序数组
private static void orderTest() {
String[] arr = new String[]{
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"
};
Selection.sort(arr);
}
}
(五)、选择排序算法的基本性质
- 对于长度为N的数组,选择排序需要大约N^2/2次比较和N次交换
在下方的N*N的表格来表示排序的轨迹,其中每个非灰色字符都表示一次比较。表格中大约一半的元素不是灰色的——即对角线和其上部分的元素。对角线上的每个元素对应着一次交换。通过上述代码可知,0——N-1的任意i都会进行一次交换和N-1-i次比较,因此总共有N次交换以及(N-1)+(N-1)+…+2+1~N^2/2次比较。
该图片引用《算法》(第四版)
三、插入排序算法
(一)、插入排序算法的概念
插入排序是一种从序列左端开始依次对数据进行排序的算法。在排序过程中,左侧的数据陆续归位,而右侧留下的就是还未被排序的数据。插入排序的思路就是从右侧的未排序区域内取出一个数据,然后将它插入到已排序区域内合适的位置上。
与选择排序一样,当前索引左边的所有元素都是有序的,但它们的最终位置还不确定,为了给更小的元素腾出空间,它们可能会被移动。但是当索引到达数组的右端时,数组排序就完成了。
和选择排序不同的是插入排序所需的时间取决于输入中元素的初始顺序。例如:对一个很大且其中的元素已经有序(或接近有序)的数组进行排序将会比对随机顺序的数组或是逆序数组进行排序要快得多。
(二)、插入排序算法的流程图
该流程图内容引用自《我的第一本算法书》
(三)、插入排序算法的代码示例
public class Insertion {
public static void sort(Comparable[] arr) {
// 将arr[] 按升序排列
int n = arr.length;
for (int i = 1; i < n; i++) {
// 将arr[i]插入到arr[i-1]、arr[i-2]、arr[i-3]...之中
for (int j = i; j > 0 && SortUtil.less(arr[j], arr[j - 1]); j--) {
SortUtil.exch(arr, j, j - 1);
}
}
}
}
(四)、插入排序算法的测试用例
public class InsertionTest {
public static void main(String[] args) {
// 空数组测试
emptyTest();
// 无序数组测试
disorderTest();
// 有序数组测试
orderTest();
// 部分有序数组测试
partiallyOrderedTest();
}
// 空数组
private static void emptyTest() {
String[] arr = new String[0];
Insertion.sort(arr);
}
// 无序数组
private static void disorderTest() {
String[] arr = new String[]{
"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"
};
Insertion.sort(arr);
}
// 有序数组
private static void orderTest() {
String[] arr = new String[]{
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"
};
Insertion.sort(arr);
}
// 部分有序的数组
private static void partiallyOrderedTest(){
String[] arr = new String[]{
"E", "X", "A", "M", "P", "L", "E"
};
Insertion.sort(arr);
}
}
(五)、插入排序算法的基本性质
1、随机排序的数组的分析
- 对于随机排列的长度为N且主键不重复的数组,平均情况下插入排序需要N^2/4次比较以及N2/4次交换。最坏情况下需要~N2/2次比较和~N^2/2次交换,最好情况下需要N-1次比较和0次交换。
在下方N*N的轨迹表可以很容易就得到交换和比较的次数。最坏情况下对角线之下所有元素都需要移动位置,最好情况下都不需要。对于随机排列的数组,在平均情况下每个元素都可能向后移动半个数组的长度,因此交换总数是对角线之下的元素总数的二分之一。
比较的总次数是交换的次数加上一个额外的项,该项为N减去被插入的元素正好是已知的最小的元素的次数。在最坏情况下(逆序数组)。这一项对于总数可以忽略不计,最好情况下(数组已经有序)这一项等于N-1。
该图片引用《算法》(第四版)
2、部分有序的数组
(1)、典型的部分有序数组
1、数组中每个元素距离它的最终位置都不远。
2、一个有序的大数组接一个小数组。
3、数组中只有几个元素的位置不正确。
(2)、部分有序数组的分析
- 插入排序需要的交换操作和数组中倒置的数量相同,需要的比较次数大于等于倒置的数量,小于等于倒置的数量加上数组的大小再减一。
每次交换都改变了两个顺序颠倒的元素的位置,相当于减少了一对倒置,当倒置数量为0时,排序就完成了。每次交换都对应着一次比较,且1到N-1之间的每个i都可能需要一次额外的比较(在a[i]没有达到数组的左端时)
四、希尔排序算法
(一)、希尔排序算法的概念
希尔排序:基于插入排序的快速排序算法。对于大规模乱序数组插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点地从数组的一段移动到另一端。例如:如果主键最小的元素正好在数组的尽头,要将它挪到正确的位置就需要N-1次移动。希尔排序为了加快速度简单地改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将举办有序的数组排序。
(二)、希尔排序算法的代码示例
public class Shell {
public static void sort(Comparable[] arr) {
// 将a[]按升序排列
int n = arr.length;
int h = 1;
while (h < n / 3) {
h = 3 * h + 1;
}
while (h >= 1) {
for (int i = h; i < n; i++) {
// 将a[i]插入到a[i-h],a[i-2*h],a[i-3*h]...之中
for (int j = i; j >= h && SortUtil.less(arr[j], arr[j - h]); j -= h) {
SortUtil.exch(arr, j, j - h);
}
}
h = h / 3;
}
}
}
(三)、希尔排序算法的测试用例
public class ShellTest {
public static void main(String[] args) {
// 空数组测试
emptyTest();
// 非空数组测试
test();
}
// 空数组
private static void emptyTest() {
String[] arr = new String[0];
Shell.sort(arr);
}
// 非空数组
private static void test() {
String[] arr = new String[]{
"S", "H", "E", "L", "L", "S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"
};
Selection.sort(arr);
}
}
(四)、希尔排序算法的基本性质
- 使用递增序列1,4,13,40,121,364…的希尔排序所需的比较次数不会超出N的若干倍乘以递增序列的长度。