20162322 朱娅霖 作业005&006 栈,队列

20162322 2017-2018-1 《程序设计与数据结构》第五、六周学习总结

教材学习内容总结

集合的介绍(总述)

  1. 集合是收集并组织其他对象的对象。主要分为线性集合(集合中的元素排成一行)和非线性集合(按不同于一行的方式来组织元素)。
  2. 决定因素:加入集合的次序、元素之间的固有关系。

抽象数据类型

  1. 抽象
  2. 封装
  3. 抽象数据类型(abstract data type, ADT)
  4. 数据结构

我们研究数据结构就是研究其逻辑结构物理结构数据运算
逻辑结构:本周我们学习的栈和队列均属于线性结构
物理结构:我们主要研究顺序结构链式结构,本周我们分别用数组和链表来实现栈、队列。
数据运算:在实现栈和队列时,具体实现了插入删除修改运算(查找和排序)

Java Collection API

  1. Java Collection API(appilication programming interface):使用不同方式实现几类集合的一组类。
  2. 我们通过学习Java Collection API,学习集合的设计与实现,来深入理解所用的方法及数据结构,避免Java Collection API中的集合类过少,不能满足我们的需求的情况。

下面是具体内容:

第14章 栈

栈集合

(stack)是一个线性集合,后进先出(last in, first out, LIFO),反序

栈中的操作

操作 描述
push 将元素添加到栈顶
pop 删除栈顶的元素
peek 查看栈顶元素
isEmpty 判定栈是否为空
size 判定栈中元素的个数

异常

  • 入栈时栈满
  • 出栈时栈空
  • 扫描完表达式时栈中的值多于一个

抛出异常的方式:

if ( ! theStack.isEmpty())
    element = theStack.pop();

try {
    element = theStack.pop();
}
catch (EmptyCollectionException exception) {
    System.out.println ("No elements avaliable.")
}

用数组实现栈

数组的容量是固定的,那么若出现“入栈时栈满”这种异常该怎么办呢?

教材中给出三种解决方式:

  • 手动扩充容量
    - 若满则抛出异常。
    - 实现add操作时,让它返回一个状态指示符,来检查操作是否成功。
  • 当需要时自动扩展基本数据结构的容量。

具体代码实现要点:

  1. 变量count既表示数组中用来保存入栈元素的下标位置,也表示栈中当前元素的个数。
  2. 泛型数组不能数理化,我们可以通过将Object数组强制类型转换为泛型数组的方式进行实例化: stack = (T[ ])(Object [ DEFAULT_CAPACITY ]);
  3. push操作:(1)确保栈不满(2)注意count
  4. pop操作:(1)确保栈不空(2)stack[count] = null (3)stack--

用链表实现栈

链式结构使用对象引用变量来建立对象之间联系的一种数据结构。
链表由对象组成,其中每个对象指向表中的下一个对象。
结点存储在链表中的对象。

  1. 访问元素:创建一个新引用变量非常重要,然后先将该引用指向表的第一个元素。对于一个链表,访问表中元素的唯一方法是从第一个元素开始,从前往后沿表前进。
  2. 插入结点:修改引用步骤的次序很重要。
  3. 删除结点
  4. 哨兵结点
  5. 没有链的元素:我们必须将链表的细节与表中保存的元素分开。我们可以定义一个独立的结点类,将元素连在一起。结点类包含两个引用:一个用来指向链表中的下一个结点,一个用来指向保存在表中的元素。
  6. 双向链表

具体代码实现要点:

  1. 需要一个引用指向表的第一个结点、维护表中的个数。
  2. 其余与用数组实现一样。

第15章 队列

队列是一个线性集合,在一端添加元素,在另一端删除元素,先进先出(first in , first out, FIFO),原序。

队列中的操作

操作 描述
enqueue 将元素添加到队尾
dequeue 从队头删除元素
first 检测队头的元素
isEmpty 判定队列是否为空
size 判定队列中元素的个数

用链表实现队列

队列与栈的区别在于,我们必须要操作链表的两端。因此,除了一个指向链表首元素的引用外,还需要跟踪另一个指向链表末元素的引用。再增加一个整形变量count来跟踪队列中的元素个数。

在课上,娄老师带领我们考虑了边界情况,即操作队头,操作队尾的情况,怎么能省去判断的这一环节呢?这一问题将在接下来的“教材学习中的问题和解决过程”环节具体讨论。

用数组实现队列

分析:

  1. 将队列的首元素总是存储在数组的索引0处,由于队列处理会影响到集合的两端,因此从队列中删除元素的时候,该策略要求移动元素,这样操作的效率就很低
  2. 如果将队列的末端总是存储在数组索引0处,当一个元素要入列时,是在末端进行操作的,这就意味着,每个enqueue操作都会使得队列中的所有元素在数组中移动一位,效率仍然很低。
  3. 如果不固定在哪一端,当元素出列时,队列的前端在数组中前移,当元素入列时,队列的末端也在数组中前移,当队列的末端到达了数组的末端将出现难题,此时加大数组容量是不切合实际的,因为这样将不能有效的利用数组的空闲空间。

因此我们的解决方法是将数组看作是环形(环形数组)的,这样就可以除去在队列的数组实现,环形数组并不是一种新的结构,它只是一种把数组用来存储队列的方法,环形数组即数组的最后一个索引后面跟的是第一个索引。

实现方案:用两个整数值来表示队列的前端和末端,当添加和删除元素时,这些值会改变。注意,front的值表示的是队列首元素存储的位置,rear的值表示的是数组的下一个可用单元(不是最后一个元素的存储位置),而由于rear的值不在表示队列的元素数目,因此我们需要使用一个单独的整数值来跟踪元素计数。

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

  • 问题:在课上,娄老师:“为了操作的统一,去掉判断位置的环节我们可以应用头结点来解决问题”,并且在黑板上画了一个上空下空的一个结点(指的是元素和地址均指向空?)我存在以下疑问:
  1. 头结点究竟是什么?元素指向空,这个我理解,但是这个头结点不应该指向队列的第一个元素吗?为何指向也是空?
  2. 引入头结点之后该如何操作,使得操作统一,使我们可以不通过判断语句来操作队列?
  • 问题解决方案:
  1. 针对头结点的概念问题我自然是百度的了:头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。老师画得上空下空,应该是强调头结点的数据域可以不存储任何信息,而头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)
  2. 对带头结点的链表,在表任何结点之间插入结点或删除表中任何结点,所要做的都是修改前一结点的指针域,因为任何元素结点都有前驱结点。若链表没有头结点,则首元素结点没有前驱结点,在其前插入结点或删除该结点时操作会复杂些。

具体代码对比( LinkedList1为“使用头结点实现链表”的情况,LinkedList2为“不使用头结点实现链表”的情况):

(1)在构造方法中:

   public LinkedList1() {
        //生成一个头结点,让头指针和尾指针都指向它
        head = tail = new Entry<T>(null);
        size = 0;
    }
 public LinkedList2() {
        head = tail = null;
        size = 0;
    }

(2)在 isEmpty()方法中:

//  LinkedList1
public boolean isEmpty() {
        return head.next == null; //当头结点的next为空,表明整个链表为空
    }
// LinkedList2
 public boolean isEmpty() {
        return head == null; //头指针为空,表示整个链表为空
    }

(3)在 add方法中:

//  LinkedList1
//默认在原链表尾部添加新的结点
    public boolean add(T element) {
        Entry<T> newEntry = new Entry<T>(element);
        tail.next = newEntry;//原链表尾指针的next指向新的结点
        tail = newEntry;//尾指针向后移动一位,指向新的结点
        size++;
        return true;
    }
// LinkedList2
 public boolean add(T element) {
        Entry<T> newEntry = new Entry<T>(element);
        if (head == null) //如果头指针为空,直接将新结点赋值给头指针和尾指针
            head = tail = newEntry;
        else {
            tail.next = newEntry; //否则,尾指针的next指向新的结点,尾指针后移一位,指向新的结点
            tail = newEntry;
        }
        size++;
        return true;
    }

(4)在remove方法中:

//  LinkedList1
 public T remove(int index) {
        //得到索引index的前一个结点
        Entry<T> preEntry = previous(index);
        T oldVal = preEntry.next.element;
        //如果preEntry的next刚好是最后一个结点,修改tail为preEntry
        if (preEntry.next == tail)
            tail = preEntry;
        preEntry.next = preEntry.next.next;
        size--;
        return oldVal;
    }
// LinkedList2
public T remove(int index) {
        //获得索引值为index-1的结点
        Entry<T> preEntry = previous(index);
        T oldVal;
        if (preEntry == null) {//index=0 直接操作头指针
            oldVal = head.element;
            head = head.next;
        } else { //其他情况,用获得的前一个结点完成移除操作
            oldVal = preEntry.next.element;
            //如果preEntry的next刚好是最后一个结点,修改tail为preEntry
            if (tail == preEntry.next)
                tail = preEntry;
            preEntry.next = preEntry.next.next;
        }
        size--;
        return oldVal;
    }

综上
最后,再来理一下我们使用头结点的逻辑:对于一个链表,我们在操作的时候只能从前到后,因此,在没有头结点时,我们在插入链表元素时,需要考虑链首、链中和链尾三种情况,三种情况处理方式都不同。(检讨:以前没有考虑全面)有没有什么方式能够省去判断处理位置的情况呢?—>设置一个元素为空的头结点指向链表的第一个元素,这样链表的每一个元素结点都被一个结点指向,在插入链表元素时,链首、链中和链尾三种情况的处理方式就一样啦~

进一步思考:在删除时,链尾的情况需要单独讨论,有没有什么办法能够也省去这一步的判断呢?我想,可以仿照头结点,设置一个“尾结点”,让链尾的元素指向这个尾结点?但是又想这其实并不太方便,头结点之所以方便是因为链表第一个元素是确定的,增加一个头结点又不会影响什么;而添加“尾结点”并不像头结点那么方便,由于链表元素的个数是不确定的,因此,还得先找到这个最后的元素,就感觉略麻烦,因此还是保留判断语句好些。

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

  • 问题:

ArrayStack.java :第一行“public class ArrayStack implements Stack”下面有红线,没有出现红灯泡,将鼠标放在那行出现“Cannot access zylstack.Stack”的字样。但是我这里的Stack完全和书324的一样。这个ArrayStack中除了要求的pop,peek,isEmpty方法之外也是与书上一样的。

LinkedStack.java :24行“top = top.getNext(); ”中的“top.getNext();”,37行“current = current.getNext();”中的“current.getNext();”和46行“temp.setNext(top); ”中的“temp.setNext”下面有红线,没有出现红灯泡,将鼠标放在那行出现“Cannot access zylstack.LinearNode”的字样。

ArrayStack.java 能编译成功,
编译 LinkedStack.java 之后,显示的问题是:
“Error:(9, 40) java: 找不到符号
符号: 类 Stack”

  • 问题解决方案:

我在网上百度很久都没有解决我的问题。问了张旭升、张师瑜、王老师,他们也都不知道。最后去找了娄老师,老师说代码没有问题。然后在IDEA中重新建了一个项目,就没有再出现此类问题了。

代码托管

结对及互评

  • 博客中值得学习的或问题:
    • 排版很漂亮,逻辑清晰,很有进步!
    • 只是罗列知识点,希望能有更多自己的思考
    • 图片较多
  • 其他
    • 楠楠很棒!加油加油!

本周结对学习情况

  • 20162323
  • 结对学习内容
    - 一起讨论教材学习中的问题

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

吾日三省吾身:
我究竟学会了什么?
我是否只是记忆住了知识,然后在考完试,就因时间的流逝而遗忘?
我是否理解那些难懂的概念,并能够对其进行迁移?

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20
第二周 300/500 2/4 18/38
第三周 500/1000 3/7 22/60
第四周 300/1300 2/9 30/90
  • 计划学习时间:10小时

  • 实际学习时间:12小时

  • 改进情况:多独立动手编代码!

参考资料

原文地址:https://www.cnblogs.com/zyl905487045/p/7661970.html