归并排序

我认为归并排序至少有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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容