归并排序
归并排序是另一类不同的排序方法,这种方法是运用分治法解决问题的典型范例。 归并排序的基本思想是基于合并操作,即合并两个已经有序的序列是容易的,不论这两个序列是顺序存储还是链式存储,合并操作都可以在 Ο(m+n)时间内完成(假设两个有序表的长度分别为 m 和 n)。为此,由分治法的一般设计步骤得到归并排序的过程为:
- 划分:将待排序的序列划分为大小相等(或大致相等)的两个子序列;
- 治理:当子序列的规模大于 1 时,递归排序子序列,如果子序列规模为 1 则成为有
序序列; - 组合:将两个有序的子序列合并为一个有序序列。
下图显示了归并算法的执行过程。假设待排序序列为{4, 8, 9, 5, 2, 1, 4, 6},如图所示,归并排序导致了一系列递归的调用,而这一系列调用过程可以由一个二叉树来表示。树中每个结点由两个序列组成,上端为该结点所表示的递归调用的输入,而下端为相应递归调用的输出。树中的边用表示递归调用方向的两条边取代,边上的序号表示各个递归调用发生的次序。

归并排序中一个核心的操作是将一个序列中前后两个相邻的子序列合并为一个有序序列。完整代码如下:
public class MergeSort {
/**
*归并排序方法
**/
public void mergeSort(int[] a,int left,int right){
if(left<right){
int middle = (left+right)/2;
mergeSort(a, left, middle);
mergeSort(a,middle+1,right);
merge(a,left,middle,right);//合并
}
}
private void merge(int[] a, int left, int middle, int right) {
int [] tmpArray = new int[a.length];
int rightStart = middle+1;
int tmp = left;
int third = left;
//比较两个小数组相应下标位置的数组大小,小的先放进新数组
while(left<=middle&&rightStart<=right){
if(a[left]<=a[rightStart]){
tmpArray[third++] = a [left++];
}else{
tmpArray[third++] = a[rightStart++];
}
}
//如果左边还有数据需要拷贝,把左边数组剩下的拷贝到新数组
while(left<=middle){
tmpArray[third++] = a[left++];
}
//如果右边还有数据......
while(rightStart<=right){
tmpArray[third++] = a[rightStart++];
}
while(tmp<=right){
a[tmp] = tmpArray[tmp++];
}
}
public static void main(String[] args){
MergeSort mergeSort = new MergeSort();
int [] a = new int[]{90,3,2,67,44,-9,87,65,11,9,2,8};
mergeSort.mergeSort(a, 0, a.length-1); //调用归并排序方法
}
}
算法 merge 的时间复杂度为Θ(n)。因为假设待合并的两个子序列总长为 n,则这 n 个元素在从数组 a 移动到 b 的过程中,每个元素移动一次,而每次元素移动最多只需要一次比较;最后从数组 b 移回 a 也只需要 n 次移动操作即可,因此,算法 merge 的时间复杂度为Θ(n)。
- 空间效率:在归并排序中,为了将子序列合并需要使用额外的存储空间,这个辅助存储空间的最大值不超过 n,因此归并算法的空间复杂度为Θ(n)。
- 时间效率:归并算法是一个典型的分治算法,因此,它的时间复杂度可以用 Master Method 来求解。通过对算法的分析我们写出算法时间复杂度的递推关系式:T(n) = 2T(n/2) + Θ(n) 该递推式满足 Master Method 的第二种情况,因此 T(n) = Ο(n log n)。 与快速排序和对排序相比,归并排序的优点是它是一种稳定的排序方法。上述算法实现 是归并排序的递归形式,这种形式简洁易懂,然而实用性较差,归并排序还可以按照自底向 上的方式给出其他实现。
基数排序
基数排序是一种“低位优先”的排序方法,它的基本思想是通过反复的对子关键字排序来完成排序。假设元素r[i]的关键字为keyi,keyi是由d位十进制数组成,即keyi=ki1 ki2… kid,则每一位可以视为一个子关键字,其中ki1是最高位,kid是最低位,每一位的值都在 0 到 9的范围内,此时基数rd =10。如果ki1是由d个英文字母组成,即keyi=ki1 ki2… kid,其中’a’≤ kij≤’z’(1≤ j ≤d),则基数rd = 26。 排序时先按最低位的值对元素进行初步排序,在此基础上再按次低位的值进行进一步排序。依次类推,由低位到高位,每一趟都是在前一趟的基础上,根据关键字的某一位对所有元素进行排序,直到最高位,这样就完成了计数排序的全过程。
下面我们先通过一个例子来说明基数排序的基本过程。假设对 7 个元素进行排序,每个元素的关键字是 1000 以下的正整数。在此,每个关键字由三位子关键字构成k1 k2 k3,k1代表关键字的百位,k2代表关键字的十位,k3代表关键字的个位,基数rd = 10。排序的过程如图所示:

基数排序代码如下:
public class BasicSort {
public void sort(int [] array){
int max = 0;//获取最大值
for(int i = 0;i<array.length;i++){
if(max<array[i]){
max = array[i];
}
}
int times = 0;//获取最大值位数
while(max>0){
max = max/10;
times++;
}
List<ArrayList> queue = new ArrayList<ArrayList>();//多维数组
for(int i = 0;i<10;i++){
ArrayList q = new ArrayList<>();
queue.add(q);
}
for(int i = 0;i<times;i++){
for(int j = 0;j<array.length;j++){
//获取对应的位的值(i为0是各位,1是10位,2是百位)
int x = array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
ArrayList q = queue.get(x);
q.add(array[j]);//把元素添加进对应下标数组
// queue.set(x,q);//待定
}
//开始收集
int count = 0;
for(int j = 0;j<10;j++){
while(queue.get(j).size()>0){
ArrayList<Integer> q = queue.get(j);//拿到每一个数组
array[count] = q.get(0);
q.remove(0);
count++;
}
}
}
}
public static void main(String[] args){
BasicSort basicSort = new BasicSort();
int [] a = {136,2,6,8,9,2,8,11,23,56,34,90,89,29,145,209,320,78,3};
basicSort.sort(a);
}
}
需要注意的是,在基数排序的过程中,当针对每一个子关键字进行排序时,需要使用稳定的排序方法。当基数 rd 不太大时,对于每个子关键字的排序而言,计数排序是一种很好的选择,因为其他稳定的排序方法时间复杂度太高。从算法的执行过程中可以看出,对 n 个具有 d 位子关键字的元素进行排序,每一位子关键字的排序采用计数排序时,需要Θ(n + rd)的时间;排序一共进行 d 趟,因此基数排序的时间复杂度 T(n) = Θ(d(n + rd))。当 d 为常数,且 rd 不太大,即 rd = O(n)时,基数排序可以在线性时间内完成。

1280

被折叠的 条评论
为什么被折叠?



