堆排序

堆排序的复杂度是 O(nlogn) O ( n l o g n ) ,因为使用了一种特殊的数据结构,因此称之为堆。后面将会解释。


定义:设 T=(V,E) T = ( V , E ) 是一棵完全二叉树,映射 a:VM a : V → M 将树中的节点映射的一个有序集合 M M ,每个顶点的 a(u) 都在集合 M M 中。

解释一下,V 表示顶点的集合 E E 表示边集合

1. 堆特征

对某个顶点 uV,如果其直接后代节点小于等于顶点 u u 上的值,即

vV(u,v)Ea(u)a(v)

则称其具备堆特征(heap property)。上面用记号 (u,v) ( u , v ) 这种写法来表示一条边,后面类推。

说通俗点,就是说二叉树中任意一个节点值都大于等于它的孩子节点值,则该顶点具备了堆特征。所以堆特征,是对于一个节点来说的,它是节点的性质。

2. 堆

如果树 T T 中的所有顶点都具备堆特征,则称 T 是一个堆(heap)。即

(u,v)Ea(u)a(v) ∀ ( u , v ) ∈ E : a ( u ) ≥ a ( v )

3. 半堆

如果树 T T 中除了根节点外的所有顶点都具备堆特征,则称 T 是一个半堆(semi-heap)


这里写图片描述
图1 含有10个节点的堆

需要注意的是,叶节点属于平凡情况,本身固有 堆特征

4. 堆排序

在实现中,并不需要借助指针来表示树结构,因为完全二叉树可以使用数组高效的表示。

4.1 堆排序算法


这里写图片描述 这里写图片描述 这里写图片描述
 (a)        (b)        (c)
这里写图片描述 这里写图片描述
(d)      (e)
图2 从堆中取最大元素并将剩余节点恢复成堆

如果序列被调整为堆,则可立即从根节点取得一个最大元素(图2(a))。为了能够获取下一个最大值,剩余节点必须要重新调整为一个新的堆。

重新调整可以按照下面的方式进行:

b b 是深度最大的一个叶节点,为了方便,选取最后一个节节点,即最下层的最右边那个。把 b的值写入根节点,然后删除 b b ,参考图2(b)。这时候树变成了一个半堆,因为根节点已经不具备堆特征了。


将半堆调整为堆是相当容易的。具体做法如下。

如果根节点具备堆特征,什么都不用做,否则将其与最大后代(设为 v)交换,如图2(c)。这时候很有可能 v v 也丢失了堆特征。同样的,可以将以 v 为根节点的半堆调整为堆。当顶点都具备了堆特征后,这个过程就停止,最终的情况是到达了叶节点,因为叶节点固有堆特征。

这里把这个过程称之为 sink(v)


4.2 sink 算法

sink(v) 算法的目的是把半堆调整为堆。

输入:根节点为 v v 的半堆
输出:堆
算法:

while not hasHeapProperty(v):
    选择最大后代 w
    exchange a(v) and a(w)
    v = w

4.3 buildHeap算法

sink算法也可以将任意的一个树调整为堆。自底向上,针对树中内节点(不是叶节点的节点)调用sink算法,当然,因为叶节点固有堆特征,因此可以忽略而不必再调用sink。所有不是叶节点的节点,通常称之为内节点

输入:任意一棵完全二叉树 T,深度为 d(T) d ( T )
输出:堆
算法:

for i = d(T)-1 to 0:    # 从倒数第二层开始
    for v in d(v) == i: # 从右往左数
        sink(v)

4.4 heapSort算法

输入:一棵完全二叉树 T T ,根节点为 r
输出:降序排列的数组。
算法:

buildHeap
while not isLeaf(r):
    output a(r)
    b = getLastVertex()
    a(r) = a(b)
    delete b
    sink(r)
output a(r)

4.5 实现


这里写图片描述
图3 用数组表示的含有10个节点的完全二叉树

对于用数组表示的完全二叉树来说,具有以下性质。

  • 根节点存储在索引为 0 的位置
  • 位置为 v v 的节点的两个后代的位置分别是 2v+1 2v+2 2 v + 2
  • 位置 0,...,n/21 0 , . . . , n / 2 − 1 的节点是内节点,位置 n/2,...,n1 n / 2 , . . . , n − 1 都是叶子节点

在实现这个算法时,可以借助一些技巧而不使用任何额外的空间。在heapSort过程中,a(r)并不需要真的输出,实际编写过程中,把它存放到最后一个被删除的叶节点中。删除叶节点 b b 的意思只是表示在调整堆的过程中叶节点b不用再考虑。

总结一下heapSort:

  • 输出根节点的值
  • 选择最后一个叶节点 b b
  • b 的值写入根
  • 删除 b b <script type="math/tex" id="MathJax-Element-38">b</script>
  • sink(r)

5. C代码实现

void exchange(int a[], int i, int j){
    int tmp = a[i];
    a[i] = a[j];
    a[j] = tmp;
}

void sink(int a[], int v, int n){
   int w = 2*v + 1;// 第一个直接后代
   while(w < n){
       if(w+1 < n && a[w+1] > a[w]) ++w;
       if(a[v] >= a[w]) return; // 具备堆特征
       exchange(a, v, w);
       v = w;
       w = 2*v + 1;
   }
}

void buildHeap(int a[], int n){
    for(int v = n/2 - 1; v >= 0; --v){
        sink(a, v, n);
    }
}

void helpSort(int a[], int n){
    buildHeap(a, n);
    while(n > 1){
        --n;
        exchange(a, 0, n);
        sink(a, 0, n);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值