資料結構與演算法 - 棧和佇列
棧(stack)
先進後出,刪除與加入均在棧頂操作
棧也稱為堆疊,是一種線性表。
堆疊的特性: 最先放入堆疊中的內容最後被拿出來,最後放入堆疊中的內容最先被拿出來, 被稱為先進後出、後進先出。
堆疊中兩個最重要的操作是PUSH和POP,兩個是相反的操作。
PUSH:在堆疊的頂部加入一 個元素。
POP:在堆疊頂部移去一個元素, 並將堆疊的大小減一。
在成員變數方面,Vector提供了elementData , elementCount, capacityIncrement三個成員變數。其中
elementData :"Object[]型別的陣列",它儲存了Vector中的元素,可以隨著元素的增加而動態的增長,如果在初始化Vector時沒有指定容器大小,則使用預設大小為10.
private static final int DEFAULT_SIZE = 10;初始化的值
protected int elementCount; 棧元素數量(非空元素的長度)
/** * 使用指定的初始容量和容量增量構造一個空的向量。 */ public Vector(int initialCapacity, int capacityIncrement) { //初始化 super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; }
protected int capacityIncrement;擴容增長因子向量的大小大於其容量時,容量自動增加的量。
如果在建立Vector時,指定了capacityIncrement的大小;則,每次當Vector中動態陣列容量增加時>,增加的大小都是capacityIncrement。
如果容量的增量小於等於零,則每次需要增大容量時,向量的容量將增大一倍。
容量是最多能夠容納多少元素,而大小是目前容納了多少元素
得到最後元素的下標
public synchronized E peek(){ try{ return(E)elementData[elementCount-1]; //當前陣列[當前陣列長度-1] >>得到最後元素的下標 }catch(IndexoutOfBoundsException e){ throw new EmptyStackException(); //得到下標,肯定會丟擲異常 }}
出棧
@Suppres swarnings("unchecked") public synchronized E pop(){ if(slementCount==0){ //棧元素數量為0,表示空棧 throw new EmptyStackException(); //空棧異常
}
final int index = --elementCount; //將來要出棧的非空元素下標,棧數量-1就是下標
棧數量:1 2 3 4
下標:0 1 2 3
final E obj=(E)elementData[index]; //拿到棧頂元素,讓它等於obj
elementData[index]=nul1;把棧頂變成null,下次再出棧,非空元素長度減一得到下標,又是有資料的了
modCount++; //發生改變,進行加一操作
return obj;
}
入棧
public synchronized void addElement(E object){ if(elementCount==elementData.1ength){//判斷是否棧滿 growByOne(); //棧滿,擴容一次,長度不定 }
elementData[ elementCount++]=object;
modCount++;
}
private void growByOne(){ int adding=0; //要新增的數量
if(capacityIncrelent <=0){ if((adding=elementData.length)==0){ //如果是空棧,要新增元素的話,讓adding=1,增加一個元素 存疑? adding=1; } else{ adding = capacityIncrement; //capacityIncrement用它來判斷需要擴容多少
}
E[] newData=newElementArray(elementData. length +adding);//新建立一個數組,把它的長度擴容成 增加的長度+擴容的
System. arraycopy(elementData,0, newData,0, elementCount);//拷貝資料
elementData=newData;
}
為什麼每個方法裡都要有全域性變數和區域性變數
安全問題:因為elementData可以會進行入棧和出棧操作,如果直接使用elementData,不能進行邊遍歷邊刪除,所以要使用區域性變數Object[] dumpArray
棧裡面可以有重複的元素
棧的遍歷可以從棧頂也可以從棧底,這個需要根據自己需求,為自己服務
棧的經典應用
字尾表示式
931 - 3 * + 10 2 / +
923 * + 10 2 / +
96 + 10 2 / +
15 10 2 / +
15 5 +
20
中綴表示式 轉 字尾表示式: 數字輸出,運算子進棧,括號匹配出棧,棧頂優先順序低出棧(精髓就是優先順序越高越靠前)
9 + (3 - 1) × 3 + 10 ÷ 2 >> 931 - 3 * + 10 2 / +
佇列
相對於棧而言,佇列的特性是:先進先出
-
先排隊的小朋友肯定能先打到飯!
佇列的順序儲存
缺點:出隊複雜度高0(n)
容易假溢位
容易造成資源浪費
佇列的鏈式儲存及結構模式
佇列的鏈式儲存結構,其實就是線性表的單鏈表,只不過它只能尾進頭出而已
出隊:只需要讓頭指標指向a2,a1就出隊了
入隊:只需要讓尾指標指向新結點,讓an的next結點指向新進來的結點
佇列也分成兩種:
-
靜態佇列(陣列實現)
-
動態佇列(連結串列實現)
值得注意的是:往往實現靜態佇列,我們都是做成迴圈佇列
做成迴圈佇列的好處是不浪費記憶體資源!
類到底採用什麼樣的資料結構
transient Link<E>voidLink;頭指標
public Linkedlist(){初始化 voidLink=new Link<E>(null, null, null); 建立
voidLink. previous = voidLink;
voidLink. next = voidLink; //前後指標都指向自己(自己抱著自己),後面進來的資料到底採用什麼樣的資料結構要看add方法
佇列隨機位置插入
public void add (int location, E object){ 二分法查詢 link就是準備在這個位置插入一個新結點進來,當前結點 Link<E> previous = link.previous; Link<E> newLink = new Link<E>(object,previous(previous),link(next));//新結點 previous.next = newLink; link.previous = newLink; }
入隊(只有隊頭的情況下)
private boolean addlastImpl(E object){ Link<E>oldLast=voidLink. previous; //在沒有任何元素的情況下,void.link.previous等於它自己,也就是oldLast為head
Link<E> newlink =new Link<E>(object, oldLast(previous指向head), voidlink(next也指向head));
voidLink. previous =newlink;
oldLast.next=newlink;
size++;
modCount++;
return true;
}
本身就是一個迴圈,頭尾可以選擇,然後按照自己的選擇寫資料結構
出隊
voidlink = head
first = 0
next = 1
privateE removeFirstImpl(){
Link<E>first = voidLink. next; //頭指標指向了第一個元素=first
if(first = voidlink){ //如果first不等於voidlink,說明是有元素的 Link<E>next = first.next; //讓1=next,然後first.next指向它
voidLink.next = next;
next. previous = voidLink;
size--;
modCount++;
return first.data;
}
throw new NoSuchElementException();
}
&n