第七周

Posted yangkaihan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第七周相关的知识,希望对你有一定的参考价值。

学号 2019-2020-20182321 《数据结构与面向对象程序设计》第七周学习总结

教材学习内容总结

  • 栈是一个线性集合,其内部元素遵循先进后出的规律,即先进来的最后才能出去。(因其删除和添加元素都是在栈的一段进行)
  • 栈的操作都在栈顶进行,如果把栈看成是一长串数组,那么栈顶就类似于数组的末尾,栈通常有着五种基本操作
  1. pop 删除栈顶元素
  2. push 将元素添加到栈顶
  3. peek 查看栈顶元素
  4. isEmpty 栈是否为空
  5. size 判定栈中元素的个数
  • java可以用泛型来定义类,即可以定义一个类,它保存操作管理直到实例化时才会需要决定类型的对象。如Competition 是一个类型为T的类,当实例化对象的时候,它可以实例化为整数型,也可以实例化为浮点型等等。
  • 实现栈的方法可以是利用数组或者是利用链表,但在实现的时候也必须要顾忌几个方面的异常
  1. 入栈时如果栈满
  2. 出栈时如果栈空
  3. 扫描完表达式时栈中的值如果多余一个
  • 栈满的原因应该是数据结构出的问题,即算法出了问题,严格意义上说,满栈是不存在的,这辈子都不可能满栈的,虽然听起来是不太可能,但是从栈的角度来说,栈是无限的,是一个无限集,只不过是数据结构有限罢了
  • 链表相对于数组的缺点是,链表在指定性上不如数组,如果想要直接访问某个在栈中的元素,用数组时我们就可以直接跳到那里,但是用链表的话就必须要一级一级的往下走,且必须设置头指针,否则会出现头指针缺失,回不去。但是链表相对于数组的优点在于,数组的大小是有限度的,而链表是无限的,它可以无限扩大,也就是我们说的能够构成一个无限的栈。
  • 学习了如何使用链表和数组来实现栈,同时,用来实现栈的功能,删除节点,添加节点等等。
  • 队列与栈不同,它是从头出,尾进,所以需要两个指针来实现它的功能
  • 队列也可以用数组和链表来实现
  • 使用数组时我们可以考虑使用“环形”数组,虽然其本质上还是线性构成的,但是可以实现了环形的效果。
  • 分析算法有两种:时间复杂度和空间复杂度
  • 时间复杂度其实说白了,就是看代码运行了多少次,代码运行的次数决定着这个程序运行的时间,我们会用大O法来说明时间复杂度的情况
  • 空间复杂度是说程序用了多少空间。

教材中遇到的问题和解决过程

  • 问题1:链表和数组之间的优缺点在哪
  • 问题1解决方法:数组就是一枚可以精准打击的导弹,而链表就是一列不会回头的列车,使用数组时,我们像访问某个地方,数组就会帮我们直接跳转到那里,但是局限性是,数组是有范围的,正如导弹也是有其攻击范围一样;而链表则是所向披靡,永远也没有终点,当你想无限扩大你的栈时,链表也可以无限扩展(只要你的内存够多),但是问题是链表是个没有回头路的火车(除非用头指针将其返回从头开始),而且其无法精准定位,必须要一个一个的访问遍历才能到达目的地。总的来说,在查找这方面数组确实可控性更强,但是在构建这一方面链表的稳定性更棒,解决代码问题还是要具体问题具体分析,究竟哪一个更好还是要弄清双方的优缺点再加以利用。
  • 问题2:当队列的头指针和尾指针都不断向上走时(即不断出元素和不断入元素),我们如何在不增加队列空间内存的情况下,使其正常运行呢?
  • 问题2解决方法:可以将其转换成一个“环形”的数组,虽然这里说是环形,但是我还是想说清楚这个数组本质上还是线性的,只是在当尾指针指向数组的底部的时候,我们必须将其再重新放回到数组的头部罢了,这样就不会浪费过多的空间,让我们可以随意的出入队。
    下面为代码
package zhan;



public class CircularArrayQueue<T> implements Queue<T> {
    private final int DEFAULT_CAPACITY = 10;
    private int front, rear, cout;
    private T[] queue;

    public CircularArrayQueue() {
        front = rear = cout = 0;
        queue = (T[]) (new Object[DEFAULT_CAPACITY]);
    }

    @Override
    public void enqueue(T element) {
        if (cout == queue.length)

            expandCapacity();
        queue[rear] = element;
        rear = (rear+1)%queue.length;
        cout++;
    }

    private void expandCapacity() {
        T[] larger = (T[]) (new Object[queue.length * 2]);
        for (int index = 0; index < cout; index++)
            larger[index] = queue[(front + index) % queue.length];
            front = 0;
            rear = cout;
            queue = larger;

    }




    @Override
    public T dequeue() throws  EmptyCollectionException{
        T s;
        if(front == rear&&queue[front]==null)
        throw new EmptyCollectionException("队列已空");
        else {
            s = queue[front];
            queue[front]=null;
            front=(front+1)%queue.length;
            cout--;
        }
        return s;
    }

    @Override
    public T first() throws EmptyCollectionException {
        if(front == rear&&queue[front]==null)
        {
            throw new EmptyCollectionException("数组已空");
        }
        else
            return  queue[front];
    }

    @Override
    public boolean isEmpty() {
        if(cout == 0)
            return false;
        else
            return true;
    }

    @Override
    public int size() {
        return cout;
    }
    public String toString()
    {
        String result="";
        for(int i = 0 ;i<queue.length;i++)
        {
            result += "   " + queue[i];
        }
        return result;
    }

}

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

  • 问题1:后缀表达式如何转换成前缀表达式
  • 问题1解决方法:
    《问题1解决方法》
    如上图所示,当我们要将一个后缀表达式abcd-*+ef/-转换成前缀表达式的时候,可以遵循以下几个步骤
从左到右扫描后缀表达式:

1.如果是操作数,则直接将其指针压入栈中
2.如果是操作符,则依次弹出两个栈顶指针进行字符串拼接后,再和操作符进行拼接,返回新的指针压入栈中(先弹出的拼接在后弹出的后面,操作符拼接在最前面)
3.直到扫描结束,将栈顶指针弹出,即为前缀表达式字符串的指针

于是,我们可以得到以下结果

《代码问题解决方案1》

下面为我们的核心代码


import java.util.Scanner;
import java.util.Stack;
public class change {
    private Stack<String> stack;
    private final  char add = '+';
    private final char subtract='-';
    private final char multiply='*';
    private final char divide='/';
    public change()
    {
        stack = new Stack<String>();
    }
    public String qianzhui(String s)
    {
        String op1="";
        String op2="";
        String result="";
        String token ;
        Scanner tokenizer = new Scanner(s);
        while (tokenizer.hasNext()) {
            token = tokenizer.next();
            if (isOpearator(token)) {
                op1 = (stack.pop());
                op2 = (stack.pop());
                result = evalSingleop(token.charAt(0),op2,op1);
                stack.push(result);

            }
            else
                stack.push(token);
        }
        return  result;
    }
    public boolean isOpearator(String token)
    {
        return (token.equals("+")||token.equals("-")||token.equals("/")||token.equals("*"));
    }
    public String evalSingleop(char opeation,String op1,String op2)
    {
        String result = "";
        switch (opeation)
        {
            case add:
                result="+"+op1+" "+op2;
                break;
            case subtract:
                result = "-"+op1+" "+op2;
                break;
            case multiply:
                result =("*"+op1)+" "+op2;
                break;
            case divide:
                result = ("/"+op1)+" "+op2;

        }
        return result;

    }
}
  • 问题2:使用数组时java空指针异常:java.lang.NullPointException
  • 问题2解决方法:这个是在编写java的时候最常遇到的问题之一,我们创建一个关于我们自己编写的类的数组的时候,虽然用到了new,但是其实往往只是声明了一个空间位置而已,但是并没与实例化它。
  Scanner scan = new Scanner(System.in);
        Comparebale[] data = new Comparebale[12];
        Comparebale target ;
        Searching searching = new Searching();
        String s = "19 14 23 1 68 20 84 27 55 11 10 79";
        String[] n = s.split("\\s");

        for(int i = 0;i<n.length;i++)
        {
            int num = Integer.parseInt(n[i]);
            data[i].geti = num;
        }

以上就是一个错误的写法,因为虽然我们给datanew了一波,但是这也只是为他提供了12个位置的空间内存而已,而并没有实例化这个对象,正确写法是这样的

  Scanner scan = new Scanner(System.in);
        Comparebale[] data = new Comparebale[12];
        Comparebale target ;
        Searching searching = new Searching();
        String s = "19 14 23 1 68 20 84 27 55 11 10 79";
        String[] n = s.split("\\s");

        for(int i = 0;i<n.length;i++)
        {
            int num = Integer.parseInt(n[i]);
            data[i]=new Comparebale(num);
        }

即对每一个数组元素都要new一下,这样才能真正的实例化对象,不会造成空指针错误。

  • 问题3:出队时,队列总是为空
  • 问题3解决方法:
    以下为源代码
  public T dequeue() throws  EmptyCollectionException{
        T s;
        if(front == rear)
        throw new EmptyCollectionException("队列已空");
        else {
            s = queue[front];
            queue[front]=null;
            front=(front+1)%queue.length;
            cout--;
        }
        return s;
    }

我原本想的是,如果头指针和尾指针指向同一个方向的时候,那么,这个队列就是一个空队列,但是运行程序的时候我发现队列总是为空,这又是怎么回事呢?debug发现,原来是我的入队代码的原因

 public void enqueue(T element) {
        if (cout == queue.length)

            expandCapacity();
        queue[rear] = element;
        rear = (rear+1)%queue.length;
        cout++;
    }

每次新元素入队后,我的尾指针都会向后跳多一位,加入数组个数是10,当到了a[9]的时候,queue[9]=10,接着rear就会跳到开头头指针的位置rear=0,于是rear就和front相等了,从而程序就出现了问题,无论怎样都会显示队列已空。经过思考,我给这个出栈加多了一个限制条件

    public T dequeue() throws  EmptyCollectionException{
        T s;
        if(front == rear&&queue[front]==null)
        throw new EmptyCollectionException("队列已空");
        else {
            s = queue[front];
            queue[front]=null;
            front=(front+1)%queue.length;
            cout--;
        }
        return s;
    }

这样,代码就可以照常运转了。

《代码队列问题3》

代码托管

(statistics.sh脚本的运行结果截图)

上周考试错题总结

上周无考试

  • 上周博客互评情况

其他(感悟、思考等,可选)

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20
第二周 300/500 2/4 18/38
第三周 623/1000 3/7 22/60
第四周 600/1600 2/9 22/82
第五周 1552/2987 2/11 22/94
第六周 892/3879 2/11 22/114
第七周 2284/6163 2/13 22/134

参考:软件工程软件的估计为什么这么难软件工程 估计方法

  • 计划学习时间:10小时

  • 实际学习时间:8小时

  • 改进情况:

(有空多看看现代软件工程 课件
软件工程师能力自我评价表
)

参考资料

以上是关于第七周的主要内容,如果未能解决你的问题,请参考以下文章

学习进度条(第七周)

第七周.02.Tree LSTM代码讲解

第七周学习进度表

20165331 第七周学习总结

第七周进度

第七周-学习进度条