- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章java 排序算法之归并排序由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
归并排序(merge sort)是利用 归并 的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略 :
即:分而治之 。
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并.
。
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归实现(也可采用迭代的方式实现)。分阶段可以理解为就是递归拆分子序列的过程.
注:这些数从始至终都在一个数组里(只是抽象的想想成两个数组),除了排序时会把要排序的数copy到一个临时数组里面,这里如果不懂后面看了代码后,再返回来思考就懂了。一定要思考!!!! 。
下面的动图可以很直观的看到整个算法实现的过程,初体验一下吧,后面的代码可以结合上面的图,和这个动图,来整理一下逻辑 。
多个数排序动图:
。
对于分阶段相对较简单,下面着重来对治阶段进行分析.
合并相邻有序子序列分析:下图以 归并排序的最后一次合并 为例来分析,即对应上图的 [4,5,7,8] 和 [1,2,3,6] 两个已经有序的子序列合并为最终序列 [1,2,3,4,5,6,7,8],下图展示了实现步骤 。
如图所示:这是最后一次的合并 操作,是两个有序序列合并为最终的有序序列.
temp
中,并且将自己的指针右移动一位。
先实现合并方法,这个是该算法中最重要的,你也看可以直接看后面的完整算法代码,再返回来思考,这个都随你。此次是因为 分 的步骤需要用到 合 的这个,所以我这里就先放 合并的代码了.
/** * * 最难的是合并,所以可以先完成合并的方法,此方法请参考 基本思想 和 思路分析中的图解来完成 * 动脑筋 * * * * @param arr 要排序的原始数组 * @param left 因为是合并,所以要得到左右两边的的数组信息,这个是左侧数组的第一个索引值 * @param mid 因为是一个数组,标识是第一个数组的最后一个索引,即 mid+1 是右侧数组的第一个索引,即中间索引 * @param right 右侧数组的结束索引,右边数组的最后一个数 * @param temp 临时空间,临时数组 */ public void merge(int[] arr, int left, int mid, int right, int[] temp) { // 定时临时变量,用来遍历数组比较 int l = left; // 左边有序数组的初始索引 int r = mid + 1; // 右边有序数组的初始索引 int t = 0; // temp 数组中当前最后一个有效数据的索引 // 1. 按思路先将两个数组(有序的)有序的合并到 temp 中 // 因为是合并两个数组,所以需要两边的数组都还有值的时候才能进行 比较合并 // 若其中一个数组的值遍历完了(取完了),那么就跳出该循环,把另一个数组所剩的值,直接copy进临时数组即可 while (l <= mid && r <= right) { // 如果左边的比右边的小,则将左边的该元素填充到 temp 中 // 并移动 t++,l++,好继续下一个 if (arr[l] < arr[r]) { temp[t] = arr[l]; t++;//移动 temp 临时数组中当前最后一个有效数据的索引 l++; //左边原始数组的索引移动 } // 否则则将右边的移动到 temp 中 else { temp[t] = arr[r]; t++;//移动 temp 临时数组中当前最后一个有效数据的索引 r++;//右边原始数组的索引移动 } } // 2. 如果有任意一边的数组还有值,则依序将剩余数据填充到 temp 中 // 如果左侧还有值 while (l <= mid) { temp[t] = arr[l]; t++; l++; } // 如果右侧还有值 while (r <= right) { temp[t] = arr[r]; t++; r++; } // 3. 将 temp 数组,拷贝到原始数组 // 注意:只拷贝当前temp有效数据到对应的原始数据中,通俗点说,就是原始数组中要排序的数据,通过temp排成了有序的,然后将temp中的有序数据将原始数组中原来要排序的那部分覆盖了就行了 int tempL = left; // 从左边开始拷贝,左边第一个值的索引 t = 0; // temp 中的有效值索引,有效值边界可以通过 right 判定,因为合并两个数组到 temp 中,边界就是 right ,这里自己好好想想 /* * 对于 8,4,5,7,1,3,6,2 这个数组, * 从栈顶开始合并:8,4 | 5,7 1,3 | 6,2 * 先左递归的话: * 第一次:8,4 合并:tempL=0,right=1 * 第二次:5,7 合并:tempL=2,right=3 * 第三次:4,8 | 5,7 进行合并,tempL=0,right=3 * 直到左递归完成,得到左侧一个有序的序列:4,5,7,8 然后再开始递归右边那个无序的 * 最后回到栈底分解成两个数组的地方,将两个数组合并成一个有序数组 * 这里真的得自己想了,我只能这么说了。 */ System.out.println("tempL=" + tempL + "; right=" + right); while (tempL <= right) { arr[tempL] = temp[t]; tempL++; t++; } }
需要注意的是:这个图一定要看明白:
l = 0,r = 1,那么 mid = 0,边界判定时要用 l <= mid && r <= right
,否则就会跳过对比合并了
完整代码如下 。
@Test public void sortTest() { int[] arr = new int[]{8, 4, 5, 7, 1, 3, 6, 2}; int[] temp = new int[arr.length]; mergeSort(arr, 0, arr.length - 1, temp); System.out.println("排序后:" + Arrays.toString(arr)); } /** * 分 和 合并 */ public 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); // 最后合并 // 由于是递归,合并这里一定是栈顶的先执行(两边数组各只有一个数时) // 第一次:left = 0,right=1 // 第二次:left = 2,right=3 // 第三次:left = 0,right=3// System.out.println("left=" + left + ";right=" + right); merge(arr, left, mid, right, temp); } } /** * * 最难的是合并,所以可以先完成合并的方法,此方法请参考 基本思想 和 思路分析中的图解来完成 * 动脑筋 * * * * @param arr 要排序的原始数组 * @param left 因为是合并,所以要得到左右两边的的数组信息,这个是左侧数组的第一个索引值 * @param mid 因为是一个数组,标识是第一个数组的最后一个索引,即 mid+1 是右侧数组的第一个索引,即中间索引 * @param right 右侧数组的结束索引,右边数组的最后一个数 * @param temp 临时空间,临时数组 */ public void merge(int[] arr, int left, int mid, int right, int[] temp) { // 定时临时变量,用来遍历数组比较 int l = left; // 左边有序数组的初始索引 int r = mid + 1; // 右边有序数组的初始索引 int t = 0; // temp 数组中当前最后一个有效数据的索引 // 1. 按思路先将两个数组(有序的)有序的合并到 temp 中 // 因为是合并两个数组,所以需要两边的数组都还有值的时候才能进行 比较合并 // 若其中一个数组的值遍历完了(取完了),那么就跳出该循环,把另一个数组所剩的值,直接copy进临时数组即可 while (l <= mid && r <= right) { // 如果左边的比右边的小,则将左边的该元素填充到 temp 中 // 并移动 t++,l++,好继续下一个 if (arr[l] < arr[r]) { temp[t] = arr[l]; t++;//移动 temp 临时数组中当前最后一个有效数据的索引 l++; //左边原始数组的索引移动 } // 否则则将右边的移动到 temp 中 else { temp[t] = arr[r]; t++;//移动 temp 临时数组中当前最后一个有效数据的索引 r++;//右边原始数组的索引移动 } } // 2. 如果有任意一边的数组还有值,则依序将剩余数据填充到 temp 中 // 如果左侧还有值 while (l <= mid) { temp[t] = arr[l]; t++; l++; } // 如果右侧还有值 while (r <= right) { temp[t] = arr[r]; t++; r++; } // 3. 将 temp 数组,拷贝到原始数组 // 注意:只拷贝当前temp有效数据到对应的原始数据中,通俗点说,就是原始数组中要排序的数据,通过temp排成了有序的,然后将temp中的有序数据将原始数组中原来要排序的那部分覆盖了就行了 int tempL = left; // 从左边开始拷贝,左边第一个值的索引 t = 0; // temp 中的有效值索引,有效值边界可以通过 right 判定,因为合并两个数组到 temp 中,边界就是 right ,这里自己好好想想 /* * 对于 8,4,5,7,1,3,6,2 这个数组, * 从栈顶开始合并:8,4 | 5,7 1,3 | 6,2 * 先左递归的话: * 第一次:8,4 合并:tempL=0,right=1 * 第二次:5,7 合并:tempL=2,right=3 * 第三次:4,8 | 5,7 进行合并,tempL=0,right=3 * 直到左递归完成,得到左侧一个有序的序列:4,5,7,8 然后再开始递归右边那个无序的 * 最后回到栈底分解成两个数组的地方,将两个数组合并成一个有序数组 * 这里真的得自己想了,我只能这么说了。 */ System.out.println("tempL=" + tempL + "; right=" + right); while (tempL <= right) { arr[tempL] = temp[t]; tempL++; t++; } }
测试输出 。
tempL=0; right=1 tempL=2; right=3 tempL=0; right=3 tempL=4; right=5 tempL=6; right=7 tempL=4; right=7 tempL=0; right=7 排序后:[1, 2, 3, 4, 5, 6, 7, 8] 。
从这里也可以看到,先左递归的话,可以看到最开始合并的索引是 0,1 也就是在栈顶开始首次递归时:两个数组中分别只有一个数字.
最后一次合并:则是回到了最初栈底开始分解的方法,将两个数组 0到7 进行排序到临时数组 temp ,最后将temp中的数据再从 0到7 覆盖到原始数组中。完成了排序 .
8 个数字,会进行 7 次 合并 。
结合上面动图进行思考.
根据上述所讲的基本思想和思路分析,对代码进行了一些改进修改,减小代码的臃肿.
public class MyMergeSortTest { @Test public void sortTest() { int[] arr = new int[]{8, 4, 5, 7, 1, 3, 6, 2}; mergeSort(arr); System.out.println("排序后:" + Arrays.toString(arr)); } public void mergeSort(int arr[]) { int[] temp = new int[arr.length]; doMergeSort(arr, 0, arr.length - 1, temp); } /** * 分治 与 合并 * * @param arr * @param left * @param right * @param temp */ private void doMergeSort(int[] arr, int left, int right, int[] temp) { // 当还可以分解时,就做分解操作 if (left < right) { int mid = (left + right) / 2; // 先左递归分解 doMergeSort(arr, left, mid, temp); // 再右递归分解 doMergeSort(arr, mid + 1, right, temp); // 左递归分解到栈顶时,其实也是分为左右数组 // 左右都到栈顶时,开始合并: // 第一次:合并的是 0,1 的索引,分解到最后的时候,其实只有一个数为一组,所以第一次合并是合并两个数字 merge(arr, left, mid, right, temp); } } /** * 开始合并,每次合并都是两组,第一次合并是两个数字,左右一组都只有一个数字 * * @param arr * @param left * @param mid * @param right * @param temp */ private void merge(int[] arr, int left, int mid, int right, int[] temp) { // 1. 按照规则,将 temp 数组填充 // 2. 当任意一边填充完成后,剩余未填充的依次填充到 temp 数组中 // 3. 将 temp 数组的有效内容,拷贝会原数组,也就是将这次排序好的数组覆盖回原来排序的原数组中 int l = left; // 左侧数组初始索引 int r = mid + 1; // 右侧数组初始索引 int t = 0; // 当前 temp 中有效数据的最后一个元素索引 // 1. 按照规则,将 temp 数组填充 // 当两边都还有数字可比较的时候,进行比较,然后填充 temp 数组 // 只要任意一边没有数值可用时,就仅需到下一步骤 while (l <= mid && r <= right) { // 当左边比右边小时,则填充到 temp 数组中 if (arr[l] < arr[r]) { // 赋值完成后,t 和 l 都需要 +1,往后移动一个位置 // t++ 是先把 t 进行赋值,再进行 t+1 操作 temp[t++] = arr[l++]; } else { // 当不满足时,则说明 右侧数字比左侧的小,进行右侧的填充 temp[t++] = arr[r++]; } } // 2. 有可能有其中一边会有剩余数字未填充到 temp 中,进行收尾处理 while (l <= mid) { temp[t++] = arr[l++]; } while (r <= right) { temp[t++] = arr[r++]; } // 3. 将这个有序数组,覆盖会原始排序的数组中 t = 0; int tempL = left; // 从左侧开始,到右侧结束,就是这一次要合并的两组数据 while (tempL <= right) { arr[tempL++] = temp[t++]; } }}
。
/** * 大量数据排序时间测试 */ @Test public void bulkDataSort() { int max = 80000;// max = 8; int[] arr = new int[max]; for (int i = 0; i < max; i++) { arr[i] = (int) (Math.random() * 80000); } if (arr.length < 10) { System.out.println("原始数组:" + Arrays.toString(arr)); } Instant startTime = Instant.now(); int[] temp = new int[arr.length]; mergeSort(arr, 0, arr.length - 1, temp); if (arr.length < 10) { System.out.println("排序后:" + Arrays.toString(arr)); } Instant endTime = Instant.now(); System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); }
多次测试输出 。
共耗时:26 毫秒 共耗时:37 毫秒 共耗时:30 毫秒 共耗时:30 毫秒 。
。
归并排序比较占用内存,但却是一种效率高且稳定的算法.
改进归并排序在归并时先判断前段序列的最大值与后段序列最小值的关系再确定是否进行复制比较。如果前段序列的最大值小于等于后段序列最小值,则说明序列可以直接形成一段有序序列不需要再归并,反之则需要。所以在序列本身有序的情况下时间复杂度可以降至O(n).
TimSort可以说是归并排序的终极优化版本,主要思想就是检测序列中的天然有序子段(若检测到严格降序子段则翻转序列为升序子段)。在最好情况下无论升序还是降序都可以使时间复杂度降至为O(n),具有很强的自适应性.
。
最好时间复杂度 最坏时间复杂度 平均时间复杂度 空间复杂度 稳定性 传统归并排序 O(nlogn) O(nlogn) O(nlogn) T(n) 稳定 改进归并排序 [1] O(n) O(nlogn) O(nlogn) T(n) 稳定 TimSort O(n) O(nlogn) O(nlogn) T(n) 稳定
我个人感觉归并排序的逻辑相比快速排序来说更为容易理解,而且时间复杂度和快排一样。关于快排的有关讲解请看java 排序算法之快速排序.
到此这篇关于java 排序算法之归并排序的文章就介绍到这了,更多相关java 归并排序内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://www.cnblogs.com/ljz111/p/15214257.html 。
最后此篇关于java 排序算法之归并排序的文章就讲到这里了,如果你想了解更多关于java 排序算法之归并排序的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
滑动窗口限流 滑动窗口限流是一种常用的限流算法,通过维护一个固定大小的窗口,在单位时间内允许通过的请求次数不超过设定的阈值。具体来说,滑动窗口限流算法通常包括以下几个步骤: 初始化:设置窗口
表达式求值:一个只有+,-,*,/的表达式,没有括号 一种神奇的做法:使用数组存储数字和运算符,先把优先级别高的乘法和除法计算出来,再计算加法和减法 int GetVal(string s){
【算法】前缀和 题目 先来看一道题目:(前缀和模板题) 已知一个数组A[],现在想要求出其中一些数字的和。 输入格式: 先是整数N,M,表示一共有N个数字,有M组询问 接下来有N个数,表示A[1]..
1.前序遍历 根-左-右的顺序遍历,可以使用递归 void preOrder(Node *u){ if(u==NULL)return; printf("%d ",u->val);
先看题目 物品不能分隔,必须全部取走或者留下,因此称为01背包 (只有不取和取两种状态) 看第一个样例 我们需要把4个物品装入一个容量为10的背包 我们可以简化问题,从小到大入手分析 weightva
我最近在一次采访中遇到了这个问题: 给出以下矩阵: [[ R R R R R R], [ R B B B R R], [ B R R R B B], [ R B R R R R]] 找出是否有任
我正在尝试通过 C++ 算法从我的 outlook 帐户发送一封电子邮件,该帐户已经打开并记录,但真的不知道从哪里开始(对于 outlook-c++ 集成),谷歌也没有帮我这么多。任何提示将不胜感激。
我发现自己像这样编写了一个手工制作的 while 循环: std::list foo; // In my case, map, but list is simpler auto currentPoin
我有用于检测正方形的 opencv 代码。现在我想在检测正方形后,代码运行另一个命令。 代码如下: #include "cv.h" #include "cxcore.h" #include "high
我正在尝试模拟一个 matlab 函数“imfill”来填充二进制图像(1 和 0 的二维矩阵)。 我想在矩阵中指定一个起点,并像 imfill 的 4 连接版本那样进行洪水填充。 这是否已经存在于
我正在阅读 Robert Sedgewick 的《C++ 算法》。 Basic recurrences section it was mentioned as 这种循环出现在循环输入以消除一个项目的递
我正在思考如何在我的日历中生成代表任务的数据结构(仅供我个人使用)。我有来自 DBMS 的按日期排序的任务记录,如下所示: 买牛奶(18.1.2013) 任务日期 (2013-01-15) 任务标签(
输入一个未排序的整数数组A[1..n]只有 O(d) :(d int) 计算每个元素在单次迭代中出现在列表中的次数。 map 是balanced Binary Search Tree基于确保 O(nl
我遇到了一个问题,但我仍然不知道如何解决。我想出了如何用蛮力的方式来做到这一点,但是当有成千上万的元素时它就不起作用了。 Problem: Say you are given the followin
我有一个列表列表。 L1= [[...][...][.......].......]如果我在展平列表后获取所有元素并从中提取唯一值,那么我会得到一个列表 L2。我有另一个列表 L3,它是 L2 的某个
我们得到二维矩阵数组(假设长度为 i 和宽度为 j)和整数 k我们必须找到包含这个或更大总和的最小矩形的大小F.e k=7 4 1 1 1 1 1 4 4 Anwser是2,因为4+4=8 >= 7,
我实行 3 类倒制,每周换类。顺序为早类 (m)、晚类 (n) 和下午类 (a)。我固定的订单,即它永远不会改变,即使那个星期不工作也是如此。 我创建了一个函数来获取 ISO 周数。当我给它一个日期时
假设我们有一个输入,它是一个元素列表: {a, b, c, d, e, f} 还有不同的集合,可能包含这些元素的任意组合,也可能包含不在输入列表中的其他元素: A:{e,f} B:{d,f,a} C:
我有一个子集算法,可以找到给定集合的所有子集。原始集合的问题在于它是一个不断增长的集合,如果向其中添加元素,我需要再次重新计算它的子集。 有没有一种方法可以优化子集算法,该算法可以从最后一个计算点重新
我有一个包含 100 万个符号及其预期频率的表格。 我想通过为每个符号分配一个唯一(且前缀唯一)的可变长度位串来压缩这些符号的序列,然后将它们连接在一起以表示序列。 我想分配这些位串,以使编码序列的预
我是一名优秀的程序员,十分优秀!