20172314 2018-2019-1《程序设计与数据结构》第五周学习总结

教材学习内容总结

查找

  • 查找:在某个项目组(查找池)中寻找某一指定目标元素,或者确定该指定目标并不存在。

  • 目标是高效的完成查找,使得为了寻找目标所做的比较操作次数最小化,且查找池中项目的数目就定义了问题的大小。

  • 在查找对象的过程中需要进行对象之间的比较,我们用Searching类实现Comparable接口,但使用查找排序方法时必须实例化Searching类,为了解决这个问题需要把所有方法声明为静态或泛型的。静态方法(类方法)通过使用static修饰符声明,调用静态方法不用实例化该类的一个对象。静态方法不是作用于具体的对象中,因此他们不能引用实例变量,但可以引用静态变量,因为静态变量的存在于具体的对象无关。main方法必须用static修饰符修饰,只能方法静态或局部变量。

  • 创建一个泛型方法:在方法头的返回类型前插入一个泛型声明即可:

    public static <T extends Comparable< T>> boolean linearSearch (T[] data, int min, int max, T target)
    

    这样含有返回类型和参数类型的方法就可以使用泛型参数了。需注意,泛型声明需位于返回类型之前,这样泛型才可作为返回类型的一部分。创建泛型方法之后就不用在每次使用Searching类的方法之后都去实例化该类,只需要用类名和要用来替换泛型的具体数据类型,就可以调用静态方法。

  • 两种常见的查找方式

    • 线性查找法
      • 从列表头开始依次比较每一个值,直到找到目标元素,最终找到目标元素或者到达列表尾得出不存在目标元素。
    • 二分查找法
      • 使用二分查找法的前提是查找池中的项目组是已经排好序的。
      • 二分查找法是从排序列表的中间开始查找,如果没有找到,根据中间数值与目标数值的大小比较,从前一半或后一半接着查找,每次比较操作之后,将消减一半查找池,剩下的一半查找池将表示可行候选项。
    • 查找算法中的比较
      • 二分查找的优势:

        线性查找的复杂度为O(n),二分查找的复杂度是对数级的,为O(log2n),这使得他对于大型查找池非常有效率。

      • 线性查找的优势:

        线性查找比二分查找简单,编程调试容易,并且线性查找无需花费额外成本来排序该查找列表。

      • 对于二者的选择需要在将查找池保持为排序状态和提高查找效率的努力之间权衡。

排序

  • 排序:基于某一标准,基于某个规定顺序将某一组项目排序。

  • 基于效率排序算法

    • 顺序排序:使用一对嵌套循环对n个元素排序,大约需要n^2次比较。
    • 对数排序:对n个元素进行排序通常需要大约nlog2n次比较。
    • n较小时,两种算法之间几乎不存在实际差别。
  • 选择排序:

    • 算法通过反复地将某一特定值放到它在列表中的最终已排序位置,从而完成对某一列值的排序。
    • 一般为:扫描整个列表找出最小值,将这个值与该列表第一个位置处的值交换。然后扫描剩下的找出最小的与第二个位置的数交换,以此类推,这一过程结束时就排好序了。
  • 插入排序法

    • 算法通过反复将某一特定值插入到该列表某个已排序的子集中来完成对列表值的排序。
    • 对头两个值依据其相对大小进行排序,第三个值插入到前两个之间的恰当位置,以此类推,该插入过程需要对数组中的其他元素移位。
  • 冒泡排序法

    • 使用了两个嵌套循环的顺序排序算法。他通过反复重复比较列表中的相邻元素,并在他们彼此不符顺序时将他们互换来完成对值的排序。
    • 扫描列表比较临近的元素,如果他们不是按相对顺序排序则互换,把最大值,冒泡到列表最后一个值,然后再次扫描列表,冒泡出倒数第二个值。直至所有元素都被冒泡到正确位置。
  • 快速排序法

    • 通过使用一个任意选定的分区元素将该列表分区,然后对分区元素的任一边的子列表进行递归排序。
    • 一般策略:先选择一个列表元素作为分区元素,分隔列表,使得小于分区元素的所有元素位于该元素的左边,所有大于该分区元素的位于右边。然后对这两个分区进行递归式排序。
  • 归并排序法

    • 是一种递归算法,将列表递归式分成两半,直至每一子列表中都含有一个元素,然后将这些子列表按顺序重组。


  • 基数排序法

    • 基于队列处理的。对三位数的排序,首先要对个位、十位、百位数字依次进行排序,
    • 关键字中的每个数字/字符的每种可能值,都会创建一个单独的队列,队列的数目称为基数。例如对12 34 32 进行排序,这些两位数的每个数字位都取1~4之间的数,就意味着需要4个队列,基数为4。

教材学习中的问题和解决过程

  • 问题一:在看课本的时候,对于基数排序法不怎么理解,没能理解前两次遍历的意义,我觉得直接进行第三次遍历就行了。而且为什么要先排个位数最后是百位数,这样做的原因是什么。

  • 问题一解决:结合图示

    发现由于数字被储存在队列中,所以进队列出队列的顺序是很重要的,先进先出,对于图中的例子来说,第一次遍历对个位数进行排序,出队列时按照数字0~5分别从自己的队列中出来,对于十位的第四个数的那个队列来说,第一次遍历的价值就体现出来,145 143 442 341按顺序从右到左先进入队列,到第三次遍历时,对于数字1的队列,143比145先出来进入到新队列,由于在第一次遍历时已经对个位进行了排序,143比145小,在百位上已经完成了排序。所以说,先比较个位然后十位百位,可以巧妙的按从小到大的顺序排序。

  • 问题二:在书上基数排序法中看到代码

        if(temp.length()>position){
                    digit = Character.digit(temp.charAt(temp.length()-1 - position), 10);//十进制
                }else{
                    digit = Character.digit('0', 10);//十进制
                }
    

    不知道方法digit = Character.digit('0', 10);是什么意思。同时也不是很理解基数排序代码的含义。

  • 问题二解决:通过查找资料发现java.lang.Character.digit()方法

    意思是输出ch的indax进制表示。
    所以说课本代码的意思是输出temp.charAt(temp.length()-1 - position)的十进制表示。对代码的理解为:

       for (int scan = 0; scan < list.length; scan++) {
               //scan查找每个数字
               temp = String.valueOf(list[scan]);// /转化为字符串
               if(temp.length()>position){
                   //例如三位数在对百位排序,就把对应位次的字符换为十进制的数字
                   digit = Character.digit(temp.charAt(temp.length()-1 - position), 10);//十进制
               }else{
                   //例如对于两位数在对百位排序,就在高位添加0,对数字不影响
                   digit = Character.digit('0', 10);//十进制
               }
               //对本次遍历的数排序。例如第一次遍历对个位数进行排序,则按照个位数排序的结果存入队列中
               digitQueues[digit].add(new Integer(list[scan]));// 队列
           }
    

代码调试中的问题和解决过程

  • 问题一:运行出现错误

  • 问题一解决: for循环的条件必须满足在间隔固定数后的也比position小,所以就会产生出界,需要添加条件scan + i <= position; scan++),但由于i是大于1的,所以满足scan + i <= position,一定有scan <= position,所以改为for (scan = 0; scan + i <= position; scan++)

  • 问题二:如何计算代码运行时间?

  • 问题二解决:从网上找到了两种办法

    • 第一种(以毫秒为单位):

      long startTime = System.currentTimeMillis(); //获取开始时间

      doSomething(); //测试的代码段

      long endTime = System.currentTimeMillis(); //获取结束时间

      System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间

    • 第二种(以纳秒为单位):

      long startTime=System.nanoTime(); //获取开始时间

      doSomeThing(); //测试的代码段

      long endTime=System.nanoTime(); //获取结束时间

      System.out.println("程序运行时间: "+(endTime-startTime)+"ns");

  • 问题三:出现多次输出

    一开始只是在每个方法的开始和结尾计时,输出结束时间-开始时间的差值,运行结果却发现对于5种排序方法竟然有17个输出。并且有些比较次数为0.

  • 问题三解决:

    对于插入排序法来说,由于count2在循环内部,输出的count2直接是声明变量的值0,而输出比预计多猜测是因为迭代时造成累计输出,然后添加了每个方法的输出语句,证明猜测是正确的

    然后发现插入排序的次数一直是0

    后来发现需要在while循环外面再加一次计数。

代码托管

上周考试错题总结

  • 错题一:

  • 错题一解析:无序列表的元素按照特殊顺序放置,这种顺序与元素本身无关,列表的使用者决定元素的顺序。

  • 错题二:

  • 错题二解析:Java Collections API包含三个索引列表的实现,add(int index,E element),remove(int index),set(int index,E element)

结对及互评

  • 20172305谭鑫谭鑫的博客对于问题的记录一直以来都很详细,不仅记录了他的解决办法,还有他的思考过程,以及扩充知识,他能够发现问题并能主动学习的精神值得学习。
  • 20172323王禹涵王禹涵的博客最明显的有点就是重点的彩色标识和手写问题解释,可以看出来他有认真的学习总结,同时他的教材内容总结部分也是自己的理解,很用心。

其他

这次的作业小组配合很多,在做PP9.3时,我们三个人的比较次数都不完全一样,最后在群里一个方法一个方法测试,讨论正确的比较计数。感觉小组合作的作用是非常大的!

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积)
目标 5000行 30篇 400小时
第一周 0/0 1/1 8/8
第二周 1163/1163 1/2 15/23
第三周 774/1937 1/3 12/50
第四周 3596/5569 2/5 12/62
第五周 3329/8898 2/7 12/74

参考:

posted @ 2018-10-17 18:20  FYiW  阅读(256)  评论(2编辑  收藏  举报