Java資料結構與排序演算法 (一)

Tongson發表於2018-07-10

資料結構(Data Structures)

顧名思義


陣列

陣列實現採用“基於下標訪問”(Index-based access)的模式。


連結串列

連結串列實現則是基於“節點”(Node)“位置”(Position)的概念。

所謂連結串列(Linkedlist),就是按線性次序排列的一組資料節點。

每個節點的 next引用都相當於一個連結或指標,指向另一節點。

藉助於這些 next引用,我們可以從一個節點移動至其它節點。

連結串列的第一個和最後一個節點,分別稱作連結串列的首節點(Head)和末節點(Tail)。

public interface Position {

    /**
     * 返回存放於該位置的元素
     *
     * @return 存放於該位置的元素
     */
    Object getElem();


    /**
     * 將給定元素存放至該位置,返回此前存放的元素
     *
     * @param e
     * @return 此前存放的元素
     */
    Object setElem(Object e);
}

/**
 * <b>Create Date:</b> 2018/6/27<br>
 * <b>Email:</b> tongsonloo@gmail.com<br>
 * <b>Description:</b>
 * 每個節點的 next 引用都相當於一個連結或指標,指向另一節點。藉助於這些 next 引用,我們可以從一個節點移動至其它節點。
 * 連結串列的第一個和最後一個節點,分別稱作連結串列的首節點(Head)和末節點(Tail)。
 * 末節點的特徵是,其 next 引用為空。如此定義的連結串列,稱作單連結串列(Singly linkedlist)。
 * 與陣列類似,單連結串列中的元素也具有一個線性次序⎯⎯若 P 的 next 引用指向 S,則 P 就是 S的直接前驅,而 S 是 P 的直接後繼。
 * 與陣列不同的是,單連結串列的長度不再固定,而是可以根據實際需要不斷變化。
 * 如此一來,包含 n 個元素的單連結串列只需佔用 O(n)空間⎯⎯這要比定長陣列更為靈活。 <br>
 * <p>
 * 單連結串列節點類
 *
 * @author tongs
 */
public class Node implements Position {
    /**
     * 資料物件
     */
    private Object element;
    /**
     * 指向後繼節點
     */
    private Node next;

    /**************************** 建構函式 ****************************/
    /**
     * 指向資料物件、後繼節點的引用都置空
     */
    public Node() {
        this(null, null);
    }

    /**
     * 指定資料物件及後繼節點
     *
     * @param e
     * @param n
     */
    public Node(Object e, Node n) {
        element = e;
        next = n;
    }

    /**************************** Position介面方法 ****************************/

    /**
     * 返回存放於該位置的元素
     *
     * @return
     */
    @Override
    public Object getElem() {
        return element;
    }

    /**
     * 將給定元素存放至該位置,返回此前存放的元素
     *
     * @param e
     * @return
     */
    @Override
    public Object setElem(Object e) {
        Object oldElem = element;
        element = e;
        return oldElem;
    }

    /**************************** 單連結串列節點方法 ****************************/

    /**
     * 取當前節點的後繼節點
     *
     * @return
     */
    public Node getNext() {
        return next;
    }

    /**
     * 修改當前節點的後繼節點
     *
     * @param newNext
     */
    public void setNext(Node newNext) {
        next = newNext;
    }
}
複製程式碼

節點的插入與刪除

寫方法唄。


棧與佇列

最簡單、最基本、最重要的。


遵循後進先出(Last-in-first-out,LIFO)的原則

public interface Stack {
    /**
     * @return 返回棧中元素數目
     */
    int getSize();

    /**
     * 判斷棧是否為空
     *
     * @return
     */
    boolean isEmpty();


    /**
     * 取棧頂元素(但不刪除)
     *
     * @return
     * @throws ExceptionStackEmpty
     */
    Object top() throws ExceptionStackEmpty;

    /**
     * 入棧
     *
     * @param ele
     */
    void push(Object ele);

    /**
     * 出棧
     *
     * @return
     * @throws ExceptionStackEmpty
     */
    Object pop() throws ExceptionStackEmpty;
}
複製程式碼

陣列實現

public class StackArray implements Stack {

    /**
     * 陣列的預設容量
     */
    public static final int CAPACITY = 1024;

    /**
     * 陣列的實際容量
     */
    protected int capacity;

    /**
     * 物件陣列
     */
    protected Object[] mStack;

    /**
     * 棧頂元素的位置
     */
    protected int top = -1;

    /**
     * 按預設容量建立棧物件
     */
    public StackArray() {
        this(CAPACITY);
    }


    /**
     * 按指定容量建立棧物件
     *
     * @param cap
     */
    public StackArray(int cap) {
        capacity = cap;
        mStack = new Object[capacity];
    }


    /**
     * 獲取棧當前的規模
     *
     * @return
     */
    @Override
    public int getSize() {
        return (top + 1);
    }

    /**
     * 測試棧是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return (top < 0);
    }


    /**
     * 入棧
     *
     * @param obj
     * @throws ExceptionStackFull
     */
    @Override
    public void push(Object obj) throws ExceptionStackFull {
        if (getSize() == capacity) {
            throw new ExceptionStackFull("意外:棧溢位");
        }
        mStack[++top] = obj;
    }


    /**
     * 取棧頂元素
     *
     * @return
     * @throws ExceptionStackEmpty
     */
    @Override
    public Object top() throws ExceptionStackEmpty {
        if (isEmpty()) {
            throw new ExceptionStackEmpty("意外:棧空");
        }
        return mStack[top];
    }


    /**
     * 出棧
     *
     * @return
     * @throws ExceptionStackEmpty
     */
    @Override
    public Object pop() throws ExceptionStackEmpty {
        Object elem;
        if (isEmpty()) {
            throw new ExceptionStackEmpty("意外:棧空");
        }
        elem = mStack[top];
        mStack[top--] = null;
        return elem;
    }
}

複製程式碼

從程式碼看出來,只是基於資料做一些簡單的方法。

連結串列實現

public class StackList {
    /**
     * 指向棧頂元素
     */
    protected Node top;
    /**
     * 棧中元素的數目
     */
    protected int size;


    /**
     * 構造方法(空棧)
     */
    public StackList() {
        top = null;
        size = 0;
    }
    
    /**
     * 查詢當前棧的規模
     *
     * @return
     */
    public int getSize() {
        return size;
    }
    
    /**
     * 判斷是否棧空
     *
     * @return
     */
    public boolean isEmpty() {
        return (top == null) ? true : false;
    }

    /**
     * 壓棧
     *
     * @param elem
     */
    public void push(Object elem) {
        //建立一個新節點,將其作為首節點插入
        Node v = new Node(elem, top);
        //更新首節點引用
        top = v;
        //更新規模記錄
        size++;
    }
    
    /**
     * 讀取(但不刪除)棧頂
     *
     * @return
     * @throws ExceptionStackEmpty
     */
    public Object top() throws ExceptionStackEmpty {
        if (isEmpty()) {
            throw new ExceptionStackEmpty("意外:棧空");
        }
        return top.getElem();
    }
    
    /**
     * 彈出棧頂
     *
     * @return
     * @throws ExceptionStackEmpty
     */
    public Object pop() throws ExceptionStackEmpty {
        if (isEmpty()) {
            throw new ExceptionStackEmpty("意外:棧空");
        }
        Object temp = top.getElem();
        //更新首節點引用
        top = top.getNext();
        //更新規模記錄
        size--;
        return temp;
    }
}
複製程式碼

陣列與連結串列的區別與分析

時間複雜度是一樣的。 陣列初始化的時候決定了MaxSize,而連結串列則解決了這個問題。

應用

Java虛擬機器中的棧、表示式中的括號匹配、HTML檔案中的標誌匹配等。


佇列

遵循“先進先出”(First-In-First-Out, FIFO)的原則

public interface Queue {

    /**
     * 返回佇列中元素數目
     *
     * @return
     */
    int getSize();


    /**
     * 判斷佇列是否為空
     *
     * @return
     */
    boolean isEmpty();


    /**
     * 取隊首元素(但不刪除)
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    Object front() throws ExceptionQueueEmpty;


    /**
     * 入隊
     *
     * @param obj
     * @throws ExceptionQueueFull
     */
    void enqueue(Object obj) throws ExceptionQueueFull;


    /**
     * 出隊
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    Object dequeue() throws ExceptionQueueEmpty;

    /**
     * 遍歷
     */
    void Traversal();
}
複製程式碼

陣列實現

public class QueueArray implements Queue {

    /**
     * 陣列的預設容量
     */
    public static final int CAPACITY = 1000;
    /**
     * 陣列的實際容量
     */
    protected int capacity;
    /**
     * 物件陣列
     */
    protected Object[] mQueue;
    /**
     * 隊首元素的位置
     */
    protected int f = 0;
    /**
     * 隊尾元素的位置
     */
    protected int r = 0;

    /**
     * 構造方法(空佇列)
     */
    public QueueArray() {
        this(CAPACITY);
    }

    /**
     * 按指定容量建立物件
     *
     * @param cap
     */
    public QueueArray(int cap) {
        capacity = cap;
        mQueue = new Object[capacity];
    }

    @Override
    public int getSize() {
        return (capacity - f + r) % capacity;
    }


    @Override
    public boolean isEmpty() {
        return (f == r);
    }

    @Override
    public Object front() throws ExceptionQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionQueueEmpty("意外:佇列空");
        }
        return mQueue[f];

    }

    @Override
    public void enqueue(Object obj) throws ExceptionQueueFull {
        if (getSize() == capacity - 1) {
            throw new ExceptionQueueFull("Queue overflow.");
        }
        mQueue[r] = obj;
        r = (r + 1) % capacity;
    }

    @Override
    public Object dequeue() throws ExceptionQueueEmpty {
        Object elem;
        if (isEmpty()) {
            throw new ExceptionQueueEmpty("意外:佇列空");
        }
        elem = mQueue[f];
        mQueue[f] = null;
        f = (f + 1) % capacity;
        return elem;
    }

    @Override
    public void Traversal() {
        for (int i = f; i < r; i++) {
            System.out.print(mQueue[i] + " ");
        }
        System.out.println();
    }
}
複製程式碼

連結串列實現

public class QueueList {
    /**
     * 指向表首元素
     */
    protected Node head;
    /**
     * 指向表末元素
     */
    protected Node tail;
    /**
     * 佇列中元素的數目
     */
    protected int size;


    /**
     * 構造方法(空佇列)
     */
    public QueueList() {
        head = tail = null;
        size = 0;
    }


    /**
     * 查詢當前佇列的規模
     *
     * @return
     */
    public int getSize() {
        return size;
    }


    /**
     * 判斷佇列是否為空
     *
     * @return
     */
    public boolean isEmpty() {
        return (0 == size) ? true : false;
    }


    /**
     * 入隊
     *
     * @param obj
     */
    public void enqueue(Object obj) {
        Node node = new Node();
        node.setElem(obj);
        //新節點作為末節點插入
        node.setNext(null);
        if (0 == size) {
            //若此前佇列為空,則直接插入
            head = node;
        } else {
            //否則,將新節點接至佇列末端
            tail.setNext(node);
        }
        //更新指向末節點引用
        tail = node;
        //更新規模
        size++;
    }


    /**
     * 出隊
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    public Object dequeue() throws ExceptionQueueEmpty {
        if (0 == size) {
            throw new ExceptionQueueEmpty("意外:佇列空");
        }
        Object obj = head.getElem();
        head = head.getNext();
        size--;
        if (0 == size) {
            //若佇列已空,須將末節點引用置空
            tail = null;
        }
        return obj;
    }

    /**
     * 取(並不刪除)隊首元素
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    public Object front() throws ExceptionQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionQueueEmpty("意外:佇列空");
        }
        return head.getElem();
    }

    /**
     * 遍歷(不屬於ADT)
     */
    public void Traversal() {
        Node p = head;
        while (null != p) {
            System.out.print(p.getElem() + " ");
            p = p.getNext();
        }
    }
}
複製程式碼

陣列與連結串列的區別與分析

時間複雜度是一樣的。 陣列初始的時候決定化MaxSize,而連結串列則解決了這個問題。

應用

迴圈分配器


雙端佇列

雙端佇列(Double-ended queue),簡稱為Deque。顧名思義,也就是前端與後端都支援插入和刪除操作的佇列。

public interface Deque {
    /**
     * 返回佇列中元素數目
     *
     * @return
     */
    int getSize();

    /**
     * 判斷佇列是否為空
     *
     * @return
     */
    boolean isEmpty();

    /**
     * 取首元素
     * (但不刪除)
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    Object first() throws ExceptionQueueEmpty;

    /**
     * 取末元素
     * (但不刪除)
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    Object last() throws ExceptionQueueEmpty;

    /**
     * 將新元素作為首元素插入
     *
     * @param obj
     */
    void insertFirst(Object obj);

    /**
     * 將新元素作為末元素插入
     *
     * @param obj
     */
    void insertLast(Object obj);

    /**
     * 刪除首元素
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    Object removeFirst() throws ExceptionQueueEmpty;

    /**
     * 刪除末元素
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    Object removeLast() throws ExceptionQueueEmpty;

    /**
     * 遍歷
     */
    void Traversal();
}

public class DLNode implements Position {
    /**
     * 資料物件
     */
    private Object element;
    /**
     * 指向前驅節點
     */
    private DLNode prev;
    /**
     * 指向後繼節點
     */
    private DLNode next;

    /**************************** 建構函式 ****************************/
    public DLNode() {
        this(null, null, null);
    }

    /**
     * 注意三個引數的次序:資料物件、前驅節點、後繼節點
     *
     * @param e 資料物件
     * @param p 前驅節點
     * @param n 後繼節點
     */
    public DLNode(Object e, DLNode p, DLNode n) {
        element = e;
        prev = p;
        next = n;
    }


    /**************************** Position介面方法 ****************************/

    /**
     * 返回存放於該位置的元素
     *
     * @return
     */
    @Override
    public Object getElem() {
        return element;
    }

    /**
     * 將給定元素存放至該位置,返回此前存放的元素
     *
     * @param e
     * @return
     */
    @Override
    public Object setElem(Object e) {
        Object oldElem = element;
        element = e;
        return oldElem;
    }

    /**************************** 雙向連結串列節點方法 ****************************/

    /**
     * 找到後繼位置
     *
     * @return
     */
    public DLNode getNext() {
        return next;
    }

    /**
     * 找到前驅位置
     *
     * @return
     */
    public DLNode getPrev() {
        return prev;
    }

    /**
     * 修改後繼位置
     *
     * @param newNext
     */
    public void setNext(DLNode newNext) {
        next = newNext;
    }

    /**
     * 修改前驅位置
     *
     * @param newPrev
     */
    public void setPrev(DLNode newPrev) {
        prev = newPrev;
    }
}

public class DequeDLNode implements Deque {
    /**
     * 指向頭節點(哨兵)
     */
    protected DLNode header;
    /**
     * 指向尾節點(哨兵)
     */
    protected DLNode trailer;
    /**
     * 佇列中元素的數目
     */
    protected int size;

    /**
     * 建構函式
     */
    public DequeDLNode() {
        header = new DLNode();
        trailer = new DLNode();
        header.setNext(trailer);
        trailer.setPrev(header);
        size = 0;
    }

    /**
     * 返回佇列中元素數目
     *
     * @return
     */
    @Override
    public int getSize() {
        return size;
    }

    /**
     * 判斷佇列是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return (0 == size) ? true : false;
    }

    /**
     * 取首元素(但不刪除)
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    @Override
    public Object first() throws ExceptionQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionQueueEmpty("意外:雙端佇列為空");
        }
        return header.getNext().getElem();
    }


    /**
     * 取末元素(但不刪除)
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    @Override
    public Object last() throws ExceptionQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionQueueEmpty("意外:雙端佇列為空");
        }
        return trailer.getPrev().getElem();
    }

    /**
     * 在佇列前端插入新節點
     *
     * @param obj
     */
    @Override
    public void insertFirst(Object obj) {
        DLNode second = header.getNext();
        DLNode first = new DLNode(obj, header, second);
        second.setPrev(first);
        header.setNext(first);
        size++;
    }


    /**
     * 在佇列後端插入新節點
     *
     * @param obj
     */
    @Override
    public void insertLast(Object obj) {
        DLNode second = trailer.getPrev();
        DLNode first = new DLNode(obj, second, trailer);
        second.setNext(first);
        trailer.setPrev(first);
        size++;
    }


    /**
     * 刪除首節點
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    @Override
    public Object removeFirst() throws ExceptionQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionQueueEmpty("意外:雙端佇列為空");
        }
        DLNode first = header.getNext();
        DLNode second = first.getNext();
        Object obj = first.getElem();
        header.setNext(second);
        second.setPrev(header);
        size--;
        return (obj);
    }

    /**
     * 刪除末節點
     *
     * @return
     * @throws ExceptionQueueEmpty
     */
    @Override
    public Object removeLast() throws ExceptionQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionQueueEmpty("意外:雙端佇列為空");
        }
        DLNode first = trailer.getPrev();
        DLNode second = first.getPrev();
        Object obj = first.getElem();
        trailer.setPrev(second);
        second.setNext(trailer);
        size--;
        return (obj);
    }

    /**
     * 遍歷
     */
    @Override
    public void Traversal() {
        DLNode p = header.getNext();
        while (p != trailer) {
            System.out.print(p.getElem() + " ");
            p = p.getNext();
        }
        System.out.println();
    }
}
複製程式碼

向量

對陣列結構進行抽象與擴充套件之後,就可以得到向量結構,因此向量也稱作陣列列表(Array list)。

向量提供一些訪問方法,使得我們可以通過下標直接訪問序列中的元素,也可以將指定下標處的元素刪除,或將新元素插入至指定下標。

為了與通常陣列結構的下標(Index)概念區分開來,我們通 常將序列的下標稱為秩(Rank)。

public interface Vector {

    /**
     * 返回向量中元素數目
     *
     * @return
     */
    int getSize();

    /**
     * 判斷向量是否為空
     *
     * @return
     */
    boolean isEmpty();

    /**
     * 取秩為r的元素
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    Object getAtRank(int r) throws ExceptionBoundaryViolation;

    /**
     * 將秩為r的元素替換為obj
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation;

    /**
     * 插入obj,作為秩為r的元素;返回該元素
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation;

    /**
     * 刪除秩為r的元素
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    Object removeAtRank(int r) throws ExceptionBoundaryViolation;
}
複製程式碼

基於陣列的簡單實現

public class VectorArray implements Vector {
    /**
     * 陣列的容量
     */
    private final int N = 1024;
    /**
     * 向量的實際規模
     */
    private int n = 0;
    /**
     * 物件陣列
     */
    private Object[] A;

    /**
     * 建構函式
     */
    public VectorArray() {
        A = new Object[N];
        n = 0;
    }

    /**
     * 返回向量中元素數目
     *
     * @return
     */
    @Override
    public int getSize() {
        return n;
    }


    /**
     * 判斷向量是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return (0 == n) ? true : false;
    }


    /**
     * 取秩為r的元素
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object getAtRank(int r) throws ExceptionBoundaryViolation {
        if (0 > r || r >= n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        return A[r];
    }

    /**
     * 將秩為r的元素替換為obj
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
        if (0 > r || r >= n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        Object bak = A[r];
        A[r] = obj;
        return bak;
    }

    /**
     * 插入obj,作為秩為r的元素;返回該元素
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
        if (0 > r || r > n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        if (n >= N) {
            throw new ExceptionBoundaryViolation("意外:陣列溢位");
        }
        for (int i = n; i > r; i--) {
            //後續元素順次後移
            A[i] = A[i - 1];
        }
        //插入
        A[r] = obj;
        //更新當前規模
        n++;
        return obj;
    }

    /**
     * 刪除秩為r的元素
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object removeAtRank(int r) throws ExceptionBoundaryViolation {
        if (0 > r || r >= n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        Object bak = A[r];
        //後續元素順次前移
        for (int i = r; i < n; i++) {
            A[i] = A[i + 1];
        }
        //更新當前規模
        n--;
        return bak;
    }
}
複製程式碼

基於可擴充陣列的實現

public class VectorExtArray implements Vector {

    /**
     * 陣列的容量,可不斷增加
     */
    private int N = 8;

    /**
     * 向量的實際規模
     */
    private int n;

    /**
     * 物件陣列
     */
    private Object A[];

    /**
     * 建構函式
     */
    public VectorExtArray() {
        A = new Object[N];
        n = 0;
    }

    /**
     * 返回向量中元素數目
     *
     * @return
     */
    @Override
    public int getSize() {
        return n;
    }

    /**
     * 判斷向量是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return (0 == n) ? true : false;
    }

    /**
     * 取秩為r的元素
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object getAtRank(int r) throws ExceptionBoundaryViolation {
        if (0 > r || r >= n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        return A[r];
    }

    /**
     * 將秩為r的元素替換為obj
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
        if (0 > r || r >= n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        Object bak = A[r];
        A[r] = obj;
        return bak;
    }

    /**
     * 插入obj,作為秩為r的元素;並返回該元素
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
        if (0 > r || r > n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        if (N <= n) {
            //空間溢位的處理
            N *= 2;
            //開闢一個容量加倍的陣列
            Object B[] = new Object[N];
            for (int i = 0; i < n; i++) {
                //A[]中內容複製至B[]
                B[i] = A[i];
            }
            //用B替換A(原A[]將被自動回收)
            A = B;
        }
        for (int i = n; i > r; i--) {
            //後續元素順次後移
            A[i] = A[i - 1];
        }
        //插入
        A[r] = obj;
        //更新當前規模
        n++;
        return obj;
    }

    /**
     * 刪除秩為r的元素
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object removeAtRank(int r) throws ExceptionBoundaryViolation {
        if (0 > r || r >= n) {
            throw new ExceptionBoundaryViolation("意外:秩越界");
        }
        Object bak = A[r];
        for (int i = r; i < n - 1; i++) {
            //後續元素順次前移
            A[i] = A[i + 1];
        }
        //更新當前規模
        n--;
        return bak;
    }
}
複製程式碼

效能分析

insertAtRank()、removeAtRank()方法都需要耗費O(n)時間。其它那些基於位置的操作則只需要 O(1)的時間。

應用

java.util.ArrayList 類和 java.util.Vector 類


列表

與向量相對應地,列表ADT則是對連結串列結構的抽象。列表提供的訪問、更新方法,按照物件導向的規範對列表的節點物件進行了封裝,稱作位置(Position)。

public interface List {
    
    /**
     * 查詢列表當前的規模
     *
     * @return
     */
    int getSize();


    /**
     * 判斷列表是否為空
     *
     * @return
     */
    boolean isEmpty();
    
    /**
     * 返回第一個元素(的位置)
     *
     * @return
     */
    Position first();
    
    /**
     * 返回最後一個元素(的位置)
     *
     * @return
     */
    Position last();

    /**
     * 返回緊接給定位置之後的元素(的位置)
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     * @throws ExceptionBoundaryViolation
     */
    Position getNext(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation;
    
    /**
     * 返回緊靠給定位置之前的元素(的位置)
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     * @throws ExceptionBoundaryViolation
     */
    Position getPrev(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation;

    /**
     * 將e作為第一個元素插入列表
     *
     * @param e
     * @return
     */
    Position insertFirst(Object e);
    
    /**
     * 將e作為最後一個元素插入列表
     *
     * @param e
     * @return
     */
    Position insertLast(Object e);

    /**
     * 將e插入至緊接給定位置之後的位置
     *
     * @param p
     * @param e
     * @return
     * @throws ExceptionPositionInvalid
     */
    Position insertAfter(Position p, Object e) throws ExceptionPositionInvalid;
    
    /**
     * 將e插入至緊靠給定位置之前的位置
     *
     * @param p
     * @param e
     * @return
     * @throws ExceptionPositionInvalid
     */
    Position insertBefore(Position p, Object e) throws ExceptionPositionInvalid;
    
    /**
     * 刪除給定位置處的元素,並返回之
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     */
    Object remove(Position p) throws ExceptionPositionInvalid;
    
    /**
     * 刪除首元素,並返回之
     *
     * @return
     */
    Object removeFirst();
    
    /**
     * 刪除末元素,並返回之
     *
     * @return
     */
    Object removeLast();
    
    /**
     * 將處於給定位置的元素替換為新元素,並返回被替換的元素
     *
     * @param p
     * @param e
     * @return
     * @throws ExceptionPositionInvalid
     */
    Object replace(Position p, Object e) throws ExceptionPositionInvalid;
    
    /**
     * 位置迭代器
     *
     * @return
     */
    Iterator positions();
    
    /**
     * 元素迭代器
     *
     * @return
     */
    Iterator elements();
}
複製程式碼

基於雙向連結串列實現的列表

public class ListDLNode implements List {
    /**
     * 列表的實際規模
     */
    protected int numElem;
    
    /**
     * 哨兵:首節點+末節點
     */
    protected DLNode header, trailer;

    /**
     * 建構函式
     */
    public ListDLNode() {
        //空表
        numElem = 0;
        //首節點
        header = new DLNode(null, null, null);
        //末節點
        trailer = new DLNode(null, header, null);
        //首、末節點相互連結
        header.setNext(trailer);
    }

    /**************************** 輔助方法 ****************************/

    /**
     * 檢查給定位置在列表中是否合法,若是,則將其轉換為*DLNode
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     */
    protected DLNode checkPosition(Position p) throws ExceptionPositionInvalid {
        if (null == p) {
            throw new ExceptionPositionInvalid("意外:傳遞給List_DLNode的位置是null");
        }
        if (header == p) {
            throw new ExceptionPositionInvalid("意外:頭節點哨兵位置非法");
        }
        if (trailer == p) {
            throw new ExceptionPositionInvalid("意外:尾結點哨兵位置非法");
        }
        DLNode temp = (DLNode) p;
        return temp;
    }

    /**************************** ADT方法 ****************************/

    /**
     * 查詢列表當前的規模
     */
    @Override
    public int getSize() {
        return numElem;
    }

    /**
     * 判斷列表是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return (numElem == 0);
    }

    /**
     * 返回第一個元素(的位置)
     *
     * @return
     * @throws ExceptionListEmpty
     */
    @Override
    public Position first() throws ExceptionListEmpty {
        if (isEmpty()) {
            throw new ExceptionListEmpty("意外:列表空");
        }
        return header.getNext();
    }


    /**
     * 返回最後一個元素(的位置)
     *
     * @return
     * @throws ExceptionListEmpty
     */
    @Override
    public Position last() throws ExceptionListEmpty {
        if (isEmpty()) {
            throw new ExceptionListEmpty("意外:列表空");
        }


        return trailer.getPrev();
    }

    /**
     * 返回緊靠給定位置之前的元素(的位置)
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Position getPrev(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation {
        DLNode v = checkPosition(p);
        DLNode prev = v.getPrev();
        if (prev == header) {
            throw new ExceptionBoundaryViolation("意外:企圖越過列表前端");
        }
        return prev;
    }

    /**
     * 返回緊接給定位置之後的元素(的位置)
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Position getNext(Position p) throws ExceptionPositionInvalid, ExceptionBoundaryViolation {
        DLNode v = checkPosition(p);
        DLNode next = v.getNext();
        if (next == trailer) {
            throw new ExceptionBoundaryViolation("意外:企圖越過列表後端");
        }
        return next;
    }

    /**
     * 將e插入至緊靠給定位置之前的位置
     *
     * @param p
     * @param element
     * @return
     * @throws ExceptionPositionInvalid
     */
    @Override
    public Position insertBefore(Position p, Object element) throws ExceptionPositionInvalid {
        DLNode v = checkPosition(p);
        numElem++;
        DLNode newNode = new DLNode(element, v.getPrev(), v);
        v.getPrev().setNext(newNode);
        v.setPrev(newNode);
        return newNode;
    }

    /**
     * 將e插入至緊接給定位置之後的位置
     *
     * @param p
     * @param element
     * @return
     * @throws ExceptionPositionInvalid
     */
    @Override
    public Position insertAfter(Position p, Object element) throws ExceptionPositionInvalid {
        DLNode v = checkPosition(p);
        numElem++;
        DLNode newNode = new DLNode(element, v, v.getNext());
        v.getNext().setPrev(newNode);
        v.setNext(newNode);
        return newNode;
    }

    /**
     * 將e作為第一個元素插入列表
     *
     * @param e
     * @return
     */
    @Override
    public Position insertFirst(Object e) {
        numElem++;
        DLNode newNode = new DLNode(e, header, header.getNext());
        header.getNext().setPrev(newNode);
        header.setNext(newNode);
        return newNode;
    }

    /**
     * 將e作為最後一個元素插入列表
     *
     * @param e
     * @return
     */
    @Override
    public Position insertLast(Object e) {
        numElem++;
        DLNode newNode = new DLNode(e, trailer.getPrev(), trailer);
        if (null == trailer.getPrev()) {
            System.out.println("!!!Prev of trailer is NULL!!!");
        }
        trailer.getPrev().setNext(newNode);
        trailer.setPrev(newNode);
        return newNode;
    }

    /**
     * 刪除給定位置處的元素,並返回之
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     */
    @Override
    public Object remove(Position p) throws ExceptionPositionInvalid {
        DLNode v = checkPosition(p);
        numElem--;
        DLNode vPrev = v.getPrev();
        DLNode vNext = v.getNext();
        vPrev.setNext(vNext);
        vNext.setPrev(vPrev);
        Object vElem = v.getElem();
        //將該位置(節點)從列表中摘出,以便系統回收其佔用的空間
        v.setNext(null);
        v.setPrev(null);
        return vElem;
    }

    /**
     * 刪除首元素,並返回之
     *
     * @return
     */
    @Override
    public Object removeFirst() {
        return remove(header.getNext());
    }

    /**
     * 刪除末元素,並返回之
     *
     * @return
     */
    @Override
    public Object removeLast() {
        return remove(trailer.getPrev());
    }

    /**
     * 將處於給定位置的元素替換為新元素,並返回被替換的元素
     *
     * @param p
     * @param obj
     * @return
     * @throws ExceptionPositionInvalid
     */
    @Override
    public Object replace(Position p, Object obj) throws ExceptionPositionInvalid {
        DLNode v = checkPosition(p);
        Object oldElem = v.getElem();
        v.setElem(obj);
        return oldElem;
    }

    /**
     * 位置迭代器
     *
     * @return
     */
    @Override
    public Iterator positions() {
        return new IteratorPosition(this);
    }

    /**
     * 元素迭代器
     *
     * @return
     */
    @Override
    public Iterator elements() {
        return new IteratorElement(this);
    }

}
複製程式碼

效能分析

從方法可以看出O(1)

應用


序列

public interface Sequence extends Vector, List {
    /**
     * 若0 <= r < getSize(),則返回秩為r的元素所在的位置;否則,報錯
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    Position rank2Pos(int r) throws ExceptionBoundaryViolation;

    /**
     * 若p是序列中的合法位置,則返回存放於p處的元素的秩;否則,報錯
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     */
    int pos2Rank(Position p) throws ExceptionPositionInvalid;
} 
複製程式碼

基於雙向連結串列實現序列

實現序列最自然、最直接的方式,就是利用雙向連結串列。

public class SequenceDLNode extends ListDLNode implements Sequence {

    /**
     * 檢查秩r是否在[0, n)之間
     */
    protected void checkRank(int r, int n) throws ExceptionBoundaryViolation {
        if (r < 0 || r >= n) {
            throw new ExceptionBoundaryViolation("意外:非法的秩" + r + ",應該屬於[0, " + n + ")");
        }
    }

    /**
     * 若0 <= r < getSize(),則返回秩為r的元素所在的位置;否則,報錯--O(n)
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Position rank2Pos(int r) throws ExceptionBoundaryViolation {
        DLNode node;
        checkRank(r, getSize());
        if (r <= getSize() / 2) {
            //若秩較小,則
            //從前端開始
            node = header.getNext();

            for (int i = 0; i < r; i++) {
                //逐一掃描
                node = node.getNext();
            }
        } else {
            //若秩較大,則
            //從後端開始
            node = trailer.getPrev();
            for (int i = 1; i < getSize() - r; i++) {
                //逐一掃描
                node = node.getPrev();
            }
        }
        return node;
    }

    /**
     * 若p是序列中的合法位置,則返回存放於p處的元素的秩;否則,報錯--O(n)
     *
     * @param p
     * @return
     * @throws ExceptionPositionInvalid
     */
    @Override
    public int pos2Rank(Position p) throws ExceptionPositionInvalid {
        DLNode node = header.getNext();
        int r = 0;
        while (node != trailer) {
            if (node == p) {
                return (r);
            }
            node = node.getNext();
            r++;
        }
        throw new ExceptionPositionInvalid("意外:作為引數的位置不屬於序列");
    }

    /**
     * 取秩為r的元素--O(n)
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object getAtRank(int r) throws ExceptionBoundaryViolation {
        checkRank(r, getSize());
        return rank2Pos(r).getElem();
    }

    /**
     * 將秩為r的元素替換為obj--O(n)
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object replaceAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
        checkRank(r, getSize());
        return replace(rank2Pos(r), obj);
    }

    /**
     * 插入obj,作為秩為r的元素--O(n);返回該元素
     *
     * @param r
     * @param obj
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object insertAtRank(int r, Object obj) throws ExceptionBoundaryViolation {
        checkRank(r, getSize() + 1);
        if (getSize() == r) {
            insertLast(obj);
        } else {
            insertBefore(rank2Pos(r), obj);
        }
        return obj;
    }

    /**
     * 刪除秩為r的元素--O(n)
     *
     * @param r
     * @return
     * @throws ExceptionBoundaryViolation
     */
    @Override
    public Object removeAtRank(int r) throws ExceptionBoundaryViolation {
        checkRank(r, getSize());
        return remove(rank2Pos(r));
    }
}
複製程式碼

基於陣列實現序列

採用這種資料結構,在插入或刪除操作之後,我們只需掃描一遍陣列,即可找到需要修正秩的那些位置,並將其秩加一。

跟Vector的陣列實現類似吧。不寫了哦。

效能分析

insertFirst()、insertBefore()、insertAfter()和 remove()方法都需要耗費O(n)時間。其它那些基於位置的操作則只需要 O(1)的時間。

應用


迭代器

在對向量、列表和序列進行處理(比如,查詢某一特定的元素)時,一種典型的操作就是依次訪問或修改其中的各個元素。迭代器是軟體設計的一種模式,它是對“逐一訪問所有元素”這類操作的抽象。

實現

public interface Iterator {
    /**
     * 檢查迭代器中是否還有剩餘的元素
     *
     * @return
     */
    boolean hasNext();

    /**
     * 返回迭代器中的下一元素
     *
     * @return
     */
    Object getNext();
}
複製程式碼

基於列表實現的位置迭代器

public class IteratorPosition implements Iterator {
    /**
     * 列表
     */
    private List list;
    /**
     * 當前(下一個)位置
     */
    private Position nextPosition;

    /**
     * 預設構造方法
     */
    public IteratorPosition() {
        list = null;
    }

    /**
     * 構造方法
     *
     * @param L
     */
    public IteratorPosition(List L) {
        list = L;
        if (list.isEmpty()) {
            //若列表為空,則//當前位置置空
            nextPosition = null;
        } else {
            //否則//從第一個位置開始
            nextPosition = list.first();
        }
    }

    /**
     * 檢查迭代器中是否還有剩餘的位置
     *
     * @return
     */
    @Override
    public boolean hasNext() {
        return (nextPosition != null);
    }

    /**
     * 返回迭代器中的下一位置
     *
     * @return
     * @throws ExceptionNoSuchElement
     */
    @Override
    public Object getNext() throws ExceptionNoSuchElement {
        if (!hasNext()) {
            throw new ExceptionNoSuchElement("意外:沒有下一位置");
        }
        Position currentPosition = nextPosition;
        if (currentPosition == list.last()) {
            //若已到達尾位置,則
            //不再有下一個位置
            nextPosition = null;
        } else {
            //否則
            //轉向下一位置
            nextPosition = list.getNext(currentPosition);
        }
        return currentPosition;
    }
}
複製程式碼

基於列表實現的元素迭代器

public class IteratorElement implements Iterator {
    /**
     * 列表
     */
    private List list;
    /**
     * 當前(下一個)元素的位置
     */
    private Position nextPosition;

    /**
     * 預設構造方法
     */
    public IteratorElement() {
        list = null;
    }

    /**
     * 構造方法
     *
     * @param L
     */
    public IteratorElement(List L) {
        list = L;
        //若列表為空,則
        if (list.isEmpty()) {
            //當前元素置空
            nextPosition = null;
        } else {//否則
            //從第一個元素開始
            nextPosition = list.first();
        }
    }

    /**
     * 檢查迭代器中是否還有剩餘的元素
     *
     * @return
     */
    @Override
    public boolean hasNext() {
        return (null != nextPosition);
    }


    /**
     * 返回迭代器中的下一元素
     *
     * @return
     * @throws ExceptionNoSuchElement
     */
    @Override
    public Object getNext() throws ExceptionNoSuchElement {
        if (!hasNext()) {
            throw new ExceptionNoSuchElement("意外:沒有下一元素");
        }
        Position currentPosition = nextPosition;
        //若已到達尾元素,則
        if (currentPosition == list.last()) {
            //不再有下一元素
            nextPosition = null;
        } else {//否則
            //轉向下一元素
            nextPosition = list.getNext(currentPosition);
        }
        return currentPosition.getElem();
    }
}

複製程式碼

應用

迭代器有什麼優點呢? java.util.Iterator的大多數實現都提供了故障快速修復(Fail-fast)的機制 ⎯⎯⎯⎯⎯在利用迭代器遍歷某一容器的過程中,一旦發現該容器的內容有所改變,迭代器就會丟擲ConcurrentModificationException意外錯並立刻退出。

在 Java 中,可以通過多個迭代器同時對同一連結串列進行遍歷。不過,正如上面所提到的,一旦其中某個迭代器修改了連結串列的內容,所有的迭代器都會成為非法的。


總結

這文章算是資料結構的入門吧。

很基礎很簡單。

java.util.List 介面所提供的功能, java.util 中的 ArrayList 類Vector 類都是基於陣列實現的,而LinkedList類則是基於連結串列實現的。

該基於陣列實現還是基於連結串列實現的,這兩種實現各有利弊,在解決實際問題時,我們需要在二者之間做一權衡。

計算機的資料結構差不多都是這樣子的,無論什麼語言。

下一篇打算加深一下,講樹、串、圖。

相關文章