学习几种排序算法,并讨论这些算法的复杂度html
查找(searching) 是在某个项目组中寻找某一指定目标元素, 或者肯定该组中并不存在该目标元素的一个过程。对其进行查找的项目组有时
也称为查找池( search pool)。java
咱们的目标就是尽量高效地完成查找。从算法分析的角度而言,高效的查找会使该过程所作的比较操做次数最小化。同时该查找池中项目的数目定义了该问题的大小。git
查找某一对象,必须可以将其跟另外一个对象进行比较。咱们对这些算法的实现就是对某个Comparable对象的数组进行查找。
如:public class Searching<T extends Comparable<T>>
这个泛型声明的实际结果就是以用任何实现Comparable接口的类来实例化Searching类。算法
以Comparable接口的方式定义的Searching要求咱们在使用查找或排序方法时必须实例化因而引出了泛型静态方法。数组
System. out.printinl("squace root of 27: ”+ Math.sqrt(27));
安全
要建立个泛型方法, 只需在方法头的返回类型前插入一个泛型声明便可:数据结构
public static<T extends Comparable< T> > boolean lineatsearch (T[] data, int min, int max, T target)
泛型声明必须位于返回类型以前,这样泛型才可做为返回类型的部分。函数
线性查找法代码:学习
public static<T extends Comparable<?super T> > boolean lineatsearch (T[] data, int min, int max, T target) int index = min; boolean found = false; while (!found && index <= max) if (data [index]. compareToltarget) == 0 found=true; index++; return found;
该while循环将遍历数组元素,在找到元素或到达数组尾时其会终止测试
二分查找法
public static <T extends Comparable<T>> boolean binarySearch(T[] data, int min, int max, T target) { boolean found = false; int midpoint = (min + max) / 2; // determine the midpoint(定义了用于查找(可行候选项)的数组部分) if (data[midpoint].compareTo(target) == 0) found = true; else if (data[midpoint].compareTo(target) > 0) { if (min <= midpoint - 1) found = binarySearch(data, min, midpoint - 1, target); } else if (midpoint + 1 <= max) found = binarySearch(data, midpoint + 1, max, target); return found; } }
binarySearch方法是递归实现的。若是没有找到目标元素,且有更多待查找数据,则该方法将调用其自身,同时传递参数,这些参数缩减了数组内可行候选项的规模。min和max索引用于肯定是否还具备更多待查找数据。这就是说,若是削减后的查找区域一个元素也不含有,则该方法将不会调用其自身且会返回一个false 值。
查找算法的比较
若是查找池中有n个元素,平均而言,在咱们找到所查找的那个元素以前咱们将不得不考察n/2个元素。所以,线性直找算法具备线性时间复杂度O(n),由于是依次每回查找一个元素, 因此复杂度是线性的一直接与待查找元素数目成比例。
找到位于该查找地中某元素的预期情形是大约(log2n)/2次比较。所以,二分查找具备一个对数算法且具备时间复杂度O(log2n)。与线性查找相比,n值较大时,二分查找要快得多。
两种查找算法的优点
1.线性查找般比查找要简单,线性查找无需花费额外成原本排序该查找列表。
2.二分查找的复杂度是对教级的,这使得它对于大型查找池很是有效率。对于小型问题,这两种类型的算法之间几乎不存在实用差异。可是,随着n的变大,二分查找就会变得吸引人。
排序是基于某一标准,将某一组项目按照某个规定顺序排列的一个过程。
基于效率排序算法一般也分为两类:顺序排序,它一般使用一对嵌套循环对n个元素排序,须要大约n^2次比较;以及对数排序, 它对n个元素进行排序一般须要大约nlog2 n次比较。与查找算法中同样,在n较小时,这两类算法之间几乎不存在实际差异。
本章学习三种顺序排序选择排序、 插入排序以及冒泡排序,以及两种对数排序快速排序和归并排序。
选择排序法
-选择持排字算法的通常策略:打描整个列表以找出最小值。将这个值与该列表第一个位置处的值交换。扫描(除了第一个值的)剩余部分列表并找出最小值,而后将它和该列表第二个位置处的值交换,扫描(除了前两个值的)剩余部分列表并找出最小值,而后将它和该列表第三个位置处的值交换,直到全部的数字排完。
插入排序算法经过反复地将某一特定值插入到该列表某个已排序的子集中来完成对列表值的排序。
插入排序算法的通常策略:对列表中的头两个值依据其相对大小对其进行排序,若是有必要则将它们互换。将列表的第三个值插入到头两个(已排序的)值中的恰当位置。而后将第四个值插入到列表头三个值中的正确位置。每作出一次插入, 该排序子集中的值数目就会增长一个。继续这一过程, 直至列表中的全部元素都获得彻底排序。该插入过程须要对数组中的其余元素移位,以给插入元素腾出空间。
冒泡排序算法经过重复地比较相邻元素且在必要时将它们互换,从而完成对某个列表的排序。
冒泡排序算法的通常策略:扫描该列表且比较邻接元素,若是它们不是按相对顺序排列则将其互换。这就像把最大值“冒泡”到列表的最后位置,这是它在最终已排序列表中的恰当位置。而后再次扫描该列表,冒泡出倒数第二个值。继续这一过程, 直至全部元素都被冒泡到它们正确的位置。
快速排序法
归并排序(merge sort)算法是另外一种递归排序算法, 经过将列表递归式分红两半直至每一子列表都只含有一个元素,而后将这些子列表按顺序重组,这样就完成了对列表的排序。
归并排序算法的 通常策:首先将该列表分红两个大约相等的部分,而后对每一部分列表递归调用其自身。继续该列表的递归分解,直至达到该递归的基本情形,这时活列表被分割成长度为1的列表,根据定义,它是已排序的了。而后,随着程序控制权传园率该通归调用结构,该算法将两个递归调用所产生的那两个排序子列表归并为个排序列表。
但有时代码倒是
<T extends Comparable<T>>
和 <T extends Comparable<? super T>>
有什么不一样呢?
<T extends Comparable<T>>
类型 T 必须实现 Comparable 接口,而且这个接口的类型是 T。只有这样,T 的实例之间才能相互比较大小。例如,在实际调用时若使用的具体类是 Dog,那么 Dog 必须 implements Comparable<
<T extends Comparable<? super T>>
类型 T 必须实现 Comparable 接口,而且这个接口的类型是 T 或 T 的任一父类。这样声明后,T 的实例之间,T 的实例和它的父类的实例之间,能够相互比较大小。例如,在实际调用时若使用的具体类是 Dog (假设 Dog 有一个父类 Animal),Dog 能够从 Animal 那里继承 Comparable,或者本身 implements Comparable 。<
按我理解这样声明的好处就是,例如对 Animal/Dog 这两个有父子关系的类来讲: <T extends Comparable<? super T>>
能够接受 List<Animal>
,也能够接收 List
因此,<T extends Comparable<? super T>> 这样的类型参数对所传入的参数限制更少,提升了 API 的灵活性。总的来讲,在保证类型安全的前提下,要使用限制最少的类型参数。
能够参考一下这篇文章:
如何理解 Java 中的 <T extends Comparable<? super T>>
问题1:在完成蓝墨云线性表实践时,实现选择排序法的时候,排出来的结果是
问题1解决方案:当时的代码是这样的
选择排序的逻辑确实是个人代码所体现的那样,可是为何会出错呢?
因而我就进行了单步调试,发现由于while(temp2.getNext()!=null)
这行代码是只有temp2以后还存在元素时才会进入循环体,这样就有一个问题,若是temp2就是最后一个元素了,就不会在进行比较,因而发生了上面的错误。如今的关键就是要解决最后两个数比较的位置在哪。
我先将
这段代码放在了第二个循环体的后面,结果陷入了无限死循环;我又把他放在了第一个循环的外面,这个if条件句不会成立。最后我修改成
才解决了问题。
问题2:pp9.3中要求编写代码来计算每次代码执行时间,时间该如何计算呢?
var count=1000; var begin=new Date(); for(var i=0;i<count;i++){ document.createElement("div"); } var end=new Date(); var time=end-begin; console.log("time is="+time);
方案二:咱们可使用System类的currentTimeMillis()方法来返回当前的纳秒数,并保存到一个变量中,在方法执行完毕后再次调用 System的nanoTime()方法,并计算两次调用之间的差值,就是方法执行所消耗的纳秒数。由于队友建议ms为单位对于计算机来讲时间有点长,因此获得的可能值会为0,因此使用ns为单位。
long startTime = System.nanoTime(); //获取开始时间 doSomething(); //测试的代码段 long endTime = System.nanoTime(); //获取结束时间 System.out.println("程序运行时间:" + (endTime - startTime) + "ns"); //输出程序运行时间
(statistics.sh脚本的运行结果截图)
博客中值得学习的或问题:
教材内容总结很精简,我以为能够添加一下对方法的简单的解释。
教材问题2,其实能够写在教材内容总结里,有点缺乏图示。但都是本身总结,进行了学习和简化。
天天都要花好几个小时站在操场,还落了好多课,感受少了好多时间学习,并且这周的任务量也不轻,本身仍是要多抽出时间来学习。
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | ||
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 10/10 | |
第二周 | 326/326 | 1/2 | 18/28 | |
第三周 | 784/1110 | 1/3 | 25/53 | |
第四周 | 2529/3638 | 2/5 | 37/90 | |
第五周 | 1254/4892 | 2/7 | 20/110 |
计划学习时间:28小时
实际学习时间:20小时
最近由于学校的活动少了一些时间来学习。