文章目录

  • 堆结构
  • 堆中元素的关键字大小关系
  • 堆中的操作
  • 遍历和查找
  • 移除
  • 插入
  • Java实现堆


堆结构

**堆是一种完全的二叉树:除了树的最后一层节点不需要是满的,其它的每一层从左到右都是 满的。**注意下面两种情况,第二种最后一层从左到右中间有断隔,那么也是不完全二叉树。

Java使用堆 java堆数据结构的使用_数组

堆通常用数组来实现:

Java使用堆 java堆数据结构的使用_子节点_02

这种用数组实现的二叉树,假设节点的索引值为index,那么:

  • 节点的左子节点是 2*index+1;
  • 节点的右子节点是 2*index+2;
  • 节点的父节点是(index-1)/2。(整除)

堆中元素的关键字大小关系

堆中每一个节点的关键字都大于(或等于)这个节点的子节点的关键字

这里要注意堆和前面说的二叉搜索树的区别,二叉搜索树中所有节点的左子节点关键字都小于右子节点关键字,在二叉搜索树中通过一个简单的算法就可以按序遍历节点。但是在堆中,按序遍历节点是很困难的。如上图所示,堆只有沿着从根节点到叶子节点的每一条路径是降序排列的,指定节点的左边节点或者右边节点,以及上层节点或者下层节点由于不在同一条路径上,他们的关键字可能比指定节点大或者小。所以相对于二叉搜索树,堆是弱序的。

堆中的操作

遍历和查找

堆是弱序的,所以想要遍历堆是很困难的,基本上,堆是不支持遍历的

对于查找,由于堆的特性,在查找的过程中,没有足够的信息来决定选择通过节点的两个子节点中的哪一个来选择走向下一层,所以也很难在堆中查找到某个关键字

因此,堆这种组织似乎非常接近无序,不过,堆能够快速的移除最大(或最小)节点,也就是根节点,以及能够快速插入新的节点,这两个操作就足够了。

移除

移除是指删除关键字最大的节点(或最小),也就是根节点。

根节点在数组中的索引总是0,即maxNode = heapArray[0]。

移除根节点之后,那树就空了一个根节点,也就是数组有了一个空的数据单元,这个空单元我们必须填上。第一种方法是将数组所有数据项都向前移动一个单元,这比较费时。所以我们一般使用第二种方法:

  1. 移走根;
  2. 把最后一个节点移动到根的位置;
  3. 一直向下筛选这个节点(与比它大的子节点交换,如果两个子节点都比它大,就与较大的子节点交换),直到它在一个大于它的节点之下,小于它的节点之上为止。

Java使用堆 java堆数据结构的使用_数组_03

插入

插入时,选择向上筛选,节点初始时插入到数组最后第一个空着的单元,然后进行向上筛选的算法。 向上筛选和向下不同,向上筛选只用和一个父节点进行比较,比父节点小就停止筛选了。

Java使用堆 java堆数据结构的使用_数组_04

Java实现堆

package heap;


public class HeapTest {
    /**
     * 测试程序
     */
    public static void main(String[] args) {
        Heap heap = new Heap(100);
        heap.insert(55);
        heap.insert(33);
        heap.insert(66);
        heap.insert(11);
        heap.insert(22);
        heap.insert(99);
        heap.insert(8);

        heap.displayHeap();

        heap.remove();
        heap.displayHeap();

        System.out.println(heap.change(1,99));
        heap.displayHeap();
    }
}

/**
 * 封装节点的类
 */
class Node{
    private int data;
    //此处还可封装其他属性

    //构造方法
    public Node(int data){
        this.data = data;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }
}

/**
 * 封装堆的类
 */
class Heap{
    private Node[] heapArray;
    private int maxSize;
    private int currentItems;//当前数据项的个数

    //构造方法
    public Heap(int maxSize){
        this.maxSize = maxSize;
        heapArray = new Node[maxSize];
        currentItems = 0;
    }

    //实现对这种数据结构的基本功能

    //判断堆是不是空的
    public boolean isEmpty(){
        return currentItems == 0;
    }

    //判断堆是不是满的
    public boolean isFull(){
        return currentItems == maxSize;
    }

    //实现插入新的元素
    public boolean insert(int data){
        if (isFull()){
            return false;//表示插入失败
        }else {
            Node newNode = new Node(data);
            //直接把新节点插入到数组中
            heapArray[currentItems] = newNode;
            //判断新节点是不是比父节点大,是的话,就把父节点移动下来
            trickleUp(currentItems++);
            return true;
        }
    }

    //实现把节点向上移动
    public void trickleUp(int index){
        int parent = (index-1)/2;
        Node currentNode = heapArray[index];//把当前节点暂时存储起来
        while (index>0 && currentNode.getData()>heapArray[parent].getData()){
            //当索引值大于零,并且当前节点的数据大于父节点的数据时,下拉父节点
            heapArray[index] = heapArray[parent];
            index = parent;
            parent = (index-1)/2;
        }
        //结束while循环后,index的位置就是新插入节点应该在的位置
        heapArray[index] = currentNode;
    }

    //实现移除根节点
    public Node remove(){
        Node root = heapArray[0];
        heapArray[0] = heapArray[--currentItems];
        heapArray[currentItems] = null;
        //更新的top节点,移动到叶子节点去
        trickleDown(0);
        return root;
    }

    //实现向下移动
    public void trickleDown(int index){
        Node top = heapArray[0];
        int largeChildIndex;
        int leftChildIndex;
        int rightChildIndex;
        while (index < currentItems/2){
            //向下移动节点的循环条件:当前节点必须有子节点

            //判断当前节点有没有向下移动的必要
            //进一步判断当前节点的子节点哪个大,然后大的子节点和当前节点进行比较
            leftChildIndex = index*2+1;
            rightChildIndex = leftChildIndex+1;
            //得到较大的子节点的索引
            if (rightChildIndex<currentItems && heapArray[leftChildIndex].getData()<heapArray[rightChildIndex].getData()){
                //右子节点存在,且左子节点的值小于右子节点
                largeChildIndex = rightChildIndex;
            }else {
                largeChildIndex = leftChildIndex;
            }

            //比较父节点和其较大的子节点大小
            if (top.getData()>= heapArray[largeChildIndex].getData()){
                //父节点已经比子节点中较大的值大
                break;
            }else {
                heapArray[index] = heapArray[largeChildIndex];//较大的子节点拉到父节点位置
                index = largeChildIndex;
            }
        }
        //while循环结束后,index的位置就是top节点应该在的位置
        heapArray[index] = top;
    }

    //改变某一个节点的值
    public boolean change(int index, int newValue){
        if (index<0 || index >= currentItems){
            return false;//指定的下标不合法
        }
        int oldValue = heapArray[index].getData();
        heapArray[index].setData(newValue);//修改掉节点的值
        if (newValue> oldValue){
            //新的值比老的值大,那么新的值有可能比父节点的值大,需要向上移动
            trickleUp(index);
        }else {
            //否则向下移动
            trickleDown(index);
        }
        return true;
    }

    public void displayHeap(){
        System.out.println("遍历堆:");
        for(int i=0;i<currentItems;i++){
            if (heapArray[i] != null){
                System.out.print(heapArray[i].getData()+"  ");
            }else {
                System.out.print("--");
            }
        }
        System.out.println();
    }

}