归并排序
最好情况:Ο(nlogn)
最坏情况:Ο(nlogn)
平均情况:Ο(nlogn)
辅助空间:Ο(n)
稳定性:稳定
归并排序是创建在归并操作上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。
归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。
递归法
过程:
- 计算左右边界和中间分割线
- 递归左右数组
- 将左右数组合并
3.1. 提取左右数组为左右临时数组
3.2. 在左右临时数组提取较大(小)的元素放入原数组
public static void main(String[] args) {
int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0};
int length1 = arr.length;
leftArr = new int[length];
rightArr = new int[length];
mergeSortRecursion(arr, 0, length - 1);
}
static void mergeSortRecursion(int[] arr, int left, int right) { //递归实现的归并排序
int middle = (left + right) / 2; //取中间位置
if (left < right) {
mergeSortRecursion(arr, left, middle); //递归左边数组
mergeSortRecursion(arr, middle + 1, right); //递归右边数组
merge(arr, left, middle, right); //将已经排序的左边数组和右边数组进行排序
print(arr);
}
}
static void merge(int[] arr, int left, int middle, int right) {
int[] leftArr = new int[right - left + 1];
int[] rightArr = new int[right - left + 1];
System.arraycopy(arr, left, leftArr, 0, middle - left + 1); //将左边数组拷贝到leftArr
System.arraycopy(arr, middle + 1, rightArr, 0, right - middle); //将右边数组拷贝到rightArr
leftArr[middle - left + 1] = Integer.MAX_VALUE; //在数组最后面设置正无穷,使下面循环内的判断不会越界
rightArr[right - middle] = Integer.MAX_VALUE;
for (int k = left, i = 0, j = 0; k <= right; k++) {
if (leftArr[i] <= rightArr[j]) {
arr[k] = leftArr[i++];
} else {
arr[k] = rightArr[j++];
}
}
}
输出:
5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 10 9 1 7 3 13 0
5 6 8 12 2 4 10 9 1 7 3 13 0
5 6 8 12 2 4 10 9 1 7 3 13 0
5 6 8 12 2 4 10 9 1 7 3 13 0
2 4 5 6 8 10 12 9 1 7 3 13 0
2 4 5 6 8 10 12 1 9 7 3 13 0
2 4 5 6 8 10 12 1 7 9 3 13 0
2 4 5 6 8 10 12 1 7 9 3 13 0
2 4 5 6 8 10 12 1 7 9 0 3 13
2 4 5 6 8 10 12 0 1 3 7 9 13
0 1 2 3 4 5 6 7 8 9 10 12 13
迭代法
过程:
- 计算步长,步长每次*2
- 根据步长计算左右边界和中间分割线
- 将左右数组合并
- 重新计算左右边界
static void mergeSortIteration(int[] arr, int left, int right) { //迭代实现的归并排序
for (int gap = 1; gap <= right - left; gap *= 2) {
int leftPoint = left, rightPoint; //left和right都是数组的左右边界,leftPoint和rightPoint都是代表数组位置的游离指针;
while (leftPoint + gap <= right) {
rightPoint = leftPoint + gap * 2 - 1;
int middle = (leftPoint + rightPoint) / 2; //middle一定要放在检查rightPoint的前面,这样才能准确分块
if (rightPoint > right) {
rightPoint = right;
}
merge(arr, leftPoint, middle, rightPoint);
leftPoint = rightPoint + 1;
print(arr);
}
}
}
输出:
5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 10 9 1 7 3 13 0
5 8 6 12 2 4 9 10 1 7 3 13 0
5 8 6 12 2 4 9 10 1 7 3 13 0
5 8 6 12 2 4 9 10 1 7 3 13 0
5 6 8 12 2 4 9 10 1 7 3 13 0
5 6 8 12 2 4 9 10 1 7 3 13 0
5 6 8 12 2 4 9 10 1 3 7 13 0
2 4 5 6 8 9 10 12 1 3 7 13 0
2 4 5 6 8 9 10 12 0 1 3 7 13
0 1 2 3 4 5 6 7 8 9 10 12 13
堆排序
最好情况:Ο(nlogn)
最坏情况:Ο(nlogn)
平均情况:Ο(nlogn)
辅助空间:Ο(1)
稳定性:不稳定
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构(通常堆是通过一维数组来实现的),并同时满足堆的性质:即子结点的键值总是小于(或者大于)它的父节点。
过程:
- 创建初始堆(最大堆或最小堆)
1.1. 调整每个非叶子节点及其子堆 - 根节点与最后元素交换
2.1. 逐渐缩小堆
2.2. 从堆顶进行堆调整
public static void main(String[] args) {
int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0, 11};
sortHeap(arr);
}
static void sortHeap(int[] arr) {
buildHeap(arr);
for (int i = arr.length - 1; i >= 1; i--) { //对最大堆进行排序
exchange(arr, 0, i); //将第根节点元素与最后节点(i)元素交换
heapAdjust(arr, 0, i); //最后节点元素被固定,即可交换的数组长度为i
print(arr);
}
}
static void buildHeap(int[] arr) { //建堆(最大堆)
for (int i = (arr.length - 1) / 2; i >= 0; i--) {
heapAdjust(arr, i, arr.length); //自下向上对每个非叶子节点进行调整
print(arr);
}
}
static void heapAdjust(int[] arr, int i, int heapSize) {
int leftChild = i * 2 + 1; //i节点(父节点)的左孩子和右孩子
int rightChild = leftChild + 1;
int largest = i; //父节点、左孩子、右孩子进行大小比较
if (leftChild < heapSize && arr[leftChild] > arr[i]) {
largest = leftChild;
}
if (rightChild < heapSize && arr[rightChild] > arr[leftChild]) {
largest = rightChild;
}
if (largest != i) { //如果最大值不是父节点,那么不符合堆性质,调整最大值位置
exchange(arr, i, largest);
heapAdjust(arr, largest, heapSize); //从被调整的孩子继续递归
}
}
static void exchange(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
static void print(int[] arr) {
for (int a : arr) { //输出
System.out.print(a + "\t");
}
System.out.println();
}
输出:
5 8 6 12 2 4 11 9 1 7 3 13 0 10
5 8 6 12 2 13 11 9 1 7 3 4 0 10
5 8 6 12 7 13 11 9 1 2 3 4 0 10
5 8 6 12 7 13 11 9 1 2 3 4 0 10
5 8 13 12 7 6 11 9 1 2 3 4 0 10
5 12 13 9 7 6 11 8 1 2 3 4 0 10
13 12 11 9 7 6 10 8 1 2 3 4 0 5
12 9 11 8 7 6 10 5 1 2 3 4 0 13
11 9 10 8 7 6 0 5 1 2 3 4 12 13
10 9 6 8 7 4 0 5 1 2 3 11 12 13
9 8 6 5 7 4 0 3 1 2 10 11 12 13
8 7 6 5 2 4 0 3 1 9 10 11 12 13
7 5 6 3 2 4 0 1 8 9 10 11 12 13
6 5 4 3 2 1 0 7 8 9 10 11 12 13
5 3 4 0 2 1 6 7 8 9 10 11 12 13
4 3 1 0 2 5 6 7 8 9 10 11 12 13
3 2 1 0 4 5 6 7 8 9 10 11 12 13
2 0 1 3 4 5 6 7 8 9 10 11 12 13
1 0 2 3 4 5 6 7 8 9 10 11 12 13
0 1 2 3 4 5 6 7 8 9 10 11 12 13
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。
快速排序
最好情况:Ο(nlogn)
最坏情况:Ο(n2)
平均情况:Ο(nlogn)
辅助空间:Ο(logn)~ Ο(n)
稳定性:不稳定
快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。
过程:
- 按已坐好位置的基准将数组分为左右两个子数组
- 将比基准小的元素放到前面,比基准大的元素放到后面
- 将基准放置到准确位置
public static void main(String[] args) {
int[] arr = new int[]{5, 8, 6, 12, 2, 4, 10, 9, 1, 7, 3, 13, 0, 11};
quickSort(arr, 0, arr.length - 1);
}
static void quickSort(int[] arr, int left, int right) { //递归分配基准的左右数组
if (left < right) {
int index = partition(arr, left, right);
quickSort(arr, left, index - 1);
quickSort(arr, index + 1, right);
}
}
static int partition(int[] arr, int left, int right) { //比基准小的放到基准左边,比基准大的放到基准右边
int pivot = arr[right]; //基准值,不一定是最右侧的元素
int lastArrTail = left; //被排序数列最左侧的游离指针,指针所在位置及往前位置的元素,皆比基准小
for (int i = left; i < right; i++) { //将比基准小的元素与比基准大的元素进行交换
if (arr[i] <= pivot) {
exchange(arr, i, lastArrTail++);
}
}
exchange(arr, right, lastArrTail); //将基准放在游离指针的位置,该位置及往后的元素比基准大,该位置往前的元素比基准小
print(arr);
return lastArrTail; //排布完成后,返回该基准的位置,该基准已经坐在正确的排列位置上了,但基准的前后序列可能还存在乱序
}
输出:
5 8 6 2 4 10 9 1 7 3 0 11 12 13
0 8 6 2 4 10 9 1 7 3 5 11 12 13
0 2 4 1 3 5 9 8 7 6 10 11 12 13
0 2 1 3 4 5 9 8 7 6 10 11 12 13
0 1 2 3 4 5 9 8 7 6 10 11 12 13
0 1 2 3 4 5 9 8 7 6 10 11 12 13
0 1 2 3 4 5 6 8 7 9 10 11 12 13
0 1 2 3 4 5 6 8 7 9 10 11 12 13
0 1 2 3 4 5 6 7 8 9 10 11 12 13
0 1 2 3 4 5 6 7 8 9 10 11 12 13
快速排序是不稳定的排序算法,不稳定发生在基准元素与A[tail+1]交换的时刻。