冒泡排序
描述: 通过两两比较相邻元素,如果存在逆序就进行交换,从而使小元素向前移大元素向后沉。具体如下:
- 设初始n个元素,首先第1和第2个元素比较,若逆序就交换;然后比较第2和第3个元素,以此类推直到第n-1和第n个元素;此时完成第一趟比较,作用是使最大元素被交换到最后也就是第n个位置上。
- 然后进行第二趟比较,对前n-1个元素进行同样操作,使次大元素被交换到第n-1个位置上。
- 以此类推,直到循环结束或某趟比较中没有发生元素交换。
java代码
public void bubbleSort(int[] arr) {
int temp;
// 外层循坏控制比较趟数,趟数 = 元素个数 - 1
for (int i = 1; i < arr.length; i++) {
// 内层循环控制每趟比较次数,次数 = 元素个数 - 趟次
for (int j = 0; j < arr.length - i; j++) {
// 前面元素大于后面元素时交换位置
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
优化: 相邻元素的两两交换中,除了使最大元素被交换到最后,同时也使所有较大元素都发生了相对后移,这样就可能在n数趟的交换之后完成排序程序可以提前结束。这时可以设置一个标识变量供外层循环判断是否提前结束程序。代码:
public void bubbleSort(int[] arr) {
int temp;
boolean flag; // 标识变量
for (int i = 1; i < arr.length; i++) {
flag = true;
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = false; // 发生交换标识变量置反
}
}
if (flag) { // 判断是否结束
break;
}
}
}
算法分析
- 时间复杂度 O ( n 2 ) Ο(n^2) O(n2),最好情况初始完全有序,n个元素只需比较n-1次,不移动;最坏情况完全逆序,n个元素比较n-1趟,首趟比较n-1次,后面每趟都比前面少比较一次,一共比较了 ∑ i = 1 n − 1 i ≈ n 2 2 \sum_{i=1}^{n-1}i \approx \frac{n^2}{2} ∑i=1n−1i≈2n2次,而每次比较之后元素都会交换位置,每次交换移动3次,共移动 3 n 2 2 \frac{3n^2}{2} 23n2次,所以平均情况下比较 n 2 4 \frac{n^2}{4} 4n2次,移动 3 n 2 4 \frac{3n^2}{4} 43n2次,时间复杂度 O ( n 2 ) Ο(n^2) O(n2)。
- 空间复杂度 O ( 1 ) Ο(1) O(1),相邻元素交换时只需要一个辅助空间temp。
- 排序稳定,内层for循环中的if语句可以看出。
- 顺序结构链式结构都适用,其实可以发现只要是对相邻元素进行操作的排序都可以用于链式结构,只是将顺序结构中的交换位置变成修改指针变量。
- 平均情况下与直接插入排序相比可能增加少量的比较次数,但移动次数增加较多,所以平均时间性能比直接插入排序低,当初始元素无序程度较高且元素个数n较大时,不宜采用。
快速排序
描述: 快速排序是由冒泡排序改进而来,因为克服了冒泡排序每次元素交换只能消除一个逆序对的不足,所以大大加快了排序速度,快速排序其实就是给是给基准元素找正确的索引位置。算法步骤如下:
- 假设有下面一组初始元素,开始时将序列第一个元素17作为基准值保存在临时变量num中,low、high指针分别指向序列首元素和尾元素。
- 因为基准元素在最左侧,所以先从最右侧开始扫描,high位置15<17,将15取出放入low位置,结果如下:
- 发生元素移动后再从左侧开始扫描,low位置15<17,low指针右移;low位置20>17,将low位置值取出放到high位置,结果如下:
- 从右侧扫描,high位置20>17,high指针左移;high位置17=17,high左移;high位置7<17,将high位置值放入low位置。结果如下:
- 从左侧扫描,low位置7<17,low右移;low位置3<17,low右移,此时low=high,结束本趟循环。可以发现此时low、high指针所指位置就是基准值17需要放入的地方。
- low(high)指针最后所在位置将原序列划分为左右两个子序列,分别对子序列递归调用以上步骤,直到序列元素个数为1,也就是low=high。
java代码
import java.util.Arrays;
public class QuickSort {
// 将序列中选取的基准值插入到正确位置并返回位置下标
private static int getIndex(int[] arr, int low, int high) {
int num = arr[low];
while (low < high) {
/*
* 外层循环已经限制low<high,为什么内层还要限制可能有人会有疑惑,
* 这是因为内层对low、high指针进行了操作,若不加以限制可能会出现
* high<=low时内层循坏继续的情况,导致返回的下标可能是错的。
*/
while (low < high && arr[high] >= num) {
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= num) {
low++;
}
arr[high] = arr[low];
}
arr[low] = num;
return low;
}
// 对左右两边子序列递归排序
private static void qSort(int[] arr, int low, int high) {
if (low < high) {
int index = getIndex(arr, low, high);
qSort(arr, low, index - 1);
qSort(arr, index + 1, high);
}
}
// 给外部调用提供一个接口
public static void quickSort(int[] arr) {
qSort(arr, 0, arr.length - 1);
}
// 测试
public static void main(String[] args) {
int[] arr = new int[1000];
for (int i = 0; i < 1000; i++) {
arr[i] = (int) (Math.random() * 1000);
}
quickSort(arr);
System.out.println(Arrays.toString(arr));
}
}
算法分析
- 平均情况下时间复杂度 O ( n log 2 n ) Ο(n\log_2{n}) O(nlog2n),也是最好情况的时间复杂度;最坏情况初始元素有序,每次划分都有个子序列为空,其时间复杂度 O ( n 2 ) Ο(n^2) O(n2)。
- 快速排序是递归的,执行时需要一个栈存放相应数据,最好情况下以类似完全二叉树形式递归,空间复杂度 O ( log 2 n ) Ο(\log_2{n}) O(log2n)。最坏情况初始元素有序,以类似斜树形式递归,空间复杂度 O ( n ) Ο(n) O(n)。
- 排序不稳定,因为元素是跳跃移动的。
- 适用于顺序结构。
- 在元素个数n较大时,平均情况下快速排序是所有内部排序算法中速度最快的。
优化: 为了避免时间复杂度中所说的最坏的情况,可以采取“三者取中”原则,就是每次递归前先将序列首中尾三者的中间数作为基准值,交换到序列的第一个位置。同时在递归过程中当子序列长度小于某一临界值时可以改为直接插入排序,使排序更快速。