参考书目:《大话数据结构》
本文涉及的排序算法:
1.冒泡排序(优化版)
2.简单选择排序
3.直接插入排序
4.希尔排序
5.堆排序
6.归并排序(递归版)+归并排序(递归优化版)
7.快速排序+快速排序(四度优化版)
0.基本思想:一种交换排序,两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录位置。
2.代码实现:
public class BubbleSort {
public static void sort(int[] a) {
int i, j;
boolean flag = true;
int n = a.length - 1;
for (i = 1; i < n && flag; i++) {
flag = false;
for (j = n - 1; j >= i; j--) {
if (a[j] > a[j + 1]) {
swap(a, j, j + 1);
flag = true;
}
}
}
}
private static void swap(int[] a, int x, int y) {
int temp;
temp = a[x];
a[x] = a[y];
a[y] = temp;
}
}
3.分析:
0.基本思想:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换。
1.思路图解:
2.代码实现:
public class SelectSort {
public static void sort(int[] a) {
int i, j;
int n = a.length - 1;
int min;
for (i = 1; i < n; i++) {
min = i;
for (j = i + 1; j <= n; j++) {
if (a[j] < a[min]) {
min = j;
}
}
if (i != min) {
swap(a, i, min);
}
}
}
private static void swap(int[] a, int x, int y) {
int temp;
temp = a[x];
a[x] = a[y];
a[y] = temp;
}
}
3.分析:
0.基本思想:将一个记录插入到以及排好序的有序表中,从而得到一个新的、记录数增1的有序表。
1.思路图解:
2.代码实现:
public class InterSort {
public static void sort(int[] a) {
int i, j;
int n = a.length - 1;
for (i = 1; i < n; i++) {
if (a[i] > a[i + 1]) {
a[0] = a[i + 1]; // 设置哨兵
for (j = i; a[j] > a[0]; j--) {
a[j + 1] = a[j];
}
a[j + 1] = a[0];
}
}
}
}
3.分析:
0.基本思想:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,所有记录恰被分成一组,算法便终止。因此,将关键字较小的记录,不是一步一步地往前挪动,而是跳跃式地往前移,从而使得每完成一轮循环后,整个序列就朝着有序坚实地迈进一步。
1.思路图解:
2.代码实现:
public class ShellSort {
public static void sort(int[] a) {
int i, j;
int n = a.length - 1;
int increment = a.length - 1;
do {
increment = increment / 3 + 1;
for (i = increment + 1; i <= n; i++) {
if (a[i - increment] > a[i]) {
a[0] = a[i]; // 设置哨兵
for (j = i - increment; j > 0 && a[0] < a[j]; j -= increment) {
a[j + increment] = a[j];
}
a[j + increment] = a[0];
}
}
} while (increment > 1);
}
}
3.分析:
0.基本思想:将待排序的序列构造成一个大顶锥(适用于从小到大排序),此时整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余n-1个序列重新构造成一个堆,这样就会得到n个元素的次大值。如此反复执行,便能得到一个有序序列。
1.思路图解:
2.代码实现:
public class HeapSort {
public static void sort(int[] a) {
int i;
int n = a.length - 1;
for (i = n / 2; i > 0; i--) {
//构建大顶堆
HeapAdjust(a, i, n);
}
HeapSort(a);
}
private static void HeapSort(int[] a) {
int i;
int n = a.length - 1;
for (i = n; i > 1; i--) {
swap(a, 1, i);
HeapAdjust(a, 1, i - 1); //将a[1...i-1]重新调整为大顶堆
}
}
private static void HeapAdjust(int[] a, int s, int m) {
int j;
int temp = a[s];
for (j = 2 * s; j <= m; j *= 2) {
// 从上到下,从左到右的非叶子结点
if (j < m && a[j] < a[j + 1]) {
j++; // j为关键字中较大记录的下标
}
if (temp >= a[j]) {
break; // 不需要调整,所以a[j]本就应插入在位置s上
}
a[s] = a[j];
s = j;
}
a[s] = temp; // 将一开始此结点的值赋值给目前的s下标结点的值,达到结点交换的目的,从而构成此局部的堆
}
private static void swap(int[] a, int x, int y) {
int temp;
temp = a[x];
a[x] = a[y];
a[y] = temp;
}
}
3.分析:
0.基本思想:初始序列含有n个元素,则可看成是n个有序子序列,每个子序列的长度为1,然后两两归并,得到Math.ceil(n/2)个长度为2或1的有序子序列;再两两归并,…,如此重复,直到得到一个长度为n的有序序列位置,这种排序方法称为2路归并排序。
1.思路图解:
2.代码实现:
public class MergeSort {
public static void sort(int[] a) {
int n = a.length - 1;
MSort(a, a, 1, n);
}
//将SR[s..n]归并为有序的TR1[s..n]
private static void MSort(int[] SR, int[] TR1, int s, int n) {
int m;
int[] TR2 = new int[n + 1]; // SR, TR1, TR2等长
if (s == n) {
TR1[s] = SR[s];
} else {
m = (s + n) / 2; // 将SR[s..n]平均分为SR[s..m]和SR[m+1..n]
MSort(SR, TR2, s, m); // 递归将SR[s..m]归并为有序的TR2[s..m]
MSort(SR, TR2, m + 1, n); // 递归将SR[m+1..n]归并为有序的TR2[m+1..n]
Merge(TR2, TR1, s, m, n); // 将TR2[s..m]和TR2[m+1..n]归并到TR1[s..n]
}
}
// 将有序的SR[s..m]和SR[m+1..n]归并为有序的TR[i..n]
private static void Merge(int[] SR, int[] TR, int s, int m, int n) {
int j, k, l; // k为左块的起始下标,j为右块的起始下标
for (k = s, j = m + 1; s <= m && j <= n; k++) {
//SR中记录由小到大归并入TR
if (SR[s] < SR[j]) {
TR[k] = SR[s++];
} else {
TR[k] = SR[j++];
}
}
if (s <= m) {
for (l = 0; l <= m - s; l++) {
TR[k + l] = SR[s + l]; // 将剩余的SR[s..m]复制到TR
}
}
if (j <= n) {
for (l = 0; l <= n - j; l++) {
TR[k + l] = SR[j + l]; // 将剩余的SR[j..m]复制到TR
}
}
}
}
2.5.代码实现(优化版):
public class MergeSort2 {
public static void sort(int[] a) {
int n = a.length - 1;
MSort(a);
}
private static void MSort(int[] SR) {
int n = SR.length - 1;
int[] TR = new int[n + 1];
int k = 1;
while (k < n) {
MergePass(SR, TR, k, n);
k = 2 * k;
MergePass(TR, SR, k, n);
k = 2 * k;
}
}
// 将SR[s..n]归并为有序的TR[s..n]
private static void MergePass(int[] SR, int[] TR, int s, int n) {
int i = 1;
int j;
while (i <= n - 2 * s + 1) {
Merge(SR, TR, i, i + s - 1, i + 2 * s - 1);
i = i + 2 * s;
}
if (i < n - s + 1) {
Merge(SR, TR, i, i + s - 1, n);
} else {
for (j = i; j <= n; j++) {
TR[j] = SR[j];
}
}
}
// 将有序的SR[s..m]和SR[m+1..n]归并为有序的TR[i..n]
private static void Merge(int[] SR, int[] TR, int s, int m, int n) {
int j, k, l; // k为左块的起始下标,j为右块的起始下标
for (k = s, j = m + 1; s <= m && j <= n; k++) {
// SR中记录由小到大归并入TR
if (SR[s] < SR[j]) {
TR[k] = SR[s++];
} else {
TR[k] = SR[j++];
}
}
if (s <= m) {
for (l = 0; l <= m - s; l++) {
TR[k + l] = SR[s + l]; // 将剩余的SR[s..m]复制到TR
}
}
if (j <= n) {
for (l = 0; l <= n - j; l++) {
TR[k + l] = SR[j + l]; // 将剩余的SR[j..m]复制到TR
}
}
}
}
3.分析:
0.基本思想:通过一趟排序将待排序记录分割成独立的两部分,其中每一部分记录的关键字均比另一部分记录的关键字小,则可以分别对着两部分记录继续进行排序,以达到整个序列有序的目的。
1.思路图解:
2.代码实现:
public class QuickSort {
public static void sort(int[] a) {
int n = a.length - 1;
QSort(a, 1, n);
}
private static void QSort(int[] a, int low, int high) {
int pivot; // 枢轴的下标,将某个数放在此位置,使得它左边的值都比它小,右边的都比它大
if (low < high) {
pivot = Partition(a, low, high); // 将a[low..high]一分为二,算出枢轴下标pivot
QSort(a, low, pivot - 1); // 对低子表递归排序
QSort(a, pivot + 1, high); // 对高子表递归排序
}
}
// 交换顺序表a中子表的记录,使枢轴记录到为,并返回其位置
private static int Partition(int[] a, int low, int high) {
int pivotkey = a[low]; // 用子表的第一个记录作枢轴记录
while (low < high) {
// 从表的两端交替向中间扫描
while (low < high && a[high] >= pivotkey) {
high--;
}
swap(a, low, high); // 将比枢轴值小的记录交换到低端
while (low < high && a[low] <= pivotkey) {
low++;
}
swap(a, low, high); // 将比枢轴值大的记录交换到高端
}
return low; // 最终low == high,所有返回枢轴所在位置
}
private static void swap(int[] a, int x, int y) {
int temp;
temp = a[x];
a[x] = a[y];
a[y] = temp;
}
}
2.代码实现(优化版):
public class QuickSortUp {
// 1.优化选取枢轴(三数取中),选取更较为合适的枢轴值,使得左小右大更均匀;
// 2.优化交换操作为替换,分析得知,swap交换操作本身不必要;
// 3.优化小数组时的排序方案为直接插入排序,而对于大数组则采用快排;
// 4.优化递归操作,将对高子表的递归转为只对低子表的迭代版递归
public static void sort(int[] a) {
int n = a.length - 1;
QSort(a, 1, n);
}
private static void QSort(int[] a, int low, int high) {
int ORDINARY_LEN = 7; // 数组长度阈值,当小于时,可用直接插入排序
int pivot; // 枢轴的下标,将某个数放在此位置,使得它左边的值都比它小,右边的都比它大
if ((high - low) > ORDINARY_LEN) {
while (low < high) {
pivot = Partition(a, low, high); // 将a[low..high]一分为二,算出枢轴下标pivot
QSort(a, low, pivot - 1); // 对低子表递归排序
low = pivot + 1; // 尾递归
}
} else {
InterSort.sort(a);
}
}
// 交换顺序表a中子表的记录,使枢轴记录到为,并返回其位置
private static int Partition(int[] a, int low, int high) {
int pivotkey;
int m = low + (high - low) / 2;
if (a[low] > a[high]) {
swap(a, low, high);
}
if (a[m] > a[high]) {
swap(a, high, m);
}
if (a[m] > a[low]) {
swap(a, m, low);
}
pivotkey = a[low]; // 此时a[low]为三数取中得到的中间值
a[0] = pivotkey; // 哨兵
while (low < high) {
// 从表的两端交替向中间扫描
while (low < high && a[high] >= pivotkey) {
high--;
}
a[low] = a[high]; //改交换为替换
while (low < high && a[low] <= pivotkey) {
low++;
}
a[high] = a[low];
}
a[low] = a[0];
return low; // 最终low == high,所有返回枢轴所在位置
}
private static void swap(int[] a, int x, int y) {
int temp;
temp = a[x];
a[x] = a[y];
a[y] = temp;
}
}
3.分析: