# 优先级队列 - 堆实现 - 小顶堆实现

如下有三个链表 分别 使用指针 p 指向 头部

p
1 -> 4 -> 5 -> null
p
1 -> 3 -> 4 -> null
p
2 -> 6 -> null

我们分别把 各个链表 的头部 元素 放到 小顶堆中

p
1 -> 4 -> 5 -> null
p
1 -> 3 -> 4 -> null
p
2 -> 6 -> null
# 先将 三个链表的头部元素 放到 小顶堆中,由于 1 和 1 相同所以不用 交换位置
小顶堆  1 1 2
空链表
# 移除 小顶堆的 堆顶元素 (最小的元素) 放到 链表中
小顶堆  1 2
空链表  1

将第一个链表的 p 指针向后移动一步,将当前指向 的 元素放到 小顶堆中

     p
1 -> 4 -> 5 -> null
p
1 -> 3 -> 4 -> null
p
2 -> 6 -> null
# 将 小顶堆的 堆顶元素 (最小的元素) 放到 链表中
小顶堆  2 4
空链表  1 -> 1

将第二个链表的 p 指针向后移动一步,将当前指向的 元素放到 小顶堆中

     p
1 -> 4 -> 5 -> null
     p
1 -> 3 -> 4 -> null
p
2 -> 6 -> null
# 将 小顶堆的 堆顶元素 (最小的元素) 放到 链表中
小顶堆  4  3
空链表  1 -> 1 -> 2

将第三个链表的 p 指针向后移动一步,将当前指向的 元素放到 小顶堆中

     p
1 -> 4 -> 5 -> null
     p
1 -> 3 -> 4 -> null
     p
2 -> 6 -> null
# 将 小顶堆的 堆顶元素 (最小的元素) 放到 链表中
小顶堆  4  6
空链表  1 -> 1 -> 2 -> 3

小顶堆中 3 被移除了 那么就将 第二个链表的 p 指针向后移动一步,将当前指向的 元素 放到小顶堆中

     p
1 -> 4 -> 5 -> null
          p
1 -> 3 -> 4 -> null
     p
2 -> 6 -> null
# 将 小顶堆的 堆顶元素 (最小的元素) 放到 链表中 被移除的 4 是 从第一个链表中 取出的,因为这次也是4 并不交换位置
小顶堆  6  4
空链表  1 -> 1 -> 2 -> 3 -> 4

小顶堆中第一个链表的 4 被移除了 那么就将 第一个链表的 p 指针向后移动一步,将当前指向的 元素 放到小顶堆中

          p
1 -> 4 -> 5 -> null
          p
1 -> 3 -> 4 -> null
     p
2 -> 6 -> null
# 将 小顶堆的 堆顶元素 (最小的元素) 放到 链表中
小顶堆  6  5
空链表  1 -> 1 -> 2 -> 3 -> 4 -> 4

现在 三个链表的 p 指针 都指向了 null 那么就将小顶堆中 剩余的 按照 小的开始放到链表中

# 将 小顶堆的 堆顶元素 (最小的元素) 放到 链表中
小顶堆 
空链表  1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6

# 演示图:

recording

会了大顶堆 在看 小顶堆 代码就会很轻松

# 代码:

MinHeap

// 小顶堆
public class MinHeap
{
    ListNode[] array;
    int size;
    public MinHeap(int capacity)
    {
        array = new ListNode[capacity];
    }
    public boolean offer(ListNode e)
    {
        // 判断堆中是否 满了
        if(isFull())
            return false;
        // 获取 最后一个元素
        int child = size++;
        // 通过公式 得出 当前元素的 父节点
        int parent = (child - 1) / 2;
        // 如果 不是 root 节点 和 当前的元素值 小于 父节点的元素值 就满足条件
        while(child > 0 && e.val < array[parent].val)
        {
            // 将 父节点的 元素值 赋值给 当前元素
            array[child] = array[parent];
            // 将 父节点的索引位置 赋值到 当前索引
            child = parent;
            // 再次计算 当前 节点的 父节点的索引位置
            parent = (child - 1) / 2;
        }
        // 将 当前的 元素赋值 到 当前节点中
        array[child] = e;
        return true;
    }
    public ListNode poll()
    {
        // 判断 堆中是否为空
        if(isEmpty())
            return null;
        // 将 root 节点 与 最后一个节点 交换位置 ,方便移除 提高效率
        swap(0, size - 1);
        // 大小 减一
        size--;
        // 拿到 最后 的值
        ListNode e = array[size];
        // 将最后的 值赋值为 null 交给 GC 垃圾回收处理
        array[size] = null;
        // 将 当前的 root 节点 向下比较 找到自己合适的位置
        down(0);
        return e;
    }
    private void swap(int i, int j)
    {
        // 经典的位置交换
        ListNode t = array[i];
        array[i] = array[j];
        array[j] = t;
    }
    private void down(int parent)
    {
        // 通过公式 获取 当前节点的 左孩子
        int left = 2 * parent + 1;
        // 通过公式 获取 当前节点的 右孩子
        int right = left + 1;
        // 将当前 节点 假定为 最小的 元素 赋值给 min
        int min = parent;
        // 判断 如果 left 没有超出 索引范围 并且 left 所在的 值 比 min 所在的值 还要小
        if(left < size && array[left].val < array[min].val)
            // 就将 left 索引赋值给 min
            min = left;
        // 与上面判断同理
        if(right < size && array[right].val < array[min].val)
            min = right;
        // 判断如果 min 不等于 parent 说明上面是有符合交换条件的 就执行 if 方法体
        if(min != parent)
        {
            // 交换位置
            swap(min, parent);
            // 将当前的 min 进行递归调用 重复这个 工作 直到 该 值 找到自己合适的位置即可
            down(min);
        }
    }
    public boolean isEmpty()
    {
        return size == 0;
    }
    public boolean isFull()
    {
        return size == array.length;
    }
}

ListNode

/**
 * Leetcode 很多链表题目用到的节点类
 */
public class ListNode {
    public int val;
    public ListNode next;
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(64);
        sb.append("[");
        ListNode p = this;
        while (p != null) {
            sb.append(p.val);
            if (p.next != null) {
                sb.append(",");
            }
            p = p.next;
        }
        sb.append("]");
        return sb.toString();
//        return String.valueOf(this.val);
    }
    public static ListNode of(int... elements) {
        if (elements.length == 0) {
            return null;
        }
        ListNode p = null;
        for (int i = elements.length - 1; i >= 0; i--) {
            p = new ListNode(elements[i], p);
        }
        return p;
    }
}

测试代码:

public class DemoTest
{
    // 合并多个有序链表
    private ListNode merge(ListNode[] list)
    {
        MinHeap heap = new MinHeap(list.length);
        // 1. 将链表的头节点 添加到 小顶堆中
        for (ListNode h : list)
        {
            if(h != null)
            {
                heap.offer(h);
            }
        }
        // 2. 不断从堆顶移除最小元素,加入新链表
        ListNode s = new ListNode(-1, null);
        // 定义辅助变量
        ListNode t = s;
        // 判断如果 heap 对象不为空
        while(! heap.isEmpty())
        {
            // 从 heap 堆中 弹出 顶部 元素
            ListNode min = heap.poll();
            // 将 min 节点 添加到 t 链表中
            t.next = min;
            // 更新 t 节点
            t = min;
            if(min.next != null)
                // 将 上面 poll 处的 min 的 next 节点 在 添加到 heap 对象中这样就起到了 向后指向的 作用
                heap.offer(min.next);
        }
        //s.next 返回 除了 哨兵节点外的 其它元素
        return s.next;
    }
    public static void main(String[] args)
    {
        ListNode[] list = {
                ListNode.of(1, 4, 5),
                ListNode.of(1, 3, 5),
                ListNode.of(2, 6),
                null,
        };
        System.out.println(new DemoTest().merge(list));
    }
}

运行结果:

[1,1,2,3,4,5,5,6]