您的位置:首页 > 其它

第3章 表 栈 和 队列

2016-07-04 16:17 204 查看

第3章 表 栈 和 队列

每一种有意义的程序都将显示的至少使用一种这样的数据结构,而栈总是要被间接的使用到

3.1 抽象数据类型

抽象数据类型(ADT)是带有一组操作的一些对象的集合,抽象数据类型是数学的抽象,在ADT的定义中没有提到关于这组操作时如何实现的任何解释。

3.2 表ADT

我们将处理形如A0 A1 A2 .... AN-1 的一般的表,我们说这个表的大小为N
我们将大小为0的表称为空表

我们说Ai 后继Ai-1
或继 Ai-1之后并称Ai-1前驱Ai不定义A0的前驱元和An-1的后继元

3.2.1 表的简单数组实现

对表的所有操作可以用数组来实现

将旧数组赋值给新数组就可以实现数组扩容

插入和删除潜藏着昂贵的开销,主要是看插入和删除在什么地方(线性开销0(N-i))

3.2.2 简单链表

为了避免插入和删除的线性开销,我们需要保证表可以不连续存储,所有可以采用链表

链表有一系列节点组成,这些节点不必在内存中相连,每一个节点均含有表元素和到包含该元素的后继元的节点的链

查找一个元素需要花费O(i)的时间,

联表示适合插入和删除,不需要移动多余的项

3.3 JAVA Collection API中的表

3.3.1 Collection接口

集合collection的概念在Collection接口中得到抽象,它存储一组类型相同的对象,

Collection接口扩展了Iterable(java.lang)接口,实现Iterable的那些类可以拥有增强for循环

3.3.2 Iterator接口

实现Iterator(java.util)接口的思路是,通过iterator方法,每一个集合均可创建并返回给客户一个实现Iterator接口的对象,并将当前位置的概念在对象中存储下来

Iterator的remove()的主要优点在于Collection的remove()方法必须首先找到要被删除的项,如果知道要删除的项的位置,那么删除开销会小很多

当直接使用Iterator遍历时,重要的是要记住一个基本法则, 如果对正在被迭代的集合进行结构上的改变,那么迭代器就不在合法,这也是更喜欢使用迭代器remove()方法的原因

3.3.3 List接口ArrayList和LinkedList类

ArrayList的优点在于,对get() set()方法的调用花费常数时间,其缺点是新项的插入和删除代价昂贵,除非变动的是最后一个元素

LinkedList的优点在于插入和删除开销都很小队已知位置,对添加和删除都是常数时间,缺点是 它不易做索引,因此对get的调用时昂贵的,除非调用非常靠近表的端点(如果对表的调用靠近表的尾端,那么搜索也可以从尾部开始)

如果我们通过向表的前段添加一些项来构造一个List

对于LinkedList它运行时间是O(N)
对于ArrayList它的运行时间是O(N2)

如果我们是对集合进行遍历

对于LinkedList它运行时间是
O(N2)
对于ArrayList它的运行时间是O(N)

对于搜索而言,ArrayList
和LinkedList都是低效的,对Collection的contains()和remove()两个方法均花费线性时间

trimTosize()方法将容量设置为集合的长度,操作,ArrayList调用该方法可以避免浪费空间

扩容代码: int newCapacity = (oldCapacity * 3)/2 + 1;

3.3.4 例子 remove()方法对LinkedList类的使用

eg. 假如一个表包含 6,5,1,4,2
需要将表中的偶数都删除

1. 显然对于ArrayList这几乎是一个失败的策略(任意地方删除都很昂贵)

2. LinkedList暴漏了两个问题

(1)get()调用效率不高

(2)remove()方法效率同样很低

3. 因此我们应该使用一个迭代器一步一步遍历该表,但是我们使用的是Collection的remove()方法来删除一个偶数值,这不是高效的操作,(因为remove()方法必须再次搜索该项,它花费线性时间)但更糟的是程序会产生异常

4 在迭代器找到一个偶数值时,我们可以使用该迭代器来删除这个值(对于迭代器而言,只用花费常数时间)

public static void removeEvenver(List<Integer> 1st) {

Interator<Integer> itr = 1st.iterator();

while (itr.hasNext()) {

if(ist.next() % 2 == 0) {

itr.remove() ;

}

}

}

3.3.5 关于ListIterator接口

ListIterator扩展了List和Iterator的功能,previous() hasPrevious()使得表可以从后面想前遍历

3.4 ArrayList类的实现

为了和类库ArrayList类区别 我们编写的类叫做MyArrayList

实现MyArrayList包括的细节

1. MyArrayList将保持基础数组,数组的容量,及当前项

2. MyArrayList提供一种机制来改变数组的容量,允许GC回收老数组

3. MyArrayList 提供get()
和set()的实现

4. MyArrayList 提供基本的方法 size
,isEmpty
,clear
还提供remove
以及两个不同版本的add
如果数组的大小容量都相同,那么这两个add()将增加容量

5. MyArrayList将提供一个实现Iterator接口的类,

3.4.2 迭代器java嵌套类和内部类

3.5 LinkedList类的实现

MyLinkedList 设计要求

1. MyLinkedList类本身,它包含到两端的链,表的大小及一些方法

2. Node类,它可以是一个私有的嵌套类,一个节点包含数据以及前一个节点和下一个节点的链,还有一些构造方法

3. LinkedListIterator类,该类抽象了位置概念,是一个私有类,并实现接口Iterator

3.6 栈ADT

3.6.1 栈模型

栈是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈的顶(top)

当栈为空的时候进行pop()和top()一般别认为栈ADT中的一个错误,

当栈满时,运行push()是一个实现限制,并不是错误。

栈有时又叫做LIFO(后进先出)表

3.6.2 栈实现

由于栈是一个表,因此可以使用任何实现表的方法来实现栈,因为栈的操作时常数时间操作,

我们给出两种方法来实现

1. 栈的链表实现

使用单链表,通过在表的顶端插入来实现push(),通过删除表顶端元素来实现pop()

top()操作只是获得顶端元素并返回它的值

2. 栈的数组实现方法

模仿了ArrayList的add()操作

这些操作不仅以常数时间运行,最现代化的计算机把栈操作作为它的指令的一部分,栈有可能成为数组之后的最基本的数据结构了

3.6.3应用

1. 平衡符号

编译器检查程序语法错误,但常常由于缺少一个符号,引起编译成列出很多错误提示,但是并没有找到真正的错误

2 后缀表达式

4.99 1.06*5.99 + 6.99 1.06* 这种记法叫做后贼或者逆波兰

3. 中缀到后缀的转换

可以用栈将一个标准形式的表达式(或叫作中缀的表达式转换成后缀)

4. 方法调用

当调用一个新的方法时,主调例程的所有局部变量需要由系统存储起来,否则被调用的新方法将会重写由主调例程的变量所使用的内存,不仅如此,主调例程的当前位置也要储存,这些变量一般由编译器指派给机器的寄存器

当存在方法调用的时候,需要储存的所有重要信息,诸如寄存器的值(对应变量的名字)返回地址(可以从程序的计数器中得到,一般是在一个寄存器中)等都要以抽象的形式存在堆的顶部

递归的所有工作都可以由一个栈来完成,而这正是实现递归的每一种程序设计语言中实际发生的事情。所存储的信息或称为活动记录,或叫作栈帧。

3.7队列ADT

队列也是表,使用队列时,插入在一段进行而删除在另一端进行

3.7.1 队列模型

队列的基本操作时入队enqueue,它是在表的末端(队尾)插入一个元素,和出队,它是删除dequeue并返回在表的开头(队头)的元素

3.7.2 队列的数组实现

对于队列而言任何的表的实现都是合法的,队列通常不是很大
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: