【算法】合并k个有序的链表-基于最小堆的思想

来源:互联网 发布:w10怎么安装软件 编辑:程序博客网 时间:2024/05/21 19:49

题目大意

合并k个已经排序好了的链表。


大致思路

可以借助堆排序的思想,使用小顶堆来实现。
先将K个链表的头元素构建成一个最小堆,之后,取堆顶元素,这个结点就是最小的,放入保存结果的集合,接着将原堆顶元素所在链表的下一元素入堆并重新构建最小堆,之后,再取堆顶元素,这个结点就是第二小的……如此往复操作,知道将所有链表的所有元素都取完。
但是,在将原堆顶元素对应的链表中的下一元素入堆时,会遇到一种情况——不存在下一个元素,即原堆顶元素所在链表都取完值了,此时则将最小堆的末尾元素换置换置堆顶,此时真是的最小堆大小将减一。


具体实现如下:

public class MergeKList {    static class Node implements Comparable<Node> {        int val;//元素的值        int indexForLists;//该元素所在的链表处于K个链表的位置        int indexInList;//该元素处在对应链表的位置        public Node(int val, int indexForLists, int indexInList) {            this.val = val;            this.indexForLists = indexForLists;            this.indexInList = indexInList;        }        @Override        public int compareTo(Node o) {            if (val == o.val) return 0;            else if (val > o.val) return 1;            else return -1;        }    }    public static List<Integer> mergeKList(ArrayList<ArrayList<Integer>> lists) {        if (lists == null || lists.size() == 0) return null;        List<Integer> list = new LinkedList<>();        //首先将k个链表的首个元素取出构成最小堆        Node[] minHeap = new Node[lists.size()];        int trueLen = 0;        Iterator<ArrayList<Integer>> iterator = lists.iterator();        while (iterator.hasNext()) {            ArrayList<Integer> arrayList = iterator.next();            if (arrayList != null && arrayList.size() > 0) {                minHeap[trueLen] = new Node(arrayList.get(0), trueLen, 0);                trueLen++;            } else {                //去除无效的链表                iterator.remove();            }        }        for (int i = trueLen / 2 - 1; i >= 0; i--) {            buildMinHeap(minHeap, i, trueLen);        }        while (true) {            //取出最小堆堆堆顶元素            Node heapTop = minHeap[0];            list.add(heapTop.val);            //取原堆顶元素所在链表的下一元素            ArrayList<Integer> arrayList = lists.get(heapTop.indexForLists);            if (heapTop.indexInList < arrayList.size() - 1) {//如果还有下一个元素,则把它对应的Node节点放到堆定                int indexInList = heapTop.indexInList + 1;                minHeap[0] = new Node(arrayList.get(indexInList), heapTop.indexForLists, indexInList);                //重新构建最小堆                buildMinHeap(minHeap, 0, trueLen);            } else {//如果不存在下一个元素,即原堆顶元素所在链表都取完值了,则将最小堆的末尾元素换置换置堆顶,trueLen--                minHeap[0] = minHeap[trueLen - 1];                minHeap[trueLen - 1] = null;                trueLen--;                //如果最小堆真实的大小只剩为1,则表示之剩下一个链表的剩下元素没有合并了,且直接储存结果的集合末尾添加即可                if (trueLen==1) {                    heapTop = minHeap[0];                    list.add(heapTop.val);                    ArrayList<Integer> lastArrayList = lists.get(heapTop.indexForLists);                    for (int i = heapTop.indexInList+1; i < lastArrayList.size(); i++) {                        list.add(lastArrayList.get(i));                    }                    break;                } else {                    //重新构建最小堆                    buildMinHeap(minHeap, 0, trueLen);                }            }        }        return list;    }    public static void buildMinHeap(Node[] nodes, int i, int heapSize) {        int min, left, right;        while (true) {            min = i;            left = 2 * i + 1;            right = left + 1;            if (left < heapSize && nodes[left].compareTo(nodes[min]) < 0) {                min = left;            }            if (right < heapSize && nodes[right].compareTo(nodes[min]) < 0) {                min = right;            }            if (min != i) {                Node temp = nodes[min];                nodes[min] = nodes[i];                nodes[i] = temp;                i = min;            } else {                break;            }        }    }}

假设总共有k个list,每个list的最大长度是n
维护一个大小为k的堆,每次取堆顶的最小元素放到结果中,然后读取该元素的下一个元素放入堆中,重新维护好。因为每个链表是有序的,每次又是去当前k个元素中最小的,所以当所有链表都读完时结束,这个时候所有元素按从小到大放在结果链表中。这个算法每个元素要读取一次,即是k*n次,然后每次读取元素要把新元素插入堆中要log k的复杂度,所以总时间复杂度是O(nk logk)。空间复杂度是堆的大小,即为O(k)

原创粉丝点击