学号 2019-2020-20182321 《数据结构与面向对象程序设计》第七周学习总结
教材学习内容总结
- 栈是一个线性集合,其内部元素遵循先进后出的规律,即先进来的最后才能出去。(因其删除和添加元素都是在栈的一段进行)
- 栈的操作都在栈顶进行,如果把栈看成是一长串数组,那么栈顶就类似于数组的末尾,栈通常有着五种基本操作
- pop 删除栈顶元素
- push 将元素添加到栈顶
- peek 查看栈顶元素
- isEmpty 栈是否为空
- size 判定栈中元素的个数
- java可以用泛型来定义类,即可以定义一个类,它保存操作管理直到实例化时才会需要决定类型的对象。如Competition
是一个类型为T的类,当实例化对象的时候,它可以实例化为整数型,也可以实例化为浮点型等等。 - 实现栈的方法可以是利用数组或者是利用链表,但在实现的时候也必须要顾忌几个方面的异常
- 入栈时如果栈满
- 出栈时如果栈空
- 扫描完表达式时栈中的值如果多余一个
- 栈满的原因应该是数据结构出的问题,即算法出了问题,严格意义上说,满栈是不存在的,这辈子都不可能满栈的,虽然听起来是不太可能,但是从栈的角度来说,栈是无限的,是一个无限集,只不过是数据结构有限罢了
- 链表相对于数组的缺点是,链表在指定性上不如数组,如果想要直接访问某个在栈中的元素,用数组时我们就可以直接跳到那里,但是用链表的话就必须要一级一级的往下走,且必须设置头指针,否则会出现头指针缺失,回不去。但是链表相对于数组的优点在于,数组的大小是有限度的,而链表是无限的,它可以无限扩大,也就是我们说的能够构成一个无限的栈。
- 学习了如何使用链表和数组来实现栈,同时,用来实现栈的功能,删除节点,添加节点等等。
- 队列与栈不同,它是从头出,尾进,所以需要两个指针来实现它的功能
- 队列也可以用数组和链表来实现
- 使用数组时我们可以考虑使用“环形”数组,虽然其本质上还是线性构成的,但是可以实现了环形的效果。
- 分析算法有两种:时间复杂度和空间复杂度
- 时间复杂度其实说白了,就是看代码运行了多少次,代码运行的次数决定着这个程序运行的时间,我们会用大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解决方法:
如上图所示,当我们要将一个后缀表达式abcd-*+ef/-转换成前缀表达式的时候,可以遵循以下几个步骤
从左到右扫描后缀表达式:
1.如果是操作数,则直接将其指针压入栈中
2.如果是操作符,则依次弹出两个栈顶指针进行字符串拼接后,再和操作符进行拼接,返回新的指针压入栈中(先弹出的拼接在后弹出的后面,操作符拼接在最前面)
3.直到扫描结束,将栈顶指针弹出,即为前缀表达式字符串的指针
于是,我们可以得到以下结果
下面为我们的核心代码
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;
}
这样,代码就可以照常运转了。
代码托管
(statistics.sh脚本的运行结果截图)
上周考试错题总结
上周无考试
-
上周博客互评情况
- 20182334
- 结对照片
其他(感悟、思考等,可选)
链与栈的内容感觉还好,队列也还没那么难,理解清楚它的意思就行了,就是在关于链如何回到链头的方法上有一定的问题,在类里运行可以成功,但是在驱动程序里运行失败了,还需要我下周继续研究研究。
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 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 | 42/156 | |
参考:软件工程软件的估计为什么这么难,软件工程 估计方法 |
-
计划学习时间:10小时
-
实际学习时间:42小时
-
改进情况:我觉得我还可以顶一会,这周java的作业较为集中,花费的时间也变多
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)