数据结构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]