第3章 表 栈 和 队列

来源:互联网 发布:数据反馈的拳击手套 编辑:程序博客网 时间:2024/05/22 04:55

第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 表的简单数组实现

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

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

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

 

3.2.2 简单链表

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

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

查找一个元素需要花费Oi)的时间,

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

 

3.3 JAVA Collection API中的表

3.3.1 Collection接口

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

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

3.3.2 Iterator接口

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

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

 

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

3.3.3 List接口ArrayListLinkedList

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

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

 

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

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

 

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

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

 

对于搜索而言,ArrayListLinkedList都是低效的,对Collectioncontains()remove()两个方法均花费线性时间

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

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

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

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

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

2. LinkedList暴漏了两个问题

1get()调用效率不高

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

3. 因此我们应该使用一个迭代器一步一步遍历该表,但是我们使用的是Collectionremove()方法来删除一个偶数值,这不是高效的操作,(因为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扩展了ListIterator的功能,previous()  hasPrevious()使得表可以从后面想前遍历

 

3.4 ArrayList类的实现

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

实现MyArrayList包括的细节

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

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

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

4. MyArrayList 提供基本的方法 sizeisEmptyclear还提供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. 栈的数组实现方法

模仿了ArrayListadd()操作

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

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 队列的数组实现

对于队列而言任何的表的实现都是合法的,队列通常不是很大

0 0
原创粉丝点击