算法之归并排序/JAVA
程序员文章站
2022-06-07 08:21:26
...
0.3 归并排序
0.3.1 自顶向下的归并排序
算法思想:归并排序是分治思想的典型应用,归并排序又叫二路归并,是将一个数组从中间分成两个子数组,然后对子数组再归并,最后再将两个子数组进行合并。具体算法流程如下:
在实现归并排序的并的时候,有如下几种情况:
- 左边小数组被用尽:取右边小数组元素进行并
- 右边小数组被用尽:取左边小数组元素进行并
- 左边数组元素小于右边数组元素:取左边小数组元素进行并
- 右边数组元素小于左边元素:取右边小数组元素进行并
所以我们很容易就可以实现并的部分
private static Comparable[] aux;
public static void merge(Comparable[] a ,int low ,int mid ,int high){
int i = low;
int j = mid+1;
//将a[low]-a[high]复制到临时数组aux
for (int k = low; k < high; k++) {
aux[k] = a[k];
}
//进行并
for (int k = low; k <=high ; k++) {
if (i > mid) {
//左边用尽
a[k] = aux[j++];
} else if (j < high) {
//右边用尽
a[k] = aux[i++];
} else if (less(aux[i], aux[j])) {
//左比右小
a[k] = aux[i++];
} else {
//右比左小
a[k] = aux[j++];
}
}
}
而从上方归并排序的图解中我们可以看出,用递归的方式进行分治是最简便的,所以我们归并排序的算法可以用以下代码实现。
public class Merge {
private static Comparable[] aux;
/**
* 具体算法实现
*/
public static void sort(Comparable[] a) {
aux = new Comparable[a.length];
sort(a,0,a.length-1);
}
public static void sort(Comparable[] a ,int low ,int high){
//右小于左则不可分
if (high <= low) {
return;
}
//一分为二
int mid =low + (high - low)/2;
//左边再分
sort(a ,low ,mid);
//右边分
sort(a ,mid+1 ,high);
//治
merge(a, low, mid, high);
}
public static void merge(Comparable[] a ,int low ,int mid ,int high){
int i = low;
int j = mid+1;
//将a[low]-a[high]复制到临时数组aux
for (int k = low; k <= high; k++) {
aux[k] = a[k];
}
//进行并
for (int k = low; k <=high ; k++) {
if (i > mid) {
//左边用尽
a[k] = aux[j++];
} else if (j > high) {
//右边用尽
a[k] = aux[i++];
} else if (less(aux[i], aux[j])) {
//左比右小
a[k] = aux[i++];
} else {
//右比左小
a[k] = aux[j++];
}
}
}
/**
* 对两个元素进行比较,如果v<w返回true
* @param v
* @param w
* @return
*/
private static boolean less(Comparable v ,Comparable w){
/**
* compareTo 方法
* 返回为正数表示v>w, 返回为负数表示v<w, 返回为0表示v==w
*/
return v.compareTo(w) < 0;
}
/**
* 交换a[i]和a[j]
* @param a
* @param i
* @param j
*/
private static void exch(Comparable[] a, int i, int j){
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
/**
* 输出数组
* @param a
*/
private static void show(Comparable[] a){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
/**
* 测试数组是否有序
* @param a
* @return
*/
private static boolean isSort(Comparable[] a){
for (int i = 0; i < a.length; i++) {
if (less(a[i],a[i-1])) {
return false;
}
}
return true;
}
/**
* 主函数
* @param args
*/
public static void main(String[] args) {
String[] a = {"5","3","6","5","7","9","2","1","3","8"};
sort(a);
assert isSort(a);
show(a);
}
}
特点:
- 时间复杂度:O(1/2NlgN)~O(NlgN)
- 归并排序所需要的时间与NlgN成正比
- 可以处理百万级大规模数组,因为其只需要遍历比整个数组多个对数因子的时间就能将一个庞大的数组排序
- 需要辅助数组
- 空间复杂度:O(1)
- 是渐进最优的基于比较的排序算法,因为没有任何排序算法能够用少于~NlgN次比较将数组排序。
对于小规模数组中使用插入排序
上文我们说到,归并排序再大规模数组中具有优势,而选择排序或插入排序再小规模数组中的速度肯能超过归并排序,这是因为归并排序使用递归会使小规模问题中的方法调用过于频繁,所以我们可以考虑使用插入排序代替归并排序来进行效率的提高。
0.3.2 自底向上的归并排序
在理解了自顶向下的归并排序后,我们可以有这样的一种思考:我们可以直接将数组中的第0-1,2-3,4-5……进行归并,然后再将0-3,4-7……进行归并,以此类推,最后依然可以完成归并,这样就少了分治中的分的部分。同时这种方式的代码要精简的多。代码如下:
public class MergeLowToTop {
private static Comparable[] aux;
/**
* 具体算法实现
*/
public static void sort(Comparable[] a) {
aux = new Comparable[a.length];
//从1开始,进行lgN次归并
for (int size = 1; size < a.length; size *= 2) {
//确定每次并的最小数组
for (int low = 0; low < a.length - size; low += 2*size) {
//high要考虑最高是否会超过数组长度
merge(a, low, low+size-1, Math.min(low+size*2-1, a.length-1));
}
}
}
}
特点:
- 相比于自顶向下,这种方式更适合于链表结构,因为每次只需要重新组织链表连接就是实现排序,而不用创建新的节点
上一篇: AVL树的插入与输出