1、Queue队列
先进先出

2、双端队列 --- Deque
Deque的实现类是LinkedList,ArrayDeque,LinkedBlockingDeque。
ArrayDeque底层实现是数组,LinkedList底层实现是链表。

双端队列可以作为普通队列使用,也可以作为栈使用。Java官方推荐使用Deque替代Stack使用
103. 二叉树的锯齿形层序遍历
给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
解题思路:
这个题是一个变形的二叉树层序遍历,可以用BFS求解,值得注意的是,每隔一层,输出的值顺序是相反的。
- 本来想的是利用双端队列直接排列节点的顺序,奇数层和偶数层的时候,分别从头插入和从尾插入,但是调试了几次用例没通过,元素的添加和取出比较绕
 - 其实完全可以按照正常的BFS模板去写,奇数层和偶数层的时候,利用一个双端队列作为一个中转,分别从头插入和从尾插入当前节点,然后在转为list链表。
 
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        ArrayDeque<TreeNode> queue = new ArrayDeque<>();
        queue.offerFirst(root);
        int level = 1;
        while (!queue.isEmpty()) {
            // 这里定义一个双端队列
            ArrayDeque<Integer> list = new ArrayDeque<>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode treeNode = queue.poll();
                // 这里把当前值存入双端队列,根据层数不同,选择插入尾部或者头部
                if (level % 2 != 0) {
                    list.offerLast(treeNode.val);
                } else {
                    list.offerFirst(treeNode.val);
                }
                if (treeNode.left != null) {
                    queue.offerLast(treeNode.left);
                }
                if (treeNode.right != null) {
                    queue.offerLast(treeNode.right);
                }
            }
            // 这个把临时创建的双端队列存入结果数据中
            res.add(new ArrayList<>(list));
            level++;
        }
        return res;
    }
} 
 
3、优先级队列 -- PriorityQueue
队列是先进先出,优先级队列就是在队列的基础上,增加了一个优先级的概念。
1.使用无序数组实现
offer:直接插入在数组最后面
poll:定义一个max指针,先指向最后面,然后不断往前遍历,如果前面的优先级大,那么就更新max的值,找到最大的max,删除该位置元素,然后后面数组向前移动
peek:先找到max指针,然后返回该位置的值
2、使用有序数组实现
offer:把数据插入到数组最后面,与前一个进行比较,如果比他小,则交换位置
poll : 移除最后一个元素
peek: 返回最后一个元素
3、使用堆实现
堆:是一种基于树的数据结构,通常用完全二叉树实现,有如下特性:
- 大顶堆:任一节点与C与他的父节点P,有P.value ≥ C.value
 - 小顶堆:任一节点与C与他的父节点P,有P.value ≤ C.value
 
如图就是一个大顶堆,粉色为索引,蓝色为优先级数据。
从索引0开始存储节点数据有如下规律:
- 节点i的父节点索引为 floor((i-1)/2) ,i>0
 - 节点i的左子节点为 2i+1,右子节点为 2i+2,当然索引都要小于堆容量size
 
    // 插入元素:
    // 1、假设插入到最后的位置,
    // 2、把要插入的元素优先级和父节点比较,如果比他大,交换位置,一直保持大顶堆
    // 3、直到要插入的元素比父节点小了,或者是根节点了,就把当前元素插到这个位置。
    public boolean offer(Priority offered) {
        if (isFull()) {
            return false;
        }
        int child = size;
        size++;
        int parent = (child - 1) / 2;
        while (child > 0 && offered.priority > array[parent].priority) {
            array[child] = array[parent];
            child = parent;
            parent = (child - 1) / 2;
        }
        array[child] = offered;
        return true;
    } 
    // 移除元素
    // 1、其实移除的就是根节点元素,但是要保持大顶堆,就需要进行额外操作
    // 2、把最后一个元素替换到根节点的位置,然后和左右子节点比较,把大的节点提上来
    // 3、不选循环提节点的操作,直到节点的位置比子节点都大
    public Priority poll() {
        if (isEmpty()) {
            return null;
        }
        swap(0, size - 1);
        size--;
        Priority removed = array[size];
        down(0);// 根节点下沉
        return removed;
    }
    // 将元素下沉,构建大顶堆
    private void down(int parent) {
        int left = 2 * parent + 1;
        int right = left + 1;
        int max = parent;
        if (left < size && array[left].priority > array[max].priority) {
            max = left;
        }
        if (right < size && array[right].priority > array[max].priority) {
            max = right;
        }
        if (max != parent) {
            swap(parent, max);
            down(max);
        }
    }
    // 交换节点位置
    private void swap(int x, int y) {
        Priority temp = array[x];
        array[x] = temp;
        array[y] = temp;
    } 
    // 堆顶元素
    public Priority peek() {
        if (isEmpty()) {
            return null;
        }
        return array[0];
    } 
 
23、合并 K 个升序链表
解题:
之前学习链表的时候,我们知道了怎么合并两个有序链表,那么合并多个有序链表,就循环遍历,两两合并就可以了。
现在,学习了优先级链表,也可以用该数据结构进行解题。
采用小顶堆,Java代码中可以直接使用 PriorityQueue,将给出的多个链表中的头节点放到优先级队列中,然后取出最小的一个节点 (如果这个节点在原来链表中有next节点,那么就把next节点在加入优先级队列中。不断循环,就可以得到排序的链表。)
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode newList = new ListNode();
        ListNode head = newList;
        PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val - o2.val;
            }
        });
        for (ListNode l : lists) {
            if (l != null) {
                priorityQueue.add(l);
            }
        }
        while (!priorityQueue.isEmpty()) {
            ListNode curr = priorityQueue.poll();
            head.next = curr;
            head = head.next;
            if (curr.next != null) {
                priorityQueue.add(curr.next);
            }
        }
        return newList.next;
    }
}
也可以吧所有的节点都加入到优先级链表中,然后在一个个取出来,但是会占用很大的堆空间。 
 
4、阻塞队列
适用于生产者消费者模式,消费的时候,要保证已经有东西生产出来了,生产的时候,要保证队列没有满。
阻塞队列,单锁实现。
public class MyBlockingQueue {
    private final int[] array;
    private int head;
    private int tail;
    private int size;
    // 可重入锁
    private ReentrantLock lock = new ReentrantLock();
    private Condition headWaits = lock.newCondition();
    private Condition tailWaits = lock.newCondition();
    public MyBlockingQueue(int capacity) {
        this.head = 0;
        this.tail = 0;
        this.array = new int[capacity];
    }
    public void offer(int value) throws InterruptedException {
        // 加锁
        lock.lockInterruptibly();
        try {
            while (isFull()) { //while循环,防止虚假唤醒
                // 满了就等待
                tailWaits.await();
            }
            // 添加元素
            array[tail] = value;
            if (++tail == array.length) {
                tail = 0;
            }
            size++;
            // 唤醒poll方法
            headWaits.signal();
        } finally {
            // 解锁
            lock.unlock();
        }
    }
    // 添加一个等待时间
    public boolean offer(int value, Long timeout) throws InterruptedException {
        // 加锁
        lock.lockInterruptibly();
        long nanos = TimeUnit.MICROSECONDS.toNanos(timeout);
        try {
            while (isFull()) { //while循环,防止虚假唤醒
                // 满了就等待
                if (nanos > 0) {
                    // 假设要求等待5s,返回值就是等待后还剩余的时间。
                    // 假设等待了1s后,被唤醒,但是又被其他线程抢了锁,这里就要重新等待,但是不能等待5s,而是4s
                    nanos = tailWaits.awaitNanos(nanos);
                }
                return false;
            }
            // 添加元素
            array[tail] = value;
            if (++tail == array.length) {
                tail = 0;
            }
            size++;
            // 唤醒poll方法
            headWaits.signal();
            return true;
        } finally {
            // 解锁
            lock.unlock();
        }
    }
    public int poll() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (isEmpty()) { //while循环,防止虚假唤醒
                // 满了就等待
                headWaits.await();
            }
            int res = array[head];
            array[head] = 0;
            if (++head == array.length) {
                head = 0;
            }
            size--;
            // 唤醒offer方法
            tailWaits.signal();
            return res;
        } finally {
            lock.unlock();
        }
    }
    public int peek() {
        return array[head];
    }
    public boolean isEmpty() {
        return size == 0;
    }
    public boolean isFull() {
        return size == array.length;
    }
} 
 
上述代码,offer和poll操作用的是同一把锁,因此这两个操作是相互干扰的,即一个执行的时候,另外一个会阻塞,这样是不合理的。
因此,可以使用两把锁,分别控制offer和poll操作,但是这个会引入一个问题,就是size++/size-- 的问题。因为++/--操作不是原子性的,那么,在多线程的情况下就是不安全的(为什么一把锁的时候是线程安全呢?因为一把锁的时候,offer拿锁,poll会阻塞,因此在size++的过程中,就不可能出现size++的操作,故是线程安全的,而两把锁的时候,offer和poll互不影响,因此就可能在size++的时候出现size-- 导致线程不安全。)
两把锁代码如下,每个锁控制着自己对应的Condition,因此唤醒操作只能在锁存在的时候才可以唤醒。为了避免死锁,需要保证一把锁解开后,才可以去给另一把锁上锁。
    private ReentrantLock tailLock = new ReentrantLock();
    private Condition tailWaits = tailLock.newCondition();
    private ReentrantLock headLock = new ReentrantLock();
    private Condition headWaits = headLock.newCondition();
    private AtomicInteger size; // 原子性
    public void offer(int value) throws InterruptedException {
        // 加锁
        tailLock.lockInterruptibly();
        try {
            while (isFull()) { //while循环,防止虚假唤醒
                // 满了就等待
                tailWaits.await();
            }
            // 添加元素
            array[tail] = value;
            if (++tail == array.length) {
                tail = 0;
            }
            size.getAndIncrement();
        } finally {
            // 解锁
            tailLock.unlock();
        }
        
        headLock.lockInterruptibly();
        try {
            // 唤醒poll方法,只能在tailLock锁解锁之后唤醒,避免死锁
            headWaits.signal(); 
        } finally {
            headLock.unlock();
        }
    }
    public int poll() throws InterruptedException {
        headLock.lockInterruptibly();
        int res;
        try {
            while (isEmpty()) { //while循环,防止虚假唤醒
                // 满了就等待
                headWaits.await();
            }
            res = array[head];
            array[head] = 0;
            if (++head == array.length) {
                head = 0;
            }
            size.getAndDecrement();
        } finally {
            headLock.unlock();
        }
        
        tailLock.lockInterruptibly();
        try {
            // 唤醒offer方法,只能在headLock锁解锁之后唤醒,避免死锁
            tailWaits.signal();
        } finally {
            tailLock.unlock();
        }
        return res;
    } 
上述代码实现了两把锁分别控制offer和poll,但是还存在一个问题,就是每次offer的时候,为了唤醒poll,还是给headlock加了锁;每次poll的时候为了唤醒offer,还是给taillock加了锁,为了提升效率,可以做如下优化:
思想:级联思想
- offer的时候:只有队列中元素从0到1,才触发唤醒poll操作,其余的poll线程唤醒交给poll方法自身
 - poll的时候:只有队列中元素从满到不满才触发唤醒offer操作,如遇offer线程唤醒交给offer自身。
 优化代码如下:
public void offer(int value) throws InterruptedException { // 加锁 tailLock.lockInterruptibly(); int c; // 记录队列增加前的元素个数 try { while (isFull()) { //while循环,防止虚假唤醒 // 满了就等待 tailWaits.await(); } // 添加元素 array[tail] = value; if (++tail == array.length) { tail = 0; } c = size.getAndIncrement(); // 队列中元素不满,唤醒其他offer线程 if (c < array.length - 1) { tailWaits.signal(); } } finally { // 解锁 tailLock.unlock(); } // 队列中元素从0到1,才触发唤醒poll操作 if (c == 0) { headLock.lockInterruptibly(); try { // 唤醒poll方法,只能在tailLock锁解锁之后唤醒,避免死锁 headWaits.signal(); } finally { headLock.unlock(); } } } public int poll() throws InterruptedException { headLock.lockInterruptibly(); int res; int c; // 记录队列减少前的元素个数 try { while (isEmpty()) { //while循环,防止虚假唤醒 // 满了就等待 headWaits.await(); } res = array[head]; array[head] = 0; if (++head == array.length) { head = 0; } c = size.getAndDecrement(); // 队列中有元素,唤醒其他poll线程 if (c > 1) { headWaits.signal(); } } finally { headLock.unlock(); } // 队列从满到不满的时候,加锁唤醒offer线程 if (c == array.length) { tailLock.lockInterruptibly(); try { // 唤醒offer方法,只能在headLock锁解锁之后唤醒,避免死锁 tailWaits.signal(); } finally { tailLock.unlock(); } } return res; }




















