zoukankan      html  css  js  c++  java
  • 归并排序及深入

    归并排序是一种思想,而不是一段实现代码。是将各个子数组排好序,再整体排序的思想。比如大数据处理中的多路归并。这里以典型的二路归并为例

    借助于辅助数组,空间复杂度为o(n)的二路归并,见自己之前的博客

    http://www.cnblogs.com/bobodeboke/p/3416716.html

    以此衍生出来的题目:

    // ***衍生出来的题目一:给两个有序数组a和b,已知数组a的末尾还有足够的空间可以容纳b,写一个函数将b合并到数组a中
    // 和归并排序中mergeArr的方法没什么区别,使用两个指针,对比这两个指针的当前元素;只不过从最大下标处开始归并

    // ****归并衍生问题二:原地归并*************************/
    // 题目:数组arr[0,mid-1]和数组arr[mid,num-1]都 有序,将其merge为一个有序数组arr[0,num-1]
    // 限制条件,要求空间复杂度为O(1)

    对于原地归并,可以参见这篇博客http://blog.csdn.net/zhongkeli/article/details/8786694

    注意点:

    1,重点是手摇发实现内存交换,所谓手摇法。是对要求两个数据序列序列内顺序不变,但两个序列的位置改变的问题。比如之前碰到的字符串左旋

    2,注意一前一后两个指针的转化

    // 如果没有控件复杂度的要求,那么采用归并排序中类似的归并算法,其空间复杂度为O(n)
        public void merge(int[] arr, int mid) {
            int[] tmpArr = new int[arr.length];
            int i = 0;
            int j = mid;
            int k = 0;
            while (i < mid && j <= arr.length - 1) {
                if (arr[i] <= arr[j]) {
                    tmpArr[k++] = arr[i++];
                } else {
                    tmpArr[k++] = arr[j++];
                }
            }
            while (i < mid) {
                tmpArr[k++] = arr[i++];
            }
            while (j <= arr.length - 1) {
                tmpArr[k++] = arr[j++];
            }
            // 重新复制到arr数组中
            for (i = 0; i < k; i++) {
                arr[i] = tmpArr[i];
            }
        }
    
        // 因为要求空间复杂度为O(1),因此采用原地归并
        // 原地归并排序所利用的核心思想便是“反转内存”的变体,即“交换两段相邻内存块”,对于反转内存的相关文章,曾在文章“关于反转字符串(Reverse
        // Words)的思考及三种解法”中对一道面试题做了分析。这一思想用到的地方很多,在《编程珠玑》中被称为“手摇算法”。
        // 也就是两块交换内存,两个块的位置发生变化,而每一个块内部的相对顺序保持不变
        public void merge2(int[] array, int mid) {
            // 使用一前一后两个指针
            int end = array.length - 1;
            // 第一个有序子数组开始的地方
            int i = 0;
            // 第二个有序子数组开始的地方
            int j = mid;
            // 第一步:i向后移动,找到第一个array[i]>array[j]的位置,这时0——i-1一定是数组整体最小块
            while (i < j && j <= end) {
                // 注意外层判断了i<j,并不能代表里层继续成立
                while (i < j && array[i] <= array[j]) {
                    i++;
                }
                // 之后记录下index=after,再让after向后走,知道找到第一个array[after]>array[pre]的部分;这个时候mid-after-1的整体一定小于pre-mid
                int old_j = j;
                while (j <= end && array[j] <= array[i]) {
                    j++;
                }
    
                // 接下来pre_mid-1和mid——after交换内存,采用手摇法,两次块内翻转,依次整体翻转
                shouyao(array, i, old_j, j-1);
                // 之后将pre移动[old_after,after)个距离,前面的顺序排列,后面又构成两个有序子数组
                i += j - old_j;
    
            }
        }
    
        private void shouyao(int[] array, int pre, int mid, int after) {
            reverse(array, pre, mid - 1);
            reverse(array, mid, after);
            reverse(array, pre, after);
        }
    
        private void reverse(int[] array, int pre, int end) {
            for (int i = pre, j = end; i <= j; i++, j--) {
                swap(array, i, j);
            }
    
        }
    
        private void swap(int[] array, int i, int j) {
            int tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    手摇法实现原地归并

    /******衍生问题三,归并及堆排序在大数据中的运用*******/

    题目1,从n个元素中选择前k小的元素

    方法一:快排的思路,见上篇博客。时间复杂度为O(n)

    不过这种方法的缺点,一是需要将所有的元素读入内存;而是需要修改输入的数组

    方法二:

    1)取前面k个元素组成一个最大堆

    2)从剩余的元素中依次取出一个元素和堆顶元素进行比较,如果比堆顶元素小,那么替换堆顶元素,并进行一次自上而下的调整

    3)以此类推,最终堆中剩余的k个元素即是前k个最小的元素(注意堆排序中adjustDown和adjustUp的实现)

    题目2,有20个有序数组,每个数组有500个int元素,降序排列。要求从这10000个元素中选出最大的500个。


    1)从20个数组中各取出一个数,并记录每个数的来源数组,建立一个20个元素的大根堆。此时堆顶就是当前最大的元素,取出堆顶元素(不同于上一题,上一题是抛弃堆顶元素)

    2)从堆顶元素的来源数组中取出下一个数加入堆,调整堆后,再取最大值

    3)如此进行500次即可。时间复杂度为(o(nlog2k))

    备注:

    1)其实分成20个较小的数组,并且内部先排好序的过程就是归并的过程

    2)例题1)取出最小的k个元素;和例题2中取出最大的k个元素都是采用最大堆。其实关键是看抛弃堆顶元素还是保留堆顶元素

    3)例题2因为需要记录元素的来源,因此需要封装一种结构

    struct Elemtype{

    int elem;

    int source;

    }

  • 相关阅读:
    file is universal (3 slices) but does not contain a(n) armv7s slice error for static libraries on iOS
    WebImageButton does not change images after being enabled in Javascript
    ajax OPTION
    编程遍历页面上所有TextBox控件并给它赋值为string.Empty?
    获取海洋天气预报
    C#线程系列教程(1):BeginInvoke和EndInvoke方法
    js控制只能输入数字和小数点
    Response.AddHeader(,)
    ManualResetEvent的理解
    Convert.ToInt32、int.Parse(Int32.Parse)、int.TryParse、(int) 区别
  • 原文地址:https://www.cnblogs.com/bobodeboke/p/3940045.html
Copyright © 2011-2022 走看看