One of mergesort’s most attractive properties is that it guarantees to sort any array of N items in time proportional to N * log N. Its prime disadvantage is that it uses extra space proportional to N.
Top-down mergesort
It is one of the best-known examples of the utility of the divide-and-conquer paradigm for efficient algorithm design. This recursive code is the basis for an inductive proof that the algorithm sorts the array: if it sorts the two subarrays, it sorts the whole array, by merging together the subarrays.
To understand mergesort, it is worthwhile to consider carefully the dynamics of the method calls, shown in the trace at right. To sort a[0..15], the sort() method calls itself to sort a[0..7] then calls itself to sort a[0..3] and a[0..1] before finally doing the first merge of a[0] with a[1] after calling itself to sort a[0] and then a[1] (for brevity, we omit the calls for the base-case 1-entry sorts in the trace). Then the next merge is a[2] with a[3] and then a[0..1] with a[2..3] and so forth. From this trace, we see that the sort code simply provides an organized way to sequence the calls to the merge() method.
public class Merge { // stably merge a[lo .. mid] with a[mid+1 .. hi] using aux[lo .. hi] public static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) { // precondition: a[lo .. mid] and a[mid+1 .. hi] are sorted subarrays assert isSorted(a, lo, mid); assert isSorted(a, mid + 1, hi); // copy to aux[] for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } // merge back to a[] int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) a[k] = aux[j++]; else if (j > hi) a[k] = aux[i++]; else if (less(aux[j], aux[i])) a[k] = aux[j++]; else a[k] = aux[i++]; } // postcondition: a[lo .. hi] is sorted assert isSorted(a, lo, hi); } // mergesort a[lo..hi] using auxiliary array aux[lo..hi] private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; sort(a, aux, lo, mid); sort(a, aux, mid + 1, hi); merge(a, aux, lo, mid, hi); } public static void sort(Comparable[] a) { Comparable[] aux = new Comparable[a.length]; sort(a, aux, 0, a.length - 1); assert isSorted(a); } /** * ******************************************************************** * Helper sorting functions * ********************************************************************* */ // is v < w ? private static boolean less(Comparable v, Comparable w) { return (v.compareTo(w) < 0); } // exchange a[i] and a[j] private static void exch(Object[] a, int i, int j) { Object swap = a[i]; a[i] = a[j]; a[j] = swap; } /** * ******************************************************************** * Check if array is sorted - useful for debugging * ********************************************************************* */ private static boolean isSorted(Comparable[] a) { return isSorted(a, 0, a.length - 1); } private static boolean isSorted(Comparable[] a, int lo, int hi) { for (int i = lo + 1; i <= hi; i++) if (less(a[i], a[i - 1])) return false; return true; } }
Another approach is described as Indirect sort. This sort algorithm does not rearrange the array, but returns an int[] array, say perm, such that perm[i] is the index of the ith smallest entry in the array:
public class Merge { /** * ******************************************************************** * Helper sorting functions * ********************************************************************* */ // is v < w ? private static boolean less(Comparable v, Comparable w) { return (v.compareTo(w) < 0); } // exchange a[i] and a[j] private static void exch(Object[] a, int i, int j) { Object swap = a[i]; a[i] = a[j]; a[j] = swap; } /** * ******************************************************************** * Check if array is sorted - useful for debugging * ********************************************************************* */ private static boolean isSorted(Comparable[] a) { return isSorted(a, 0, a.length - 1); } private static boolean isSorted(Comparable[] a, int lo, int hi) { for (int i = lo + 1; i <= hi; i++) if (less(a[i], a[i - 1])) return false; return true; } /** * ******************************************************************** * Index mergesort * ********************************************************************* */ // stably merge a[lo .. mid] with a[mid+1 .. hi] using aux[lo .. hi] private static void merge(Comparable[] a, int[] index, int[] aux, int lo, int mid, int hi) { // copy to aux[] for (int k = lo; k <= hi; k++) { aux[k] = index[k]; } // merge back to a[] int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) index[k] = aux[j++]; else if (j > hi) index[k] = aux[i++]; else if (less(a[aux[j]], a[aux[i]])) index[k] = aux[j++]; else index[k] = aux[i++]; } } // return a permutation that gives the elements in a[] in ascending order // do not change the original array a[] public static int[] indexSort(Comparable[] a) { int N = a.length; int[] index = new int[N]; for (int i = 0; i < N; i++) index[i] = i; int[] aux = new int[N]; sort(a, index, aux, 0, N - 1); return index; } // mergesort a[lo..hi] using auxiliary array aux[lo..hi] private static void sort(Comparable[] a, int[] index, int[] aux, int lo, int hi) { if (hi <= lo) return; int mid = lo + (hi - lo) / 2; sort(a, index, aux, lo, mid); sort(a, index, aux, mid + 1, hi); merge(a, index, aux, lo, mid, hi); } }
Top-down merge sort uses between 1⁄2*N*lgN and N*lgN compares to sort any array of length N, also it uses at most 6 N lgN array accesses to sort an array of length N.
We can cut the running time of mergesort substantially with some carefully considered modifications to the implementation.
Use insertion sort for small subarrays. We can improve most recursive algorithms by handling small cases differently, because the recursion guarantees that the method will be used often for small cases, so improvements in handling them lead to improvements in the whole algorithm. In the case of sorting, we know that insertion sort (or selection sort) is simple and therefore likely to be faster than mergesort for tiny subarrays. As usual, a visual trace provides insight into the operation of mergesort. The visual trace on the facing page shows the operation of a mergesort implementation with a cutoff for small subarrays. Switching to insertion sort for small subarrays (length 15 or less, say) will improve the running time of a typical mergesort implementation by 10 to 15 percent.
Test whether the array is already in order. We can reduce the running time to be linear for arrays that are already in order by adding a test to skip the call to merge() if a[mid] is less than or equal to a[mid+1]. With this change, we still do all the recursive calls, but the running time for any sorted subarray is linear.
Eliminate the copy to the auxiliary array. It is possible to eliminate the time(but not the space) taken to copy to the auxiliary array used for merging. To do so, we use two invocations of the sort method: one takes its input from the given array and puts the sorted output in the auxiliary array; the other takes its input from the auxiliary array and puts the sorted output in the given array. With this approach, in a bit of recursive trickery, we can arrange the recursive calls such that the computation switches the roles of the input array and the auxiliary array at each level.
Below is the code to implemente all of the improvments:
public class MergeX { private static final int CUTOFF = 7; // cutoff to insertion sort // This class should not be instantiated. private MergeX() { } private static void merge(Comparable[] src, Comparable[] dst, int lo, int mid, int hi) { // precondition: src[lo .. mid] and src[mid+1 .. hi] are sorted subarrays assert isSorted(src, lo, mid); assert isSorted(src, mid+1, hi); int i = lo, j = mid+1; for (int k = lo; k <= hi; k++) { if (i > mid) dst[k] = src[j++]; else if (j > hi) dst[k] = src[i++]; else if (less(src[j], src[i])) dst[k] = src[j++]; // to ensure stability else dst[k] = src[i++]; } // postcondition: dst[lo .. hi] is sorted subarray assert isSorted(dst, lo, hi); } private static void sort(Comparable[] src, Comparable[] dst, int lo, int hi) { // if (hi <= lo) return; if (hi <= lo + CUTOFF) { insertionSort(dst, lo, hi); return; } int mid = lo + (hi - lo) / 2; sort(dst, src, lo, mid); sort(dst, src, mid+1, hi); // if (!less(src[mid+1], src[mid])) { // for (int i = lo; i <= hi; i++) dst[i] = src[i]; // return; // } // using System.arraycopy() is a bit faster than the above loop if (!less(src[mid+1], src[mid])) { System.arraycopy(src, lo, dst, lo, hi - lo + 1); return; } merge(src, dst, lo, mid, hi); } /** * Rearranges the array in ascending order, using the natural order. * @param a the array to be sorted */ public static void sort(Comparable[] a) { Comparable[] aux = a.clone(); sort(aux, a, 0, a.length-1); assert isSorted(a); } // sort from a[lo] to a[hi] using insertion sort private static void insertionSort(Comparable[] a, int lo, int hi) { for (int i = lo; i <= hi; i++) for (int j = i; j > lo && less(a[j], a[j-1]); j--) exch(a, j, j-1); } // exchange a[i] and a[j] private static void exch(Comparable[] a, int i, int j) { Comparable swap = a[i]; a[i] = a[j]; a[j] = swap; } // is a[i] < a[j]? private static boolean less(Comparable a, Comparable b) { return (a.compareTo(b) < 0); } /*********************************************************************** * Check if array is sorted - useful for debugging ***********************************************************************/ private static boolean isSorted(Comparable[] a) { return isSorted(a, 0, a.length - 1); } private static boolean isSorted(Comparable[] a, int lo, int hi) { for (int i = lo + 1; i <= hi; i++) if (less(a[i], a[i-1])) return false; return true; } }
Bottom-up mergesort The recursive implementation of mergesort is prototypical of the divide-and-conquer algorithm design paradigm, where we solve a large problem by dividing it into pieces, solving the subproblems, then using the solutions for the pieces to solve the whole problem. Even though we are thinking in terms of merging together two large subarrays, the fact is that most merges are merging together tiny subarrays. Another way to implement mergesort is to organize the merges so that we do all the merges of tiny subarrays on one pass, then do a second pass to merge those subarrays in pairs, and so forth, continuing until we do a merge that encompasses the whole array. This method requires even less code than the standard recursive implementation. We start by doing a pass of 1-by-1 merges (considering individual items as subarrays of size 1), then a pass of 2-by-2 merges (merge subarrays of size 2 to make subarrays of size 4), then 4-by-4 merges, and so forth. The sec- ond subarray may be smaller than the first in the last merge on each pass (which is no problem for merge()), but otherwise all merges involve subar- rays of equal size, doubling the sorted subarray size for the next pass.
public class MergeBU { // stably merge a[lo..m] with a[m+1..hi] using aux[lo..hi] private static void merge(Comparable[] a, Comparable[] aux, int lo, int m, int hi) { // copy to aux[] System.arraycopy(a, lo, aux, lo, hi + 1 - lo); // merge back to a[] int i = lo, j = m + 1; for (int k = lo; k <= hi; k++) { if (i > m) a[k] = aux[j++]; else if (j > hi) a[k] = aux[i++]; else if (less(aux[j], aux[i])) a[k] = aux[j++]; else a[k] = aux[i++]; } } // bottom-up mergesort public static void sort(Comparable[] a) { int N = a.length; Comparable[] aux = new Comparable[N]; for (int n = 1; n < N; n = n + n) { for (int i = 0; i < N - n; i += n + n) { int m = i + n - 1; int hi = Math.min(i + n + n - 1, N - 1); merge(a, aux, i, m, hi); } } assert isSorted(a); } /** * ******************************************************************** * Helper sorting functions * ********************************************************************* */ // is v < w ? private static boolean less(Comparable v, Comparable w) { return (v.compareTo(w) < 0); } // exchange a[i] and a[j] private static void exch(Object[] a, int i, int j) { Object swap = a[i]; a[i] = a[j]; a[j] = swap; } /** * ******************************************************************** * Check if array is sorted - useful for debugging * ********************************************************************* */ private static boolean isSorted(Comparable[] a) { for (int i = 1; i < a.length; i++) if (less(a[i], a[i - 1])) return false; return true; } }
Bottom-up merge sort uses between 1⁄2 * N* lgN and N* lgN compares and at most 6 * N * lgN array accesses to sort an array of length N. When the array length is a power of 2, top-down and bottom-up mergesort perform precisely the same compares and array accesses, just in a different order. When the array length is not a power of 2, the sequence of compares and array accesses for the two algorithms will be different.
相关推荐
- **性能评估**:对不同的数据结构和算法进行时间和空间复杂度的分析,评估其效率。 - **团队合作**:在项目开发过程中,培养团队协作能力,共同完成一个大型项目的设计与实现。 通过本教程的学习,不仅能够掌握...
在IT领域,掌握数据结构和算法是至关重要的,特别是对于编程语言如C#的开发者来说。数据结构是存储和组织数据的方式,而算法是解决问题或执行任务的特定步骤。本资源"**c#数据结构和算法源码**"提供了一个实践学习的...
标题中的"two-phase-merge_sort-.rar_2phase merge sort_merge_sort_two merge"指的是一个采用两阶段归并排序算法的程序或文档集合。这个算法是针对大数据量、无法一次性加载到内存中的情况设计的,常见于外部排序...
《数据结构与算法分析(Java英文版)》是一本介绍如何利用Java语言处理实际问题中的数据结构和算法的专业书籍。本书由Robert Lafore编写,通过丰富的插图和浅显易懂的语言,帮助读者理解并掌握数据结构和算法的核心...
- **排序算法**:详细讨论了几种常见的排序算法,包括插入排序(Insertion Sort)、选择排序(Selection Sort)、冒泡排序(Bubble Sort)和归并排序(Merge Sort)。每种算法都有其特定的优势和适用场景。 ##### ...
《数据结构算法与应用--C++语言描述》是一本深入探讨数据结构和算法的书籍,其源代码由学习者精心整理并优化,便于理解和实践。这个压缩包包含了多个章节的练习代码,每个文件都按照“章_节_编号”的规则进行命名,...
C++ STL,全称为Standard Template Library(标准模板库),是C++编程语言中的一部分,它提供了丰富的容器、迭代器、算法和函数对象等组件,极大地简化了数据结构和算法的实现。余文溪的《C++ STL--数据结构与算法...
Java作为一种广泛应用的编程语言,提供了丰富的工具和库来实现各种数据结构和算法。本项目基于Java语言,深入探讨并实现了多种常用的数据结构与算法,旨在帮助开发者巩固理论知识,提高实际编程能力。 首先,我们要...
### 数据结构排序算法详解 #### 冒泡排序(Bubble Sort) 冒泡排序是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复进行的,直到没有...
该文档提供了Acm竞赛中常用的算法与数据结构的知识点总结,包括算法、数据结构、算法与数据结构的结合和实践经验等方面的内容。该文档对Acm竞赛的参赛者和关心算法与数据结构的人来说具有重要的参考价值。
数据结构是计算机科学中的核心部分,它探讨了如何有效地存储和组织数据,以便进行高效的查询、更新和操作。在这个压缩包文件中,我们...通过学习和实践这些排序算法,你将能够更好地掌握数据结构和算法,提升编程能力。
### 数据结构与算法分析——C++语言描述 #### 核心知识点概览 根据所提供的文件信息,本资料涉及《数据结构与...对于想要深入了解数据结构与算法的学生和开发者来说,《数据结构与算法分析》是一本不可多得的好书。
数据结构常用算法c++实现,程序目录如下: Array shuffle Prime test(trial division) Prime test(Miller-Rabin's method) 2D Array Arbitary Integer Linear congruential generator Maximum subarray problem Bit...
标题“数据结构与算法分析C++语言描述”表明了这本书的主要内容是关于数据结构和算法的,并且使用C++编程语言进行实现和描述。 #### 描述解析 描述部分简短地提到了这本书为扫描版,希望对读者有所帮助。这暗示了...
通过上述各章节的概述,我们可以看出《数据结构与算法分析—C语言描述》一书覆盖了广泛的计算机科学领域内的核心知识点,不仅包含了基本的数据结构和算法介绍,还深入探讨了各种高级技术和设计理念。这使得本书成为...