八大排序算法(4) 堆排序

基本思想

是对简单选择排序的改进,是一种树形结构的排序。
利用堆的特性,快速选择出序列中的最大最小元素。

堆的定义:
这里写图片描述

用树表示更加直观:
这里写图片描述
即:父节点 不大于/不小于 其子节点的完全二叉树。
降序时称为 大堆顶,升序时称为 小堆顶。

这样的堆,其堆顶就是整个序列中最大/最小的元素。
不断的取出堆顶、将剩下序列重组成堆的过程就叫堆排序。

树采用顺序存储:{96,83,27,38,11,09}, {12,36,24,85,47,30,53,91}
序列是树的顺序存储,因此 节点、父、子 在序列中的位置为 i,2i,2i+1, 反映到序列中的 index 为 i - 1, 2*i-1, 2*i。

重点

 1. 初始化时将整个序列转换成堆  
 2. 取出堆顶后,将剩下的元素快速组成堆

先说第二点的算法思路:

 1. 取出堆顶后 i 后,将堆底元素取出补为堆顶
 2. 检查到堆被破坏,将 堆顶 与其子节点中小的元素交换
 3. 对交换后的子树重复2

示例图:
这里写图片描述

再说第一点的算法思路:

 1. 假设 长度 n 的 序列 k 为堆
 2. 最后一个节点是 节点 n/2 的子节点,那么从 n/2 开始向上筛选
 3. 从 父子节点 中选举最小的为 父节点
 4. 对交换后的子节点 重复 3,直到叶子节点
 5. 检查下一个 n/2 - 1 ,重复 3, 直到 0

示例图:
这里写图片描述

代码:

void printList(int *l, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", l[i]);
    }
    printf("\n");
}

/*
 * 修复堆
 * 
 * 从 父子 节点中选举最小的为父节点。
 * 如果最小的不是父节点的话,那么交换过的子节点也需要检查。
 */
void adjustHeap(int *l, int n, int topIndex, int adjustIndex) {
    // 要检查的元素 index
    int minIndex = adjustIndex;
    // 堆顶不为 0 时,计算 adjustIndex 的子节点 index 要考虑到偏移
    int lcIndex = 2 * (adjustIndex - topIndex + 1) - 1 + topIndex;
    int rcIndex = 2 * (adjustIndex - topIndex + 1) + topIndex;

    // 跟 左子节点比较,替换 小的 index
    if (lcIndex < n && l[minIndex] > l[lcIndex] ){
        minIndex = lcIndex;
    }
    // 跟 右子节点比较,替换 小的 index
    if (rcIndex < n && l[minIndex] > l[rcIndex]){
        minIndex = rcIndex;
    }
    // 如果小的index 不是 堆顶的话,那么换顶,然后检查子堆
    if ( minIndex != adjustIndex ){
        int k = l[minIndex];
        l[minIndex] = l[adjustIndex];
        l[adjustIndex] = k;
        // 子堆 minIndex 换了 堆顶,要检查一下
        adjustHeap(l, n, topIndex, minIndex);
    }
}

/*
 * 创建堆
 * 
 * 1、假设 长度 n 的 序列 k 为堆
 * 2、最后一个节点为 n / 2 的子节点,从 n / 2 开始向上筛选
 * 3、从 父子节点中选举最小的为 父节点
 * 4、检查下一个 n / 2 - 1 ,重复 3, 直到 0
 */
void buildHeap(int *l, int n) {
    for (int i = n / 2; i > 0;i-- ) {
        // 节点 i 对应的 index 是 i - 1, 子节点 index  2 * i - 1、 2 * i
        int min = i - 1;
        int lc = 2 * i - 1;
        int rc = 2 * i;

        // 选举最小的为 堆顶
        if (l[i - 1] > l[lc]){
            min = lc;
        }
        if ( 2*i < n && l[min] > l[rc]){
            min = rc;
        }

        if ( min != i-1 ) {
            int k = l[i - 1];
            l[i - 1] = l[min];
            l[min] = k;
            // 子堆 min 换了堆顶,要检查堆 min
            adjustHeap(l, n, 0, min);
        }
    }
}


int main() {
    int list[50] = { 10, 7, 1 , 8, 5, 12, 6, 3, 9 };
    int n = 9;
    printList(list, n);

    // 创建小堆顶
    buildHeap(list, n);
    printList(list, n);

    printf("======\n");

    // 建好堆后,初始堆顶就是最小的
    for (int i = n - 1; i > 0; i--) {
        //将堆顶换到堆底.  这是小堆顶,每次都将最小的元素换到堆底,然后 堆缩小 1,最后得出降序
        int exchange = list[0];
        list[0] = list[i];
        list[i] = exchange;
        // 堆 0 被破坏,执行检查
        adjustHeap(list, i, 0, 0);
        printList(list, n);
    }


    system("pause");
    return 0;
}

以上

原文链接 http://blog.csdn.net/u011546766/article/details/74045324

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值