四方显神

导航

数据结构012_排序算法(归并排序、基数排序)

其实我到希尔排序就有点不懂了。就反反复复码很多遍。最后搞得能敲出来但是清楚知道还是不懂。

但是我有良好的自信系统,我知道我不属于笨蛋,如果不懂,那就是确实需要多付出一些精力。

我期待我像小学生背乘法口诀,不知所云,但突然有一天茅塞顿开,啊原来是这么用的。

本篇内容:归并排序、基数排序

一、归并排序:

图示:

代码:

package com.njcx.test5;

import java.util.Arrays;

public class SortAlgorithm {

    /**
     * 归并排序(Merge-sort)是利用归并的思想实现的排序方法,该方法采用经典的分治(divide-and-conquer)策略,
     * 
     * 归并排序的示例图很像完全二叉树,可以使用递归实现归并排序,也可以使用迭代实现。 分的阶段可以理解成递归拆分子序列的过程。
     * 治的阶段需要将两个已经有序的子序列合并成一个有序序列。
     */
    public static void main(String[] args) {
        int[] arr = { 8, 4, 5, 3, 1, 7, 6, 2 };
        System.out.println("原始数组:" + Arrays.toString(arr));
        int[] temp = new int[arr.length];// 归并排序是需要一个额外的空间开销的
        mergeSort(arr, 0, arr.length - 1, temp);
        System.out.println("归并排序后的数组:" + Arrays.toString(arr));
    }

    /**
     * 分+合方法 【*】
     */
    public static void mergeSort(int[] arr, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;// 中间索引
            // 向左递归进行分解
            mergeSort(arr, left, mid, temp);
            // 向右递归分解
            mergeSort(arr, mid + 1, right, temp);

            // 每分解一次就合并一次
            merge(arr, left, mid, right, temp);
        }

    }

    /**
     * 合并的方法
     * 
     * @param arr
     *            排序的原始数组
     * @param left
     *            左边有序序列的初始索引
     * @param mid
     *            中间索引
     * @param right
     *            右边索引
     * @param temp
     *            做中转的数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;// 初始化i,表示左边有序序列的初始索引
        int j = mid + 1;// 初始化j,表示右边有序序列的初始索引
        int t = 0;// 指向temp数组的当前索引

        // (一) 先把左右两边的数据按照规则填充到temp中 直到左右两边有序序列有一边处理完毕为止
        while (i <= mid && j <= right) {
            // 如果左边有序序列的当前元素,小于右边有序序列的当前元素
            // 就将左边的当前元素拷贝到temp数组
            if (arr[i] <= arr[j]) {
                temp[t] = arr[i];
                t += 1;
                i += 1;
            } else if (arr[j] < arr[i]) {
                temp[t] = arr[j];
                t += 1;
                j += 1;
            }
        }
        // (二) 把有剩余数据的一边,所有数据依次全部填充到temp
        while (i <= mid) {// 说明坐标的有序序列还有剩余的元素
            temp[t] = arr[i];
            t += 1;
            i += 1;
        }
        while (j <= right) {
            temp[t] = arr[j];
            t += 1;
            j += 1;
        }

        // (三) 将temp数组的元素拷贝到arr 
        // 注意:这里并不是每一次都8个全部拷贝
        t = 0;
        int tempLeft = left;
        System.out.println("tempLeft=" + tempLeft + ",right=" + right); //方便理解加的一个输出过程
        while (tempLeft <= right) {
            // 第一次合并时,tempLeft=0,right=1
            // 第二次tempLeft=2,right=3
            // 第三次tempLeft=0,right=3
            // 最后一次tempLeft=0,right=7
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        }
    }

}

运行结果:

原始数组:[8, 4, 5, 3, 1, 7, 6, 2]
tempLeft=0,right=1
tempLeft=2,right=3
tempLeft=0,right=3
tempLeft=4,right=5
tempLeft=6,right=7
tempLeft=4,right=7
tempLeft=0,right=7
归并排序后的数组:[1, 2, 3, 4, 5, 6, 7, 8]

 二、基数排序(桶排序)

  • 基数排序(redix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort。它通过键值各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用。
  • 基数排序属于稳定性的排序,基数排序法是效率高的稳定性排序法。
  • 基数排序是桶排序的扩展。
  • 基数排序是1887年赫尔曼.何乐礼发明的,它是这样实现的:将整数按位数切割成不同的数字,然后按个位数分别比较。

基本思想:

将所有待比较数值统一为同样的数位长度,数位较短的数前面补0,然后从低位开始,依次进行依次排序。这样从最低位排序一直到最高位排序完成后,数列就变成一个有序序列。

图示:

 

 代码:

package com.njcx.test5;

import java.util.Arrays;

public class RedixSort {

    public static void main(String[] args) {
        int[] arr = { 53, 3, 542, 748, 14, 214 };
        // redixSort_step(arr);
        redixSort(arr);
    }

    /**
     * 常规代码
     * 
     * @param arr
     */
    public static void redixSort(int[] arr) {

        // 1.得到数组中最大的数的位数
        int max = arr[0];
        for (int item : arr) {
            if (item > max)
                max = item;
        }
        // 2.得到最大数是几位数
        int maxLength = (max + "").length();// 这个方法很巧妙

        int[][] bucket = new int[10][arr.length];
        int[] bucketElementCounts = new int[10];
        // 3.干正事 最大几位数就循环几次
        for (int turn = 0, n = 1; turn < maxLength; turn++, n *= 10) {

            // 放进桶
            for (int i = 0; i < arr.length; i++) {
                // int digitElement = arr[i] / (turn * 10) % 10;我这样写是不对的,报算数错误,
                int digitElement = arr[i] / n % 10;// 这里比较高明,引入了n
                bucket[digitElement][bucketElementCounts[digitElement]] = arr[i];
                bucketElementCounts[digitElement]++;// 可以啊写出来了
            }

            // 桶里拿出放进arr,j表示10个桶的编号
            int index = 0;
            for (int j = 0; j < bucket.length; j++) {
                if (bucketElementCounts[j] > 0) {
                    for (int k = 0; k < bucketElementCounts[j]; k++) {
                        arr[index] = bucket[j][k];
                        index++;
                    }
                }
                bucketElementCounts[j] = 0;
            }
            System.out.println("第" + (turn + 1) + "轮排序后的数组:" + Arrays.toString(arr));
        }

        // System.out.println("基数排序后的数组:" + Arrays.toString(arr));

    }

    /**
     * 逐步分析代码
     * 
     * @param arr
     */
    public static void redixSort_step(int[] arr) {

        // 第一轮排序(针对每个元素的个位进行排序)

        // 定义一个二维数组表示10个桶,每个桶就是一个一位数组
        // 说明:1.二维数组包含10个一维数组 2.为了防止在放入数的时候溢出,则每个一位数组大小定为arr.length
        // 因此基数排序是空间换时间的经典算法
        int[][] bucket = new int[10][arr.length]; // 这里为什么一定要把二维数组定死,只定一维大小10不可以吗

        // 每个桶都有一个值(下标)表示这个桶的当前数据
        // 为了纪录每个桶中,实际存放了多少数据,我们定义一个一维数组来纪录各个桶每次放入的数据个数
        // 可以这样理解,比如bucketElementCounts[0],纪录的就是bucket[0]桶放入数据的个数
        // 【啊好难理解,细细品是可以明白的,明天早上还能明白才是真的懂哦】
        int[] bucketElementCounts = new int[10];

        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素的个位
            int digitOfElement = arr[j] % 10;// 经典取个位法了铁铁
            // 放入到对应的桶中 【*】
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;// 它就是一个值,这里是放进了数据,这个值要计数+1
        }
        // 按照桶的数据取出放进arr(一维数组的下标依次取出数据,放入原来的数组)
        int index = 0;
        // 遍历每一个桶并将桶中的数放入到原数组arr
        for (int k = 0; k < bucketElementCounts.length; k++) {
            if (bucketElementCounts[k] != 0) { // 这个桶里有数据,才进行放进arr的操作
                // 循环该桶(即第k个桶),放入
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    // 取出元素,放
                    arr[index] = bucket[k][l];
                    index++;
                    // 【*】这里两句可以写成一句arr[index++] = bucket[k][l];
                }
            }
            // 【*】第一轮处理后需要将每个bucketElements[k]置为0,不然后续排序桶中会有上一轮的数据
            bucketElementCounts[k] = 0;
        }
        System.out.println("第一轮排序后的数组:" + Arrays.toString(arr));

        // ===============================================================

        // 第二轮排序 几乎没改动代码
        for (int j = 0; j < arr.length; j++) {
            int digitOfElement = arr[j] / 10 % 10; // 经典取十位了铁铁【靠,我这里竟然以为和arr[j]%10/10等价,嘿嘿,我这个憨猪】
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        index = 0;
        for (int k = 0; k < bucketElementCounts.length; k++) {
            if (bucketElementCounts[k] != 0) {
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    arr[index] = bucket[k][l];
                    index++;
                }
            }
            bucketElementCounts[k] = 0;
        }
        System.out.println("第二轮排序后的数组:" + Arrays.toString(arr));

        // ===============================================================
        // 第三轮排序 几乎没改动代码
        for (int j = 0; j < arr.length; j++) {
            int digitOfElement = arr[j] / 100;// 经典取百位
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            bucketElementCounts[digitOfElement]++;
        }
        index = 0;
        for (int k = 0; k < bucketElementCounts.length; k++) {
            if (bucketElementCounts[k] != 0) {
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    arr[index] = bucket[k][l];
                    index++;
                }
            }
        }
        System.out.println("第三轮排序后的数组:" + Arrays.toString(arr));
    }

}

运行结果:

第1轮排序后的数组:[542, 53, 3, 14, 214, 748]
第2轮排序后的数组:[3, 14, 214, 542, 748, 53]
第3轮排序后的数组:[3, 14, 53, 214, 542, 748]
基数排序后的数组:[3, 14, 53, 214, 542, 748]

 

posted on 2020-10-09 20:40  szdbjooo  阅读(300)  评论(0编辑  收藏  举报