赞
踩
参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure
归并就是将两个或两个以上的有序数据序列合并成一个有序数据序列的过程。
采用归并的思想进行排序—归并排序。
假设初始序列含有 n个记录,则可看成是 n个有序的子序列;每个子序列的长度为 1,然后两两归并,得到 n/2 个长度为 2 或 1 有序的子序列,再两两归并,... 如此重复,直至得到一个长度为 n 的有序序列为止。这种排序方法称为 2-路归并。
2-路归并排序的核心操作是将一维数组中前后相邻的两个有序序列归并为一个有序序列。
一趟归并排序的操作是,调用 [n/2h] 次算法 merge 将SR[1..n] 中前后相邻且长度为 h 的有序段进行两两归并,得到前后相邻、长度为 2h 的有序段,并存放在TR[1..n]中,整个归并排序需进行[log2n] 趟。
- //package sort.mergingSort;
- public class MergeSort {
- /**
- * @param args
- */
- public static void merge(int[] L, int s, int m, int n){
- //将有序的L[s...m]和L[m+1...n]归并为有序的L[s...n],借用临时数字(额外的辅存)
- int i=s, j=m+1, k=0;
- int[] tmp = new int[L.length];
- for(k = s; i<=m && j<=n; k++){
- if(L[i] <= L[j])
- tmp[k] = L[i++];
- else
- tmp[k] = L[j++];
- }
- //复制剩余的元素
- while(i <= m)
- tmp[k++] = L[i++];
- while(j <= n)
- tmp[k++] = L[j++];
-
- //将临时数组里的已经排好序的元素返回给L
- for(k=s; k <= n; k++ )
- L[k] = tmp[k];
- }
- //将L[]归并排序
- public static void mergeSort(int[] L, int start, int end){
- if(start < end){
- int m = (start+end)/2;//整个子序列会逐渐一分为2,然后2分为4,4分为8....,主要依赖于L的长度,
- //直至每个子序列仅含有一个元素,就开始合并
- mergeSort(L, start, m);//递归的将L[start...m]归并为有序的L[start...m]
- mergeSort(L, m+1, end);//递归的将L[m+1...end]归并为有序的L[m+1...end]
- merge(L, start, m, end);//将有序的L[start...m]和L[m+1...end]归并到L[start...end]
- }
- //当start == end时,说明此时子序列中只有一个元素,即在此后,每2个单元素将会合并成含有2个元素的子序列
- }
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- int[] test = {0, 46, 55, 13, 42, 94, 5, 17, 70}; //0号单元未使用
- mergeSort(test, 1, test.length-1);
- for(int i = 1; i <= test.length-1; i++)
- System.out.print(test[i]+" ");
- }
- }
运行结果:
在归并排序算法中,递归深度为O(log2n),记录关键字的比较次数为O(nlog2n)。算法总的时间复杂度为O(nlog2n)。
归并排序占用附加存储较多,需要另外一个与原待排序记录数组同样大小的辅助数组。这是这个算法的缺点。
与快速排序和堆排序相比,归并排序的最大特点是,它是一种稳定的排序方法。
从上表可以得出如下几个结论:
(1)从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后两者相比较的结果是,在n较大时,归并排序所需时间较堆排序省,但它所需的辅助存储量最多。
(2)“简单排序”包括除希尔排序之外的所有插入排序,冒泡排序和简单选择排序,其中以直接插入排序为最简单,当序列中的记录“基本有序”或n值较小时,它是最佳的排序方法,因此常将它和其它的排序方法,诸如快速排序、归并排序等结合在一起使用。
(3)基数排序的时间复杂度也可写成O(d·n)。因此,它最适用于n值很大而关键字较小的序列。若关键字也很大,而序列中大多数记录的“最高位关键字”均不同,则亦可先按“最高位关键字”不同将序列分成若干“小”的子序列,而后进行直接插入排序。
(4)从方法的稳定性来比较,基数排序是稳定的,所有时间复杂度为O(n2)的简单排序法(除简单选择)也是稳定的,而快排、堆排序和希尔排序等时间性能较好的排序方法都是不稳定的。一般来说,排序过程中的“比较”是在“相邻的两个记录关键字”间进行的排序方法是稳定的。
各种内排序方法的选择:
1.从时间复杂度选择
对元素个数较多的排序,可以选快速排序、堆排序、归并排序,元素个数较少时,可以选简单的排序方法。
2.从空间复杂度选择
尽量选空间复杂度为O(1)的排序方法,其次选空间复杂度为O(log2n)的快速排序方法,最后才选空间复杂度为O(n)二路归并排序的排序方法。
3.一般选择规则
(1)当待排序元素的个数n较大,排序码分布是随机,而对稳定性不做要求时,则采用快速排序为宜。
(2)当待排序元素的个数n大,内存空间允许,且要求排序稳定时,则采用二路归并排序为宜。
(3)当待排序元素的个数n大,排序码分布可能会出现正序或逆序的情形,且对稳定性不做要求时,则采用堆排序或二路归并排序为宜。
(4)当待排序元素的个数n小,元素基本有序或分布较随机,且要求稳定时,则采用直接插入排序为宜。
(5)当待排序元素的个数n小,对稳定性不做要求时,则采用直接选择排序为宜,若排序码不接近逆序,也可以采用直接插入排序。冒泡排序一般很少采用。
讨论:
本次讨论的多数排序算法是在顺序存储结构上实现的,因此在排序过程中需进行大量记录的移动。当记录很大(即每个记录所占空间较多)时,时间耗费很大,此时可采用静态链表作存储结构。如表插入排序、链式基数排序,以修改指针代替移动记录。
但是,有的排序方法,如快速排序和堆排序,无法实现表排序。在这种情况下可以进行“地址排序”,即另设一个地址向量指示相应记录;同时在排序过程中不移动记录而移动地址向量中相应分量的内容。
内部排序可能达到的最快速度是什么
本次讨论的各种排序方法,其最坏情况下的时间复杂度或为O(n^2 ),或为O(nlogn),其中O(n^2 )是它的上界,那么O(nlogn)是否是它的下界,也就是说,能否找到一种排序方法,它在最坏情况下的时间复杂度低于O(nlogn)呢?
若二叉树的高度为 h,则叶子结点的个数不超过 2h-1;反之,若有 u 个叶子结点,则二叉树的高度至少为 [log2u]向上取整+1。即,描述 n个记录排序的判定树上必存在一条长度为 [log2(n!)]向上取整 的路径。
由此得到下述结论:任何一个借助“比较”进行排序的算法,在最坏情况下所需进行的比较次数至少为 [log2(n!)]向上取整 。 习健:根据斯特林公式,有 [log2(n!)]向上取整 =O(nlogn),所以从数量级上,借助于“比较”进行排序的算法在最坏情况下能达到的最好的时间复杂度为O(nlogn)
本文中的代码可从这里下载:https://github.com/qingyujean/data-structure
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。