归并排序
官方介绍
归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
用自己的话形容:给一个无序集合数组,使用分治思想,二分法进行拆分,将大问题拆分成小问题,从上往下把问题集合缩小到一,再从下往上,陆续把两个集合归并。从而得到一个有序集合。它的时间复杂性是O(nlogn).为什么呢?
数组长度是N,归并排序是用二分法将不停将问题集合缩小,直至集合大小为1.那么得到,k是树的深度,得到
. 每一层处理的节点数是N,那么时间复杂度就是O(nlogn)
这个算法的思想和快速排序有些类似。都是分治处理,区别是排序的顺序相反。快排是在分治的过程中排序,当分治到最后一层时,已经有序,后面没有操作了;归并排序是分治过程中只单纯获取到小集合,归并过程中将小集合合并排序。由于快排没有创建临时空间,所以它分治过程中是使用了比较和交换,导致快排算法是不稳定的;归并排序是要创建临时空间,将两个小集合合并进去,过程中无需交换,所以可以保持稳定性。所以归并是稳定的。
以下是我的java实现
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] arr = {2,10,4,5,7,3,2,1,5,9,3,2,10,6,4,8,9,7,1};
int[] dd = mergeS(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(dd));
}
static int[] mergeS(int[] arrs, int low, int high) {
if (low == high) {
int[] tmp = {arrs[low]};
return tmp;
}
int mid = (high - low)/2 + low;
//先获取左集合
int[] left = mergeS(arrs, low, mid);
//获取右集合
int[] right = mergeS(arrs, mid + 1, high);
//创建临时空间,将左右集合合并
int[] tmp = new int[high - low + 1];
int n = 0;
int l1 = 0;
int r1 = 0;
while (!(l1 == left.length && r1 == right.length)) {
//当左集合到底了,只处理右集合
if (l1 == left.length) {
tmp[n++] = right[r1++];
continue;
}
//当右集合当底了,处理左集合
if (r1 == right.length) {
tmp[n++] = left[l1++];
continue;
}
//比较元素大小,从小到大设置值到tmp上
tmp[n++] = left[l1] < right[r1]? left[l1++]:right[r1++];
}
return tmp;
}
}
堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆的性质:即子结点的键值或索引总是小于(或者大于)它的父节点
自己理解:我们一般很容易理解数组和链表。那么堆是什么数据结构呢?堆是一个完全二叉树。数的结点有值。堆有两种
大根堆:父结点的值总是大于等于左右节点的值;
小跟堆:父结点的值总是小于等于左右节点的值;
大根堆是用于升序排序;小跟堆是用于降序排序。
大根堆,最大值总是在根结点。
堆排序,利用这个特性,可以获取到一个大根堆的最大值,然后将最大值移除,调整堆,就可以获取到新的最大值。如此递归,就可以获取到有序集合。
给一个无需集合,通过以下3步,就得到有序集合。
步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)
步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。
步骤三 然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
以下是我的java实现
import java.util.Arrays;
import java.util.Collections;
public class HeapSort {
public static void main(String[] args) {
int[] arr = { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 };
// heapAdjust(arr,0, arr.length);
// System.out.println(Arrays.toString(arr));
headSort(arr);
System.out.println(Arrays.toString(arr));
}
//堆调整,给出父结点,对它的左右子树进行调整,构成一个该父结点起始的大根堆
public static void heapAdjust(int[] arr, int index, int length) {
int boy = 2 * index + 1;
while (boy < length) {
//对比左右子树,如果左子树大于右子树,则使用右子树
if (boy + 1 < length && arr[boy] < arr[boy + 1]) {
boy = boy + 1;
}
//父结点大于等于左右节点,无需调整。大根堆性质
if (arr[index] >= arr[boy]) {
return;
}
//交换父结点和最大子节点
int tmp = arr[index];
arr[index] = arr[boy];
arr[boy] = tmp;
//继续调整子节点
index = boy;
boy = 2 * index + 1;
}
}
public static void headSort(int[] arr) {
System.out.format("初始值: \t", arr.length -1);
printPart(arr, 0, arr.length - 1);
//这个last。这是一个公式,根据完全二叉树的性质,得到的最后一个非叶子结点的索引
int last = arr.length / 2 -1;
//构建大根堆 根据完全二叉树性质,从最后一个非叶子节点往前的节点,都是非叶子节点,都处理一遍堆调整,那么得到的就是一个大根堆
for (int i = last; i >= 0;i--) {
heapAdjust(arr, i, arr.length);
}
System.out.format("大根堆: \t", arr.length -1);
printPart(arr, 0, arr.length - 1);
int k = arr.length - 1;
for(;k > 0;k--) {
//将最大值根节点和最后一个节点交换,并且将最后一个节点移除出大根堆
//为什么是移除最后一个节点?因为是要将最大值放在数组末尾,才能移除处理。
int tmp = arr[k];
arr[k] = arr[0];
arr[0] = tmp;
//注意k--,每次调整大根堆,都会把数组长度缩小。实现当前最大值移除大根堆的目的
heapAdjust(arr, 0, k);
System.out.format("第 %d 趟: \t", arr.length - k);
printPart(arr, 0, arr.length - 1);
}
}
public static void printPart(int[] list, int begin, int end) {
for (int i = 0; i < begin; i++) {
System.out.print("\t");
}
for (int i = begin; i <= end; i++) {
System.out.print(list[i] + "\t");
}
System.out.println();
}
}