1.容易蒙圈的概念问题
一般我在学习一个新的东西的时候都会在脑子里有一大堆问题!那么到底堆是什么?是一种什么样的数据结构呢?用堆可以来干什么呢?
让我来自问自答哈:堆其实说白了就是一种二叉树这类的数据结构,哪种?堆就是一种基于完全二叉树的结构(完全二叉树就是除了最底层,其它层都必须填满,最后一层可以从左到右填满);平时生活中,我们有时会说一堆人,一堆某某东西,其实数据结构里的堆也和生活中的类似,不同的就是数据结构里的堆是由一些按照某种优先级来组织成的队列,所以堆又叫做优先队列,显而易见,堆是可以把某种优先级最高或者最低的那个对象快速的取出,在想要实现这种类似功能的时候很有用。
2.堆的重要性质:
·有两种堆,最大值堆和最小值堆,最大值堆就是根节点是最大的元素,最小值堆反之。
·堆是一颗完全二叉树,用数组来存储
·堆中的数据局部有序,即父节点一定大于它的左右子节点,如下图假如是最大值堆,那么根节点1一定大于4和8节点,但是堆的局部有序性就是指1的两个子节点4和8的大小没有特定要求。
3.实现堆的一大堆操作来袭
实现堆先要建一个堆类
定义它的属性和方法
我这里定义了如下的方法(基本全了):
①构造函数(设置初始值)
②返回当前元素个数
③判断当前节点是否是叶节点
由于堆是用数组存储的,具体看下面的图
从数组的下标0开始存储堆,可以自己找到规律:
第i个节点的——
父节点为(i-1)/2;//为负的时候就代表没有父节点
左子节点为2i+1;
右子节点为2i+2;
那么叶结点就是下标从n/2开始,后面所有的都是叶结点
④返回当前节点的左孩子节点位置
⑤返回当前节点的右孩子节点位置
⑥返回当前节点的父节点位置
⑦建堆函数
有两种方法,一种是利用插入函数,逐个插入数据。另一种是对已经存有数据的数组进行堆排序。我们这里采用的是第二种方法。基本思路:依次从树的倒数第二层往上遍历节点。如果当前节点的值小于它的某一个叶节点,我们调用下拉函数进行下拉操作。而由于叶节点不可能再往下走,所以我们直接从倒数第二层开始遍历即可。倒数第二层的位置:n/2-1。
⑧下拉函数
判断当前节点和它两个子节点的大小关系,如果当前节点小于它的子节点。那么就将该节点往下拉。与较大的子节点交换位置
⑨插入函数
首先将要插入的数据加到堆的一个叶节点中,也就是当前数组的尾部。然后判断该节点和其父节点的大小关系,如果该节点大于其父节点,就把其上拉,和父节点交换位置,重复该过程直到该节点到了正确的位置
⑩移除函数
先把根节点和最后一个叶节点交换位置,把堆的元素大小减1。对改变后的根节点进行下拉操作,直到正确的位置。最后再返回被替换的那个叶节点的值
JAVA实现
package com.hash;
public class Node {
public int key;//关键字,也就是权值
public String value;
public Node(int key,String value){
this.key=key;
this.value=value;
}
}
package com.hash;
import java.util.ArrayList;
public class Heap {
public ArrayList<Node>nodeList;//存储数据的数组,可以说就是堆
private int length;//堆的大小
public Heap(){//无参
}
public Heap(ArrayList<Node>nodeList){
this.nodeList=nodeList;
this.length=nodeList.size();
}
//交换函数,交换两个节点的位置
public void swap(Node nodeA,Node nodeB){
Node nodeTemp=new Node(nodeA.key,nodeA.value);
nodeA.key=nodeB.key;
nodeA.value=nodeB.value;
nodeB.key=nodeTemp.key;
nodeB.value=nodeTemp.value;
}
public int length(){
return length;
}
//判断是不是叶结点
public boolean isLeaf(int pos){
return (pos >=length/2)&&(pos<length);
}
//返回左孩子节点的地址
public int leftChild(int pos){
return 2*pos+1;
}
//返回右孩子节点的地址
public int rightChild(int pos){
return 2*pos+2;
}
//返回父节点的地址
public int parent(int pos){
return (pos-1)/2;
}
//建堆函数
public void buildHeap(){
for(int i=length/2-1;i>=0;i--)
shiftDown(i);//从倒数第二层开始向上逐层遍历
}
//下拉函数
public void shiftDown(int pos){
while(!isLeaf(pos)){//到叶结点就停止
int j=leftChild(pos);
int r=rightChild(pos);
if((r<length)&&(nodeList.get(r).key>nodeList.get(j).key))
j=r;
if(nodeList.get(pos).key>nodeList.get(j).key) return;
swap(nodeList.get(pos),nodeList.get(j));//如果根节点的值小于较大子节点的值就交换位置
pos=j;//继续往原来值较大的节点走
}
}
//插入新节点
public void insert(Node node){
nodeList.add(node);
int curr=nodeList.size()-1;//将新节点放在最后
//加入以后开始上拉
while((curr!=0)&&(nodeList.get(curr).key>nodeList.get(parent(curr)).key)){
swap(nodeList.get(curr),nodeList.get(parent(curr)));
curr=parent(curr);
}
length++;
}
//取最大值函数
public Node removeFirst(){
if(length<=0){
System.out.println("Heap is empty");
return null;
}
swap(nodeList.get(0),nodeList.get(--length));//和最后一个位置交换
if(length!=0) shiftDown(0);
return nodeList.get(length);//返回最大值
}
public static void main(String[] args) {
ArrayList<Node>nodeList=new ArrayList<Node>();
nodeList.add(new Node(1,"A"));
nodeList.add(new Node(2,"B"));
nodeList.add(new Node(3,"C"));
nodeList.add(new Node(4,"D"));
Heap heap=new Heap(nodeList);
System.out.println("当前的数组结构");
for(int i=0;i<heap.length();i++){
System.out.println(heap.nodeList.get(i).key+"-"+heap.nodeList.get(i).value);
}
heap.buildHeap();
System.out.println("当前的堆结构");
for(int i=0;i<heap.length();i++){
System.out.println(heap.nodeList.get(i).key+"-"+heap.nodeList.get(i).value);
}
Node result=heap.removeFirst();
System.out.println("最大值:"+result.key+result.value);
result=heap.removeFirst();
}
}
结果