要期中考了……我真的是什么也不会啊,书都没看过TAT。

好吧整理一下二叉堆,这里就以最大堆为例好了。

首先二叉堆其实是一棵CBT,满足父节点的键值大于左右子节点的键值(wikipedia把这个叫键值,其实我也不知道该叫什么),并且左右子树也是一个二叉堆。二叉堆一般是用数组来存储的(斜堆、左式堆是用树的结构)。

如果根节点在数组下标1的位置的话,数组下标n的位置的节点的儿子坐标是2n和2n+1。如果根节点在下标为x的位置的话,记住这个结论就可以求得此情况下数组下标为n的位置的儿子的下标,我们可以先假设将根节点挪到下标为1的位置上,相当于整个堆向左移(x - 1)个位置,那么原来下标为n的位置移动后的下标是(n - (x - 1)),代入之前的公式可以求得移动后,下标为(n - (x - 1))的位置的两个儿子此时的下标为2(n - (x - 1))和2(n - (x - 1))+ 1,最后再将整个堆向右移(x - 1)个位置,得到在原先的堆中,下标为n的位置的节点的儿子的下标为2(n - (x - 1))+ (x - 1)和2(n - (x - 1))+ (x - 1)+ 1,即2n - x + 1和2n - x + 2。

如果根节点在数组下标为1的位置的话,数组下标为n的位置的节点的父节点坐标是floor(n / 2),这里的floor是下取整函数,用上面同样的思想可以求得当根节点下标为x时,下标为n位置的节点的父节点下标为floor((n + x - 1)/ 2)。

如果是根节点下标为1的d堆,则下标位置为n的节点的父节点和最左子节点及最右子节点的下标值分别为:floor((n + d - 2)/ d),(n - 1)d + 2,nd + 1。更一般的结果,节点下标为n的第k个孩子的下标为(n - 1)d + 1 + k。

如果二叉堆有n个节点,则堆的高度为floor(logn),这个和二叉树的性质是一样的。如果是d堆的话,则有floor(logd(n(d - 1)))。

如果二叉堆有n个节点,那么下标大于floor(n / 2)的位置的节点(即堆在数组中最后一个节点的父节点)均为叶节点,证明这个结论可以用反证法,假设下标大于floor(n / 2)的节点中有一个不是叶节点,那么这个节点的子节点的存储位置的下标必然大于n,而这个堆在数组中的最大下标已知是n,产生矛盾,这样就可以证明这个结论。而最大堆中的最小值也一定在某个叶节点中,因为非叶节点的子节点值一定比它自身小。

由无序数组构建堆

然后讲一下构建堆的操作。

由一个无序数组构建堆最直观的方法是将其一个个插入一个空树中,这样的方法是最low的,时间复杂度是O(NlgN)。

其次是利用Max-Heapify算法。Max-Heapify算法就是对任意一个节点,假设它的左右子树都已经满足堆序(如果无左右子树则认为其已经满足堆序,如果只有左子树则比较左子树即可),那么只要比较左右子树的根节点和自身的大小,如果三个节点也满足堆序那么就不需要做任何操作,如果不满足就交换三者中两者的位置使三者满足堆序并对相应的根节点被改变的那棵子树使用Max-Heapify算法进行调整,这样的算法如果用递归实现的话时间复杂度可以表示为 T ( N ) <= T (2 N / 3) + Θ (1)(因为对子树进行递归调用,子树最坏情况大约为2n/3个节点,即原来一整棵树的最后一层恰好是左半边全满的情况),解得为O(lgN)。

而如果对树的每一个节点均调用一次Max-Heapify,这样也还是比较浪费,因为可以知道节点总数为n的堆下标大于floor(n / 2)的位置的节点均为叶节点(上面有讲过这一点),叶节点可以认为是一个已经符合要求的二叉树,那么只需对非叶节点调用Max-Heapify就可以了,这样构建堆的时间复杂度为O(N)计算如下:

其中N是节点数,n是节点height。

下面是这种建堆算法的代码:

void Max_Heapify(int H[], int size, int root){
    ;
     + ;

    if(l_child <= size&&r_child <= size){
        if(H[l_child] < H[r_child]&&H[root] < H[r_child]){
            swap(H, r_child, root);
            Max_Heapify(H, size, r_child);
        }
        else if(H[r_child] < H[l_child]&&H[root] < H[l_child]){
            swap(H, l_child, root);
            Max_Heapify(H, size, l_child);
        }
        else{
            return;
        }
    }
    else if(l_child <= size){
        if(H[root] < H[l_child]){
            swap(H, l_child, root);
            Max_Heapify(H, size, l_child);
        }
        else{
            return;
        }
    }
    else{
        return;
    }
}

void Build_Max_Heapify(int H[], int size){
    int i;

    ; i >= ; i--){
        Max_Heapify(H, size, i);
    }
}

插入节点

插入节点的时间复杂度是O(logN),注意循环中如果没有满足跳出循环的条件,tmp要整除2,代码如下:

void Insert(int H[], int size, int val){
    ;

    if(tmp >= MaxSize)
        return;
     >= ){
        ] < val){
            H[tmp] = H[tmp / ];
            tmp = tmp / ;
        }
        else{        break;
        }
    }
    H[tmp] = val;
}

当然插入节点也可以用Max-Heapify来实现:

void Insert_2(int H[], int size, int val){
    int i;

     >= MaxSize)
        return;
    H[size + ] = val;
    ) / ; i >= ; i = i / ){
        Max_Heapify(H, size + , i);
    }
}

删除根节点

删除节点的时间复杂度是O(logN),注意如果最后tmp是某叶节点下标,则直接赋值并跳出循环,代码如下:

void Delete_Root(int H[], int size){
    , val = H[size];

    ){
         +  <= size - 1){
            ] < H[tmp *  + ]&&val < H[tmp *  + ]){
                H[tmp] = H[tmp *  + ];
                tmp = tmp *  +;
            }
             + ] < H[tmp * ]&&val < H[tmp * ]){
                H[tmp] = H[tmp * ];
                tmp = tmp * ;
            }
            else{           break;
            }
        }
         <= size - 1){
            ] > val){
                H[tmp] = H[tmp * ];
                tmp = tmp * ;
            }
            else{           break;
            }
        }
        else{        break;
        }
    }  H[tmp] = val;
}

当然删除节点也可以用Max-Heapify来实现:

void Delete_Root_2(int H[], int size){
    H[] = H[size];
    Max_Heapify(H, size - , );
}

调整节点的值

调整节点的值主要有增大和减小两种情况,增大节点值时,以该节点为根的子树一定符合堆序,所以只要向上延其祖先节点的路径调整堆序即可,减小节点值时,以该节点为根的子树不一定符合堆序,而整棵树的其余部分一定符合堆序,所以只要向下对子树调整堆序即可。代码就不写了,反正就是调用Max_Heapify。

删除任意节点

删除任意节点的算法和调整节点值的算法差不多,将数组最后一个节点的值移动到被删除节点的位置,如果该节点值变大了,则相当于使用增大节点值的算法,反之则相当于使用减小节点值的算法。

当然也可以使用增大节点值的算法将被删除节点的值变为无穷大,则其最终将出现在根节点位置上,最后再调用删除根节点的算法。

void Delete_Node(int H[], int index, int size){
    if(H[index] < H[size]){
         >= ){
            ] < H[size]){
                H[index] = H[index / ];
            }
            else{
                break;
            }
            index /= ;
        }
        H[index] = H[size];
    }
    else{
         <= size - ){
             +  <= size - &&H[index * ] < H[index *  + ]){
                 + ] > H[size]){
                    H[index] = H[index *  + ];
                    index = index *  + ;
                }
                else{
                    break;
                }
            }
            else{
                ] > H[size]){
                    H[index] = H[index * ];
                    index = index * ;
                }
                else{
                    break;
                }
            }
        }
        H[index] = H[size];
    }
}

合并二叉堆

合并操作并不是二叉堆所擅长的,左式堆和斜堆什么的会好一点,斐波那契堆我不会啊……事实上左式堆的插入和删除操作都是通过合并操作来实现的。合并二叉堆的话,有两种方法,一种是将第二个堆一个个的插入第一个堆,算法复杂度为O(MlogN),另一种是直接接在第一个堆的数组后面,再维护,算法复杂度是O(2M + N),这里注意一下直接接在后面依次赋值也是需要时间的,为O(M)。

最后是完整的测试代码:

//
//  main.c
//  Binary Heap
//
//  Created by 余南龙 on 2016/11/18.
//  Copyright © 2016年 余南龙. All rights reserved.
//

#include <stdio.h>
#define MaxSize 100

void swap(int H[], int a, int b){
    int tmp = H[a];

    H[a] = H[b];
    H[b] = tmp;
}

void Max_Heapify(int H[], int size, int root){
    ;
     + ;

    if(r_child <= size){
        if(H[l_child] < H[r_child]&&H[root] < H[r_child]){
            swap(H, r_child, root);
            Max_Heapify(H, size, r_child);
        }
        else if(H[r_child] < H[l_child]&&H[root] < H[l_child]){
            swap(H, l_child, root);
            Max_Heapify(H, size, l_child);
        }
        else{
            return;
        }
    }
    else if(l_child <= size){
        if(H[root] < H[l_child]){
            swap(H, l_child, root);
            Max_Heapify(H, size, l_child);
        }
        else{
            return;
        }
    }
    else{
        return;
    }
}

void Build_Max_Heapify(int H[], int size){
    int i;

    ; i >= ; i--){
        Max_Heapify(H, size, i);
    }
}

int Initial(int H[]){
    , val;

    ){
        scanf("%d", &val);
         == val){
            break;
        }
        else{
            H[++size] = val;
        }
    }

    return size;
}

void Output(int H[], int size){
    int i;

    ; i <= size; i++){
        printf("%d ", H[i]);
    }
    putchar('\n');
}

void Insert(int H[], int size, int val){
    ;

    if(tmp >= MaxSize)
        return;
     >= ){
        ] < val){
            H[tmp] = H[tmp / ];
            tmp = tmp / ;
        }
        else{
            H[tmp] = val;
            break;
        }
    }
    ){
        H[tmp] = val;
    }
}

void Insert_2(int H[], int size, int val){
    int i;

     >= MaxSize)
        return;
    H[size + ] = val;
    ) / ; i >= ; i = i / ){
        Max_Heapify(H, size + , i);
    }
}

void Delete_Root(int H[], int size){
    , val = H[size];

    ){
         +  <= size - ){
            ] < H[tmp *  + ]&&val < H[tmp *  + ]){
                H[tmp] = H[tmp *  + ];
                tmp = tmp *  +;
            }
             + ] < H[tmp * ]&&val < H[tmp * ]){
                H[tmp] = H[tmp * ];
                tmp = tmp * ;
            }
            else{
                H[tmp] = val;
                break;
            }
        }
         <= size - ){
            ] > val){
                H[tmp] = H[tmp * ];
                tmp = tmp * ;
            }
            else{
                H[tmp] = val;
                break;
            }
        }
        else{
            H[tmp] = val;
            break;
        }
    }
}

void Delete_Root_2(int H[], int size){
    H[] = H[size];
    Max_Heapify(H, size - , );
}

void Delete_Node(int H[], int index, int size){
    if(H[index] < H[size]){
         >= ){
            ] < H[size]){
                H[index] = H[index / ];
            }
            else{
                break;
            }
            index /= ;
        }
        H[index] = H[size];
    }
    else{
         <= size - ){
             +  <= size - &&H[index * ] < H[index *  + ]){
                 + ] > H[size]){
                    H[index] = H[index *  + ];
                    index = index *  + ;
                }
                else{
                    break;
                }
            }
            else{
                ] > H[size]){
                    H[index] = H[index * ];
                    index = index * ;
                }
                else{
                    break;
                }
            }
        }
        H[index] = H[size];
    }
}

int main(){
    int H[MaxSize], size, val, index;

    printf("Input an array using -1 as an end:");
    size = Initial(H);
    Build_Max_Heapify(H, size);
    printf("Which number do you want to insert to the Heap:");
    scanf("%d", &val);
    Insert_2(H, size, val);
    size++;
    Output(H, size);
    printf("Delete the root:\n");
    Delete_Root_2(H, size);
    size--;
    Output(H, size);
    printf("Which node do you want to delete:");
    scanf("%d", &index);
    Delete_Node(H, index, size);
    size--;
    Output(H, size);
}

二叉堆复习(包括d堆)的更多相关文章

  1. [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法

    二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...

  2. 《数据结构与算法分析:C语言描述》复习——第五章“堆”——二叉堆

    2014.06.15 22:14 简介: 堆是一种非常实用的数据结构,其中以二叉堆最为常用.二叉堆可以看作一棵完全二叉树,每个节点的键值都大于(小于)其子节点,但左右孩子之间不需要有序.我们关心的通常 ...

  3. 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器

    [摘要] timers模块部分源码和定时器原理 示例代码托管在:http://www.github.com/dashnowords/blogs 一.概述 Timer模块相关的逻辑较为复杂,不仅包含Ja ...

  4. 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器

    目录 一.概述 二. 数据结构 2.1 链表 2.2 二叉堆 三. 从setTimeout理解Timer模块源码 3.1 timers.js中的定义 3.2 Timeout类定义 3.3 active ...

  5. 二叉堆(二)之 C++的实现

    概要 上一章介绍了堆和二叉堆的基本概念,并通过C语言实现了二叉堆.本章是二叉堆的C++实现. 目录1. 二叉堆的介绍2. 二叉堆的图文解析3. 二叉堆的C++实现(完整源码)4. 二叉堆的C++测试程 ...

  6. 二叉堆的实现(数组)——c++

    二叉堆的介绍 二叉堆是完全二元树或者是近似完全二元树,按照数据的排列方式可以分为两种:最大堆和最小堆.最大堆:父结点的键值总是大于或等于任何一个子节点的键值:最小堆:父结点的键值总是小于或等于任何一个 ...

  7. 笔试算法题(46):简介 - 二叉堆 & 二项树 & 二项堆 & 斐波那契堆

    二叉堆(Binary Heap) 二叉堆是完全二叉树(或者近似完全二叉树):其满足堆的特性:父节点的值>=(<=)任何一个子节点的键值,并且每个左子树或者右子树都是一 个二叉堆(最小堆或者 ...

  8. 二叉堆(binary heap)—— 优先队列的实现

    二叉堆因为对应着一棵完全二叉树,因而可以通过线性数组的方式实现. 注意,数组第 0 个位置上的元素,作为根,还是第 1 个位置上的元素作为根? 本文给出的实现,以数组第 1 个位置上的元素作为根,则其 ...

  9. 【算法与数据结构】二叉堆和优先队列 Priority Queue

    优先队列的特点 普通队列遵守先进先出(FIFO)的规则,而优先队列虽然也叫队列,规则有所不同: 最大优先队列:优先级最高的元素先出队 最小优先队列:优先级最低的元素先出队 优先队列可以用下面几种数据结 ...

随机推荐

  1. 配置ssh服务允许root管理员直接登录

    配置ssh服务允许root管理员直接登录 [root@linux-node2 ~]# grep PermitRootLogin /etc/ssh/sshd_config PermitRootLogin ...

  2. 字符串格式化format使用

    顺序传参 '{}....{}'.format(value1, value2) 索引传参 '{0}....{1}'.format(value1, value2) 关键字传参 '{k1}....{k2}' ...

  3. ubuntu下 net core 安装web模板

    ---恢复内容开始--- 今天想试试在Linux用C#开发WebAPI,查了下,要用: dotnet new -t Web 来建立工程,结果我试了下,出来这段: Invalid input switc ...

  4. Java 3-Java 基本数据类型

    Java 基本数据类型 变量就是申请内存来存储值.也就是说,当创建变量的时候,需要在内存中申请空间. 内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据. 因此,通过定义不 ...

  5. studio2.3app签名打包安装失败,找不到签名证书。

    Androidstudio升级到2.3后,打包时和之前不一样了. 如果只选择V2,是未签名的.所以要把V1和V2都打勾.

  6. jQuery操作DOM节点的方法总结

    1.parent():获得当前匹配元素集合中每个元素的父元素,该方法只会向上一级对 DOM 树进行遍历 $('li.item-a').parent().css('background-color', ...

  7. Noip数学整理

    目录 Noip数学整理 序 1 取模相关 2 质数相关 3.基本操作 4.方程相关 5.数列相关 6.函数相关 Noip数学整理 序 因为某些原因, Noip对于数学方面的考纲仅停留在比较小的一部分, ...

  8. 使用Hexo + Github Pages搭建个人独立博客

    使用Hexo + Github Pages搭建个人独立博客 https://linghucong.js.org/2016/04/15/2016-04-15-hexo-github-pages-blog ...

  9. 19.python设置单线程和多线程

    1.单线程实例: 代码如下: from time import ctime,sleep def music(A): for i in range(2): print ("I was list ...

  10. python中的jion

    on将列表或元组中的每个元素连接起来,举个例子: 1 a = ["hello", "world", "dlrb"] 2 a1 = " ...