// 插入排序:顺序地把待排序的数据按关键字大小插入到已排序的子集合的适当位置
// 插入排序对于部分有序数组和小规模数组特别高效
void insertSort(int[] A, int n) {
for (int i = 1; i < n; i++) {
int tmp = A[i];
int j = i;
for (; j > 0 && A[j - 1] > tmp; j--) {
A[j] = A[j - 1];
}
A[j] = tmp;
}
}
// 希尔排序:把待排序的数据元素分成若干个小组,对同一组内使用插入法排序
// 距离逐渐减小,直到只比较相邻元素的最后一趟为止,因此也称缩小增量排序
// 希尔排序的运行时间达不到平方级别
void shellSort(int[] A, int n) {
for (int increment = n / 2; increment > 0; increment /= 2) {
for (int i = increment; i < n; i++) {
int tmp = A[i];
int j = i;
for (; j >= increment; j -= increment) {
if (tmp < A[j - increment]) {
A[j] = A[j - increment];
} else {
break;
}
}
A[j] = tmp;
}
}
}
// 选择排序:维护一个已排序集合,从未排序集合中选择最小值与第一个元素交换
// 然后增大已排序集合范围,直到最后一个元素
// 它的运行时间与输入无关
void selectSort(int[] A, int n) {
for (int i = 0; i < n - 1; i++) {
int small = i;
for (int j = i + 1; j < n; j++) {
if (A[j] < A[small]) {
small = j;
}
}
if (small != i) {
swap(A, i, small);
}
}
}
// 冒泡排序:将大元素依次沉底,然后缩小数据元素范围.有些待排序元素序列已经基本有序
// 优化:可以设一个flag变量标记本次是否有交换动作,提前结束排序过程
void bubbleSort(int[] A, int n) {
boolean flag = true;
for (int i = 1; i < n && flag; i++) {
flag = false;
for (int j = 0; j < n - i; j++) {
if (A[j] > A[j + 1]) {
flag = true;
swap(A, j, j + 1);
}
}
}
}
// 归并排序:思想是将数组分成两部分,分别进行排序,然后归并起来
static int[] aux;
public void merge(int[] A, int lo, int mid, int hi) {
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
aux[k] = A[k];
}
for (int k = lo; k <= hi; k++) {
if (i > mid) {
A[k] = aux[j++];
} else if (j > hi) {
A[k] = aux[i++];
} else if (aux[i] < A[j]) {
A[k] = aux[i++];
} else {
A[k] = aux[j++];
}
}
}
// 自顶向下
public void mergeSort(int[] A, int n) {
aux = new int[n];
mergeSort(A, 0, n - 1);
}
public void mergeSort(int[] A, int lo, int hi) {
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
mergeSort(A, lo, mid);
mergeSort(A, mid + 1, hi);
merge(A, lo, mid, hi);
}
// 快速排序:通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素
// 右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了
public void quickSort(int[] A, int n) {
quickSort(A, 0, n - 1);
}
public void quickSort(int[] A, int lo, int hi) {
if (hi <= lo) return;
int j = partition(A, lo, hi);
quickSort(A, lo, j - 1);
quickSort(A, j + 1, hi);
}
/*取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素
再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程
就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素
当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可*/
public int partition(int[] A, int lo, int hi) {
int i = lo, j = hi + 1;
int v = A[lo];
while (true) {
while (A[++i] < v) {
if (i == hi) {
break;
}
}
while (A[--j] > v) {
if (j == lo) {
break;
}
}
if (i >= j) break;
swap(A, i, j);
}
swap(A, lo, j);
return j;
}
堆排序:由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列
如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列
因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。
堆排序要分两个阶段,第一个阶段是把无序数组建立一个堆
第二个阶段是交换最大元素和当前堆的数组最后一个元素,并且进行下沉操作维持堆的有序状态
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作
一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序
那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作
因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可
分析
一个堆的高度为 lgN,因此在堆中插入元素和删除最大元素的复杂度都为 lgN。
对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 NlgN。
堆排序时一种原地排序,没有利用额外的空间
现代操作系统很少使用堆排序,因为它无法利用缓存,也就是数组元素很少和相邻的元素进行比较
int leftChild(int i) {
return 2 * i + 1;
}
void sink(int[] A, int i, int N) {
int child;
int tmp = A[i];
for (; leftChild(i) < N; i = child) {
child = leftChild(i);
if (child != N - 1 && A[child + 1] > A[child]) {
child++;
}
if (tmp < A[child]) {
A[i] = A[child];
} else {
break;
}
}
A[i] = tmp;
}
public void heapSort(int[] A, int n) {
for (int i = n / 2; i >= 0; i--) {
sink(A, i, n); // buildHeap
}
for (int i = n - 1; i > 0; i--) {
swap(A, 0, i); // delMax
sink(A, 0, i);
}
}
// 基数排序
基本思想:m位 d进制整数, 不足m 位的关键字高位补0;设置d 个桶,按关键字的最低位依次把元素放入桶中,此为第一次基数排序; 然后依次按次低位,直到 最高位. 经过 m次基数排序后所有元素已经有序.
public void radixSort(int[] A, int n) {
if (A == null || n < 1) {
return;
}
//首先确定排序的趟数;
int max = A[0];
for (int i = 1; i < n; i++) {
if (A[i] > max) {
max = A[i];
}
}
int time = 0;
//判断位数;
while (max > 0) {
max /= 10;
time++;
}
//建立10个桶;
List<ArrayList<Integer>> queue = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ArrayList<Integer> queue1 = new ArrayList<Integer>();
queue.add(queue1);
}
//进行time次分配和收集;
for (int i = 0; i < time; i++) {
//分配数组元素;
for (int j = 0; j < n; j++) {
//得到数字的第time+1位数;
int x = A[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
ArrayList<Integer> queue2 = queue.get(x);
queue2.add(A[j]);
queue.set(x, queue2);
}
int count = 0;//元素计数器;
//收集队列元素;
for (int k = 0; k < 10; k++) {
while (queue.get(k).size() > 0) {
ArrayList<Integer> queue3 = queue.get(k);
A[count] = queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
// 计数排序
public int[] countingSort(int[] A, int n) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
if (A[i] > max) {
max = A[i];
}
}
int[] count = new int[max + 1];
for (int i = 0; i < n; i++) {
count[A[i]]++;
}
int index = 0;
for (int i = 0; i < max + 1; i++) {
while (count[i] != 0) {
A[index++] = i;
count[i]--;
}
}
return A;
}