第3章总结

第3章 表、栈和队列

抽象数据类型(abstract data type,ADT)是带有一组操作的对象的集合。

数组实现表的扩容:

int[] newArr=new int[arr.length*2];

for(int i=0;i<arr.length;i++)

              newArr[i]=arr[i];

arr=newArr;

3.3 Java Collections API中的表

ADT的添加删除总结:

l 迭代器删除remove删除由next()最新返回的项,此后不能再执行remove()直到对next()的再一次调用。

迭代器删除知道确切的位置,比容器类删除具有更高的效率。

使用迭代器或增强for循还,若正在被迭代的集合进行结构上的改变(add/remove/clear),那么迭代器就不再合法抛出CurrentModificationException异常。

基于以上迭代器的两个优点,迭代器使用原则是:迭代器只有在立即使用时才获取,迭代器调用自己的remove方法是合法的。

遍历有3种方法:迭代器、增强for、索引号。

l 空器接口add/remove具为遍历式删除。当集合不允许重复,要删除的元素不在集合中时返回false。

l List接口具有Collections方法、索引式增删改查、listIterator。

静态函数写法:public、static、泛型、返回值、函数名、形参。

容器类Collections接口:判长空置空,添删容迭。Collections接口是Iterable接口的继承类可以使用增强for循还。

public interface Collection<AnyType> extends Iterable<AnyType>{

              int size();

              boolean isEmpty();

              void clear();

              boolean add(AnyType x);

              boolean remove(AnyType x);

              boolean contains(AnyType x);

              java.util.Iterator<AnyType> iterator();

}

迭代器Iterator接口:

public interface <AnyType> Iterator{

              boolean hasNext();

              AnyType next();

              void remove();

}

List接口:list接口添加了索引式的增删改查方法(add/remove/set/get)和listIterator迭代器。

public interface List<AnyType> extends Collection<AnyType>{

              void add(int idx,AnyType x);

              void remove(int idx,AnyType x);

              AnyType set(int idx,AnyType x);

              AnyType get(int idx);

              ListIterator<AnyType> listIterator(int pos);

}

List的ArrayList实现,get/set花费O(1)。add/remove花费昂贵,除非变动在ArrayList的末端执行。

List的双链表LinkedList实现,在变动项位置已知的情况下add/remove花费很小。表的前端add/remove花费常数时间,因而LinkedList提供了addFirst/removeFirst/addLast/removeLast方法。get/set调用花费O(N),除非调用接近表的端点。

对于搜索ArrayList和LinkedList实现Collections的contains和remove方法均花费O(N)。

ArrayList有容量的概念,添加需要调用ensureCapacity(),添加完调用trimToSize()节约空间。

插入删除的实现选择:

l 末端add/remove用ArrayList,LinkedList均可。

l 首端add/remove用LinkedList,此时ArrayList表现最差。

l 已知索引处add/remove用LinkedList。

未知索引处添加:要比较ArrayList的移动效率和LinkedList的遍历效率

l 遍历情况:ArrayList用索引号、迭代器均可O(N),LinkedList只能用迭代器O(N)。LinkedList用索引号遍历将花费O(N^2)。

实际表现ArrayList性能优于LinkedList。

程序计时:

long startTime=System.currentTimeMillis();

long endTime=System.currentTimeMillis();

System.out.println("程序运行时间: "+(endTime-startTime)+"ms");

去除数组偶数值,练习LinkedList的remove方法

一种想法是将奇数拷到新表,清空原表,再将奇数拷回原表。

另一种想法使用增强for循还,过程中删除元素将抛异常。

另一种想法是使用索引号遍历,ArrayList花费O(N),而LinkedList花费O(N^2)更糟糕:

public static void removeEvensVer1(List<Integer> lst){

              for(int i=0;i<lst.size();){

                                if(lst.get(i)%2==0)

                                                  lst.remove(i);

                                else

                                                  i++;

              }

}

更好的方法是避免拷贝表,在遇到那些偶数值将其删除。迭代器可以实现边遍历边删除,以O(N)实现效率很高。至此验证了LinkedList的迭代器删除是最高效的方法。

public static void removeEvensVer2(List<Integer> lst){

              Iterator<Integer> iter=lst.iterator();

              while(iter.hasNext()){

                                if(iter.next()%2==0)

                                                  iter.remove();         

              }

}

ListIterator接口

ListIterator扩展了Iterator的功能,增加了向前遍历和迭代器增改。

public interface <AnyType> ListIterator extends Iterator<AnyType>{

              boolean hasPrevious();

              AnyType previous();

              void add(AnyType x);

              void set(AnyType x);              

}

补充:ArrayList和LinkedList的API

3.4 ArrayList类的实现

泛型数组的创建是非法的。我们的做法是创建泛型类型限界的数组,然后用一个数组进行类型转换。这将产生一个编译警告,然而这在泛型集合的实现中是不可避免的。

扩容函数也用于置空和初始化。

由于一般不在链表中保存位置指针且不适合于多次遍历,因而将写成链表的内部类,位置指针保存在迭代器中。

内部类形成了迭代器与链表之间的隐式信息交流。避免了在迭代器中创建链表对象。

迭代器中名为current,实现指向next()的元素。

(原书中比较的迭代器的3种实现方法:2个类引用对象、嵌套类、内部类。考查对象间通信)

访问域名:类内this.data,外部类内Outer.this.data。

访问权限:public公共、protected同包子访问、private类内访问。

static嵌套类会开放一层,嵌套类被认为是外部类的一部分。private static意味着仅作用在外部类范围之内。

pulic class MyArrayList<AnyType> implements Iterable<AnyType>{

              private static final int DEFAULT_CAPACITY=10;

              private int theSize;

              private AnyType[] theItems;

              

              public int size(){

                                return theSize;

              }

              public boolean isEmpty(){

                                return size()==0;

              }

              public void trimToSize(){

                                ensureCapacity(size());

              }

              public void ensureCapacity(int newCapacity){

                                if(newCapacity<size())

                                                  return;

                                AnyType[] newItems=(AnyType[])new Object[newCapacity];

                                for(int i=0;i<size();i++){

                                                  newItems[i]=theItems[i];

                                }

                                theItems=newItems;

              }

              public void clear(){

                                theSize=0;

                                ensureCapacity(DEFAULT_CAPACITY);

              }

              public MyArrayList(){

                                clear();

              }

              

              

              public void add(int idx,AnyType x){

                                if(theItems.length==size())

                                                  ensureCapacity(size()*2+1);

                                for(int i=size();i>idx;i--)

                                                  theItems[i]=theItems[i--];

                                theItems[idx]=x;

                                theSize++;

              }

              public boolean add(AnyType x){//注意到元素式添加是有返回值的

                                add(size(),x);

              }

              public AnyType delete(int idx){

                                AnyType removedItem=theItems[idx];

                                for(int i=idx+1;i<size();i++)

                                                  theItems[i-1]=theItems[i];

                                theSize--;

                                return removedItem;

              }

              public AnyType set(int idx,AnyType x){

                                if(idx<0||idx>=size())

                                                  throw new ArrayIndexOutOfBoundsException();

                                AnyType oldItem=theItems[idx];

                                theItems[idx]=x;

                                return oldItem;

              }

              public AnyType get(int idx){

                                if(idx<0||idx>=size())

                                                  throw new ArrayIndexOutOfBoundsException();

                                return theItems[idx];

              }

}

3.5 LinkedList类的实现

性能:LinkedList中的除了"尾插"以外的增删改查都使用getNode()方法的都是O(N)时间,不推荐使用。LinkedList只擅长迭代器add/remove。

LinkedList用嵌套类实现节点,用索引换链/链前添加/链删除来实现索引式增删改查,迭代器具有被迭代表不修改错误检测、可跳针(next)错误检测、1跳针1删除错误检测。

LinkedList类实现:

public class MyLinkedList<AnyType> implements Iterable<AnyType>{

              private Node<AnyType> beginMarker;

              private Node<AnyType> EndMarker;

              private int theSize;

              private int modCount=0;

              

              private static Node<AnyType>{//节点声明为嵌套类

                                private AnyType data;

                                private Node<AnyType> prev;

                                private Node<AnyType> next;

                                public Node(AnyType d,Node<AnyType> p,Node<AnyType> n){

                                                  data=d;prev=p;next=n;

                                }

              }

              private Node<AnyType> getNode(int idx){}

              private void addBefore(Node<AnyType> p,AnyType x){}

              private AnyType remove(Node<AnyType> p){}

              

              public int size(){return theSize;}

              public boolean isEmpty(){return theSize==0;}

              public void clear(){}

              public void add(int idx,AnyType x){addBefore(getNode(idx),x);}

              public boolean add(AnyType x);

              public AnyType remove(int idx){return remove(getNode idx);}

              public AnyType set(int idx,AnyType newVal){

                                Node<AnyType> p=getNode(idx);

                                AnyType oldVal=p.data;

                                p.data=newVal;

                                return oldVal;

              }

              public AnyType get(int idx){return getNode(idx).data;}   

}

LinkedList的"置空"方法:

public void clear(){

              beginMarker=Node<AnyType>(null,null,null);

              endMarker=Node<AnyType>(null,beginMarker,null);

              beginMarker.next=endMarker;

              theSize=0;

              modCount++;

}

LinkedList的索引换链、链前添加、链删除:

取索引方法(双向取索引都遵循对位开号的原则):

l 正向取索引p=array[0],int i=0;i<idx。

l 反向取索引p=array[size-1],int j=size-1;i>idx。

链添改4个指针,链删改2个指针。

public Node<AnyType> getNode(int idx){

              if(idx<0||idx>=size())

                                throw new IndexOutOfBoundsException();

              Node<AnyType> p=null;

              if(idx<size()/2){

                                p=beginMarker.next;

                                for(int i=0;i<idx;i++)

                                                  p=p.next;

              }else{

                                p=endMarker.prev;

                                for(int i=size()-1;i>idx;i--)

                                                  p=p.prev;                                   

              }

              return p;

}

public void addBefore(int idx,AnyType x){

              Node<AnyType> p=getNode(idx);

              Node<AnyType> newNode=new Node<AnyType>(x,p.prev,p);

              p.prev.next=newNode;

              p.prev=newNode;

              theSize++;

              modCount++;

}

public AnyType remove(int idx){

              Node<Anytype> p=getNode(idx);

              p.prev.next=p.next;

              p.next.prev=p.prev;

              theSize--;

              modCount++;

              return p.data;

}

LinkedList的迭代器:

迭代器的current指针指到next()的返回元素。

public java.util.Iterator<AnyType> iterator(){

              return new LinkedListIterator<AnyType>();

}

private class LinkedListIterator<AnyType> implements java.util.Iterator<AnyType>{

              Node<AnyType> current=beginMarker.next;

              private int exceptedModCount=modCount;

              private boolean okToRemove=false;

              

              public boolean hasNext(){

                                return current!=endMarker;

              }

              public AnyType next(){

                                if(modCount!=exceptedModCount)

                                                  throw new java.util.ConcurrentModificationException();

                                if(!hasNext())

                                                  throw new java.util.NoSuchElementException();

                                okToRemove=true;

                                

                                AnyType nextData=current.data;

                                current=current.next;

                                return nextData;

              }

              public void remove(){

                                if(modCount!=exceptedModCount)

                                                  throw new java.util.ConcurrentModificationException();

                                if(!okToRemove)

                                                  throw new IllegalStateException();

                                MyLinkedList.this.remove(current.prev);

                                okToRemove=false;

                                expectModCount++;

              }                 

}

3.6 栈ADT

栈没有表中低效的查找和遍历,只有压栈push/弹栈pop/检栈空top,均花费常数时间。栈又叫LIFO后进先出表。

栈的链表实现:使用单链表,表顶插入实现push,表顶删除实现pop。

栈的数组实现更优:数组theArray,栈帧topOfStack(空栈topOfStack==-1)。进栈先跳帧后赋值theArray[++topOfStack]=x;出栈退针return theArray[topOfStack--];

栈实现不需要theSize

栈以非常快的常数时间运行,在带有自增和自减寻址功能的寄存器上,push和pop可以写成一条机器指令,因而栈成为数组之后最基本的数据结构。

栈意味着挂起。

栈应用1:平衡符号

平衡符号方法:

  1.         做空栈,读字符
    
  2.         开放符号入栈
    
  3.         读到封闭符号,栈空报错;栈非空而弹出符号不对应报错;正常情况弹栈(成对、对应)。
    
  4.         读完字符栈非空报错(成对)。
    

算法复杂度O(N)。

栈应用2:中缀表达式转为后缀表达式(科学计算)

中缀转后缀方法:运算符进栈、字母全输出;遇高则压栈、低同弹至低(并输出);开括号压栈,闭括弹至开。复杂度O(N)。

处理同级运算符,”低同弹至低”保证了表达式从左往右进行。注意幂运算符(如223=256)”从右往左结合”,应遵循”遇低弹至同”的原则。

注意到逆波兰表达式是从右往左算的。

栈应用3:后缀表达式计算(科学计算)

后缀表达式(postfix)又称逆波兰表达式(reverse Polish)。

逆波兰表达式计算方法:读逆波兰表达式。遇到数字则压栈。遇到运算符弹出两个数运算,运算结果再压栈。复杂度O(N)。

栈应用4:方法调用

方法调用和方法返回类似于开闭括号,平衡符号的检测算法也可用于方法调用。

主方法调用新方法时,将主方法的局部变量以及主方法的当前位置保存起来(挂起),即寄存器值和返回地址,以免被新方法重写。

新方法结束时恢复寄存器并返回转移。

实现递归的挂起方法的栈又称为”活动记录”或”栈帧”。太多的方法可能导致栈溢出,Java会抛出一个异常。

尾递归(最后一行调用递归)会消耗大量的栈空间,是典型的递归使用不当的例子。

尾递归的修复方法为:将代码放到while循还并用方法参数的赋值代替。

非递归程序要快于等价的递归程序,而速度优势的代价是程序的清晰性。

3.7 队列ADT

队列都是表头出队,表尾入队。

队列的单链表实现较简单:头针front(单链表单标记beginMarker),尾针back,数量theSize。表尾入队enqueue、表头出队dequeue。

队列的数组实现使用循还数组:循还数组使用theSize判断队大小、队空更方便。非循还数组使用:队为空back-front=-1,队大小back-front+1(循还数组为theArray.length +back -front+1)%theArray.length)。入队前先容量检查,队列占满要扩容。

有限的资源就需要用到队列。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,390评论 1 14
  • 题目一:两个班的人数 题目二:取余运算 题目三:求差值 题目四:输出操作
    落叶纷飞123阅读 118评论 0 0
  • 我曾在上一篇文章《杨过的三位师父》中说过: “黄蓉对杨过有偏见,这种偏见甚至贯穿了《神雕》整本书。儿时的杨过,顽劣...
    赵异阅读 2,872评论 2 8
  • 无论是音乐家和舞蹈家、还是体育运动员等,都经过大量的练习反复练习,才能创造出优异的成绩!在我们学习上也是...
    爱时尚8221阅读 135评论 0 0
  • 打卡日期:2018年3月4日 打卡累计天数:22/30 #宣言(只有感觉好才能做得好)# 孩子第一个30天目标: ...
    lijutong_010阅读 426评论 0 3