堆排序

堆排序(HeapSort),与归并排序一样,但不同于插入排序的是,堆排序的时间复杂度是O(nlgn),空间复杂度是O(n)。而与插入排序相同,但不同于归并排序的是,堆排序同样具有空间原址性:任何时候都只需要常数个额外的元素空间存储临时数据。

堆排序引入的算法设计技巧:使用一种我们称为“堆”的数据结构来进行信息管理。堆不仅用于堆排序中,而且它也可以构造一种有效的优先队列。

虽然“堆”这一词源自堆排序,但是目前它已经被引申为“垃圾收集存储机制”,例如在Java和Lisp语言中所定义的。强调一下,这里使用的堆不是垃圾收集存储,而是堆数据结构。

1 堆

1.1 堆的定义

(二叉)堆是一个数组,它可以被看成一个近似的完成二叉树。树上的每一个结点对应数组的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。表示堆的数组A两个属性:A.length表示给出元素的个数,heapSize表示有多少个堆元素存储在该数组中。也就是说,虽然A[0..A.length-1]可能存有数据,但只有A[0..A.heapSize]中存放的是堆的有效元素,这里,

0 <= A.heapSize <= A.length

树的根节点是A[0]

1.2 最大堆和最小堆

二叉堆可以分为两种形式:最大堆和最小堆。在这两种堆中,节点的值都是满足堆的性质,但一些细节定义则有所差异。

在最大堆中,最大堆性质是指除了根以外的所有节点i都要满足:

A[parent(i)] >= A[i]

也就是说,某个节点的值至多与其父结点一样大。因此,堆的最大元素存放在根节点中;并且,在任一子树中,该子树所包含的所有节点的值都不大于该子树根节点的值。

最小堆的组织方式正好相反:最小堆性质是指除了根以外的所有节点i都有

A[parent(i)] <= A[i]

最小堆中的最小元素存放根节点中。

在堆排序算法中,我们使用的是最大堆,源于最小堆可以自行研究。

2 结点

树的根结点是A[0]。这样给定一个下标i,我们就很容易计算得到它的父结点、左结点和右结点的下标。我们使用数组搭建堆的结构,这里运用到了HeapSort类。HeapSort类代码如下。

//
//  HeapSort.swift
//  HeapSort
//
//  CSDN:http://blog.csdn.net/y550918116j
//  GitHub:https://github.com/937447974/Blog
//
//  Created by yangjun on 15/11/15.
//  Copyright © 2015年 阳君. All rights reserved.
//

import Foundation

/// 堆排序
class HeapSort: NSObject {

    /// 数组
    private var list:Array<Int> = []
    /// 堆长度
    private var heapSize = 0

    //MARK: 初始化
    init(list:[Int]) {
        self.list = list
    }

    // MARK: - 父节点
    private func parent(i:Int) -> Int {
        return (i + 1) / 2 - 1
    }

    // MARK: 左节点
    private func left(i:Int) -> Int {
        return 2 * i + 1
    }

    // MARK: 右节点
    private func right(i:Int) -> Int {
        return 2 * i + 2
    }

}

这里我们使用数组list记录堆,并使用heapSize记录堆的有效长度。

3 维护堆的性质

在HeapSort添加maxHeapify方法体。

// MARK: 维护最大堆
private func maxHeapify(i:Int) {
    // 最大堆的基础算法,使用“逐级下降”原理,时间复杂度O(h树高)
    let l = self.left(i)
    let r = self.right(i)
    var largest = i
    // 找出最大的那个数
    if l < heapSize && self.list[l] > self.list[largest] {
        largest = l
    }
    if r < heapSize && self.list[r] > self.list[largest] {
        largest = r
    }
    // 是否需要改变
    if largest != i {
        self.exchange(i, change: largest)
        self.maxHeapify(largest)
    }
}

// MARK: 交换节点
private func exchange(i:Int, change:Int) {
    let tempI:Int = self.list[i]
    self.list[i] = self.list[change]
    self.list[change] = tempI
}

maxHeapify是用于维护最大堆性质的重要过程。它的输入为一个下标i。在调用maxHeapify的时候,我们假定根结点的left(i)和right(i)的二叉树都是最大堆。但这时list[i]有可能小于其孩子,这样就违背了最大堆的性质。maxHeapify通过让list[i]的值在最大堆中“逐级下降”,从而使得下标i为根结点的子树重新遵循最大堆的性质。

4 建堆

我们可以用自底向上的方法利用maxHeapify把一个大小为n=list.count的数组list[0…n-1]转换为最大堆。子数组list[(n/2)+1..n]中的元素都是树德叶结点。每个叶结点都可以看成只包含含一个元素的堆。过程buildMaxHeap对树中的其他结点都调用一次maxHeapify。

在HeapSort中添加方法buildMaxHeap。

// MARK: - 建立最大堆
private func buildMaxHeap() {
    // 时间复杂度O(n)
    self.heapSize = self.list.count
    for(var i = self.list.count / 2; i >= 0; i--) {
        self.maxHeapify(i)
    }
}

5 堆排序算法

初始时候,堆排序算法利用buildMaxHeap将list建成最大堆。因为数组中的最大元素总是在根结点list[0]中,我们把她与list[n-1]进行互换,我们可以让该元素放到正确的位置。

这时候,如果我们从堆中去掉结点n-1(这里通过减少heapSize的值来实现),剩余的结点中,原来根的孩子结点仍然是最大堆,而新的根结点可能会违背最大堆的性质。为了维护最大堆的性质,我们要做的就是调用maxHeapify(0),从而在list[0, heapSize]构造一个新的最大堆。

堆排序算法会不断重复这一过程,直到堆的大小从heapSize-1降到0。

在HeapSort中的方法heapSort实现如下。

// MARK: - 堆排序
/// 堆排序
///
/// - returns: [Int]
func heapSort() -> [Int]{
    // 排序结果为从小到大
    self.buildMaxHeap()
    let heap = self.list // 存储最大堆
    for(var i = self.heapSize - 1; i > 0; i--) {
        // 最后一个位置保存堆最大的数
        self.exchange(i, change: 0)
        self.heapSize--
        self.maxHeapify(0)
    }
    let heapSort = self.list
    self.list = heap // 恢复最大堆
    return heapSort
}

6 优先队列

堆排序时一个优先的算法,但是在实际应用中,还有更快的排序算法。尽管如此,堆这一数据结构仍然有很多应用。这里我们介绍堆的一种应用优先队列。和堆一样,优先队列也有两种形式:最大优先队列和最小优先队列。这里只介绍基于最大堆的最大优先队列。

6.1 返回最大的元素

在HeapSort中使用方法maxiMum实现返回最大的元素。

// MARK: 返回最大值
/// 返回最大值
///
/// - returns: Int?
func maxiMum() -> Int? {
    return self.list.first
}

6.2 返回最大元素的同时并删除

在HeapSort中使用方法maxiMum实现返回最大元素的同时并删除。

// MARK: - 返回最大值的同时删除最大值
/// 返回最大值的同时删除最大值
///
/// - returns: void
func extractMax() -> Int? {
    let max = self.list.first
    if max != nil {
        // 删除最大值,后重新维护最大堆
        self.list[0] = self.list.last!
        self.list.removeLast()
        self.heapSize = self.list.count
        self.maxHeapify(0)
    }
    return max
}

6.3 插入一个元素

在HeapSort中使用方法maxiMum实现插入一个元素,插入元素后,堆还是最大堆。

// MARK: 插入一个元素
/// 插入一个元素
///
/// - parameter x : 要插入的元素
///
/// - returns: void
func insert(x:Int) {
    self.list.append(x)
    self.buildMaxHeap()

}

5 测试

5.1 测试代码

var list:[Int] = []
for(var i = 0; i < 10; i+=2) {
    list.append(i)
}
for(var i = 9; i > 0; i-=2) {
    list.append(i)
}
let heap = HeapSort(list: list)

print("堆排序:\(heap.heapSort())")

print("堆最大值:\(heap.maxiMum())")

print("提取堆最大值并删除:\(heap.extractMax())")
print("堆排序:\(heap.heapSort())")

heap.insert(2)// 插入一个元素
print("堆排序:\(heap.heapSort())")

5.2 结果

堆排序:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
堆最大值:Optional(9)
提取堆最大值并删除:Optional(9)
堆排序:[0, 1, 2, 3, 4, 5, 6, 7, 8]
堆排序:[0, 1, 2, 2, 3, 4, 5, 6, 7, 8]

 


其他

源代码

https://github.com/937447974/Algorithms

参考资料

算法导论

文档修改记录

时间描述
2015-11-15算法完成
2015-11-16博文完成

版权所有

CSDN:http://blog.csdn.net/y550918116j

GitHub:https://github.com/937447974/Blog

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值