要期中考了……我真的是什么也不会啊,书都没看过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. sqlserver 数据简单查询

    use StudentManageDB go select StudentName as 姓名,Gender as 性别,出生日期=birthday from Students where Gende ...

  2. SCCM2012 R2实战系列之四:初始化配置

    在之前的文章中,我们已经完成了SCCM 2012 R2 独立主站点的部署.为了客户端代理软件的顺利安装和OSD操作系统的分发,我们需要配置组策略及DHCP服务.在本系列的第四部分,跟大家一起分享下如何 ...

  3. Hive调优

    Hive存储格式选择 和Hive 相关优化: 压缩参考 Hive支持的存储数的格式主要有:TEXTFILE .SEQUENCEFILE.ORC.PARQUET. 文件存储格式 列式存储和行式存储 行存 ...

  4. 对KVM虚拟机进行cpu pinning配置的方法

    这篇文章主要介绍了对KVM虚拟机进行cpu pinning配置的方法,通过文中的各种virsh命令可进行操作,需要的朋友可以参考下 首先需求了解基本的信息 1 宿主机CPU特性查看 使用virsh n ...

  5. python库pandas

    由于在机器学习中经常以矩阵的方式来表现数据,那么我们就需要一种数据结构来存储和处理矩阵.pandas库就是这样一个工具. 本文档是一个学习笔记,记录一些常用的命令,原文:http://www.cnbl ...

  6. 重识linux-linux的新增与删除用户组和切换命令

    重识linux-linux的新增与删除用户组 1 相关文件 /etc/group /etc/gshadow 2操作相关 groupadd group1 groupmod group1 groupdel ...

  7. 精通Web Analytics 2.0 (12) 第十章:针对潜在的网站分析陷阱的最佳解决方案

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第十章:针对潜在的网站分析陷阱的最佳解决方案 是时候去处理网站分析中最棘手的一些问题了,然后获得属于你的黑带,这是成为分析忍者的 ...

  8. winform 之公共控件

    Button 按钮 属性: (一).布局: 1.AutoSize:控件是否根据内容调整大小 2.Location:当前按钮位于界面位置 3.Dock:控件锁定到界面位置 -None:不锁定 4.Mar ...

  9. Centos7 下搭建SVN + Apache 服务器

    1. 安装httpd 安装httpd服务: $ sudo yum install httpd 检查httpd是否安装成功: $ httpd -version Server version: Apach ...

  10. spark 数据读取与保存

    spark支持的常见文件格式如下: 文本,json,CSV,SequenceFiles,Protocol buffers,对象文件 1.文本 只需要使用文件路径作为参数调用SparkContext 中 ...