堆排序 --- 排序算法3

纸上得来终觉浅,绝知此事要躬行.  看完了这篇博客之后 , https://blog.csdn.net/u014600626/article/details/103752297 , 自然要自己实现一下看看效果了.

     堆排序,
     1.把数组初始化成一个大顶堆,大顶堆: 任何一个根节点>左孩子 && 根节点>右孩子
     2.将堆顶元素与最后一个元素交换,堆的数量-1,需要循环N次,
     3.将剩余堆中元素重新构建,每次重构需要logN,
     4.重复2,3步,直到堆中为空.

- (void)sortAction {
    /*
     堆排序,
     1.把数组初始化成一个大顶堆,大顶堆: 任何一个根节点>左孩子 && 根节点>右孩子
     2.将堆顶元素与最后一个元素交换,堆的数量-1,需要循环N次,
     3.将剩余堆中元素重新构建,每次重构需要logN,
     4.重复2,3步,直到堆中为空.
     */
    int num = 50000;
    int a[num];

    for (int i = 0; i<num; i++) {
        a[i] = arc4random()%(num*10);
    }

    // 初始化堆, 假设有一种最差的情况,初始化的时候堆顶为最小元素, 
    // 此时需要把最小元素放到叶子节点上,每次循环都会把最小元素下移一层,需要循环的次数为树的深度
    int leftNum = num;
    while (leftNum>0) {
        // 把最小的元素向下移动一层,循环次数为完全二叉树的深度
        [self initHeap:a endIndex:num];
        leftNum /=2;
    }

    leftNum = num;
    while (leftNum>0) {
        
        // 交换堆顶元素与最后一个,
        int currentMax = a[0];
        a[0] = a[leftNum-1];
        a[leftNum-1] = currentMax;
        leftNum--;
        // 重新构建堆
        [self rebuildHeap:a endIndex:leftNum];
        
    }
    
    for (int i = 0; i<num; i++) {
        printf("%d -> ",a[i]);
    }
    printf("排序完成\n");

}

// 初始化堆结构, 从第一个非叶子节点开始逐个倒序遍历, 完成堆结构搭建
- (void)initHeap:(int [])heap endIndex:(int)endIndex {
        
    // 从第一个有叶子的节点开始
    for (int i = endIndex/2-1; i>=0; i--) {
        int left = 2*i+1;
        int right = 2*i+2;
        // 保证 i>左叶子,
        if (heap[i]<heap[left]) {
            int temp = heap[i];
            heap[i] = heap[left];
            heap[left] = temp;
        }
        // right节点可能是没有值的
        if (right>=endIndex) {
            continue;
        }
        
        // 保证i>右叶子
        if (heap[i]<heap[right]) {
            int temp = heap[i];
            heap[i] = heap[right];
            heap[right] = temp;
        }
        
    }

}

/// 重构堆, 把堆顶元素放到堆中的合适位置
- (void)rebuildHeap:(int [])heap endIndex:(int)endIndex {

    // 堆在前面已经初始化完成了,但是刚才交换了堆顶元素,需要重新构建堆,
    // 如何构建?堆顶元素是一个比较小的值,需要重新再堆中找合适的位置
    // 从第一个根节点开始构建,然后构建左子树或者右子树,
    // 比如堆顶为3, 左节点为5,右节点为7, 此时把堆顶和右节点交换, 重构右子树,左子树不需要处理, 这样时间复杂度度就是O(logN)
    int nextIndex = 0; // 下一个要重构的节点
    // 最后一个带叶子的节点endIndex/2-1,
    for (int i = 0; i<=endIndex/2-1; i=nextIndex) {
        int left = 2*i+1;
        int right = 2*i+2;
        
        // 找出左右节点中的较大者,默认左边大
        nextIndex = left;
        if (right<endIndex && heap[left] < heap[right]) {
            nextIndex = right;
        }

        if (heap[i]<heap[nextIndex]) {
            int temp = heap[i];
            heap[i] = heap[nextIndex];
            heap[nextIndex] = temp;
        } else {
            // 说明 已经满足条件了, 节点>左节点 && 节点>右节点, 堆顶元素找到了合适的位置, 结束循环
            break;
        }

        
    }
    
    
}

时间复杂度分析, 初始化堆需要N/2 * logN次;  循环2,3步,需要N*logN次, 总体是N*logN, 所以是O(NlogN), 比起O(N^2)的复杂度,这样效率明显改进了.

  

堆这个结构还有一个常用的地方, 比如数组长度超级长,长到无法用内存完全读取, 需要获取前K个较大的数字, 如何做?

1.声明一个大小为K的小顶堆,

2.然后从磁盘上循环读取数组, 比较堆顶元素和读入数字的大小,

3.堆顶元素>读入数字, 继续; 堆顶元素<读入数字, 堆顶元素pop, 读入数字入堆;

4.循环2,3步直到数组遍历完, 此时堆中的元素就是前K个较大的数字.

 

这里没有把关于堆的代码抽象出来, 在上面代码的基础上封装成一个数据结构,可以再任意地方使用,
git地址: https://github.com/guochaoshun/ios_heap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值