归并排序(Merge Sort)算法实现
java实现归并排序算法,非常基础的一个算法,先明白分治法的主要思想,分而治之,然后再统一起来完成剩下的操作,最后完成排序,内部需要用到额外的存储空间,时间复杂度是O(nlg(n));
归并排序分为两种,一种是自顶向下的,另一种是自底向上的;两种排序方法虽然都是归并排序,但是实现方式却大不一样;
1. 自顶而下的归并排序
###代码实现
private static void sort(int[] copy, int l, int r) {
if (l >= r) return;
int mid = (l + r) / 2;
sort(copy, l, mid);
sort(copy, mid+1, r);
merge(copy, l, mid, r);
}
private static void merge(int[] copy, int l, int mid, int r) {
int[] t = new int[r-l+1];
int j=l, k=mid+1;
for (int i=0; i<t.length; i++) {
if (j <= mid && k <= r) {
if (copy[j] < copy[k])
t[i] = copy[j ++];
else
t[i] = copy[k ++];
} else if (j <= mid){
t[i] = copy[j ++];
} else
t[i] = copy[k ++];
}
for (int i=l; i<=r; i++) {
copy[i] = t[i-l];
}
}
2. 自底而上的归并排序
代码实现
int[] c = {9, 0, 2, 4, 5, 6, 1, 1};
int n = c.length;
for (int k=1; k<n; k*=2) {
for (int l=0; l<n-k; l+=k*2) {
merge(c, l, l+k-1, l+k*2-1);
}
}
- 两种方法都使用了归并排序的核心程序merge函数,但是一个是采用递归实现,架构上是自顶而下,另一个是非递归自底而上的程序,两种算法区别可通过下面图解方式一目了然;
- 自顶而下的归并排序
- 自底而上的归并排序
3. 从两路归并到多路归并
代码实现
import java.util.*;
public class Main {
private static class Pair<T extends Comparable> implements Comparable<Pair<T>>{
int idx;
List<T> A;
Pair(int idx, List<T> a) {
this.idx = idx;
A = a;
}
@Override
public int compareTo(Pair<T> o) {
return A.get(idx).compareTo(o.A.get(o.idx));
}
}
public static void main(String[] args) {
List<Integer>[] arr = new ArrayList[5];
arr[0] = new ArrayList<>(Arrays.asList(1, 4, 9, 15));
arr[1] = new ArrayList<>(Arrays.asList());
arr[2] = new ArrayList<>(Arrays.asList(2, 5, 8));
arr[3] = new ArrayList<>(Arrays.asList(1, 9));
arr[4] = new ArrayList<>(Arrays.asList(3));
ArrayList<Integer> B = new ArrayList<>();
merge(arr, B);
System.out.println(B);
}
private static void merge(List<Integer>[] arr, ArrayList<Integer> B) {
PriorityQueue<Pair<Integer>> pq = new PriorityQueue<>();
for (int i=0; i<arr.length; i++) {
if (arr[i].size() > 0)
pq.offer(new Pair<Integer>(0, arr[i]));
}
while (!pq.isEmpty()) {
Pair<Integer> pair = pq.poll();
B.add(pair.A.get(pair.idx));
pair.idx ++;
if (pair.idx != pair.A.size())
pq.offer(pair);
}
}
}
- 使用了优先队列来进行取最小值的处理,这里如果使用c++的话即用pair实现,但java的话,自定义了一个pair类同样可以最终达到多路归并的效果;
- 总共元素个数是n,共有k路的话,其复杂度为O(n*lg(k));
思考和修改:
- 上面的List[]数组毕竟不常用,还是化为我们最常见的二维数组吧,再用泛型包装下,用以进行拓展使用;
import java.util.*;
public class Main<M extends Comparable> {
private class Pair<T extends Comparable> implements Comparable<Pair<T>>{
int idx;
T[] A;
Pair(int idx, T[] a) {
this.idx = idx;
this.A = a;
}
@Override
public int compareTo(Pair<T> o) {
return A[idx].compareTo(o.A[o.idx]);
}
}
public static void main(String[] args) {
Integer[][] arr = {{1, 2}, {}, {3, 5, 7}, {3, 5, 9, 10}};
List<Integer> B = new ArrayList<>();
Main<Integer> m = new Main<>();
m.merge(arr, B);
System.out.println(B);
String[][] arr2 = {{"sad", "as"}, {}, {"qw", "xaz", "ior"}, {"asjk", "tri", "dsk", "skdj"}};
List<String> C = new ArrayList<>();
Main<String> m2 = new Main<>();
m2.merge(arr2, C);
System.out.println(C);
}
private void merge(M[][] arr, List<M> B) {
PriorityQueue<Pair<M>> pq = new PriorityQueue<>();
for (int i=0; i<arr.length; i++) {
if (arr[i].length > 0)
pq.offer(new Pair<M>(0, arr[i]));
}
while (!pq.isEmpty()) {
Pair<M> pair = pq.poll();
B.add(pair.A[pair.idx]);
pair.idx ++;
if (pair.idx != pair.A.length)
pq.offer(pair);
}
}
}
结果查看:
4. 拓展-Shell排序
看到归并排序,突然想到这和希尔排序很像,于是在此将shell排序算法记录一下,作一比较:
for (int gap = n/2; gap>0; gap/=2) {
for (int i=gap; i<n; i++) {
for (int j=i-gap; j>=0 && a[j]>a[j+gap]; j-=gap) {
int temp = a[j];
a[j] = a[j+gap];
a[j+gap] = temp;
}
}
}
- 当gap为1时,shell排序即为插入排序;
与之对比的是自下而上的归并排序,Merge sort,可以参考以下核心代码实现;
int n = 10;
int[] a = new int[n+1]; //第0个位置不放置元素,从1开始;
int k=1;
boolean flag=true;
while (k<n) {
k *= 2;
for (int i=0; i<n/k; i++) {
Arrays.sort(a, 1+i*k, 1+(i+1)*k);
}
Arrays.sort(a, 1+n/k*k, n+1);
}
关于自底向上的归并排序,想要了解的多,可以参考这道PAT试题,能让你了解的更透彻;
并附上我的AC代码:
public static void main(String[] args) throws IOException {
Read.init(System.in);
int n = Read.nextInt();
int[] a = new int[n+1], b = new int[n+1];
for (int i=1; i<=n; i++) {
a[i] = Read.nextInt();
}
for (int i=1; i<=n; i++) {
b[i] = Read.nextInt();
}
int p = 2, q;
while (p <= n && b[p-1] <= b[p]) p++;
q = p;
while (p <= n && b[p] == a[p]) p++;
if (p == n+1) {
System.out.println("Insertion Sort");
Arrays.sort(b, 0, q+1);
System.out.print(b[1]);
for (int i=2; i<=n; i++)
System.out.print(" " + b[i]);
} else {
System.out.println("Merge Sort");
int k=1;
boolean flag=true;
while (flag) {
flag = false;
for (int i=1; i<=n; i++) {
if (a[i] != b[i]) {
flag = true;
break;
}
}
k *= 2;
for (int i=0; i<n/k; i++) {
Arrays.sort(a, 1+i*k, 1+(i+1)*k);
}
Arrays.sort(a, 1+n/k*k, n+1);
}
System.out.print(a[1]);
for (int i=2; i<=n; i++)
System.out.print(" " + a[i]);
}
}