美文网首页
归并排序

归并排序

作者: Stroman | 来源:发表于2018-02-02 14:10 被阅读14次

    我认为归并排序至少有4种实现方式。

    public class MergeSort {
        /**
         * 下面要实现的是传说中的归并排序算法
         * 从代码量上来讲有点复杂
         * 本算法采用非递归的方法来实现
         * 归并排序是分而治之思想的体现
         * 如果是用非递归方式来实现就不
         * 用考虑分,直接治就可以了。
         * 说得通俗一点就是直接2排序,然后
         * 4排序,然后合并起来就行。
         * 其实归并排序并没有队列的实现,因
         * 为队列的实现归根到底就是本实现,
         * 所以它是多余的。
         * 另外,就是我想到了一个定理——对于
         * 完全二叉树而言,非根结点的长度除
         * 以2-1就是其父结点的序号。
         * @param sourceArray
         */
        static public void mergeSort0(int[] sourceArray) {
            //获取数组的长度
            int arrayLength = sourceArray.length;
            //设置步长初始值为1。
            int stepWidth = 1;
            //只要步长不大于数组的长度就代表归并还得
            //往下进行。
            //用于打印第几趟归并
            int printCounter = 0;
            while (stepWidth <= arrayLength) {
                //计算每个步长的边界index
                //没办法一次性处理余数的问题
                //只能单独处理余数问题
                stepWidth *= 2;
                for (int counter = 0;counter < arrayLength;counter += stepWidth) {
                    //单独处理尾index
                    //把尾index放在循环体里面计算是非常高明的。
                    int rightIndex;
                    if (counter + stepWidth > arrayLength)
                        rightIndex = arrayLength - 1;
                    else rightIndex = counter + stepWidth - 1;
                    //中间的分界点
                    int middleIndex;
                    //用于最后一次步长的时候
                    if (stepWidth > arrayLength)
                        middleIndex = (stepWidth - 1) / 2;
                    else middleIndex = (counter + rightIndex) / 2;
                    //生成一个临时数组用于存储排好序的元素
                    //元素个数恰好等于该步长内的元素个数
                    int stepLength = rightIndex - counter + 1;
                    int[] tempArray = new int[stepLength];
                    int leftPointer = counter;
                    int rightPointer = middleIndex + 1;
                    int tempPointer = 0;
                    //只要两个分组之一没遍历完就把元素放入临时数组中
                    while (leftPointer <= middleIndex && rightPointer <= rightIndex) {
                        if (sourceArray[leftPointer] > sourceArray[rightPointer])
                            tempArray[tempPointer++] = sourceArray[leftPointer++];
                        else tempArray[tempPointer++] = sourceArray[rightPointer++];
                    }
                    //如果前半段没遍历完
                    while (leftPointer <= middleIndex)
                        tempArray[tempPointer++] = sourceArray[leftPointer++];
                    //如果后半段没遍历完
                    while (rightPointer <= rightIndex)
                        tempArray[tempPointer++] = sourceArray[rightPointer++];
                    //把临时数组中的元素赋值给原数组
                    leftPointer = counter;
                    tempPointer = 0;
                    while (tempPointer < stepLength)
                        sourceArray[leftPointer++] = tempArray[tempPointer++];
                    //打印部分
                    System.out.print("[ ");
                    for (int counter1 = 0;counter1 < arrayLength;counter1++) {
                        if ((counter1 + 1) % stepWidth == 0 && counter1 + 1 != arrayLength)
                            System.out.print(sourceArray[counter1] + " | ");
                        else System.out.print(sourceArray[counter1] + " ");
                    }
                    System.out.print("]");
                    System.out.println("归并 " + stepWidth / 2 + " 步长的一小趟结束了");
                }
                for (int element:sourceArray) {
                    System.out.print(element + " ");
                }
                System.out.println("第 " + ++printCounter + " 趟结束了");
            }
        }
    
        /**
         * 我决定精简一下上面的实现代码
         * @param sourceArray
         */
        static public void mergeSort2(int[] sourceArray) {
            int arrayLength = sourceArray.length;
            //用于打印第几趟归并
            int printCounter = 0;
            //现在这个其实是每一趟的middleindex
            for (int stepWidth = 1;stepWidth < arrayLength;stepWidth *= 2) {
                //只有这样才能合并当前步长
                //所以必须要把步长×2
                int doubleStepWidth = 2 * stepWidth;
                for (int counter = 0;counter < arrayLength;counter += doubleStepWidth) {
                    //中间的分界点的index
                    //它也有可能越界。
                    int middleIndex = counter + stepWidth;
                    //右边界指针可能越界
                    int rightIndex = middleIndex + stepWidth;
                    //生成一个临时数组用于存储排好序的元素
                    //它的元素个数可能比实际数组元素个数多
                    //所以可能发生越界错误
                    //虽然申请的数组大小可能比实际容纳的元素个数要多
                    //但是这样可以完美地解决不足一个步长的余数问题
                    int[] tempArray = new int[doubleStepWidth];
                    int leftPointer = counter;
                    //正因为rightIndex可能越界,所以rightPointer也可能越界。
                    int rightPointer = middleIndex;
                    int tempPointer = 0;
                    //只要两个分组之一没遍历完就把元素放入临时数组中
                    //此时2个分组都没有被遍历完
                    //并且前半段的index肯定比后半段的index要小
                    //所以此时只需要让有指针不要越界即可
                    while (leftPointer < middleIndex && rightPointer < rightIndex && rightPointer < arrayLength) {
                        //此处界定了结果是正向有序还是逆向有序
                        if (sourceArray[leftPointer] > sourceArray[rightPointer])
                            tempArray[tempPointer++] = sourceArray[leftPointer++];
                        else tempArray[tempPointer++] = sourceArray[rightPointer++];
                    }
                    //如果前半段没遍历完
                    //假如分组中的元素个数不到doubleStepWidth的一半的时候
                    //leftPointer就有可能越界。
                    while (leftPointer < middleIndex && leftPointer < arrayLength)
                        tempArray[tempPointer++] = sourceArray[leftPointer++];
                    //如果后半段没遍历完
                    //假如分组中的元素个数超过doubleStepWidth的一半并且不到doubleStepWidth的时候
                    //rightPointer就有可能越界。
                    while (rightPointer < rightIndex && rightPointer < arrayLength)
                        tempArray[tempPointer++] = sourceArray[rightPointer++];
                    //把临时数组中的元素赋值给原数组
                    //此时rightPointer已经是指向了双倍步长分组的最后一个位置
                    //但是这个位置可能早就已经越界了
                    //所以还要让它小于数组长度
                    leftPointer = counter;
                    tempPointer = 0;
                    while (leftPointer < rightPointer && leftPointer < arrayLength)
                        sourceArray[leftPointer++] = tempArray[tempPointer++];
                    //打印部分
                    System.out.print("[ ");
                    for (int counter1 = 0;counter1 < arrayLength;counter1++) {
                        if ((counter1 + 1) % stepWidth == 0 && counter1 + 1 != arrayLength)
                            System.out.print(sourceArray[counter1] + " | ");
                        else System.out.print(sourceArray[counter1] + " ");
                    }
                    System.out.print("]");
                    System.out.println("归并 " + stepWidth + " 步长的一小趟结束了");
                }
                for (int element:sourceArray) {
                    System.out.print(element + " ");
                }
                System.out.println("第 " + ++printCounter + " 趟结束了");
            }
        }
    
        /**
         * 下面这个是归并排序的递归实现
         * 递归实现看起来简单明了
         * 这个方法其实是整个算法的入口
         * @param sourceArray
         */
        static public void mergeSort1(int[] sourceArray) {
            int arrayLength = sourceArray.length;
            MergeSort.divideIndex(sourceArray,0,arrayLength - 1);
            for (int element:sourceArray) {
                System.out.print(element + " ");
            }
            System.out.println("归并排序递归算法实现结束了");
        }
    
        /**
         * 下面这个方法是
         * 分而治之
         * 思想中的分部分
         * @param sourceArray
         * @param leftIndex
         * @param rightIndex
         */
        static private void divideIndex(int[] sourceArray,int leftIndex,int rightIndex) {
            //这是递归的终止条件
            //这里的条件不能是小于等于
            //1、从道理上讲如果是相等,那其实没有意义。
            //2、从程序来讲等于号会导致死循环。
            //因为当步长为1的时候leftIndex和rightIndex会相等
            //但是这并不是递归结束的条件。
            if (leftIndex < rightIndex) {
                int middleIndex = (leftIndex + rightIndex) / 2;
                //已经排好序的前半段
                MergeSort.divideIndex(sourceArray,leftIndex,middleIndex);
                //已经排好序的后半段
                MergeSort.divideIndex(sourceArray,middleIndex + 1,rightIndex);
                //合并前半段和后半段
                MergeSort.mergeStep(sourceArray,leftIndex,middleIndex,rightIndex);
            }
        }
    
        /**
         * 这是分而治之策略中的
         * 治部分
         * 说得通俗一点就是合并
         * 每一趟
         * @param sourceArray
         * @param leftIndex
         * @param middleIndex
         * @param rightIndex
         */
        static private void mergeStep(int[] sourceArray,int leftIndex,int middleIndex,int rightIndex) {
            int leftPointer = leftIndex;
            int rightPointer = middleIndex + 1;
            int[] tempArray = new int[rightIndex - leftIndex + 1];
            int tempPointer = 0;
            while (leftPointer <= middleIndex && rightPointer <= rightIndex) {
                if (sourceArray[leftPointer] > sourceArray[rightPointer])
                    tempArray[tempPointer++] = sourceArray[leftPointer++];
                else tempArray[tempPointer++] = sourceArray[rightPointer++];
            }
            while (leftPointer <= middleIndex)
                tempArray[tempPointer++] = sourceArray[leftPointer++];
            while (rightPointer <= rightIndex)
                tempArray[tempPointer++] = sourceArray[rightPointer++];
            leftPointer = rightIndex;
            while (--tempPointer > -1)
                sourceArray[leftPointer--] = tempArray[tempPointer];
        }
    
        /**
         * 其实只要是递归的实现都
         * 可以用栈来转化成非递归
         * 的实现,根据这一原理可
         * 以给出归并排序的另一种
         * 非递归实现——栈实现。
         * 思路为只要分栈中栈顶的
         * 2个元素值不相等就二分
         * 然后把分好的下标入栈,
         * 直到相等为止停止入栈操
         * 作,然后把相等的2个下
         * 标推入并栈中去。
         * 如果分栈中栈顶的2个下
         * 标不等,并且小于并栈中
         * 的栈顶值就代表分栈中栈
         * 顶的2个值并没有被分组
         * 于是要把它们二分入栈,
         * 不小于的话就代表分栈栈
         * 顶的两个值是需要被合并
         * 的,已经排好序的2个分
         * 组的首尾下标,此时应该
         * 用于合并。
         * 应该根据这两个值对并栈
         * 中的栈顶方向的一对或者
         * 2对值进行合并,然后分
         * 栈pop2.
         * 如此往复,直到分栈栈顶为-1.
         * 这就是算法的思想。
         * @param sourceArray
         */
        static public void mergeSort3(int[] sourceArray) {
            int arrayLength = sourceArray.length;
            //需要创建2个数组,一个用
            // 来充当2分下标的栈,它的
            // 容量顶多是2倍的数组长度。
            // 另一个是用来合并的栈,它
            // 的容量也顶多是2被数组长度。
            //分栈
            //经过推算发现分栈所需要的空间最多是
            // 2×⌈log2⁡n⌉(这里是以2为底n的对数的上取整的意思)+1,
            // 并栈最多需要⌈log2⁡n⌉(这里是以2为底n的对数的上取整的意思)+1
            // 个存储空间,但是经过用数学归纳法证明发现
            // 2×⌈log2⁡n⌉(这里是以2为底n的对数的上取整的意思)+1
            // 肯定是小于2n的,所以出于省时省力的角度讲就把这两个
            // 栈的长度都设置为2倍的数组长度。
            int[] divideStack = new int[2 * arrayLength];
            //分栈栈顶指针
            int divideStackTop = -1;
            //并栈
            int[] mergeStack = new int[2 * arrayLength];
            //并栈栈顶指针
            int mergeStackTop = -1;
            //先把整个数组的首尾入栈
            divideStack[++divideStackTop] = 0;
            divideStack[++divideStackTop] = arrayLength - 1;
            while (divideStackTop > -1) {
                if (mergeStackTop == -1 || mergeStack[mergeStackTop] > divideStack[divideStackTop]) {
                    while (divideStack[divideStackTop] > divideStack[divideStackTop - 1]) {
                        int headIndex = divideStack[divideStackTop - 1];
                        int endIndex = divideStack[divideStackTop];
                        int middleIndex = (headIndex + endIndex) / 2;
                        divideStack[++divideStackTop] = headIndex;
                        divideStack[++divideStackTop] = middleIndex;
                        divideStack[++divideStackTop] = middleIndex + 1;
                        divideStack[++divideStackTop] = endIndex;
                    }
                    while (divideStack[divideStackTop] == divideStack[divideStackTop - 1]) {
                        mergeStack[++mergeStackTop] = divideStack[divideStackTop];
                        mergeStack[++mergeStackTop] = divideStack[divideStackTop];
                        divideStackTop -= 2;
                    }
                } else {
                    if (mergeStackTop > 2 && mergeStack[mergeStackTop - 3] == divideStack[divideStackTop]) {
                        int headIndex = mergeStack[mergeStackTop];
                        mergeStackTop -= 2;
                        mergeStack[mergeStackTop] = headIndex;
                    }
                    divideStackTop -= 2;
                    int headPointer = mergeStack[mergeStackTop];
                    int endPointer = mergeStack[mergeStackTop - 1];
                    int middlePointer = (headPointer + endPointer) / 2;
                    mergeStep(sourceArray,headPointer,middlePointer,endPointer);
                }
                for (int counter = 0;counter <= divideStackTop;counter++) {
                    System.out.print(divideStack[counter] + " ");
                }
                System.out.println("divideStack状态");
                for (int counter = mergeStackTop;counter >= 0;counter--) {
                    System.out.print(mergeStack[counter] + " ");
                }
                System.out.println("mergeStack状态");
                for (int element:sourceArray) {
                    System.out.print(element + " ");
                }
                System.out.println("当前数组状态\n");
            }
        }
    
        /**
         * 现在我要用把递归转换成非递归的通用方法把归并排序
         * 转换成非递归的实现
         * 这个通用方法可以解决这类问题
         * 而这类问题就是如何把把递归转换成非递归
         * @param sourceArray
         */
        static public void mergeSort4(int[] sourceArray) {
            int arrayLength = sourceArray.length;
            MergeSort.divideIndex0(sourceArray,0,arrayLength - 1);
            for (int element:sourceArray) {
                System.out.print(element + " ");
            }
            System.out.println("归并排序递归算法实现结束了");
        }
    
        static private void divideIndex0(int[] sourceArray,int leftIndex,int rightIndex) {
            IndexPair[] pairStack = new IndexPair[sourceArray.length];
            int pairStackPointer = -1;
            pairStack[++pairStackPointer] = new IndexPair(leftIndex,rightIndex);
            while (pairStackPointer > -1) {
                IndexPair topPair = pairStack[pairStackPointer--];
                if (topPair.getHeadIndex() < topPair.getEndIndex()) {
                    //逆序入栈
                    int headIndex = topPair.getHeadIndex();
                    int endIndex = topPair.getEndIndex();
                    int middleIndex = (headIndex + endIndex) / 2;
                    MergeSort.mergeStep(sourceArray,leftIndex,middleIndex,rightIndex);
                    pairStack[++pairStackPointer] = new IndexPair(middleIndex + 1,endIndex);
                    pairStack[++pairStackPointer] = new IndexPair(headIndex,middleIndex);
                }
            }
        }
    }
    
    public class IndexPair {
        private int headIndex;
        private int endIndex;
    
        public IndexPair(int headIndex, int endIndex) {
            this.headIndex = headIndex;
            this.endIndex = endIndex;
        }
    
        public int getHeadIndex() {
            return headIndex;
        }
    
        public int getEndIndex() {
            return endIndex;
        }
    }
    

    相关文章

      网友评论

          本文标题:归并排序

          本文链接:https://www.haomeiwen.com/subject/ujfezxtx.html