PriorityQueue

上次写完ArrayList,直接把linkedList计划取消了。过后想了想,这是我最大的弱点了,还是得写。然后找了PriorityQueue,看了源码,继续写吧。

1.继承结构

先看一张PriorityQueue,继承结构的图吧。

相比较ArrayList,PriorityQueue并没有实现Cloneable,暂时不是很明白,需要继续学习。

Collection主要描述的特征和能力,AbstractCollection和AbstractQueue也不多介绍了。Queue,一般都是想到先进先出,LinkedList根据这个特性实现的,但是是根据某种特性,先出队的。看一下Queue的主要方法,相比较的话主要是多了offer,

pull,peek方法,顾名思义,就是从后面推进去,从前面拉出来,还有偷看第一个元素。Queue在结构上应该是变简单的了的,

毕竟LinkedList在实现List的基础上就实现了Deque,Deque是Queue的加强版。

 

属性

看源码的时候,首先应该看的是他的属性,属性一般是它的具体实现的数据结构,PriorityQueue的属性有queue,size,modeCount,comparator。

首先来看queue,是一个Object[],说明PriorityQueue是由数组实现的,具体怎么实现的下面再说。

size,是它的大小,应该数组的大小并不代表容器类里面元素的数量,size还作为边界。

comparator,用于确定元素比较的顺序,规则必不可少,不然无法确定优先级。

modeCount,PriorityQueue并不是线程安全的,作为一个标签,记录是否发生了异常。

通过这些属性,可以明白PriorityQueue主要是通过comparator将数据按照一定规则储存在数组里面,size标记元素在数组

中的数量。

构造方法

构造方法决定了PriorityQueue在初始化时的状态,PriorityQueue构造方法特别多,但是多重载。

属性构造方法:

public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

 

可以看到,主要是初始化了queue和comparator,size属性使用的是默认值0。所以构造方法主要作用是给属性赋值。

capacity的作用是用于数组的扩容。为什么要扩容,当然是为了合理利用空间。

Colletion构造方法:

public PriorityQueue(Collection<? extends E> c) {
        if (c instanceof SortedSet<?>) {
            SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
            this.comparator = (Comparator<? super E>) ss.comparator();
            initElementsFromCollection(ss);
        }
        else if (c instanceof PriorityQueue<?>) {
            PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
            this.comparator = (Comparator<? super E>) pq.comparator();
            initFromPriorityQueue(pq);
        }
        else {
            this.comparator = null;
            initFromCollection(c);
        }
    }

 

这个构造方法可以说明问题。

给comparator赋值,重新排列数据。

这里主要有两个方法,initElementFromCollection和initFromCollection。

private void initElementsFromCollection(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if (a.getClass() != Object[].class)
            a = Arrays.copyOf(a, a.length, Object[].class);
        int len = a.length;
        if (len == 1 || this.comparator != null)
            for (int i = 0; i < len; i++)
                if (a[i] == null)
                    throw new NullPointerException();
        this.queue = a;
        this.size = a.length;
    }
    private void initFromCollection(Collection<? extends E> c) {
        initElementsFromCollection(c);
        heapify();
    }

initElementFromCollection相当于是数据拷贝。initFromCollection是先进行数据拷贝,然后堆化。

实现原理

了解完特征之后,再来了解怎么实现的。PriorityQueue有什么特点,每次poll的都是我优先级最高的元素,比方说,每次取都取

到最小的元素。数组的结构就是第一个元素都是最小元素,如果一棵树上没条路劲都是递增的,那么root节点就是最小元素了,而二

叉堆就是满以二叉树的数组形式进行保存。

先了解下二叉堆的特点,p代码父节点,lf代表左子节点,rt代表右子节点。

p = (lf -1) >> 1; lf = p << 1 + 1; rt = p << 1 + 2;

主要方法

看完属性,构造器,接下来应该看的add,remove方法,这里是分别对应offer,poll方法,还有heapify方法。

offer方法:

public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }

首先看offer方法,做了哪些事,判断不能为null,结构改变+1,如果容量不够就扩容,size+1,如果容量大于1,调用siftUp方法进行插入元素。

siftUp方法:

private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x);
    }

    @SuppressWarnings("unchecked")
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

   这里区分了有没有传入comparator,其实只是就是需要一个规则,没有传入就调用元素的的Comparable接口进行排序。

具体实现就是假设把元素放到最后一个位置,然后再找到一个位置的路劲,对比路径上的元素,使这条路径按规则排序。

poll方法:

public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            siftDown(0, x);
        return result;
    }
private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

    @SuppressWarnings("unchecked")
    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

   poll方法稍微复杂一点点。

具体实现就是,删掉第一个元素。

假设把最后一个元素放到第一个位置。这里假设以大小排序,父节点总是小于子节点,如果父节点大于子节点中一个或一个以上的元素的时候,和较小的元素替换位置,以此类推,保证结构继续保持有序。

 

heapify方法:

private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

 

要明白这个方法只需要搞明白一点,满二叉树,只要有一般是叶子节点,就是说,剩下的有子节点,要保持结构有序,只要从下网上逐层调用siftDown方法即可,想不到更好的办法,脑子一片空白,只能接受,读书太少,就别想太多,想了也没用。

还有一个方法是removeAt,稍微也可以注意下。

 

了解完这些方法剩下的方法,应该都很简单了。其实主要要了解的是二叉树,满二叉树,二叉堆。

迭代器

属性:

cursor:当前迭代器指向堆的对象的数组的坐标; 
lastRet:表示迭代器刚刚越过的元素; 
expectedModCount:修改次数; 
forgetMeNot:表示迭代过程中遗漏的元素; 
lastRetElt:下一个遗漏的元素。

如果不存在forgetMeNot,lastRetElt应该是很容易理解的。那怎么理解这两个属性呢,删除元素的时候,调用的是removeAt

方法,再用最后一个元素替换的时候,如果最后一个元素往上移动了,将不会再遍历到,则放入forgetMeNot中进行存储。正常遍

历完了之后再进行遍历。


 


 

 

 

   

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值