因为11.2那篇博客已经讲了基本概念,此处不多赘述。
需要注意的是我们现在讲的建堆与堆排序是有一定区别的。
目录
一、思路阐述(以大根堆为例)
1.建堆的核心:对每一个子树来说根节点始终满足大于等于它的左右子树,从最后一个度不为0的根节点开始,与他的左右子树作比较和交换(后面简称为向下调整)。交换完成后,向前一个根节点移动,重复上述操作。
2.我们还应该加入基本的修改操作,比如插入、删除、读队顶元素、堆内大小、输出堆等等一系列操作。
二、具体代码详解
数组以{27,15,19,18,28,34,65,49,25,37}为例创建大根堆。
1.定义基本属性
数组、容量、大小、父节点如何定义都要有所体现(找到最后一个度不为0的节点以及之前的所有根节点是重点)
//定义属性
private int[] elementDate;//数组
private int size;
private int DEFAULT_CAPACITY= 10;//默认容量
public Heap(){
this.elementDate =new int[DEFAULT_CAPACITY];
this.size=0;
}
public Heap(int[] array){
//重新复制数字 防止外部修改对堆内数据的影响
this.elementDate = Arrays.copyOf(array,array.length);
this.size=elementDate.length;
//寻找最后一个父节点
int parent=(size-1-1)/2;
//从最后一个父节点开始向下调整
for(int i=parent;i>=0;i--){
shiftDown(i);
}
}
2.向下调整
通过父节点找到孩子节点:
第一种情况:只有左孩子,只需让左孩子与父节点作比较,符合条件交换。
第二种情况:既有左孩子又有右孩子,左右孩子先做比较,最大的和父节点作比较,符合条件作交换。
private void shiftDown(int parent){
if(parent<0){
return;
}
//找到左孩子
int child=parent*2+1;
//判断是否有孩子
while(child<size){
//有右孩子的情况,左右孩子作比较,child赋给大的值
if(child+1<size){
if(elementDate[child]<elementDate[child+1]){
child++;
}
}
//最大的值与父节点作比较 父节点大不做操作
if(elementDate[parent]>=elementDate[child]){
break;
}
//如果父节点小 做交换
swap(elementDate,parent,child);
//并继续向下调整
parent=child;
child=parent*2+1;
}
}
3.插入元素
插入新元素到最后一个节点时,和最后一个度不为0的根节点(也就是此时它的父节点作比较)作比较,如果没有他的父节点大时,不需要调整。否则,一直向上调整。
//插入元素
public void offer(int value){
if(isFull()){
//二倍扩容把新元素放在最后一个节点后面
elementDate=Arrays.copyOf(elementDate,elementDate.length*2);
}
elementDate[size]=value;
//长度加一
size++;
//调整新节点的位置
shiftup(elementDate[size-1]);
}
private boolean isFull() {
return size==elementDate.length;
}
4.向上调整
向上调整只针对插入元素,因为新插入的元素是在最后堆尾,我们需要重新调整它到正确的位置,就需要它向上移动。
private void shiftup(int child) {
if(child>=size){
return;
}
//找到父节点和父节点比较大小
int parent=(child-1)/2;
while(child>0&&parent>=0){
//没有父节点值大时调整完成
if(elementDate[child]<=elementDate[parent]){
break;
}
//否则交换该值与父节点
//并依次向上调整
swap(elementDate,parent,child);
child=parent;
parent=(child-1)/2;
}
}
5.删除元素
删除堆顶元素时,将堆顶元素与堆尾元素做交换,size--就删除了,新的堆顶元素需要向下调整找到适合的位置。
//删除元素
public int poll(){
if(isEmpty()){
throw new RuntimeException("数组为空");
}
//交换堆顶和堆尾两个元素
int value=elementDate[0];
swap(elementDate,elementDate[0],elementDate[size-1]);
//新的堆顶元素向下做调整
size--;
shiftDown(0);
return value;
}
private boolean isEmpty() {
return size==0;
}
6.交换
插入、删除、向上调整、向下调整都会用到的交换方法。主要是在调整位置比较大小需要交换位置时使用。
private void swap(int[] elementDate, int parent, int child) {
int temp=elementDate[parent];
elementDate[parent]=elementDate[child];
elementDate[child]=temp;
}
7.输出堆及读取堆顶元素
从0下标节点开始依次输出元素,并用“,”隔开
读取堆顶元素(0下标元素)
public void display(){
StringBuilder sb=new StringBuilder();
sb.append("[");
for(int i=0;i<size;i++){
sb.append(elementDate[i]);
if(i<size){
sb.append(",");
}
}
sb.append("]");
System.out.println(sb);
}
public int peek(){
return elementDate[0];
}
三.完整代码
import java.util.Arrays;
public class Heap {
//定义属性
private int[] elementDate;//数组
private int size;
private int DEFAULT_CAPACITY= 10;//默认容量
public Heap(){
this.elementDate =new int[DEFAULT_CAPACITY];
this.size=0;
}
public Heap(int[] array){
//重新复制数字 防止外部修改对堆内数据的影响
this.elementDate = Arrays.copyOf(array,array.length);
this.size=elementDate.length;
//寻找最后一个父节点
int parent=(size-1-1)/2;
//从最后一个父节点开始向下调整
for(int i=parent;i>=0;i--){
shiftDown(i);
}
}
//插入元素
public void offer(int value){
if(isFull()){
//二倍扩容把新元素放在最后一个节点后面
elementDate=Arrays.copyOf(elementDate,elementDate.length*2);
}
elementDate[size]=value;
//长度加一
size++;
//调整新节点的位置
shiftup(elementDate[size-1]);
}
private boolean isFull() {
return size==elementDate.length;
}
//向上调整
private void shiftup(int child) {
if(child>=size){
return;
}
//找到父节点和父节点比较大小
int parent=(child-1)/2;
while(child>0&&parent>=0){
//没有父节点值大时调整完成
if(elementDate[child]<=elementDate[parent]){
break;
}
//否则交换该值与父节点
//并依次向上调整
swap(elementDate,parent,child);
child=parent;
parent=(child-1)/2;
}
}
//删除元素
public int poll(){
if(isEmpty()){
throw new RuntimeException("数组为空");
}
//交换堆顶和堆尾两个元素
int value=elementDate[0];
swap(elementDate,elementDate[0],elementDate[size-1]);
//新的堆顶元素向下做调整
size--;
shiftDown(0);
return value;
}
public void display(){
StringBuilder sb=new StringBuilder();
sb.append("[");
for(int i=0;i<size;i++){
sb.append(elementDate[i]);
if(i<size){
sb.append(",");
}
}
sb.append("]");
System.out.println(sb);
}
public int peek(){
return elementDate[0];
}
private boolean isEmpty() {
return size==0;
}
private void shiftDown(int parent){
if(parent<0){
return;
}
//找到左孩子
int child=parent*2+1;
//判断是否有孩子
while(child<size){
//有右孩子的情况,左右孩子作比较,child赋给大的值
if(child+1<size){
if(elementDate[child]<elementDate[child+1]){
child++;
}
}
//最大的值与父节点作比较 父节点大不做操作
if(elementDate[parent]>=elementDate[child]){
break;
}
//如果父节点小 做交换
swap(elementDate,parent,child);
//并继续向下调整
parent=child;
child=parent*2+1;
}
}
private void swap(int[] elementDate, int parent, int child) {
int temp=elementDate[parent];
elementDate[parent]=elementDate[child];
elementDate[child]=temp;
}
public static void main(String[] args){
int[] array={27,15,19,18,28,34,65,49,25,37};
}
}