前言
算法的核心问题是排序和搜索。这2个领域应用最广,研究也最透。本文我将讲解排序和搜索领域最高效的两个算法:快速排序算法和二分搜索算法。
教科书和很多实现库给出的这两个算法的代码非常复杂,很难理解,本文中我给出的代码是最简单的实现代码,易于理解,效率也很高。
缘起
刚才有人问我怎样实现快速排序,我在5分钟之内写了一个快速排序的Java代码出来,不知道他们有没有理解。
因此,我想到要写作这篇文章。介绍一下快速排序算法和二分搜索算法的最简实现。
我的二分搜索算法是在我用Flex开发工作流编辑器时实现的。当时的需求是在2个图形之间画出连接线,要求根据鼠标操作来绘制,并且线段的起点和终点都是在图形的外框上。
上面的描述可能比较抽象,这么说吧,原来我实现的GUI效果是,2个方框,使用鼠标把它们连接起来,我绘制的线是鼠标点下和释放这2个端点连接起来的线段。但是,这样的线段比较丑,客户要求线段的两头应该在2个方框的边框上。
怎么解决这个问题呢?我把线段看做是排序后的点的集合,然后就可以使用二分搜索算法搜索到线段和边框的交点,然后把它们绘制出来。
当时的二分搜索算法是用ActionScript3写的,现在我把它改成Java了。
快速排序算法和二分搜索算法
算法主要分为排序算法、搜索算法、图算法。图算法我用得不多,没有发言权,本文就不说了。
排序算法中最快的是快速排序算法,搜索算法中最快的是二分搜索算法。我也最喜欢这2个算法。
因为,它们是使用递归实现的,代码简洁清晰,效率又非常高。
根据我的理解,算法的本质就是数学。根据输入和设定的目标,采用有限的步骤实现输出。通常,使用计算机实现的算法,都会用到循环,这样才能发挥计算机高速运算的优势。
循环和递归是等效的,这已经被科学家所证明。数学上没有循环,只有递归的概念,因此使用递归代替循环表示算法有很多好处:
1, 递归的代码要比循环简洁很多,也优雅很多。
2, 递归的代码可以用数学方式建模,可以从数学角度验证其正确性。
很多函数式语言甚至没有循环的概念和关键字,强迫你使用递归来实现循环。如,ErLang。
递归也有一些缺点,递归使用栈来保存函数地址和参数、返回值,而栈是有一定大小的,过多的递归调用可能会造成栈溢出。
但是,递归算法会容易转变为循环。我更欣赏递归的简洁,除非真的出现栈溢出的问题,我是不会使用循环的。
二分搜索算法
理论:
二分搜索算法用于针对已排序的集合进行搜索。
它的原理是:
1, 找到排序数组的中间元素,如果它匹配目标值,那么就返回它在数组中的索引。
2, 如果没有找到,那么判断中间值比目标值大还是小,
如果中间值比目标值大,那么就对第一个元素到middle-1的元素递归这个过程。
如果中间值比目标值小,那么就对middle+1到最后一个元素。
3, 如果结束的索引小于开始的索引,返回-1,表示没有找到。
4, 如果子集合有2个元素,那么各自比较。因为Java的整数除法会舍弃小数,如果数组只有2个元素,那么middle值一直都是第一个元素。
经过上述的递归过程,最终将返回匹配元素的索引,或者是-1,表示找不到。
二分搜索算法之所以速度快,是因为它每次可以把数组切分成两半,每次递归调用都能去除一半数据,而不用匹配每一个数据。
代码:
下面是代码,逻辑清楚,代码简单。
/**
* by 沈东良/良少 http://blog.csdn.net/shendl/
* @param array
* @param start
* @param end 这是最后一个元素的索引,第一次调用应该是array.length-1
* @param value
* @return
*/
public static int binarySearch(int[] array,int start,int end,int value){
int middle=(start+end)/2;
if(end<start){
return -1;
}
if(end==(start+1)){
if(array[start]==value){
return start;
}else if(array[end]==value){
return end;
}
}else if(array[middle]==value){
return middle;
}else if(value>array[middle]){
return binarySearch(array,middle+1,end,value);
}else if(value<array[middle]){
return binarySearch(array,start,middle-1,value);
}
return -1;
}
上述代码稍加改变,就可以排序任意类型。如使用泛型,然后加上对Comparable接口的实现,即可。
快速排序算法
二分搜索算法确实非常快,但是它只能用于已排序数组。如果数组未排序呢,该怎么办呢?简单,先用快速排序算法进行排序,然后再用二分搜索进行搜索。
先排序再搜索,要比匹配每一个元素快得多。搜索引擎,数据库索引也都使用了对数据集合的排序技术,这样搜索数据才会快速。
理论:
最慢,也是最容易想到的排序算法是插入排序算法:
1, 遍历数组,找出最小的元素,把它放到第一个元素。
2, 循环查找未排序的数组,直到整个数组排序。
这需要2个嵌套的循环,意味着它的效率是O(n^2);
之所以插入排序的效率如此之地,是因为要找出整个数组中最小的数据,需要遍历整个数组的元素。
而插入排序聪明就聪明在它不查找整个数组最小、次小…的元素,而是每次仅仅把小于某个元素的值移到一边,通过迭代最终自动实现排序。这就大大节约了排序所需的计算步骤。
快速排序算法的理论:
1, 如果S中的元素个数是0或者1,那么返回。
2, 选取S中的任一元素v,称为中心点。
3, 将S集合中的元素分为2个部分:L={小于pivot 的元素+ pivot }和R={大于或者等于pivot的元素}。
4, 返回L和R。
我们使用Java使用的是就地排序的方式,因此不需要返回值。
因为Java是一种可以修改变量的语言,用函数式语言的术语来说,就是有“副作用”。我们利用这个副作用直接修改作为参数的Array,不需要返回值。
代码:
/** by 沈东良/良少 http://blog.csdn.net/shendl/
* 快速排序,有副作用 从小到大
* @param array
* @param start
* @param end这是最后一个元素的索引,第一次调用应该是array.length-1
*/
public static void quickSort(int[] array,int start,int end){
//有可能造成start>end 因为递归调用时j+1,可能引起j比end还大1。 另外如果数组是空的,或者输入错误也会出现这种情况
if(end<=start){
return;
}else {
//取中间元素为中心点,然后移到最右边
int sign=(start+end)/2;
int tmp=array[end];
array[end]=array[sign];
array[sign]=tmp;
int j=start;
for(int i=start;i<=end-1;i++){
//小于的元素和标记互换,等于的不能互换,否则会形成死循环
if(array[i]<array[end]) {
tmp=array[i];
array[i]=array[j];
array[j]=tmp;
j=j+1;
}
}
//把标记元素和第一个>=它的元素位置互换,这样数组就分成2个部分,一个部分比中心值小,一个部分比中心值大。
tmp=array[j];
array[j]=array[end];
array[end]=tmp;
quickSort(array,start,j);
quickSort(array,j+1,end);
}
}
Java的Arrays类也使用快速排序算法进行排序。但它的代码写得像天书一样。
提高快速排序算法执行效率的主要方法是对中心点进行检测,希望选中的中心点有更大的概率是整个数组的中值。
我的实现中简单的选择数组中间的值作为中心点,一般来说这样的选择效率还是不错的。
注意上面的一项实现技术,我们使用把中心数据元素移到数组最后的方式实现了数组的就地比较。这是比较常用的技术,把数据移到数组的最前面或者最后面,方便比较数据。
另外,我的Java快速排序代码使用了“副作用”,而在纯函数式语言,如Haskell,ErLang中是没有“副作用”的,也就是说变量不可以重新赋值。此时就需要返回值,每次都创建新的子数组。但函数式语言的数组处理功能很强,也会做很多性能优化,因此函数式语言实现快速排序代码更加简单,没有“副作用”,也更加数学化。
JDK使用二分搜索和快速排序算法实现搜索和排序,足见上述两个算法的性能优势。
分享到:
相关推荐
在本文中,我们将深入探讨如何使用Qt5框架和C++编程语言实现九大经典的排序算法。Qt5是一个跨平台的应用程序开发框架,它提供了丰富的库和工具,使得开发人员能够便捷地构建用户界面和应用程序逻辑。C++则是一种强大...
`Algorithm.java`文件可能包含了这些排序算法的Java实现代码,而`常见排序算法的实现与性能比较.doc`文档则可能详细比较了这些算法的性能和适用场景。`readme.txt`文件可能是对整个项目的简要说明,包括如何运行和...
在 Java 中,对列表进行排序的最快方法是使用Collections.sort()方法,该方法使用的排序算法是 Timsort,它是归并排序和插入排序的结合体,具有高效的性能和稳定性。 在上述代码中,使用了 Comparator 接口来实现...
冒泡排序算法在C语言中的实现和应用 在计算机科学中,排序算法是最基本也是最重要的算法之一。冒泡排序是一种简单的排序算法,它的主要思想是通过不断地比较相邻元素,并交换它们以达到排序的目的。在C语言中,冒泡...
尽管在最坏情况下,插入排序和其他简单排序算法可能更快,但堆排序在平均情况下的性能更稳定,尤其适用于大数据集。 总的来说,堆排序是一种效率较高的排序算法,尤其适合处理大量数据。通过理解和实现堆的构建与...
这里的“一些简单算法代码”集合提供了一些基础但实用的算法实现,旨在帮助初学者更好地理解和应用这些概念。下面我们将深入探讨这些算法及其在实际编程中的应用。 首先,排序算法是计算机科学中最基本的算法之一,...
在IT领域,排序算法是计算机科学中的...总之,掌握这些基本排序算法的原理和Java实现对于提升编程技能至关重要,它们是解决更复杂问题的基础。通过阅读和理解提供的源代码,你可以进一步巩固算法知识,提高编程能力。
数据结构中的排序算法是计算机科学中的重要概念,用于组织和管理数据,提高数据访问和...总的来说,理解和实现这些排序算法对于提升编程技能和深入理解数据结构有重要意义,也有助于在实际项目中选择合适的排序方法。
在本资源包中,“排序算法(视频和代码)”提供了丰富的学习材料,包括视频教程和实际的编程代码示例,帮助你深入理解和掌握各种排序方法。 1. **冒泡排序**:是最基础的排序算法之一,通过重复遍历数组,比较相邻...
腾讯算法面试题:64匹马8个跑道需要多少轮才能选出最快的四匹(详解),排序算法数据结构 知识点1:排序算法的应用场景 在腾讯算法面试题中,要求选出64匹马中最快的四匹,需要使用排序算法来解决这个问题。排序...
在编程领域,排序算法是计算机科学的基础之一,尤其是在数据处理和算法分析中占有重要地位。本文将详述Java语言实现的六种经典排序算法:冒泡排序、选择排序、插入排序、归并排序、希尔排序以及快速排序。这些排序...
在“5排序”文件夹中,你可以找到这些排序算法的代码实现,通过阅读和调试代码,你可以深入理解每种算法的细节。 除了基本的查找和排序,数据结构还包括更复杂的结构,如树和图。在“2树与二叉树”和“3图”文件夹...
"快速排序算法java代码" 快速排序算法是由Tony Hoare在1960年提出的一种排序算法,它的平均时间复杂度...快速排序算法是一种高效、稳定、广泛应用的排序算法,它有很多优点和缺点,但它仍然是目前最快的排序算法之一。
总结来说,C语言实现的排序算法如快速排序、归并排序和计数排序为处理大量数据提供了高效的方法。贪心算法则在处理某些特定问题时提供了一种简化问题的策略。了解和掌握这些算法及其在C语言中的实现,对于提升编程...
在实现排序算法时,使用模板可以使代码更具通用性,无需为每种数据类型单独编写排序代码。 2. **排序算法**: - **快速排序(Quick Sort)**:快速排序是一种高效的排序算法,采用分治策略。它选取一个“基准”...
7. 排序算法的选择:在选择排序算法时,需要考虑到哈希函数的选择和实现方式,以及实际应用场景的需求。 8. 数据结构的选择:在选择数据结构时,需要考虑到ハシ表、树形结构、图结构等不同的数据结构的优缺点,并...
非支配排序遗传算法(NSGA-II)是一种广泛应用的多目标优化算法,特别是在复杂问题和工程设计中。在Python环境中,这种算法通常与Jupyter Notebook结合使用,方便进行交互式编程和结果可视化。以下是对NSGA-II算法...
在计算机科学领域,排序和查找算法是核心概念,它们直接影响程序的效率和性能。排序算法是用来组织数据,使其按照特定顺序排列的算法,而查找算法则是...理解这些算法的时间复杂度对于优化代码和提高程序性能至关重要。
在计算机科学中,排序算法是最基本的算法之一。蛮力法、减治法和分治法是三种常见的算法设计策略,它们在排序算法中的应用非常广泛。本文将对蛮力法、减治法和分治法进行详细的介绍,并对其在排序算法中的应用进行...