创建一个堆
/**
* 创建一个堆
*
*/
public class TestHeap {
public int[] elem;
public int usedSize;
public static final int DEFAULT_SIZE = 10;
public TestHeap() {
this.elem = new int[DEFAULT_SIZE];
}
public void initElem(int[] array) {
for (int i = 0; i < array.length; i++) {
elem[i] = array[i];
usedSize++;
}
}
public void createHeap() {
/**
* 找到所有的根节点,从下往上,对根节点的小树进行调整
*/
for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
//找到所有的根节点
//顺序是 自下而上
shiftDown(parent,usedSize);
//所有的根节点进行的调整
//传参usedSize的原因 结束的条件
//当一个树调整完成后可能会影响后面的树,要对后面的树再进行判断与调整,
//找个条件让他停下来,就是usedSize,当child >= usedSize时就停下来
}
}
private void shiftDown(int parent, int len) {
int child = 2*parent+1;
//堆中,根节点一定有左孩子,因为1:它是根 2:它是完全二叉树
while(child < len) {
/*
child指向的一定是最大的------>这个操作实现了复用if(elem[child] > elem[parent])这一部分
右孩子比较特殊所以先判断右孩子, 如果有右孩子 && 右孩子比作孩子大 那 child++
如果不满足以上条件,说明没有右孩子或者是右孩子比左孩子小 那child不动
底下再判断一下 如果child > parent 那就交换 如果不是那就退出循环
*/
if(child+1 < len && elem[child] < elem[child+1]) {
// 如果有右孩子并且右孩子比左孩子大
child++;
}
// 此时Child 指向左右孩子种最大的
if(elem[child] > elem[parent]) {
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
parent = child;
child = parent*2+1;
}else {
break;
}
}
}
/**
* 自写
* @param parent 根节点
* @param usedSize 判断的范围
*/
private void shiftDown2(int parent, int usedSize) {
int child = parent*2+1;
while(child < usedSize) {
if(child+1 < usedSize) { //有右孩子
child = elem[child] > elem[child+1]? child:child+1;
if(elem[child] > elem[parent]) {
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
parent = child;
child = parent*2+1;
}else {
break;
}
}else { //没有
if(elem[child] > elem[parent]) {
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
break;
}else{
break;
}
}
}
}
}
建堆的时间复杂度分析
在堆中插入元素
-
插入前此时的数组已经满足了 大根堆或者是小根堆 。
- -
旁边的节点不用管,因为它一定比它所对应的根要小(小根堆)或大(大根堆)。
/**
* 在堆中插入元素
*/
public void offor (int val) {
if(isFull()) {
// 扩容
elem = Arrays.copyOf(elem, elem.length*2);
}
elem[usedSize] = val;
usedSize++;
int child = usedSize-1;
// 向上调整
shiftUp(child);
}
// 向上调整
public void shiftUp(int child) {
int parent = (child-1)/2;
while(child > 0) {
if(elem[child] > elem[parent]) {
// 交换
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
child = parent;
parent = (child-1)/2;
}else {
break;
}
}
}
public boolean isFull() {
return this.usedSize == elem.length;
}
在堆中删除(弹出)元素
删除前此时的数组已经满足了 大根堆或者是小根堆 。
/**
* 在堆中删除(弹出)元素
*/
public void pop() {
if(isEmpty()) {
return;
}
// 交换
int temp = elem[usedSize-1];
elem[usedSize-1] = elem[0];
elem[0] = temp;
usedSize--;
//数组中我们只操作下标从[0 , usedSize)这部分的数据
shiftDown3(0, usedSize);
}
//向下调整
public void shiftDown3(int parent, int len) {
int child = parent*2+1;
while(child < len) {
/*
child指向的一定是最大的------>这个操作实现了复用if(elem[child] > elem[parent])这一部分
右孩子比较特殊所以先判断右孩子, 如果有右孩子 && 右孩子比作孩子大 那 child++
如果不满足以上条件,说明没有右孩子或者是右孩子比左孩子小 那child不动
底下再判断一下 如果child > parent 那就交换 如果不是那就退出循环
*/
if(child+1 < usedSize && elem[child+1] > elem[child]){
child = child+1;
}
if(elem[child] > elem[parent]) {
// 交换
int temp = elem[child];
elem[child] = elem[parent];
elem[parent] = temp;
parent = child;
child = parent*2+1;
}else {
break;
}
}
}
public boolean isEmpty() {
return usedSize == 0;
}
PriorityQueue的特性
- Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线
程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue - PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出
ClassCastException异常 - 不能插入null对象,否则会抛出NullPointerException
- 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
- 插入和删除元素的时间复杂度为log2 n (最坏情况下向下调整,向上调整 树的高度-1次)
- PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素
/**
* 学生类
*/
class student implements Comparable<student> {
public String name;
public int age;
public student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(student o) {
return this.age - o.age; //顺序十分重要,因为底层的原码的判断条件是卡死的
}
@Override
public String toString() {
return "student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class demo {
public static void main(String[] args) {
TestPriorityQueue();
}
public static void TestPriorityQueue() {
PriorityQueue<student> q1 = new PriorityQueue<>();
q1.offer(new student("小白",10));
q1.offer(new student("小红",20));
q1.offer(new student("小黑",30));
System.out.println(q1.peek());
//小根堆
}
}
class Comp implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
}
public class demo {
public static void main(String[] args) {
TestPriorityQueue();
}
public static void TestPriorityQueue() {
PriorityQueue<Integer> q2 = new PriorityQueue<>(new Comp());
q2.offer(4);
q2.offer(3);
q2.offer(2);
q2.offer(1);
System.out.println(q2.peek());
}
}
总结:
- 当没有传数组容量的时候默认是11
- 当没有传比较器的时候,这个对象必须是可比较的,即必须继承comparable< > 方法,并且重写compareTo方法;注意基本数据类型内已经继承comparable< > 方法,并且重写compareTo方法,引用类型需要自己写,而且注意重写compareTo方法的顺序非常重要因为底层条件是卡死的。所以写的顺序决定了到底换不换。
- 当传入比较器的时候,会优先使用比较器中的compare方法,同样重写的比较器中的compare方法的顺序非常重要。
- 最后注意:基本数据类型默认是小根堆,因为进本数据类型里面继承comparable< > 方法,并且重写compareTo方法,而且compareTo方法的顺序没法给他改变。如果你想将基本数据类型改为是大根堆,你要利用优先使用比较器这特点,传入比较器,然后修改比较器中的compare方法的顺序。
以下三者是等价的
class Comp implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
}
PriorityQueue<Integer> q2 = new PriorityQueue<>(new Comp());
PriorityQueue<Integer> q3 = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
//匿名内部类,这个类实现了comparator这个接口,并重写了他的方法。
});
PriorityQueue<Integer> priorityQueue2 = new PriorityQueue<>((x,y)->{return x.compareTo(y);});
PriorityQueue<Integer> priorityQueue3 = new PriorityQueue<>((x,y)-> x.compareTo(y));
//lambda表达式