常见的排序算法 (下)
5. 归并排序
? 两个有序数组合并并不难, 但是归并的思想确实是这个, 但是如何分, 分到何时呢 ?
这个名字含义就是分为归 和 并
两个阶段执行
先说并吧, 并要求是两个已经排序好了的数组(两个连续数组是位置上也连续) , 比如1,2,3,4
, 连续数组1,2
和3,4
, 不能是 1,2
,4
进行排序 ,
对于两个已经排序好了的数据, 好处是一次遍历便可以两个数组合并成一个有序的数组
然后再说 归吧, 归的思想就是 , 4,3,2,1
, 先分 4,3
和2,1
, 将4,3
继续分 4
,3
,此时这就是俩位置有序的数组, 将他俩数组进行并 , 就成了 3,4
, 然后2,1
分为2
,1
, 此时再将这俩数组排序,此时是1,2
, 然后将3,4
和1,2
进行并排序, 此时就是1,2,3,4
, OK了
/** * 归并排序 * * * @author: <a href='mailto:'>Anthony</a> */ public class MergeSort { public static void main(String[] args) { int[] arr_1000 = Common.generate_Arr_1000(); splitAndMergerSort(arr_1000, 0, arr_1000.length - 1); Common.showArr(arr_1000); } /** * 将[1,2,3]数组分割成直到 [1][2][3]这种 ,递归实现 * * @param arr 数组 * @param start 开始位置 ,索引从0开始 * @param end 结束位置 , 索引0开始 */ private static void splitAndMergerSort(int[] arr, int start, int end) { if (start == end) return; int middle = (end + start) >> 1; splitAndMergerSort(arr, start, middle); splitAndMergerSort(arr, middle + 1, end); merge(arr, start, end, middle + 1); } /** * @param arr 数据 * @param start 索引从0开始 , 数组开始位置 * @param end 索引从0开始 , 数组结束位置 * @param delimiter 切割位置 : [1,2,4,5] 参数(arr,0 , 3 , 2) , 分隔符是 (star+end)/2+1 */ private static void merge(int[] arr, int start, int end, int delimiter) { if (start == end) return; // 1. 初始化数组 int llen = delimiter - start; int rlen = (end - delimiter) + 1; int[] left = new int[llen]; int[] right = new int[rlen]; //2. 将分割数组 , 填充数据 System.arraycopy(arr, start, left, 0, llen); System.arraycopy(arr, delimiter, right, 0, rlen); // 3.归并步骤 int l = 0; int r = 0; int len = end + 1; for (int i = start; i < len; ++i) { if (l < llen && r < rlen) { if (left[l] < right[r]) { arr[i] = left[l]; l++; } else { arr[i] = right[r]; r++; } } else { if (l < llen) { arr[i] = left[l]; l++; } if (r < rlen) { arr[i] = right[r]; r++; } } } } // 第二种合并方式 private static void merge(int[] left_arr, int[] right_arr) { int lL = left_arr.length; int rL = right_arr.length; int resultL = lL + rL; int[] result = new int[resultL]; int l = 0; int r = 0; int w = 0; while (w < resultL) { if (l < lL && r < rL) { if (left_arr[l] < right_arr[r]) { result[w] = left_arr[l]; l++; w++; } else { result[w] = right_arr[r]; r++; w++; } } else { if (l < lL) { System.arraycopy(left_arr, l, result, w, lL - l); break; } if (r < rL) { System.arraycopy(right_arr, r, result, w, rL - r); break; } } } Common.showArr(result); } }
6. 堆排序
数组他可以利用索引关系构建出一个完全二叉树 , 所以利用这个特性很好的组成堆,
堆排序 分为两个阶段
一阶段 : 堆化 - > 将数据转换成 大顶堆或者小顶堆 , 就是根节点数据大于子叶数据 , 就是大顶堆.
二阶段 : 利用大顶堆或者小顶堆进行排序 , 就是将堆顶和最后一个数据进行交换, 因为堆顶是最大值或者最小值, 然后堆化, 重复操作 ,
/** * 堆排序 * * @author: <a href='mailto:'>Anthony</a> */ public class HeapSort { public static void main(String[] args) { int[] arr_1000 = Common.generate_Arr_1000(); heap_sort(arr_1000, arr_1000.length); Common.showArr(arr_1000); } /** * 堆排序 * * @param tree * @param n */ static void heap_sort(int[] tree, int n) { // 1. 构建一个堆 build_heap(tree, n); // 2. // 堆顶和最后一个节点做交换 , 但是我们需要在数组上截取 , 所以就是每次 for (int i = n - 1; i >= 0; i--) { // 交换节点 swap(tree, i, 0); // 第0个位置 开始堆重新排序 heapify(tree, i, 0); } } /** * 构建一个 大顶堆 * * @param tree * @param n */ static void build_heap(int[] tree, int n) { // 最后一个节点 int last_node = n - 1; // 开始遍历的位置是 : 最后一个堆的堆顶 , 意思就是 , 整个树中最小的一个堆 , 其实就是最后一个节点的父节点 int parent = (last_node - 1) / 2; // 递减向上遍历 for (int i = parent; i >= 0; i--) { heapify(tree, n, i); } } /** * @param tree 代表一棵树 * @param n 代表多少个节点 * @param i 对哪个节点进行 heapify */ static void heapify(int[] tree, int n, int i) { // 如果当前值 大于 n 直接返回了 ,一般不会出现这种问题 ..... if (i >= n) { return; } // 子节点 int c1 = 2 * i + 1; int c2 = 2 * i + 2; // 假设最大的节点 为 i (父节点) int max = i; // 如果大于 赋值给 max if (c1 < n && tree[c1] > tree[max]) { max = c1; } // 如果大于 赋值给 max if (c2 < n && tree[c2] > tree[max]) { max = c2; } // 如果i所在的就是最大值我们没必要去做交换 if (max != i) { // 交换最大值 和 父节点 的位置 swap(tree, max, i); // 交换完以后 , 此时的max其实就是 i原来的数 ,就是最小的数字 ,所以需要递归遍历 heapify(tree, n, max); } } static void swap(int[] tree, int max, int i) { int temp = tree[max]; tree[max] = tree[i]; tree[i] = temp; } }
7. 桶排序
其实我认为他是 区间排序, 举个例子 [1,2,3,4,5,6]
, 将他放入6个区间内 , (0,1] ,(1,2] , 依次到最后 , 那么这个放置过程完全是可以通过计算得到的. 所以一次遍历便可以完成排序 , 如果区间内部排序, 可以选择其他排序方式.
package com.sort; import java.util.Arrays; /** * 桶排序 ,其实就是区间排序 1,2,3,4,5,6 ,我们分成 0-3, 3-6的区间(首先区间是有顺序的) * , 1,2,3 进去区间一, 4,5,6进去区间二 , 然后区间内排序, 此时就构建了新的数组 * * * @author: <a href='mailto:'>Anthony</a> */ public class BucketSort { public static void main(String[] args) { int[] arr_1000 = Common.generate_Arr_1000(); bucketSort(arr_1000, 10); Common.showArr(arr_1000); } /** * @param arr 数组 * @param bucketCount 桶的个数 */ public static void bucketSort(int[] arr, int bucketCount) { int len = arr.length; if (len <= 1 || bucketCount <= 0) { return; } // 遍历一次找到最大值 最小值 int max = arr[0], min = arr[0]; for (int i : arr) { if (i > max) { max = i; } if (i < min) { min = i; } } /** * 划分区间 , 比如 5 - 11 ,此时我们需要 / 桶数量 (假如 是 2), 如果我们不+1 , 6 / 2 = 3 ,那么 (11-5)/3=2 , 此时坐标2这个桶 * * 所以区间需要+1 操作 , 所以上面就是 7/2=3.5=4 , (11-5)/4=1 */ int range = ((max - min + 1) % bucketCount) == 0 ? (max - min + 1) / bucketCount : (max - min + 1) / bucketCount + 1; // 创建桶 ,是一个二维数组 int[][] bucket = new int[bucketCount][]; for (int i : arr) { bucket[(i - min) / range] = arrAppend(bucket[(i - min) / range], i); } for (int[] ints : bucket) { sort(ints); } int count = 0; for (int[] ints : bucket) { if (ints != null) { for (int anInt : ints) { arr[count++] = anInt; } } } } /** * 数组拷贝 * * @param arr * @param value * @return */ private static int[] arrAppend(int[] arr, int value) { //数组如果为空, 新建一个数组, if (arr == null) { arr = new int[0]; } // 数组拷贝 , 其实就是长度+1 arr = Arrays.copyOf(arr, arr.length + 1); // 将值复制 arr[arr.length - 1] = value; //返回 return arr; } private static void sort(int[] arr) { /** * 空 或者 0 / 1 都直接返回 */ if (null == arr || arr.length <= 1) { return; } // 2 3 1 for (int index = 1; index < arr.length; index++) { // 当前位置 , 开始必须从第二个开始 int temp = arr[index]; // 左边位置 int left = index - 1; // 移动坐标其实就是 ... while (left >= 0 && arr[left] > temp) { // 互换位置 arr[left + 1] = arr[left]; // 向前移动 left--; } // 最后保存数据 arr[left + 1] = temp; } } }
8. 基数排序
我没有写, 他和桶排序类似 , 一次比较个位数 , 十位数 , 百位数数据, 分成10个桶 , 对号入座, 第一遍比较个位数, 第二遍比较十位数, 第三遍比较百位数 .
相关推荐
要知道时间复杂度只是描述一个增长趋势,复杂度为O的排序算法执行时间不一定比复杂度为O长,因为在计算O时省略了系数、常数、低阶。实际上,在对小规模数据进行排序时,n2的值实际比 knlogn+c还要小。