目录
1.二叉树的实现
二叉堆是基于二叉树的堆结构,二叉堆就是一颗完全二叉树,节点编号从0开始。
问题:若一个节点k存在左右子树?如何仅用索引判断一个节点是否有子树
答:右子树的索引(k<<1)+1;
左子树的索引(k>>1)+2;
问:给一个节点k如何判断节点是否存在?
答:看其父节点的编号是否大于0
父节点的计算方法:(k-1)>>1
二叉堆的特点:
1.二叉堆是一颗完全二叉树
2.根节点节点值大于等于子树的节点值(最大堆/大根堆)
3.根节点节点值小于等于子树的节点值(最小堆/小根堆)
注意:在最大堆中,只能保证当前根节点大于等于子树所有节点,在任意子树中都满足。
节点大小与层次无关。
实现代码中用到了swap()用于元素的交换
parent()用于查找父节点
leftChild()用于查找节点的左孩子
1.1二叉堆实现的功能
完全二叉树、满二叉树建议使用顺序表存储,其他二叉树不建议(因为存储空节点会浪费大量的空间)
1.1向最大堆中添加一个新元素(在数组的末尾新增元素是O(n))
具体方法:直接在数组的末尾新增元素,保证添加元素之后,这颗二叉树仍然是完全二叉树。
添加新元素之后要调整元素的位置,使得这颗二叉树任然满足最大堆的性质。
方法:siftUp(int k):上浮操作,k为索引
public void add(int val){ //1.直接向数组的末尾添加元素 data.add(val);//数组的add默认是尾插 siftUp(data.size()-1); } public void siftUp(int k) { //k为当前索引 while (k > 0 && data.get(k) > data.get(parent(k))) { //说明没有索引到根节点,且值大于父节点的值,所以需要向上调整 swap(k, parent(k)); k = parent(k); } }
1.2删除堆顶元素
对于堆而言,最大的特点在于堆顶元素是这个集合的最值
而对于最大堆而言,堆顶元素就是最大值
而因为我们的二叉堆是基于数组实现的,尾插尾删都很简单,但是对头部的操作就比较麻烦
方法:将数组末尾的元素顶到堆顶,然后进行元素的下沉操作siftDown(int k)
用extractMax():取出当前最大值元素(也就是删除之后返回之前的最大值),实际就是取出堆顶最大值data.get(0)
public int extractMax(){ if(isEmpty()){ throw new NoSuchElementException("heap is empty now"); } int max =data.get(0); //让最后一个元素顶替根节点 // data.set(0,data.get(data.size()-1)); int lastVal = data.get(data.size() - 1); data.set(0,lastVal); //然后删除数组的最后一位 data.remove(data.size()-1); siftDown(0); return max; } //然后开始元素的下浮操作 public void siftDown(int k){ //还存在子树 while(leftChild(k)<data.size()){ int j =leftChild(k); //看是否存在右数 if(j+1< data.size()&&data.get(j+1)> data.get(j)){ //说明右子树存在,且右子树大于左子树 j =j+1; } //此时j就对应左右子树的最大值 //和当前节点比较 if(data.get(k)>=data.get(j)) { //下沉结束 break; }else{ swap(k,j); k=j; } } }
这里附上测试人员测试用的代码,因为我们举得个例可能太特殊,所以需要举很多随机数,防止巧合的发生。
int[] data = new int[100000]; ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < data.length; i++) { data[i] = random.nextInt(0, Integer.MAX_VALUE); } //把数组放入堆中 MaxHeap heap = new MaxHeap(); for (int i : data) { heap.add(i); } for (int i = 0; i < data.length; i++) { data[i] = heap.extractMax(); } System.out.println(isSorted(data)); } public static boolean isSorted(int []data){ for(int i=0;i<data.length-1;i++){ if(data[i]<data[i+1]){ System.out.println("error"); return false; } } return true;
1.3 堆化
就是将任意的数组调整为堆的结构,这个方法输出的结果是倒序
其实很简单,在上面测试的代码也用到了,一个构造方法就可以实现
//将任意数组堆化,在构造方法里面 public MaxHeap(int []arr){ //数组的初始化 data = new ArrayList<>(arr.length); for (int i:arr) { data.add(i); } //因为最后一个叶子节点的父亲就是最后一个非叶子节点 for (int i = parent(data.size()-1); i>=0; i--) { siftDown(i); } }
具体的代码如下:
package heap; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; //基于整型最大堆实现,此时根结点从零开始编号 public class MaxHeap { //用数组存储最大堆 List<Integer> data; public MaxHeap(){ //构造方法的this调用 this(10); } public MaxHeap(int size){ data =new ArrayList<>(size); } //将任意数组堆化,在构造方法里面 public MaxHeap(int []arr){ //数组的初始化 data = new ArrayList<>(arr.length); for (int i:arr) { data.add(i); } //因为最后一个叶子节点的父亲就是最后一个非叶子节点 for (int i = parent(data.size()-1); i>=0; i--) { siftDown(i); } } public void add(int val){ //1.直接向数组的末尾添加元素 data.add(val);//数组的add默认是尾插 siftUp(data.size()-1); } public int extractMax(){ if(isEmpty()){ throw new NoSuchElementException("heap is empty now"); } int max =data.get(0); //让最后一个元素顶替根节点 // data.set(0,data.get(data.size()-1)); int lastVal = data.get(data.size() - 1); data.set(0,lastVal); //然后删除数组的最后一位 data.remove(data.size()-1); siftDown(0); return max; } //然后开始元素的下浮操作 public void siftDown(int k){ //还存在子树 while(leftChild(k)<data.size()){ int j =leftChild(k); //看是否存在右数 if(j+1< data.size()&&data.get(j+1)> data.get(j)){ //说明右子树存在,且右子树大于左子树 j =j+1; } //此时j就对应左右子树的最大值 //和当前节点比较 if(data.get(k)>=data.get(j)) { //下沉结束 break; }else{ swap(k,j); k=j; } } } public int peekMax(){ if(isEmpty()){ throw new NoSuchElementException("can not find any value"); } return data.get(0);//返回堆顶元素 } //上浮操作 public void siftUp(int k) { //k为当前索引 while (k > 0 && data.get(k) > data.get(parent(k))) { //说明没有索引到根节点,且值大于父节点的值,所以需要向上调整 swap(k, parent(k)); k = parent(k); } } public void swap(int i ,int j){ int temp =data.get(i); //修改数组的值 data.set(i,data.get(j)); data.set(j,temp); } public boolean isEmpty(){ return data.size()==0; } //根据索引得到父节点的索引 private int parent(int k){ return (k-1)>>1; } private int leftChild(int k){ return (k<<1)+1; } private int rightChild(int k){ return (k<<1)+2; } @Override public String toString() { return data.toString(); } }
测试代码
public class HeapTest { public static void main(String[] args) { //这里用于实现 int[] data = {15, 17, 19, 13, 22, 16, 28, 30, 41, 62}; MaxHeap heap = new MaxHeap(data); System.out.println(heap); /* //这是测试的代码------------------------------------------------------------------------------------------------------ int[] data = new int[100000]; ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < data.length; i++) { data[i] = random.nextInt(0, Integer.MAX_VALUE); } //把数组放入堆中 MaxHeap heap = new MaxHeap(); for (int i : data) { heap.add(i); } for (int i = 0; i < data.length; i++) { data[i] = heap.extractMax(); } System.out.println(isSorted(data)); } public static boolean isSorted(int []data){ for(int i=0;i<data.length-1;i++){ if(data[i]<data[i+1]){ System.out.println("error"); return false; } } return true; }*/ //-------------------------------------------------------------------------------------------------------------------- //这里是添加元素以及堆化和删除堆顶元素的代码 // MaxHeap heap =new MaxHeap(); // int [] data ={62,41,30,28,16,13,22,19,17,15}; // for (int i:data) { // heap.add(i); // } /* heap.add(52); System.out.println(heap);*/ // //得到的是一个完全降序的数组 // for (int i = 0; i < data.length; i++) { // data[i]=heap.extractMax(); // } // //如果不写数组的toString方法,打印的是地址 // System.out.println(Arrays.toString(data)); } }
测试代码的执行