1. 程式人生 > >還債系列之資料結構——棧和佇列

還債系列之資料結構——棧和佇列

三、棧

還記得當初第一次學習程式設計的時候還是8051微控制器中的組合語言,現在還記得很清楚,當初遇到的一個簡單的資料結構就是——棧,對應的組合語言中的命令是pushpop。這個結構在生活中是有很多類似的例子的,比如水杯、碗等。該結構的特點如下:

  • 最大特點是先進後出、後進先出
  • 使用一個指標標識棧中元素的位置——棧頂,該值指的是在棧中最上面元素的位置;

針對棧的基本操作主要有:

  • 入棧
  • 出棧
  • 是否為空

具體的實現類如下:

public class Stack<T> {
    private int top = 0;
    private Object[] items;

    public
Stack(){ items = new Object[10]; } public Stack(int initSize){ items = new Object[initSize]; } public synchronized T push(T item){ if(top > items.length){ throw new IllegalStateException("elements appear in this stack are full"); } items[top++] = item; return
item; } public synchronized T pop(){ if(top <= 0){ throw new IllegalStateException("no element appear in this stack"); } T item = (T)items[top-1]; items[top-1] = null; top--; return item; } public boolean isEmpty(){ return
top == 0; } public int size(){ return top; } }

測試類如下:

com.demo.category.Stack<String> stack = new com.demo.category.Stack<>();
stack.push("test1");
stack.push("test2");
stack.push("test3");
stack.push("test4");
stack.push("test5");
System.out.println("count: " + stack.size());

String src1 = stack.pop();
String src2 = stack.pop();
String src3 = stack.pop();
System.out.println("count: " + stack.size());
System.out.println("src1: " + src1);
System.out.println("src2: " + src2);
System.out.println("src3: " + src3);

在針對棧的操作中要注意以下問題:

  • 會有數量的限制,導致越界;如果想要使用的棧是無限容量的,就要隨時保證陣列的容量比實際的元素要多,因此會涉及到陣列的擴容問題,當然如果你不是通過陣列來實現棧的,也可以使用連結串列來實現,這樣就不必要考慮容量的問題;
  • 多執行緒環境下操作的互斥;

四、佇列

佇列這種資料結構在生活中也是有各種各樣的類似的例子,比如隧道里,先進入的車肯定是先出來的,這種特點和棧正好是相反的:

  • 先進先出(FIFO),後進後出;

所以在佇列中所有的元素都是有序的,一切行為的產生都是按照順序來的,但是也有例外,比如為了區分優先順序而設計出來的優先佇列。而在佇列中又可以細分成單向佇列和雙向佇列。

4.1 單向佇列

在單向佇列中是佇列中最簡單的一種,它的特點如下:

  • 入隊:只能從隊尾新增元素,也就說不能插隊;
  • 出隊:只能從隊頭刪除元素,也就說只有輪到你的時候才能離開,否則就老老實實待著;

單向佇列如下:

public class Queue<T> {
    private Object[] items;
    private int tail = 0;
    private int head = 0;
    private int size;

    public Queue(int initSize){
        items = new Object[initSize];
    }

    public boolean add(T item){
        if(size == items.length){
            throw new IllegalStateException("no space for the new element");
        }
        items[tail] = item;
        tail++;
        size++;
        return true;
    }

    public T remove(){
        if(size == 0){
            throw new IllegalStateException("no element in this queue");
        }
        T item = (T)items[head];
        items[head] = null;
        head++;
        size--;
        return item;
    }

    public int size(){
        return size;
    }
}

在上述的例子中,佇列是通過陣列來實現的,示意圖如下:

這裡寫圖片描述

4.2 雙向佇列

雙向佇列則對於元素的出隊和入隊限制很鬆:

  • 入隊:即可以在隊頭,也可以在隊尾;
  • 出隊:隊頭可以出隊,隊尾也可以出隊;

該雙向佇列可以直接繼承自單向佇列,程式碼如下:

public class Biqueue<T> extends Queue<T> {
    public Biqueue() {
        super.items = new Object[10];
    }

    public Biqueue(int initSize) {
        super.items = new Object[initSize];
    }

    public boolean addTail(T item) {
        return super.add(item);
    }

    public T removeHead() {
        return super.remove();
    }

    // 隊頭入隊
    public boolean addHead(T item) {
        if (size == items.length) {
            throw new IllegalStateException("no space for the new element");
        }
        if (head == 0) {
            if (size > 0) {
                // 插隊是要付出代價的,即所有後面的元素都要跟著變化
                for (int i = size - 1; i >= 0; i--) {
                    items[i + 1] = items[i];
                }
            }
            items[head] = item;
            tail++;
        } else if (head > 0) {
            items[head - 1] = item;
            head--;
        }
        size++;
        return true;
    }

    // 隊尾出隊
    public T removeTail() {
        if (size == 0) {
            throw new IllegalStateException("no element in this queue");
        }
        T item = (T) items[tail - 1];
        items[tail - 1] = null;
        tail--;
        size--;
        return item;
    }
}

上述所有的操作均沒有考慮多執行緒的環境,如果要在多執行緒的環境下操作需要考慮互斥。

針對資料結構的操作,之前一直以為知道資料結構的特性即可,感覺實現起來應該不難,但是真正地去實現一個的時候,才知道要考慮的問題很多!