什么是归并排序?

什么是归并排序?

归并排序的思想是分治,先将原始数组分解成若干子数组,对子数组使用归并方法进行排序,最后合并子数组成原数组产生已经排序的数列**”**

举个例子

在桌面上有十三张扑克牌分成两组,一组6张,一组7张,两组都是按从小到大顺序排好的,并且牌面向上,两堆牌中最小的都在上面,现在要将两堆牌合成一堆,并且按顺序从小到大排列。

对于这个问题,我们可以做的很自然,过程如下示意

最初的两堆牌,如下,都是最上面的最小

img

第一次抓取,将两堆牌上面对比,将小的牌也就是1,拿走,放入第三堆中,此时如下所示,

img

这时在第二堆中的3,成为该堆牌中最上面的牌。

第二次抓取,重复第一次的逻辑,将2拿走,放入第三堆紧挨1的位置,如下所示

img

第三次、第四次…,依次重复上面的步骤,直至桌面上的两堆牌被抓取完,所在的牌都放入到了新的牌堆儿中,也就是第三堆,示意如下:

img

此时先前的两堆牌中已经没有牌了,所有牌都已经有序的放入了新的牌堆中。

归并排序

在上面的例子中,其实就是利用了归并排序的中心思想–归并,将两个已经排好序的子数组,合并成一个大的父数组。这个例子,可以认为是归并算法的归并过程。

什么是分治?

将原来大规模的问题,分解成几个小规模的与原问题类似的问题,递归的求出子问题的结果,再将子问题的结果合并出原来问题结果的的过程。

那算法中归并的过程是怎么样的呢?

现在再看,给定数列

img

按照分治,归并的规则,可以将它分为两个数列

img

这两个子数列都是按从小到大的顺序排列好的,因此可以直接进行归并操作

假定

原数列为A,其初始下标为 k=1,

左子数列为L,其初始下标 i=1,

右子数列为R,其初始下标 j=1,如下所示

img

第一步,比较两个子数列中的i,k两个位置上数的大小,如果L[i]<=R[j],则将L[i]插入到原数列A的k位置,同时k和i的位置右移,

否则将R[j]插入到A的k位置,同时k和j的位置右移,操作完成后结果如下

img

第二步,重复第一步的逻辑,结果

img

第三步

img

第四步

img

第五步

img

第六步

img

第七步

img

第八步,此时R中已经没有数据,直接将L中的剩余数据插入A中,过程结束

img

这个过程称可以叫做Merge,回忆一下整个过程,可以将这个过程分为三个步骤:

1、将原数列拆分

2、新建了两个子数列,并将原数列中的前半部分和后半部分分别存入两个子数列中

3、对子数列进行归并过程

将这个过程实现为一个函数,如下

  public static void merge(int[] array, int p, int q, int r) {   
      // 计算左右子数列的长度    
      int nLeft = q - p + 1;    
      int nRight = r - q;    
      // 分配子数列   
      int[] leftArray = new int[nLeft];    
      int[] rightArray = new int[nRight];    
      // 左子列初始化为 [p...q-p]   
      for (int i = 0; i < nLeft; i++) {     
          leftArray[i] = array[p + i];    
      }    
      // 左子列初始化为 [q+1...r]    
      for (int j = 0; j < nRight; j++) {      
          rightArray[j] = array[q + j + 1];  
      }
    int i = 0;    int j = 0;   
      for (int k = p; k <= r; k++) {   
          int lValue, rValue;     
          if (i < nLeft) {       
              lValue = leftArray[i];    
          } else {    
              // 如果左子列没有数据时      
              if (j < nRight) {   
                  array[k] = rightArray[j];      
              }      
              j++;    
              continue;  
          }     
          if (j < nRight) {  
              rValue = rightArray[j];  
          } else {     
              // 如果右子列没有数据时     
              if (i < nLeft) {  
                  array[k] = leftArray[i];  
              }       
              i++;   
              continue;   
          }
          if (lValue <= rValue) {    
              array[k] = lValue;    
              i++;    
          } else {    
              array[k] = rValue;   
              j++;    
          }   
      } 
  }

下面再看这样一个数列,怎样能够将上面提到的分治和归并思想用到此数列的排序中呢?

也许你已经想到了,将这个大数列,拆分成两个小数列呀。对的,是这么一个思路,于是数列成功被拆分成了这样:

img

但是注意到两小子数列中的数还是无序的,不满足对其进行归并排序的条件,那怎么办呢,难道要对两个子数列进行排序?其实不然,可以继续对两个子数据进行拆分,直到不能再拆分为止,也就是每个子数列中只有一个数值时,就可以不用再拆分了,因为只有一个数时,我们可以将其认为是有序的。

于是继续拆分工作:

img

这样在最后一行中各个子数列中,只有一个元素,它们都是有序的,于是我们就可以对其两两进行归并排序,并一层层向上进行归并计算,如下示意:

img

归并排序的完整实现

import java.util.Arrays;
public class MergeSort {
  public static void merge(int[] array, int p, int q, int r) { 
      // 计算左右子数列的长度    
      int nLeft = q - p + 1;   
      int nRight = r - q;  
      // 分配子数列     
      int[] leftArray = new int[nLeft];  
      int[] rightArray = new int[nRight];   
      // 左子列初始化为 [p...q-p]   
      for (int i = 0; i < nLeft; i++) {  
          leftArray[i] = array[p + i];   
      }   
      // 左子列初始化为 [q+1...r]   
      for (int j = 0; j < nRight; j++) {   
          rightArray[j] = array[q + j + 1];  
      }
   
      int i = 0;  
      int j = 0;  
      for (int k = p; k <= r; k++) {     
          int lValue, rValue;    
          if (i < nLeft) {     
              lValue = leftArray[i];     
          } else {       
              // 如果左子列没有数据时   
              if (j < nRight) {  
                  array[k] = rightArray[j]; 
              }      
              j++;     
              continue;   
          }     
          if (j < nRight) {  
              rValue = rightArray[j];   
          } else {     
              // 如果右子列没有数据时   
              if (i < nLeft) {     
                  array[k] = leftArray[i]; 
              }      
              i++;    
              continue;
          }
      if (lValue <= rValue) {  
          array[k] = lValue;  
          i++;    
      } else {    
          array[k] = rValue; 
          j++;    
      }  
      } 
  }
 
    public static void mergeSort(int[] array, int p, int r) {
        if (p < r) {
            int q = (int) Math.floor((p + r) / 2); 
            mergeSort(array, p, q);  
            mergeSort(array, q + 1, r);  
            merge(array, p, q, r);    }
  }
    
  public static void main(String[] args) { 
      int[] array = new int[] { 5, 4, 9, 3, 2, 8, 7, 9, 1 };
   
      System.out.println(Arrays.toString(array));
   
      mergeSort(array, 0, array.length - 1);
   
      System.out.println(Arrays.toString(array)); 
  }
}

ring[] args) {
int[] array = new int[] { 5, 4, 9, 3, 2, 8, 7, 9, 1 };

  System.out.println(Arrays.toString(array));

  mergeSort(array, 0, array.length - 1);

  System.out.println(Arrays.toString(array)); 

}
}




------结束------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值