- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章java 排序算法之快速排序由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
快速排序(Quicksort) 是对 冒泡排序的一种改进.
。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
该思想可以概括为:挖坑填数 + 分治法.
比如如下的示意图:
。
基本思想如上,但是实现思路有多种,一般取基准值都是以首尾或者中间,这里使用 数组中间 的作为基准值,进行讲解.
arr = [-9,78,0,23,-567-70]
L = 0
,右下标R = 数组大小 - 1
,选数组中间的值为基准值,pivot = arr[(L + R )/ 2]
,基准值为 0。再用两个变量保存 左下标 和 右下标,left = L
和right = R
,用于后面的排序做准备。
可以看到,pivot把数组分成了两组 。
可以看到左边的组已经找完了,L指向了基准值,那就跳出查找循环,右边组开始查找, 。
但是我们可以看到右边的数也都符合规则,所以R也循环遍历到了基准值的位置,此时L和R 已经碰到一起了,这一轮就结束。这一轮就称为快速排序.
继续对分出来的小组,进行上述的快速排序操作,直到组内只剩下一个数时,则排序完成.
L
和R
分别前进一步和后退一步,
如图,就可以再次利用这两个变量,对两组进行快速排序了.
这里就用到了上一次快速循环所保存的left 和 right(上图没有画出来)了.
left
直接就指向了 pivot
,所以不用进行移动查找了。R
跟 pivot
进行比较 ,-567 < -9 ,R
和left
进行交换,得到如下图,注:pivot
是一个值,不是引用类型
因为 R 和 left 没有碰头,所以还得进行一次循环比较。因为 R 就在基准点这,所以不移动,R 和pivot 比较,-9 > -567 , 所以left 前进一步, 。
此时R和left 已经碰到一起了,这一轮就结束了.
l ------------ pivot --------------- r一组从左往右找 一组从右往左找
可以看到,分组后,可以使用递归,对这一组继续分组,然后对他们进行快速排序.
。
推导法先实现第一轮 。
@Test public void processDemo() { int arr[] = {-9, 78, 0, 23, -567, 70}; System.out.println("原始数组:" + Arrays.toString(arr)); processQuickSort(arr, 0, arr.length - 1); } /** * @param arr * @param left 左边这一组的下标起始点,到中间值,则为一组 * @param right 右边这一组的下标结束点,到中间值,则为一组 */ public void processQuickSort(int[] arr, int left, int right) { /* 基本思想:选择一个基准值,将基准值小分成一组,比基准值大的分成一组。 这里的实现思路: 1. 挑选的基准值为数组中间的值 2. 中间值就把数组分成了两组 3. 左边一组,从左到右,挨个与 基准值 比较,找出比基准值大的值 4. 右边一组,从右到左,挨个与 基准值 比较,找出比基准值小的值 5. 左右两边各找到一个值,那么就让这两个值进行交换 6. 然后继续找,直到左右两边碰到,这一轮就结束。这一轮就称为快速排序 7. 继续对分出来的小组,进行上述的快速排序操作,直到组内只剩下一个数时,则排序完成 l ------------ pivot --------------- r 一组从左往右找 一组从右往左找 */ int l = left; int r = right; // 中心点,让这个点作为基准值 int pivot = arr[(left + right) / 2]; // 当他们没有碰到的时候,说明还这一轮还可以继续找 while (l < r) { // 左边组:当找到大于基准值时,退出循环,则表示该值需要交换到右侧去: arr[l] > pivot // 也就是说,如果 arr[l] < pivot,则表示还没有找到比基准值大的数 // 注意:不能等于 pivort,因为最差的情况没有找到,则最后 arr[l] 就是 pivot 这个值,也同样退出循环 while (arr[l] < pivot) { l++; // 所以让左边这一组继续找 } // 右边组:当找到小于基准值时,退出循环,则表示该值需要交换到左侧去:arr[r] < pivot // 也就是说,如果 arr[l] > pivot,则表示还没有找到比基准值小的数 //注意:这里也同样跟上面一样,不能等于 pivort while (arr[r] > pivot) { r--;//让右边组继续找 } // 当左侧与右侧相碰时,说明两边都没有找到,这一轮不用进行交换 // 等于表示,找到了中间的下标 if (l >= r) { break; } // 当找到时,则两数进行交换。 //注意:这里可能会出现有一组已经找完了,或者没有找到,但是另一组找到了,所以一个是指向 pivot 的, //另一个则是指向要交换的数,交换后 pivot的值在数组中的位置会发生改变,下次的交换方式就会发生变化。这个地方要动脑筋想想。 int temp = arr[l]; arr[l] = arr[r]; arr[r] = temp; // 当交换后, // 当数组是: {-9, 78, 0, -23, 0, 70} 时(pivot的值在数组中有多个),就可以验证这里的逻辑 // 如果没有这个判定,将会导致,l 永远 小于 r。循环不能退出来的情况,就出现死循环了 if (arr[l] == pivot) { /* l 自己不能往前移动 一步,因为当交换完成后为:{-9, 0, 0, -23, 78, 70} l = 1,arr[l] = 0 r = 4,arr[r] = 78 再经过一次循环后 l = 1,arr[l] = 0 r = 3,arr[r] = -23 交换后数组为:{-9,-23,0,0,78,70} 此时 l = 1,arr[l] = -23;r = 3,arr[r] = 0 又经过一次循环后 l = 2,arr[l] = 0 r = 3,arr[r] = 0 数组为:{-9,-23,0,0,78,70} 进入了死循环 这里好好动脑子想想 这里为什么是用r-=1呢?是因为if里面的条件是arr[l] == pivot,如果要排序的数组中不存在多个和基准值相等的值, 那么用l+=1的话,l就会跑过分界线(基准值),跑到另一组去,这个算法也就失败了, 还有一个原因是,r 是刚刚交换过的,一定比 基准值大,所以没有必要再和基准值比较了 */ r -= 1; } // 这里和上面一致,如果说,先走了上面的 r-=1 // 这里也满足(也就是说有多个值和基准值相等),那么说明,下一次是相同的两个值,一个是 r == 一个是基准值 // 但是他们是相同的值,r后退一步 l前进一步,不影响。但是再走这完这里逻辑时,就会导致 l > r,退出整个循环 if (arr[r] == pivot) { l += 1; } } System.out.println("第 1 轮排序后:" + Arrays.toString(arr)); }
注意:上述的算法特别是边界判定,就是上面「当交换后」对 r-=1 的这个边界判定时,有点难以理解,但是一定要理解为什么要这样写.
测试信息输出 。
原始数组:[-9, 78, 0, 23, -567, 70] 第 1 轮排序后:[-9, -567, 0, 23, 78, 70] 。
那么如何向左递归和右递归呢?在上面的代码后面接着实现如下 。
System.out.println("第 1 轮排序后:" + Arrays.toString(arr)); /* if (l >= r) { break; } 循环从这条代码中跳出就会l = r */ // 如果 l = r,会出现死循环,出现栈溢出 //这里也要动脑子想想 if (l == r) { l++; r--; } // 开始左递归 // 上面算法是 r--,l++ ,往两组的中间走,当 left < r 时,表示还可以继续分组 if (left < r) { processQuickSort(arr, left, r); } if (right > l) { processQuickSort(arr, l, right); }
完整实现和推导实现其实差不多了,为了加深记忆,自己按照基本思想和思路分析,默写.
/** * 快速排序默写实现 * * 基本思想:通过一趟将要排序的数据,分隔成独立的两个部分,一部分的所有数据都比另一部分的所有数据要小。 * 思路分析: * {-9, 78, 0, 23, -567, 70}; length=6 * 1. 挑选中间的值作为 基准值:(0 + (6 -1))/2= [2] = 0 * 2. 左侧 left 部分,从 0 开始到中间值 -1: 0,1: -9, 78,找出一个比基准值大的数 * 3. 右侧 right 部分,从中间值 + 1 到数组大小-1:3,5:23,-567, 70,找出一个比基准值小的数 * 4. 如果找到,则将他们进行交换,这样一轮下来,就完成了一次快速排序:一部分的所有数据都比另一部分的所有数据要小。 * 4. 如果左侧部分还可以分组,则进行左侧递归调用 * 5. 如果右侧部分还可以分组,则进行右侧递归调用 * * 简单说:一轮快速排序示意图如下: * 中间的基准值 * l ------------ pivot --------------- r * 一组从左往右找 一组从右往左找 * 找到比基准值大的数 找出一个比基准值小的数 * 然后进行交换 * */ @Test public void quickSortTest() { int arr[] = {-9, 78, 0, 23, -567, 70};// int arr[] = {-9, 78, 0, -23, 0, 70}; // 在推导过程中,将会导致交换异常的数组,在这里不会出现那种情况 int left = 0; int right = arr.length - 1; System.out.println("原始数组:" + Arrays.toString(arr)); quickSort(arr, left, right); System.out.println("排序后:" + Arrays.toString(arr)); } public void quickSort(int[] arr, int left, int right) { // 找到中间值 int pivotIndex = (left + right) / 2; int pivot = arr[pivotIndex]; int l = left; int r = right; while (l < r) { // 从左往右找,直到找到一个数,比基准值大的数 while (arr[l] < pivot) { l++; } // 从右往左找,知道找到一个数,比基准值小的数 while (arr[r] > pivot) { r--; } // 表示未找到 if (l >= r) { break; } // 进行交换 int temp = arr[l]; arr[l] = arr[r]; arr[r] = temp; // 那么下一轮,左侧的这个值将不再参与排序,因为刚交换过,一定比基准值小 // 那么下一轮,右侧的这个值将不再参与排序,因为刚交换过,一定比基准值大 r--; l++; } // 当一轮找完后,没有找到,则是中间值时, // 需要让他们擦肩而过,也就是重新分组,中间值不再参与分组 // 否则,在某些情况下,会进入死循环 if (l == r) { l++; r--; } // 如果左侧还可以继续分组,则继续快排 // 由于擦肩而过了,那么左侧的组值,则是最初的开始与中间值的前一个,也就是这里得到的 r if (left < r) { quickSort(arr, left, r); } if (right > l) { quickSort(arr, l, right); } }
另外,在实现的过程中,将某些代码为什么要那样判断边界,进行了梳理。你会发现上述代码和推导的代码有一个地方不一样。这个是我自己按逻辑进行的改进,更容易看明白一些。目前未发现 bug,如果有错请评论指出,毕竟这个算法还是有点难的.
。
/** * 大量数据排序时间测试 */ @Test public void bulkDataSort() { int max = 80000;// int 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();// processQuickSort(arr, 0, arr.length - 1); // 和老师的原版代码对比,结果是一样的 quickSort(arr, 0, arr.length - 1); if (arr.length < 10) { System.out.println("排序后:" + Arrays.toString(arr)); } Instant endTime = Instant.now(); System.out.println("共耗时:" + Duration.between(startTime, endTime).toMillis() + " 毫秒"); }
多次运行输出 。
共耗时:40 毫秒 共耗时:52 毫秒 共耗时:36 毫秒 共耗时:31 毫秒 。
。
快速排序的一次划分算法从两头交替搜索,直到low和hight重合,因此其时间复杂度是O(n);而整个快速排序算法的时间复杂度与划分的趟数有关.
理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(nlog2n).
最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O(n2).
为改善最坏情况下的时间性能,可采用其他方法选取中间数。通常采用“三者值取中”方法,即比较H->r[low].key、H->r[high].key与H->r[(low+high)/2].key,取三者中关键字为中值的元素为中间数.
可以证明,快速排序的平均时间复杂度也是O(nlog2n)。因此,该排序方法被认为是目前最好的一种内部排序方法.
从空间性能上看,尽管快速排序只需要一个元素的辅助空间,但快速排序需要一个栈空间来实现递归。最好的情况下,即快速排序的每一趟排序都将元素序列均匀地分割成长度相近的两个子表,所需栈的最大深度为log2(n+1);但最坏的情况下,栈的最大深度为n。这样,快速排序的空间复杂度为O(log2n).
以上就是java 排序算法之快速排序的详细内容,更多关于java 快速排序的资料请关注我其它相关文章! 。
原文链接:https://www.cnblogs.com/ljz111/p/15212240.html 。
最后此篇关于java 排序算法之快速排序的文章就讲到这里了,如果你想了解更多关于java 排序算法之快速排序的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!