swift算法之排序:(四)堆排序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lin1109221208/article/details/90694015

1、概述

堆排序是指利用堆这种数据结构所设计的一种排序算法。

堆是一个近似完全二叉树,并同时满足堆积的性质:子节点的键值或索引总是大于/小于它的父结点。

堆又分为 大顶堆 和小顶堆,大顶堆的特点是 该完全二叉树的根节点比其左右节点都要大,小顶堆的特点是 在小顶堆中根节点比左右子节点的值都要小。

1)大顶堆

假设根节点的编号是 i ,那么该根节点的左孩子的编号是 2i,右孩子的编号是 2i+1。

根节点和子节点的关系为:k(i)>=k(2i)、k(i)>=k(2i+1) i>=1

2)小顶堆

假设根节点的编号是 i ,那么该根节点的左孩子的编号是 2i,右孩子的编号是 2i+1。

根节点和子节点的关系为:k(i)<=k(2i)、k(i)<=k(2i+1) i>=1

扫描二维码关注公众号,回复: 6616131 查看本文章

2、算法原理

思想:将无序的序列构建成大顶堆/小顶堆,最后将排好序的堆转换成大顶堆/小顶堆的层次遍历的序列,即为所求

步骤:

1)最大堆调整Max_Heapify:将堆的末端子节点作调整,使得子结点永远小于父结点,其目的是保持最大堆的性质,是创建最大堆的核心子程序

2)创建最大堆Build_Max_Heap:将堆所有数据重新排序。其目的是将一个数组改造成最大堆,接受数组和堆大小两个参数,Build-Max-Heap 将自下而上的调用 Max-Heapify 来改造数组,建立最大堆

3)堆排序HeapSort:移除位在第一个数据的根节点,并做最大堆调整的递归运算

3、举例

以 [62, 88, 58, 47, 62, 35, 73, 51, 99, 37, 93]为例

1)先将上述序列从左往右存入完全二叉树中

2)构建 大顶堆

(1)先对子树进行调整,将其调整为大顶堆。找到完全二叉树最下方最小的以62为根节点的子树,先比较62的两个节点,93最大,由于62<93,所以交换62与93的位置。该子树的大顶堆构建完成

(2)以(1)的方式调整以47为根节点的子树,调整完成后,再分别调整以88和58为根节点的子树

(3)对整棵树进行调整,最终大顶堆构建完成

3)对大顶堆进行排序

(1)将大顶堆的第一个值(整个序列中最大的值)与大顶堆的最后一个值进行交换。

(2)交换后,最后一个值作为整个序列中最大值,将此值从大顶堆中剔除,再将剩下的元素再次进行调整为大顶堆。

4、算法实现

从小到大的排序思路:把一堆数字调整成大顶堆->堆顶元素和末尾元素交换->去掉末尾元素,继续大顶堆调整->重复以上动作

堆是通过数组实现的:

     1)父结点i的左子节点的位置---(2*i+1)

     2)父结点i的右子节点的位置---(2*i+2)

     3)子结点i的父结点的位置---floor((i-1)/2)  floor函数的作用是向下取整

    func heapSort(_ array : inout Array<Int>){
        //1、构建大顶堆
        
        //从二叉树的一边的最后一个结点开始
        for i in (0...(array.count/2-1)).reversed() {
            //从第一个非叶子结点从下至上,从右至左调整结构
            SortSummary.adjustHeap(&array, i, array.count)
        }
        //2、调整堆结构+交换堆顶元素与末尾元素
        for j in (1...(array.count-1)).reversed() {
            //将堆顶元素与末尾元素进行交换
            array.swapAt(0, j)
            //重新对堆进行调整
            SortSummary.adjustHeap(&array, 0, j)
        }
    }

    //调整大顶堆(仅是调整过程,建立在大顶堆以构建的基础上)
    func adjustHeap(_ array : inout Array<Int>, _ i : Int, _ length : Int){
        var i = i
        //取出当前元素i
        let tmp = array[i]
        var k = 2*i+1
        //从i结点的左子节点开始,也就是2i+1处开始
        while k < length {
            //如果左子节点小于右子节点,k指向右子节点
            if k+1<length && array[k]<array[k+1]{
                k += 1
            }
            //如果子节点大于父结点,将子节点值赋给父结点,不用进行交换
            if array[k]>tmp {
                array[i] = array[k]
                //记录当前结点
                i = k
            }else{
                break
            }
            //下一个结点
            k = k*2+1
        }
        //将tmp值放到最终的位置
        array[i] = tmp
    }

5、时间复杂度

1)在构建堆的过程中,由于是是完全二叉树从最下层最右边非终端节点开始构建,将它与子节点进行比较,对于每个非终端节点而言,最多进行两次比较和交换操作,因此构建堆的时间复杂度为O(n)

2)在整个排序过程中,第 i 次去堆顶记录重建堆需要时间为logi ,并且需要取 n - 1次堆记录,所以重建堆的时间复杂度为O(nlogn)。

3)堆的时间复杂度为O(nlogn)

github代码

注:排序的具体实现代码在 SortSummary.swift 文件里 调用是在 ViewController.swift

猜你喜欢

转载自blog.csdn.net/lin1109221208/article/details/90694015