目錄
1、引言
2、棧
3、隊列
引言
棧和隊列都是動態集合,可以理解為線性表或線性表實現的數據結構。它可以由數組實現,也可以由鏈表實現。
和數組鏈表等不一樣的是,棧、隊列添加、刪除數據的位置都是預先設定的。在棧中,被刪除的是最近被插入的元素,棧實現的是一種先進后出的策略。而隊列中,被刪去的總是在集合中存在時間最長的元素,隊列實現的是一種先進先出的策略。
棧和隊列是非常有用的數據結構,在計算機中很多的地方使用了棧、隊列的思想。函數執行的壓棧及出棧,消息隊列的使用等等。本文最后將介紹棧和隊列的常見使用場景,遞歸轉化。
棧
棧的常用操作為以下四種
- push,在棧頂插入一個元素
- pop,彈出棧頂的元素
- peek,查看棧頂元素
- getSize,查看棧內元素的個數
本文中棧由數組實現。
由于入棧和出棧的操作都只集中在棧頂,所以棧實現中,必須定義變量mTop,用于標記當前棧頂位置。
插入操作,在mTop位置上插入新元素,mTop自增即可。
出棧操作,返回mTop自減后的位置的元素。
獲取棧內元素個數,即為mTop值大小。
private int mTop = 0;
private static final int MAX = 20;
private Object[] mArray = null;
public DemoStack(){
mTop = 0;
mArray = new Object[MAX];
}
public int getSize(){
return mTop;
}
public boolean push(T e){
if (mTop < MAX) {
mArray[mTop] = e;
mTop ++;
return true;
}
System.out.println("stack is full");
return false;
}
public boolean isEmpty(){
return mTop == 0;
}
public T pop(){
if (mTop == 0) {
System.out.println("stack is empty");
return null;
}
T e = (T) mArray[--mTop];
return e;
}
public T peek(){
int index = mTop - 1;
return (T) mArray[index];
}
棧的實現比較簡單,但棧的用處非常大。
在java虛擬機中,函數的調用,會生成一個個函數調用棧,等函數執行完畢,得到函數的返回值,出棧,回到之前調用函數的地方。遞歸能簡便解決非常多的問題,但遞歸有個最大的問題就是執行效率不高,因為函數調用的次數太多了。結合java虛擬機的函數調用思想,可否優化遞歸呢?因為遞歸本質上是將某一個狀態的結果保存在棧中,再計算另外一個狀態。如果不用遞歸,也可以使用棧數據結構來保存部分結果。
棧的使用場景之一,就是遞歸轉化,使用棧保存部分結果,取結果再計算下一個狀態的結果。二叉樹的遍歷分為前序遍歷,中序遍歷,后序遍歷,遞歸實現非常簡單,也可以使用棧將遞歸轉化。
前序遍歷非常簡單,是三種遍歷中轉化最容易的一種。
public void preTraverseByStack(Node root) {
if (root == null) {
return;
}
DemoStack<Node> stack = new DemoStack<>();
stack.push(root);
Node temp = null;
while (!stack.isEmpty()) {
temp = stack.pop();
System.out.print(temp + " ");
if (temp.hasRightChild()) {
stack.push(temp.getRightChild());
}
if (temp.hasLeftChild()) {
stack.push(temp.getLeftChild());
}
}
System.out.println();
}
中序遍歷開始有一定困難了,中序遍歷必須先遍歷左子樹,所以需要找到最左最下方的葉子結點。如果棧中元素值為空,則說明此元素的子元素已經被遍歷過了或者不需要處理了。
public void middleTraverseByStack(Node root){
if (root == null) {
return;
}
DemoStack<Node> stack = new DemoStack<>();
stack.push(root);
Node temp = null;
while (!stack.isEmpty()) {
while (stack.peek() != null && stack.peek().hasLeftChild()) {
stack.push(stack.peek().getLeftChild());
}
if (stack.peek() == null) {
stack.pop();
}
if (!stack.isEmpty()) {
temp = stack.pop();
System.out.print(temp + " ");
stack.push(temp.getRightChild());
}
}
System.out.println();
}
后序遍歷是三種轉化中最難的,它需要先遍歷左右子樹才行,因為需要使用兩個臨時變化,記錄之前遍歷的結點和當前結點。
public void lastTraverseByStack(Node root){
if (root == null) {
return;
}
DemoStack<Node> stack = new DemoStack<>();
stack.push(root);
Node cur = null;
Node pre = null;
while (!stack.isEmpty()) {
cur = stack.peek();
if ((cur.getLeftChild() == null && cur.getRightChild() == null)
|| (pre != null && (pre == cur.getLeftChild() || pre == cur.getRightChild()))) {
System.out.print(cur + " ");
stack.pop();
pre = cur;
}else {
if (cur.hasRightChild()) {
stack.push(cur.getRightChild());
}
if (cur.hasLeftChild()) {
stack.push(cur.getLeftChild());
}
}
}
}
棧的使用場景還有很多,例如四則運算等等。在具體場景中,只要采用了合適的數據結構,事半功倍!
隊列
隊列的常用操作為:
- enqueue,入隊,在隊列頭部插入元素
- dequeue,出隊,在隊列尾部刪除元素
- getSize,獲取隊列元素個數
因為隊列需要在隊列頭部和尾部進行元素操作,所以需要兩個指針,一個指向頭部,一個指向尾部。
隊列比棧復雜,隊列涉及循環問題。如果使用隊列裝滿元素,再刪除所有元素,此時再添加新的元素,雖然隊列的mTop值已經是最大了,但尾部仍有空間,需要需要在尾部添加元素。導致mTop值會比mTail值小。
private int mTop;
private int mTail;
private static final int MAX = 10;
private Object[] mArray;
private int mLength = 0;
public DemoQueue(){
mTop = mTail = 0;
mArray = new Object[MAX];
mLength = 0;
}
public int getSize(){
return mLength;
}
public boolean enqueue(T e){
if (getSize() == MAX) {
System.out.println("queue is full");
return false;
}
if (mTop == MAX) {
mTop -= MAX;
}
mArray[mTop] = e;
mTop ++;
mLength++;
return true;
}
public T dequeue(){
if (getSize() == 0) {
System.out.println("queue is empty");
return null;
}
int index = mTail;
mTail++;
if (mTail == MAX) {
mTail -= MAX;
}
mLength--;
return (T) mArray[index];
}
隊列和棧一樣是非常重要的數據結構,在日常開發中常常用到消息隊列,可能會接到某個需要,不允許漏掉任何一個消息,此時就可以使用隊列完成需求。本例中,使用隊列遍歷二叉樹,且是從上到下從左到右的水平遍歷。
public void traverse(Node root){
if (root == null) {
return;
}
DemoQueue<Node> queue = new DemoQueue<>();
queue.enqueue(root);
Node temp = null;
while (queue.getSize() > 0) {
temp = queue.dequeue();
System.out.print(temp + " ");
if (temp.hasLeftChild()) {
queue.enqueue(temp.getLeftChild());
}
if (temp.hasRightChild()) {
queue.enqueue(temp.getRightChild());
}
}
System.out.println();
}
本文中所有代碼均已上傳到本人的github空間,歡迎訪問。