普林斯顿公开课 算法4-2:二叉堆

来源:互联网 发布:mac版本老装不了xcode 编辑:程序博客网 时间:2024/06/05 08:56

二叉树


介绍二叉堆之前首先介绍二叉树。二叉树有一个根节点,节点下又有两个子节点。完全二叉树是指一个二叉树树除了最底层,其他层都是完全平衡的。


完全二叉树最基本的性质就是它的高度是 floor(lgN)。


二叉堆


二叉堆是完全二叉树的一种,每个节点对应一个数值,而且这个数值都大于等于它子节点的数值。


下图是一个二叉堆。



二叉堆的储存


由于二叉堆是完全二叉树,所以它可以用一个数组进行储存。所以不需要创建节点对象,再建立节点之间的连接。这样节省了很多开销。


用数组a[]表示一个二叉堆有以下特性:

  • a[0]不表示任何数据,这样后续的计算就简化了很多

  • a[1]是根节点

  • a[2]a[3]是二叉树第一层的两个节点

  • a[4]a[5]a[6]a[7]是二叉树的第二层节点

以此类推


下图展示了堆的数据结构用数组进行表示的示意图。


二叉堆的访问


二叉堆有以下访问规则:

  • 二叉堆的根就是a[1],也是最大的一个元素。

  • 在a[k]位置的节点,其父节点为k/2

  • 在a[k]位置的节点,其子节点分别为2k和2k+1


二叉树的操作


二叉堆中每个子节点都不能比它的父节点大,然而在修改二叉树之后往往会违反二叉树的性质。


Swim操作


当二叉树的子节点比父节点大时,怎么办呢?没关系,只要按照以下步骤进行就行了:

  1. 将违反规则的节点与父节点进行交换

  2. 重复第一步,直到所有的节点都二叉堆的性质为止

该操作称为swim操作


Sink操作


当二叉树的父节点比任意一个子节点小时,怎么办呢?没关系,只要按照以下步骤进行就行了:

  1. 将违反规则的子节点与两个子节点中较大的子节点进行交换

  2. 重复第一步,直到符合二叉堆的性质为止

该操作称为sink操作


删除操作


从堆中删除最大的元素,需要执行以下步骤:

  • 将最小的元素与根节点进行交换

  • 对根节点执行sink操作,将最小的节点沉淀到二叉堆的最底部

这样,删除操作最多需要2lgN次比较


复杂度


二元堆的插入操作复杂度是logN,删除操作的复杂度是logN。

d元堆的插入操作复杂度是log_d(N),删除操作的复杂度是d log_d(N)。

斐波那契堆的插入操作复杂度是1,删除操作平均复杂度是logN。


不变性


对于二叉堆,需要保元素自身不会发生变化,这样才能保证二叉堆的算法能够正常工作。


因此,在Java中,二叉堆的数据类型尽量选择不可变的数据类型。所谓不可变的类型就是指当对象创建完毕之后,对象中的内容就无法再改变了。

java中不可变的类型有String Integer Double Color Vector Transaction Point2D。

可变的类型有StringBuilder Stack Counter Java数组。


不可变类型的好处就是能确保优先级队列能够正常工作,坏处就是每次有新值的时候都要创建一个新的对象,造成额外的开销。


代码

// 用二叉堆实现的最大优先级队列public class MaxHeapPQ<Item extends Comparable<Item>> {    private Item[] items;    private int N;     public MaxHeapPQ(int capacity) {        items = (Item[])new Comparable[capacity+1]; //capacity+1是因为a[0]不储存任何数据,所以需要额外的一个元素。    }     public boolean isEmpty() {        return N==0;    }     public void insert(Item item) {        N++;        items[N] = item;        swim(N);    }     public Item popMax() {        Item result = items[1];        SortUtil.exch(items, N, 1);        items[N] = null; // 防止产生游离对象造成内存泄露        N--;        sink(1);        return result;    }     public int size() {        return N;    }     private void swim(int k) {        // 一直循环直到到达根节点        while(k > 1) {            Item child = items[k];            Item parent = items[k/2];             // 如果父节点比子节点还小,就将父子进行交换            if(SortUtil.less(parent, child)) {                SortUtil.exch(items, k, k/2);                k /= 2;            } else {                break;            }        }    }     private void sink(int k) {        // 一直循环直到没有子节点        while(k*2 <= N) {            int parent = k;            int child1 = k*2;            int child2 = k*2+1;             // 如果父节点比任意一个子节点小            if(less(parent, child1) || less(parent, child2)) { // 注意:这里不能用SortUtil.less进行比较,因为空值也要进行比较                // 选出较大的子节点                int greater;                if(less(child1, child2)) { // 注意:这里child1和child2位置不要写反了                    greater = child2;                } else {                    greater = child1;                }                 // 将较大的子节点与父节点交换                SortUtil.exch(items, greater, parent);                 // 准备下一次循环                k = greater;            } else {                break;            }        }    }     // 判断是否小于。将null值看成无穷小    private boolean less(int a, int b) {        if(a > N) return true;        if(b > N) return false;        return SortUtil.less(items[a], items[b]);    }     private void debugPrint() {        for(int i=0;i<N;i++){            System.out.print(items[i+1]);            System.out.print(" ");        }        System.out.println();    }}



1 0
原创粉丝点击