算法导论读书笔记(5)

算法导论读书笔记(5)

(二叉) 数据结构是一种数据结构,它能够被视为一棵彻底二叉树。树中每一个结点与数组中存放该结点值的那个元素对应。树的每一层都是填满的,最后一层从左子树开始填。表示堆的数组 A 是一个具备两个属性的对象: A.length 是数组中元素的个数, A.heap-size 是存放在 A 中的堆元素个数。此处有 A.heap-size <= A.length 。树的根为 A [ 1 ],给定了某个结点的下标 i ,其父结点 PARENT(i) ,左儿子 LEFT(i) 和右儿子 RIGHT(i) 的下标能够简单的计算出来:java

PARENT(i)
1 return FLOOR(i / 2)
LEFT(i)
1 return 2i
RIGHT(i)
1 return 2i + 1

二叉堆有两种: 最大堆最小堆 。这两种堆都要知足各自的堆特性。在最大堆中,最大堆特性是指除了根之外的每一个结点 i ,有 A [ PARENT(i) ] >= A [ i ]。即某个结点的值至多和其父结点同样大。这样,堆中的最大元素就存放在根结点中。最小堆的组织方式恰好相反,最小堆特性是指除了根之外的每一个结点 i ,有 A [ PARENT(i) ] <= A [ i ],最小堆的最小元素是在根部。算法

堆能够被当作是一棵树,结点在堆中的高度定义为从本结点到叶子的最长简单降低路径上边的数目;定义堆的高度为树根的高度,于是树的高度为 Θ (lg n )。而堆结构上的一些基本操做的运行时间至多与树的高度成正比,为 O (lg n )。下面将介绍几个基本过程,并说明它们的用法。sql

  • MAX-HEAPOFY 过程,运行时间为 O (lg n ),是保持最大堆性质的关键
  • BUILD-MAX-HEAP 过程,以线性时间运行,能够在无序的输入数组上构造出最大堆
  • HEAP-SORT 过程,运行时间为 O ( n lg n ),对一个数组进行原地排序
  • MAX-HEAP-INSERTHEAP-EXTRACT-MAXHEAP-INCREASE-KEYHEAP-MAXIMUM 过程的运行时间为 O (lg n ),可让堆结构做为优先级队列使用。

保持堆的性质

MAX-HEAPIFY 过程的输入为一个数组 A 和下标 i 。当 MAX-HEAPIFY 被调用时,咱们假定以 LEFT(i)RIGHT(i) 为根的两棵二叉树都是最大堆,但这时 A [ i ]可能小于其子女,这样就违反了最大堆特性。 MAX-HEAPIFYA [ i ]在最大堆中“降低”,使以 i 为根的子树成为最大堆。api

MAX-HEAPIFY(A, i)
1  l = LEFT(i)
2  r = RIGHT(i)
3  if l <= A.heap-size and A[l] > A[i]
4      largest = l
5  else
6      largest = i
7  if r <= A.heap-size and A[r] > A[largest]
8      largest = r
9  if largest != i
10     exchange A[i] with A[largest]
11     MAX-HEAPIFY(A, largest)

下图描述了 MAX-HEAPIFY 的过程。在算法的每一步里,从元素 A [ i ], A [ LEFT(i) ]和 A [ RIGHT(i) ]中找出最大的,并将其下标保存在 largest 中。若是 A [ i ]是最大的,则以 i 为根的子树已经是最大堆,程序结束。不然,交换 A [ i ]和 A [ largest ],从而使 i 及其子女知足堆性质。下标为 largest 的结点在交换后的值为 A [ i ],以该结点为根的子树可能又违反了最大堆性质。于是要对该子树递归调用 MAX-HEAPIFY数组

MAX-HEAPIFY 做用在一棵以结点 i 为根的,大小为 n 的子树上时,其运行时间为调整元素 A [ i ], A [ LEFT(i) ]和 A [ RIGHT(i) ]的关系时所用的时间 Θ (1),加上对以 i 为结点的某个子结点为根的子树递归调用 MAX-HEAPIFY 所需的时间。 i 结点的子树大小至多为2 n / 3(最坏状况发生在最底层刚好半满的时候),那么 MAX-HEAPIFY 的运行时间可表示为: T ( n ) <= T (2 n / 3) + Θ (1)。该递归式的解为 T ( n ) = O (lg n )。或者说, MAX-HEAPIFY 做用于一个高度为 h 的结点所需的运行时间为 O ( h )。bash

建堆

如今能够自底向上地用 MAX-HEAPIFY 来将一个数组 A [ 1 .. n ](此处 n = A.length )变成一个最大堆。又可知子数组 A [ FLOOR(n / 2) + 1 .. n ]中的元素都是树中的叶子结点,它们均可以看作是只含一个元素的堆。过程 BUILD-MAX-HEAP 对树中的每个非叶子结点都调用了一次 MAX-HEAPIFY数据结构

BUILD-MAX-HEAP(A)
1 A.heap-size = A.length
2 for i = FLOOR(A.length / 2) downto 1
3     MAX-HEAPIFY(A, i)

下图给出了 BUILD-MAX-HEAP 过程的一个例子。ide

咱们能够计算出 BUILD-MAX-HEAP 运行时间的一个简单上界:每次调用 MAX-HEAPIFY 的时间为 O (lg n ),共有 O ( n )次调用,故运行时间是 O ( n lg n )。尽管这个界是对的,但从渐近意义上来讲不够紧确。post

实际上,咱们能够获得一个更加紧确的界。由于,在树中不一样高度的结点处运行 MAX-HEAPIFY 的时间不一样,而大部分结点的高度都比较小。关于更紧确界的分析依赖于这样的性质:一个 n 元素堆的高度为FLOOR(lg n ),而且,在任意高度 h 上,至多有CEIL( n / 2( h + 1))个结点。ui

MAX-HEAPIFY 做用在高度为 h 的结点上的时间为 O ( h ),能够将 BUILD-MAX-HEAP 的代价表示为:

最终能够得出 BUILD-MAX-HEAP 过程运行时间的界为

这说明能够在线性时间内,将一个无序数组建成一个最大堆。

堆排序算法

堆排序算法先用 BUILD-MAX-HEAP 将输入数组 A [ 1 .. n ]建成一个最大堆。此时数组中最大元素在根 A [ 1 ],能够经过将它与 A [ n ]互换来达到最终正确的位置。而后经过缩小 A.heap-seize ,能够很容易地将 A [ 1 .. n - 1 ]建成最大堆。不断的重复这一过程,堆的大小由 n - 1一直降到2。

HEAP-SORT(A)
1 BUILD-MAX-HEAP(A)
2 for i = A.length downto 2
3     exchange A[1] with A[i]
4     A.heap-size = A.heap-size - 1
5     MAX-HEAPIFY(A, 1)

HEAP-SORT 过程的时间代价为 O ( n lg n )。其中调用 BUILD-MAX-HEAP 的时间为 O ( n ), n - 1次 MAX-HEAPIFY 调用中每次的时间代价为 O (lg n )。

堆结构和堆排序算法的简单Java实现

/**
 * 堆支持的公共方法
 */
public interface Heap {
    public int[] toArray();

    public int[] toHeapArray();

    /**
     * 优先级队列支持的四个操做
     */
    public int head();

    public int extractHead();

    public void changeKey(int i, int key);

    public void add(int k);
}
/**
 * 堆结构的公共部分实现
 */
public abstract class AbstractHeap implements Heap {
    private static final int DEFAULT_CAPACITY = 10;
    protected int size;                                 // 堆中元素的个数
    protected int length;                               // 数组中元素的个数
    protected int[] elements;

    protected AbstractHeap() {
        this(DEFAULT_CAPACITY);
    }

    protected AbstractHeap(int len) {
        this.elements = new int[len];
        this.length = len;
        this.size = 0;
    }

    /**
     * 接受数组类型的参数后直接建堆
     */
    protected AbstractHeap(int[] array) {
        this.elements = Arrays.copyOf(array, array.length);
        this.length = array.length;
        buildHeap();
    }

    protected int parent(int i) {
        return (i - 1) >> 1;
    }

    protected int left(int i) {
        return (i << 1) + 1;
    }

    protected int right(int i) {
        return (i + 1) << 1;
    }

    /**
     * 维持堆特性:最大堆与最小堆除了比较部分,其他代码都是相同的,故将比较部分抽取出来留给具体的实现
     */
    protected void heapify(int i) {
        int l = left(i);
        int r = right(i);
        int index;
        if (l < size && heapifyLogic(l, i))
            index = l;
        else
            index = i;
        if (r < size && heapifyLogic(r, index))
            index = r;
        if (index != i) {
            swap(index, i);
            heapify(index);
        }
    }

    protected void buildHeap() {
        size = length;
        for (int i = (size >> 1) - 1; i >= 0; i--)
            heapify(i);
    }

    protected abstract boolean heapifyLogic(int a, int b);

    @Override
    public int head() {
        if (size == 0)
            throw new IndexOutOfBoundsException("heap underflow");
        return elements[0];
    }

    @Override
    public int extractHead() {
        if (size == 0)
            throw new IndexOutOfBoundsException("heap underflow");
        int head = head();
        elements[0] = elements[size - 1];
        size--;
        heapify(0);
        return head;
    }

    @Override
    public int[] toArray() {
        return Arrays.copyOf(elements, length);
    }

    @Override
    public int[] toHeapArray() {
        return Arrays.copyOf(elements, size);
    }

    protected void swap(int i, int j) {
        AlgorithmUtil.swap(elements, i, j);
    }
}
/**
 * 最大堆
 */
public class MaxHeap extends AbstractHeap {

    public MaxHeap() {
        super();
    }

    public MaxHeap(int len) {
        super(len);
    }

    public MaxHeap(int[] array) {
        super(array);
    }

    /**
     * 最大堆特性:要求子结点的值不大于其父结点
     */
    @Override
    protected boolean heapifyLogic(int a, int b) {
        return elements[a] > elements[b];
    }

    @Override
    public void changeKey(int i, int key) {
        if (key < elements[i])
            throw new IllegalArgumentException("new key is smaller than current key");
        elements[i] = key;
        while (i > 0 && elements[parent(i)] < elements[i]) {
            swap(i, parent(i));
            i = parent(i);
        }
    }

    @Override
    public void add(int k) {
        if (size == length)
            throw new IndexOutOfBoundsException("heap overflow");
        size++;
        elements[size - 1] = Integer.MIN_VALUE;
        changeKey(size - 1, k);
    }

    /**
     * 堆排序
     */
    public void heapSort() {
        for (int i = length - 1; i > 0; i--) {
            swap(0, i);
            size--;
            heapify(0);
        }
    }
}
/**
 * 最小堆
 */
public class MinHeap extends AbstractHeap {
    public MinHeap() {
        super();
    }

    public MinHeap(int len) {
        super(len);
    }

    public MinHeap(int[] array) {
        super(array);
    }

    /**
     * 最小堆特性:要求子结点的值不小于其父结点
     */
    @Override
    protected boolean heapifyLogic(int a, int b) {
        return elements[a] < elements[b];
    }

    @Override
    public void changeKey(int i, int key) {
        if (key > elements[i])
            throw new IllegalArgumentException("new key is larger than current key");
        elements[i] = key;
        while (i > 0 && elements[parent(i)] > elements[i]) {
            swap(i, parent(i));
            i = parent(i);
        }
    }

    @Override
    public void add(int k) {
        if (size == length)
            throw new IndexOutOfBoundsException("heap overflow");
        size++;
        elements[size - 1] = Integer.MAX_VALUE;
        changeKey(size - 1, k);
    }
}

练习

6.2-2

最小堆伪码

MIN-HEAPIFY(A, i)
1  l = LEFT(i)
2  r = RIGHT(i)
3  if l <= A.heap-size and A[l] < A[i]
4      smallest = l
5  else
6      smallest = i
7  if r <= A.heap-size and A[r] < A[smallest]
8      smallest = r
9  if smallest != i
10     exchange A[i] with A[smallest]
11     MIN-HEAPIFY(A, smallest)

6.2-5

使用迭代结构改写 MAX-HEAPIFY 过程

MAX-HEAPIFY-ITERATOR(A, i)
1  largest = -1
2  while largest != i
3      l = left(i)
4      r = right(i)
5      if l <= A.heap-size and A[l] > A[i]
6          largest = l
7      else
8          largest = i
9      if r <= A.heap-size and A[r] > A[largest]
10         largest = r
11     if largest != i
12         exchange A[largest] with A[i]
13         i = largest
14         largest = -1
相关文章
相关标签/搜索