合併k個排好的的單鏈表

原題

  Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.node

題目大意

  合併k個排好的的單鏈表。分析和描述它的複雜性。算法

解題思路

  使用小頂堆來實現,先將K個鏈表的頭結點入堆,取堆頂元素,這個結點就是最小的,接着讓取出的這個結點的下一個結點入堆,再取堆頂元素,其爲第二小的結點,一直這樣子操做,直到全部的結點都處理完,這樣就完成了鏈表的合併,更多細節請見代碼和註釋。假設K個鏈表一共有N個結點,時間複雜度是O(N*log(K)),空間複雜度是O(K)。數組

代碼實現

結點類ide

public class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

算法實現類this

public class Solution {
   public ListNode mergeKLists(ListNode[] lists) {

        // 爲空或者沒有元素
        if (lists == null || lists.length < 1) {
            return null;
        }

        // 只有一個元素
        if (lists.length == 1) {
            return lists[0];
        }

        // 建立一個小頂堆,而且使用一個匿名內部類做爲比較器
        MinHeap<ListNode> minHeap = new MinHeap<ListNode>(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                if (o1 == null) {
                    return -1;
                }

                if (o2 == null) {
                    return 1;
                }

                return o1.val - o2.val;
            }
        });


        // 將數組中鏈表的第一個結點入堆
        for (ListNode node : lists) {
            if (node != null) {
                minHeap.add(node);
            }
        }

        // 頭結點,做輔助使用
        ListNode head = new ListNode(0);
        // 當前處理的結點
        ListNode curr = head;

        while (!minHeap.isEmpty()) {
            ListNode node = minHeap.deleteTop();

            // 結點的下一個結點不爲空就將下一個結點入堆
            if (node.next != null) {
                minHeap.add(node.next);
            }

            curr.next = node;
            curr = node;
        }

        return head.next;
    }

    /**
     * 小頂堆
     *
     * @param <T>
     */
    private static class MinHeap<T> {
        // 堆中元素存放的集合
        private List<T> items;

        private Comparator<T> comp;

        /**
         * 構造一個椎,始大小是32
         */
        public MinHeap(Comparator<T> comp) {
            this(32, comp);
        }

        /**
         * 造詣一個指定初始大小的堆
         *
         * @param size 初始大小
         */
        public MinHeap(int size, Comparator<T> comp) {
            items = new ArrayList<>(size);
            this.comp = comp;
        }

        /**
         * 向上調整堆
         *
         * @param index 被上移元素的起始位置
         */
        public void siftUp(int index) {
            T intent = items.get(index); // 獲取開始調整的元素對象

            while (index > 0) { // 若是不是根元素
                int parentIndex = (index - 1) / 2; // 找父元素對象的位置
                T parent = items.get(parentIndex);  // 獲取父元素對象
                if (comp.compare(intent, parent) < 0) { //上移的條件,子節點比父節點小
                    items.set(index, parent); // 將父節點向下放
                    index = parentIndex; // 記錄父節點下放的位置
                } else { // 子節點不比父節點小,說明父子路徑已經按從小到大排好順序了,不須要調整了
                    break;
                }
            }

            // index此時記錄是的最後一個被下放的父節點的位置(也多是自身),
            // 因此將最開始的調整的元素值放入index位置便可
            items.set(index, intent);
        }

        /**
         * 向下調整堆
         *
         * @param index 被下移的元素的起始位置
         */
        public void siftDown(int index) {
            T intent = items.get(index);  // 獲取開始調整的元素對象
            int leftIndex = 2 * index + 1; // // 獲取開始調整的元素對象的左子結點的元素位置

            while (leftIndex < items.size()) { // 若是有左子結點
                T minChild = items.get(leftIndex); // 取左子結點的元素對象,而且假定其爲兩個子結點中最小的
                int minIndex = leftIndex; // 兩個子節點中最小節點元素的位置,假定開始時爲左子結點的位置

                int rightIndex = leftIndex + 1;  // 獲取右子結點的位置
                if (rightIndex < items.size()) {  // 若是有右子結點
                    T rightChild = items.get(rightIndex);  // 獲取右子結點的元素對象
                    if (comp.compare(rightChild, minChild) < 0) {  // 找出兩個子節點中的最小子結點
                        minChild = rightChild;
                        minIndex = rightIndex;
                    }
                }

                // 若是最小子節點比父節點小,則須要向下調整
                if (comp.compare(minChild, intent) < 0) {
                    items.set(index, minChild); // 將子節點向上移
                    index = minIndex; // 記錄上移節點的位置
                    leftIndex = index * 2 + 1; // 找到上移節點的左子節點的位置
                } else { // 最小子節點不比父節點小,說明父子路徑已經按從小到大排好順序了,不須要調整了
                    break;
                }
            }

            // index此時記錄是的最後一個被上移的子節點的位置(也多是自身),
            // 因此將最開始的調整的元素值放入index位置便可
            items.set(index, intent);
        }

        /**
         * 向堆中添加一個元素
         *
         * @param item 等待添加的元素
         */
        public void add(T item) {
            items.add(item); // 將元素添加到最後
            siftUp(items.size() - 1); // 循環上移,以完成重構
        }

        /**
         * 刪除堆頂元素
         *
         * @return 堆頂部的元素
         */
        public T deleteTop() {
            if (items.isEmpty()) { // 若是堆已經爲空,就報出異常
                throw new RuntimeException("The heap is empty.");
            }

            T maxItem = items.get(0); // 獲取堆頂元素
            T lastItem = items.remove(items.size() - 1); // 刪除最後一個元素
            if (items.isEmpty()) { // 刪除元素後,若是堆爲空的狀況,說明刪除的元素也是堆頂元素
                return lastItem;
            }

            items.set(0, lastItem); // 將刪除的元素放入堆頂
            siftDown(0); // 自上向下調整堆
            return maxItem; // 返回堆頂元素
        }

        /**
         * 判斷堆是否爲空
         *
         * @return true是空,false否
         */
        public boolean isEmpty() {
            return items.isEmpty();
        }
    }

}
相關文章
相關標籤/搜索