1. 前言
本文是基于本人在《算法(第4版)》的学习过程中做的笔记
接下来会介绍两种简单的初级排序算法:
- 选择排序
- 插入排序
首先是接下来要学习的排序算法的一套通用模板
/**
排序算法模板
**/
public class Example {
public static void sort(Comparable[] a) {//具体算法见下文
}
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
private static void show(Comparable[] a) {
for(int i=0; i<a.length; i++)
StdOut.print(a[i] + " ");
StdOut.println();
}
public static boolean isSorted(Comparable[] a) {
for(int i=1; i<a.length; i++) {
if(less(a[i], a[i-1])) //默认升序排列
return false;
}
return true;
}
}
2. 选择排序
2.1 简述
选择排序是一种非常简单直观的排序算法,它的基本思想是:
- 在当前的比较轮数 i 中,找到从数组下标 i+1 到 数组末尾的最小值,并与 当前的元素 a[i]进行比较,若小于当前元素a[i],则进行交换
也就是说在每一轮比较的时候,我们要做的就是找到当前元素及后所有元素的最小值,并将该最小值放入正确的位置
2.2 实现
/**
选择排序核心代码
*/
public static void sort(Comparable[] a) {
int N = a.length;
for(int i=0; i<N; i++) { //升序排列
int min = i;
for(int j=i+1; j<N; j++) {
if(less(a[j], a[min])) //每次比较两个元素大小,找到当前的最小元素
min = j;
}
exch(a, i, min);
}
}
从代码中我们可以很自然地体会当中的思想,接下来我们看下选择排序的轨迹,更加直观地观察它是怎么运作的:
根据运行轨迹我们可以发现:选择排序运行的过程中,不会去访问当前索引左侧的元素
2.3 分析
显然可以看出,在有n个元素选择排序的最坏情况下:
我们总共需要n次的交换(n次的外层循环中,每次都要交换),以及大约
n
2
/
2
n^2/2
n2/2 次的比较(最坏情况下,第一轮需要n-1次比较,第二轮需要n-2次比较,…,第n-1轮需要1次比较,而最后一轮不执行内层循环)
2.4 小结
总的来说,选择排序的思想便于理解,实现方便,并且它还有两个很鲜明的特点
- 运行时间与输入无关。不管如何,选择排序都会去扫描一边数组找出最小的元素,当使用选择排序的人可能会吃惊于:相同长度的有序数组和随机数组的排序时间一样!
- 较少的数据交换。每一个外层循环我们才会去交换一次数据,总长度为n的数组总共就交换了n次,因此选择排序中数组交换所带来的开销是较小的
3. 插入排序
3.1 简述
插入排序的思想其实很简单:
- 想象我们在玩扑克牌的过程中,对于一副抓到的手牌会进行理牌,而插入排序就是对这个理牌的过程的模拟:将第 j 张牌顺到前面合适的位置
例如:我们抓到了一手牌——5 、9 、8 、K 、7 、3
- 首先,我们很自然地会把8先挪到9的前面,这时牌序为5 、8 、9 、K 、7 、3
- 然后,将7在插入8的前面,这时为5 、7 、8 、9 、K 、3
- 最后,将A插入到最左端,得到了正确的牌序:3 、5 、7 、8 、9 、K
3.2 实现
/**
插入排序核心代码
*/
public static void sort(Comparable[] a) {
int N = a.length;
for (int i = 1; i < N; i++) {
for (int j = i; j > 0 && less(a[i], a[j - 1]); j--) { // 将a[j]置入前面序列中合适的位置
exch(a, j, j - 1);
}
}
}
我们再来观察一下插入排序的运行轨迹:
根据运行轨迹我们可以发现:插入排序运行的过程中,不会去访问当前索引右侧的元素
3.3 分析
我们先从最好情况开始分析:
- 在长度为n的数组中,当所有的元素都已升序排列后,插入排序便无需进行交换操作,仅仅需要n-1次的比较(外层循环执行了n-1次)
接下来我们看看最坏的情况:
- 所有元素都逆序排列时,第一轮需要1次比较、1次交换;第二轮需要2次比较、两次交换,…,第n-1轮需要n-1次比较、n-1次交换
3.4 小结
总的来说,插入排序对于部分有序的数组十分高效,也很适合小规模的数组