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

Tongson發表於2018-10-15

優先佇列

不僅僅是“先進先出”,而是可以插隊。

優先順序、關鍵碼(Key)、全序關係與優先佇列

將關鍵碼定義為任意物件,必須建立一種統一的、相容的形式,以支援不同物件之間的比較,確定優先順序。 實際上,作為優先佇列的一個基本要求,在關鍵碼之間必須能夠定義某種全序關係(Total order relation)。 具體來說,任何兩個關鍵碼都必須能夠比較“大小”。

舉個例子:如果將這種全序關係用“≤”表示,則該關係還必須滿足以下三條性質:

  • 自反性:對於任一關鍵碼 k,都有 k ≤ k
  • 反對稱性:若k1 ≤ k2且k2 ≤ k1,則k1 = k2
  • 傳遞性:若k1 ≤ k2且k2 ≤ k3,則k1 ≤ k3

所謂的優先佇列也是物件的一種容器,只不過其中的每個物件都擁有一個關鍵碼,在它們的關 鍵碼之間存在滿足上述性質的某種全序關係“≤”。關鍵碼可以是在物件插入優先佇列時被人為賦予 的,也可能就是物件本身具有的某一屬性。

  • 關鍵碼:物件

  • 全序關係:if(){}else(){}

關鍵碼通過全序關係確認優先順序排成優先佇列。

條目(Entry)

所謂一個條目(Entry),就是由一個物件及 其關鍵碼合成的一個物件,它反映和記錄了二者之間的關聯關係。

這樣,通過將條目物件作為優先佇列的元素,即可記錄和維護原先物件與其關鍵碼之間的關聯關係。

/**
 * <b>Description:</b> 引入條目這一概念,旨在解決上面的前一個問題。
 * 所謂一個條目(Entry),就是由一個物件及其關鍵碼合成的一個物件,它反映和記錄了二者之間的關聯關係。
 * 這樣,通過將條目物件作為優先佇列的元素,即可記錄和維護原先物件與其關鍵碼之間的關聯關係。
 * @author tongson
 */
public interface Entry {
    /**
     * 取條目的關鍵碼
     *
     * @return
     */
    public Object getKey();

    /**
     * 修改條目的關鍵碼,返回此前存放的關鍵碼
     *
     * @param k
     * @return
     */
    public Object setKey(Object k);

    /**
     * 取條目的資料物件
     *
     * @return
     */
    public Object getValue();

    /**
     * 修改條目的資料物件,返回此前存放的資料物件
     *
     * @param v
     * @return
     */
    public Object setValue(Object v);
} 
複製程式碼
public class EntryDefault implements Entry {
    protected Object key;
    protected Object value;

    /**************************** 建構函式 ****************************/
    public EntryDefault(Object k, Object v) {
        key = k;
        value = v;
    }

    /**************************** Entry介面方法 ****************************/
    /**
     * 取條目的關鍵碼
     *
     * @return
     */
    @Override
    public Object getKey() {
        return key;
    }

    /**
     * 修改條目的關鍵碼,返回此前存放的關鍵碼
     *
     * @param k
     * @return
     */
    @Override
    public Object setKey(Object k) {
        Object oldK = key;
        key = k;
        return oldK;
    }

    /**
     * 取條目的資料物件
     *
     * @return
     */
    @Override
    public Object getValue() {
        return value;
    }

    /**
     * 修改條目的資料物件,返回此前存放的資料物件
     *
     * @param v
     * @return
     */
    @Override
    public Object setValue(Object v) {
        Object oldV = value;
        value = v;
        return oldV;
    }
} 
複製程式碼

比較器

基於某種 Comparable 介面實現一個關鍵碼類,並將所有通常的比較方法封裝起來,以支援關鍵碼之間的比較。

/**
 * 基於某種 Comparable 介面實現一個關鍵碼類,並將所有通常的比較方法封裝起來,以支援關鍵碼之間的比較。
 */
public interface Comparator {
    /**
     * 若a>(=或<)b,返回正數、零或負數
     *
     * @param a
     * @param b
     * @return
     */
    public int compare(Object a, Object b);
}

複製程式碼
/**
 * Comparable物件的預設比較器
 */
public class ComparatorDefault implements Comparator {
    public ComparatorDefault() {
    }

    @Override
    public int compare(Object a, Object b) throws ClassCastException {
        return ((Comparable) a).compareTo(b);
    }
}

複製程式碼

優先佇列(interface)

public interface PQueue {
    /**統計優先佇列的規模
     * 
     * @return
     */
    public int getSize();

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

    /**若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
     * 
     * @return
     * @throws ExceptionPQueueEmpty
     */
    public Entry getMin() throws ExceptionPQueueEmpty;

    /**將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
     * 
     * @param key
     * @param obj
     * @return
     * @throws ExceptionKeyInvalid
     */
    public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid;

    /**若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
     * 
     * @return
     * @throws ExceptionPQueueEmpty
     */
    public Entry delMin() throws ExceptionPQueueEmpty;
} 
複製程式碼

排序器(interface)

/**
 * <b>Description:</b> 排序器介面 <br>
 */
public interface Sorter {
    void sort(Sequence S);
}
複製程式碼

基於優先佇列的排序器

/**
 * <b>Description:</b> 基於優先佇列的排序器 <br>
 */
public class SorterPQueue implements Sorter {
    private Comparator C;

    public SorterPQueue() {
        this(new ComparatorDefault());
    }

    public SorterPQueue(Comparator comp) {
        C = comp;
    }

    @Override
    public void sort(Sequence S) {
        //為批處理建立優先佇列而準備的序列
        Sequence T = new SequenceDLNode();
        //構建序列T
        while (!S.isEmpty()) {
            //逐一取出S中各元素
            Object e = S.removeFirst();
            //用節點元素本身作為關鍵碼
            T.insertLast(new EntryDefault(e, e));
        }
        // PQueue pq = new PQueueUnsortedList(C, T);
        // PQueue pq = new PQueueSortedList(C, T);
        PQueue pq = new PQueueHeap(C, T);

        //從優先佇列中不斷地
        while (!pq.isEmpty()) {
            //取出最小元素,插至序列末尾
            S.insertLast(pq.delMin().getValue());
            System.out.println("\t:\t" + S.last().getElem());
        }
    }
}
複製程式碼

用向量實現優先佇列

用列表實現優先佇列

基於無序列表的實現及分析

/**
 * <b>Description:</b> 基於無序列表的實現及分析 <br>
 */
public class PQueueUnsortedList implements PQueue {
    private List L;
    private Comparator C;

    /**
     * 構造方法(使用預設比較器)
     */
    public PQueueUnsortedList() {
        this(new ComparatorDefault(), null);
    }

    /**
     * 構造方法(使用指定比較器)
     *
     * @param c
     */
    public PQueueUnsortedList(Comparator c) {
        this(c, null);
    }

    /**
     * 構造方法(使用指定初始元素)
     *
     * @param s
     */
    public PQueueUnsortedList(Sequence s) {
        this(new ComparatorDefault(), s);
    }

    /**
     * 構造方法(使用指定比較器和初始元素)
     *
     * @param c
     * @param s
     */
    public PQueueUnsortedList(Comparator c, Sequence s) {
        L = new ListDLNode();
        C = c;
        if (null != s) {
            while (!s.isEmpty()) {
                Entry e = (Entry) s.removeFirst();
                insert(e.getKey(), e.getValue());
            }
        }
    }

    /**
     * 統計優先佇列的規模
     *
     * @return
     */
    @Override
    public int getSize() {
        return L.getSize();
    }

    /**
     * 判斷優先佇列是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return L.isEmpty();
    }

    /**
     * 若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
     *
     * @return
     * @throws ExceptionPQueueEmpty
     */
    @Override
    public Entry getMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty()) {
            throw new ExceptionPQueueEmpty("意外:優先佇列空");
        }
        Position minPos = L.first();
        Position curPos = L.getNext(minPos);
        //依次檢查所有位置,找出最小條目
        while (null != curPos) {
            if (0 < C.compare(minPos.getElem(), curPos.getElem())) {
                minPos = curPos;
            }
        }
        return (Entry) minPos.getElem();
    }

    /**
     * 將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
     *
     * @param key
     * @param obj
     * @return
     * @throws ExceptionKeyInvalid
     */
    @Override
    public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid {
        //建立一個新條目
        Entry entry = new EntryDefault(key, obj);
        //接至列表末尾
        L.insertLast(entry);
        return (entry);
    }

    /**
     * 若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
     *
     * @return
     * @throws ExceptionPQueueEmpty
     */
    @Override
    public Entry delMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty()) {
            throw new ExceptionPQueueEmpty("意外:優先佇列空");
        }
        Position minPos = L.first();
        Iterator it = L.positions();
        //依次檢查所有位置,找出最小條目
        while (it.hasNext()) {
            Position curPos = (Position) (it.getNext());
            // System.out.println("\t" + ((Entry)(curPos.getElem())).getKey());
            if (0 < C.compare(((Entry) (minPos.getElem())).getKey(), ((Entry) (curPos.getElem())).getKey())) {
                minPos = curPos;
            }
        }
        return (Entry) L.remove(minPos);
    }
}
複製程式碼

基於有序列表的實現及分析

/**
 * <b>Description:</b> 基於有序列表的實現及分析 <br>
 */
public class PQueueSortedList implements PQueue {
    private List L;
    private Comparator C;

    /**
     * 構造方法(使用預設比較器)
     */
    public PQueueSortedList() {
        this(new ComparatorDefault(), null);
    }

    /**
     * 構造方法(使用指定比較器)
     *
     * @param c
     */
    public PQueueSortedList(Comparator c) {
        this(c, null);
    }

    /**
     * 構造方法(使用指定初始元素)
     *
     * @param s
     */
    public PQueueSortedList(Sequence s) {
        this(new ComparatorDefault(), s);
    }

    /**
     * 構造方法(使用指定比較器和初始元素)
     *
     * @param c
     * @param s
     */
    public PQueueSortedList(Comparator c, Sequence s) {
        L = new ListDLNode();
        C = c;
        if (null != s)
            while (!s.isEmpty()) {
                Entry e = (Entry) s.removeFirst();
                insert(e.getKey(), e.getValue());
            }
    }

    /**
     * 統計優先佇列的規模
     *
     * @return
     */
    @Override
    public int getSize() {
        return L.getSize();
    }

    /**
     * 判斷優先佇列是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return L.isEmpty();
    }

    /**
     * 若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
     *
     * @return
     * @throws ExceptionPQueueEmpty
     */
    @Override
    public Entry getMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty()) {
            throw new ExceptionPQueueEmpty("意外:優先佇列空");
        }
        return (Entry) L.last();
    }

    /**
     * 將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
     *
     * @param key
     * @param obj
     * @return
     * @throws ExceptionKeyInvalid
     */
    @Override
    public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid {
        //建立一個新條目
        Entry entry = new EntryDefault(key, obj);

        //若優先佇列為空 //或新條目是當前最大者
        if (L.isEmpty() || (0 > C.compare(((Entry) (L.first().getElem())).getKey(), entry.getKey()))) {
            //則直接插入至表頭
            L.insertFirst(entry);
        } else {//否則
            //從尾條目開始
            Position curPos = L.last();
            while (0 > C.compare(((Entry) (curPos.getElem())).getKey(), entry.getKey())) {
                //不斷前移,直到第一個不小於entry的條目
                curPos = L.getPrev(curPos);
            }
            //緊接該條目之後插入entry
            L.insertAfter(curPos, entry);
        }
        return (entry);
    }

    /**
     * 若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
     *
     * @return
     * @throws ExceptionPQueueEmpty
     */
    @Override
    public Entry delMin() throws ExceptionPQueueEmpty {
        if (L.isEmpty()) {
            throw new ExceptionPQueueEmpty("意外:優先佇列空");
        }
        return (Entry) L.remove(L.last());
    }
}
複製程式碼

選擇排序與插入排序

選擇排序

插入排序

效率比較

用堆實現優先佇列

堆的定義及性質

堆結構

完全性

用堆實現優先佇列

/**
 * <b>Description:</b> 基於堆的優先佇列及其實現 <br>
 */
public class PQueueHeap implements PQueue {
    /**
     * 完全二叉樹形式的堆
     */
    private ComplBinTree H;
    /**
     * 比較器
     */
    private Comparator comp;

    /**
     * 構造方法
     */
    public PQueueHeap() {
        this(new ComparatorDefault(), null);
    }

    /**
     * 構造方法:預設的空優先佇列
     *
     * @param c
     */
    public PQueueHeap(Comparator c) {
        this(c, null);
    }

    /**
     * 構造方法:根據某一序列直接批量式構造堆演算法,S中元素都是形如(key, value)的條目
     *
     * @param S
     */
    public PQueueHeap(Sequence S) {
        this(new ComparatorDefault(), S);
    }

    /**
     * 構造方法:根據某一序列直接批量式構造堆演算法,s中元素都是形如(key, value)的條目
     *
     * @param c
     * @param s
     */
    public PQueueHeap(Comparator c, Sequence s) {
        comp = c;
        H = new ComplBinTreeVector(s);
        if (!H.isEmpty()) {
            //自底而上
            for (int i = H.getSize() / 2 - 1; i >= 0; i--) {
                //逐節點進行下濾
                percolateDown(H.posOfNode(i));
            }
        }
    }

    /*-------- PQueue介面中定義的方法 --------*/

    /**
     * 統計優先佇列的規模
     *
     * @return
     */
    @Override
    public int getSize() {
        return H.getSize();
    }

    /**
     * 判斷優先佇列是否為空
     *
     * @return
     */
    @Override
    public boolean isEmpty() {
        return H.isEmpty();
    }

    /**
     * 若Q非空,則返回其中的最小條目(並不刪除);否則,報錯
     *
     * @return
     * @throws ExceptionPQueueEmpty
     */
    @Override
    public Entry getMin() throws ExceptionPQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionPQueueEmpty("意外:優先佇列為空");
        }
        return (Entry) H.getRoot().getElem();
    }

    /**
     * 將物件obj與關鍵碼k合成一個條目,將其插入Q中,並返回該條目
     *
     * @param key
     * @param obj
     * @return
     * @throws ExceptionKeyInvalid
     */
    @Override
    public Entry insert(Object key, Object obj) throws ExceptionKeyInvalid {
        checkKey(key);
        Entry entry = new EntryDefault(key, obj);
        percolateUp(H.addLast(entry));
        return entry;
    }

    /**
     * 若Q非空,則從其中摘除關鍵碼最小的條目,並返回該條目;否則,報錯
     *
     * @return
     * @throws ExceptionPQueueEmpty
     */
    @Override
    public Entry delMin() throws ExceptionPQueueEmpty {
        if (isEmpty()) {
            throw new ExceptionPQueueEmpty("意外:優先佇列為空");
        }
        //保留堆頂
        Entry min = (Entry) H.getRoot().getElem();
        //若只剩下最後一個條目
        if (1 == getSize()) {
            //直接摘除之
            H.delLast();
        } else {//否則
            H.getRoot().setElem(((ComplBinTreeNodeRank) H.delLast()).getElem());
            //取出最後一個條目,植入堆頂
            percolateDown(H.getRoot());
        }
        //返回原堆頂
        return min;
    }

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

    /**
     * 檢查關鍵碼的可比較性
     *
     * @param key
     * @throws ExceptionKeyInvalid
     */
    protected void checkKey(Object key) throws ExceptionKeyInvalid {
        try {
            comp.compare(key, key);
        } catch (Exception e) {
            throw new ExceptionKeyInvalid("無法比較關鍵碼");
        }
    }

    /**
     * 返回節點v(中所存條目)的關鍵碼
     *
     * @param v
     * @return
     */
    protected Object key(BinTreePosition v) {
        return ((Entry) (v.getElem())).getKey();
    }

    /*-------- 演算法方法 --------*/

    /**
     * 交換父子節點(中所存放的內容)
     *
     * @param u
     * @param v
     */
    protected void swapParentChild(BinTreePosition u, BinTreePosition v) {
        Object temp = u.getElem();
        u.setElem(v.getElem());
        v.setElem(temp);
    }

    /**
     * 上濾演算法
     *
     * @param v
     */
    protected void percolateUp(BinTreePosition v) {
        //記錄根節點
        BinTreePosition root = H.getRoot();
        //不斷地
        while (v != H.getRoot()) {
            //取當前節點的父親
            BinTreePosition p = v.getParent();
            if (0 >= comp.compare(key(p), key(v))) {
                //除非父親比孩子小
                break;
            }
            //否則,交換父子次序
            swapParentChild(p, v);
            //繼續考察新的父節點(即原先的孩子)
            v = p;
        }
    }

    /**
     * 下濾演算法
     *
     * @param v
     */
    protected void percolateDown(BinTreePosition v) {
        //直到v成為葉子
        while (v.hasLChild()) {
            //首先假設左孩子的(關鍵碼)更小
            BinTreePosition smallerChild = v.getLChild();
            if (v.hasRChild() && 0 < comp.compare(key(v.getLChild()), key(v.getRChild()))) {
                //若右孩子存在且更小,則將右孩子作為進一步比較的物件
                smallerChild = v.getRChild();
            }
            if (0 <= comp.compare(key(smallerChild), key(v))) {
                //若兩個孩子都不比v更小,則下濾完成
                break;
            }

            //否則,將其與更小的孩子交換
            swapParentChild(v, smallerChild);
            //並繼續考察這個孩子
            v = smallerChild;
        }
    }
}
複製程式碼

基於堆的優先佇列及其實現

堆排序

直接堆排序

就地堆排序

Huffman 樹

氣泡排序

/**
 * <b>Description:</b> 起泡排序演算法 <br>
 */
public class SorterBubblesort implements Sorter {
    private Comparator C;

    public SorterBubblesort() {
        this(new ComparatorDefault());
    }

    public SorterBubblesort(Comparator comp) {
        C = comp;
    }

    @Override
    public void sort(Sequence S) {
        int n = S.getSize();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (0 < C.compare(S.getAtRank(j), S.getAtRank(j + 1))) {
                    Object temp = S.getAtRank(j);
                    S.replaceAtRank(j, S.getAtRank(j + 1));
                    S.replaceAtRank(j + 1, temp);
                }
            }
        }
    }
}
複製程式碼

相關文章