一、问题引入
一堆数据的值在不断变化,我们想要从这堆数据中获取到最大值或者最小值(比如电影排行榜中电影的排名,每部电影观看人数越多,那么该部电影的排名就越高)
1、数据结构解决的是数据的存储问题,算法解决的是存储了的数据的使用效率问题
2、数据结构里面的堆结构(heap)和Java语言中的数据堆不一样
3、数据结构里面的堆结构本质上是数组
4、数组的元素查询效率高(由于使用索引查询),时间复杂度为O(1),元素删除效率低(由于删除一个元素需要移动很多其他的元素)
5、二叉树的元素删除效率高(由于只需逐级调整),删除元素的时间复杂度为
6、二叉树(平衡二叉树、满二叉树、完全二叉树)
完全二叉树:深度为h的二叉数,除到底h层外,其他各层(第1层到第(h-1)层)的结点数都达到最大数目,且第h层所有的结点都连续集中在最左边,具备这两个特点的二叉树就是完全二叉树
二、认识堆
1、堆的定义
(1)堆(heap)是计算机科学中一类特殊的数据结构的统称。
(2)堆是一个可以被看作一棵树的数组对象(即将数组元素以特殊完全二叉树的形式在数组中进行存储的,特殊在子父结点在数组中的索引会满足一定的数学关系/即元素在数组中存储,但以完全二叉树结点的规律进行使用)
(3)堆的本质就是将数组元素转换为特殊完全二叉树的形式在数组中实现存储
(4)构建堆的本质就是利用堆的方式将数组中的元素在数组中进行重新排列
(5)堆最终是为优先队列准备的
2、堆的分类
(1)最大堆:根结点最大的堆,又称大根堆
(2)最小堆:根结点最小的堆,又称小根堆
3、堆的特点
(1)堆中某个结点(即元素)的值总是不大于(大根堆)或不小于(小根堆)其父结点的值
(2)一个堆可以等效于一棵完全二叉树(将堆数组中的元素从前向后按照完全二叉树的形式从上向下从左到右进行排列即可得到堆数组等效的完全二叉树)
4、完全二叉树父结点与子结点在堆数组中索引间的关系
(1)左子结点索引=父结点索引*2+1
(2)右结点索引=父结点索引*2+2
(3)父结点索引=(子结点索引-1)/2(除根结点外,根结点没有父结点)
三、创建堆
1、泛型:因为堆中的数据的类型可能为复杂数据类型,因此在创建堆时要使用泛型来规范堆中存储的数据的类型
2、新元素添加要比较:在向堆中填加新元素时必须要比较大小,因此在定义泛型时要扩展泛型,使得堆中的数据允许进行比较
public class MyHeap <T extends Comparable<T>>{
}
3、泛型在通过new赋值时必须使用给定类型来new
elements=(T[])new Comparable[DEFAULT_SIZE];
4、最大堆的效率分析
(1)获取堆顶元素的时间复杂度为
(2)向堆中添加元素的时间复杂度为
三、堆的核心代码
package com.ffyc.heap;
public class MyHeap <T extends Comparable<T>>{
private final int DEFAULT_SIZE = 10;// 使用数组作为存储结构的数据结构数组在初始时必须有一个默认长度
private T[] elements;
private int size;// 堆的容量大小(堆实际存储的元素的数目),数据结构删除元素并没有真正删除,只是让删除元素无法被访问到
public MyHeap(){
elements=(T[])new Comparable[DEFAULT_SIZE];// 向下转型
}
private int getParentIndex(int index){
return index==0?-1:(index-1)/2;
}
private int getLeftIndex(int index){
return index*2+1;
}
private void swap(int i,int j){
T tmp=elements[i];
elements[i]=elements[j];
elements[j]=tmp;
}
/**
* 上浮:将元素放在最后一个位置,不停地逐级向上进行比较,如果比父元素大,则与父元素交换位置,直至比到不能比的时候,将元素放在合适的位置上
* @param index 要上浮结点的索引
*/
private void floatUp(int index){
// 获得父结点的索引
int parentIndex=getParentIndex(index);
while (parentIndex != -1
&&
elements[index].compareTo(elements[parentIndex])>0
){
swap(index, parentIndex);
// 迭代法
index = parentIndex;
parentIndex=getParentIndex(index);
}
}
// 添加数据
public void offer(T data){
elements[size++]=data;
// 上浮问题 调整末尾元素的位置
floatUp(size-1);
}
// 判断堆是否为空
public boolean isEmpty(){
return size==0;
}
// 获得堆顶元素
public T peek(){
if(isEmpty())return null;
return elements[0];
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder("[");
for (int i = 0; i < size; i++) {
sb.append(elements[i]+",");
}
sb.deleteCharAt(sb.length()-1);
sb.append("]");
return sb.toString();
}
private void swimDown(int parentIndex){
T swimData=elements[parentIndex];
int currentIndex=parentIndex;
int leftIndex=getLeftIndex(currentIndex);
while(leftIndex<size){
int rightIndex=leftIndex+1;
int highIndex=leftIndex;// 子结点的最大值的索引
if(leftIndex < size && elements[highIndex].compareTo(elements[rightIndex])<0){
highIndex=rightIndex;
}
if(swimData.compareTo(elements[highIndex])<0){
swap(currentIndex, highIndex);
currentIndex=highIndex;
leftIndex=getLeftIndex(currentIndex);
}else{
break;
}
}
}
// 删除堆顶元素
public T poll(){
if(isEmpty()) return null;
// 交换堆顶元素和最后一个元素的位置(使用最后一个元素覆盖堆顶元素)
T data=elements[0];
elements[0]=elements[--size];
// 下沉问题 调整堆顶元素的位置
swimDown(0);
return data;
}
}
四、堆的应用
1、从一堆数据中获取第二大数据但至多只能用一次while循环(微软面试题,不能用Arrays类中的sort方法)
public static void main(String[] args) {
MyHeap<Integer>myHeap=new MyHeap<>();
myHeap.offer(3);
myHeap.offer(7);
myHeap.offer(5);
myHeap.offer(8);
myHeap.offer(2);
myHeap.offer(4);
myHeap.offer(1);
myHeap.poll();
System.out.println(myHeap.peek());
}
2、获取所有新闻中点击量最大的新闻的id
package com.ffyc.heap;
public class News implements Comparable<News>{
private int id;// 新闻的唯一标识
private int count;// 新闻的点击量
public News(int id, int count) {
this.id = id;
this.count = count;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public int compareTo(News o) {
return this.count-o.getCount();
}
public static void main(String[] args) {
MyHeap<News>myHeap=new MyHeap<>();
myHeap.offer(new News(1, 15));
myHeap.offer(new News(2, 25));
myHeap.offer(new News(3, 13));
myHeap.offer(new News(4, 12));
myHeap.offer(new News(5, 27));
// 获得新闻排行榜点击量最高的新闻的id
System.out.println(myHeap.peek().getId());
// 将新闻排行榜点击量最高的新闻的id下架
myHeap.poll();
System.out.println(myHeap.peek().getId());
}
}
五、堆的缺点(通过逐个添加元素创建的堆)
无法实现改变其中一个新闻的点击量后实现所有新闻的重新排名(根据点击量的大小进行排名)