二叉堆复习(包括d堆)
要期中考了……我真的是什么也不会啊,书都没看过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堆)的更多相关文章
- [数据结构]——二叉树(Binary Tree)、二叉搜索树(Binary Search Tree)及其衍生算法
二叉树(Binary Tree)是最简单的树形数据结构,然而却十分精妙.其衍生出各种算法,以致于占据了数据结构的半壁江山.STL中大名顶顶的关联容器--集合(set).映射(map)便是使用二叉树实现 ...
- 《数据结构与算法分析:C语言描述》复习——第五章“堆”——二叉堆
2014.06.15 22:14 简介: 堆是一种非常实用的数据结构,其中以二叉堆最为常用.二叉堆可以看作一棵完全二叉树,每个节点的键值都大于(小于)其子节点,但左右孩子之间不需要有序.我们关心的通常 ...
- 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器
[摘要] timers模块部分源码和定时器原理 示例代码托管在:http://www.github.com/dashnowords/blogs 一.概述 Timer模块相关的逻辑较为复杂,不仅包含Ja ...
- 【nodejs原理&源码杂记(8)】Timer模块与基于二叉堆的定时器
目录 一.概述 二. 数据结构 2.1 链表 2.2 二叉堆 三. 从setTimeout理解Timer模块源码 3.1 timers.js中的定义 3.2 Timeout类定义 3.3 active ...
- 二叉堆(二)之 C++的实现
概要 上一章介绍了堆和二叉堆的基本概念,并通过C语言实现了二叉堆.本章是二叉堆的C++实现. 目录1. 二叉堆的介绍2. 二叉堆的图文解析3. 二叉堆的C++实现(完整源码)4. 二叉堆的C++测试程 ...
- 二叉堆的实现(数组)——c++
二叉堆的介绍 二叉堆是完全二元树或者是近似完全二元树,按照数据的排列方式可以分为两种:最大堆和最小堆.最大堆:父结点的键值总是大于或等于任何一个子节点的键值:最小堆:父结点的键值总是小于或等于任何一个 ...
- 笔试算法题(46):简介 - 二叉堆 & 二项树 & 二项堆 & 斐波那契堆
二叉堆(Binary Heap) 二叉堆是完全二叉树(或者近似完全二叉树):其满足堆的特性:父节点的值>=(<=)任何一个子节点的键值,并且每个左子树或者右子树都是一 个二叉堆(最小堆或者 ...
- 二叉堆(binary heap)—— 优先队列的实现
二叉堆因为对应着一棵完全二叉树,因而可以通过线性数组的方式实现. 注意,数组第 0 个位置上的元素,作为根,还是第 1 个位置上的元素作为根? 本文给出的实现,以数组第 1 个位置上的元素作为根,则其 ...
- 【算法与数据结构】二叉堆和优先队列 Priority Queue
优先队列的特点 普通队列遵守先进先出(FIFO)的规则,而优先队列虽然也叫队列,规则有所不同: 最大优先队列:优先级最高的元素先出队 最小优先队列:优先级最低的元素先出队 优先队列可以用下面几种数据结 ...
随机推荐
- FIN vs RST in TCP connections different
question: The way I understand this, there are 2 ways to close TCP connection: send FIN flag send RS ...
- Android之socket多线程
一.添加权限 <uses-permission android:name="android.permission.INTERNET" /> 二.输入输出流 客户端和服务 ...
- Socket网络编程(winform)
[服务器] using System; using System.Collections.Generic; using System.ComponentModel; using System.Data ...
- pyhton框架Django之cookie和session
一,cookie和session的理解 cookies 是浏览器为 Web 服务器存储的一小段信息. 每次浏览器从某个服务器请求页面时,它向服务器回送之前收到的cookies.它保存在浏览器下的某个文 ...
- 在同一个Linux上配置多个git账户
1.首先在~/.ssh目录下执行 ssh-keygen -t rsa -C "miaoying.new@qq.com" 其中 -C "miaoying.new@qq.co ...
- fiddler--配置火狐代理配置代理
fiddler安装 下载fiddler最新版: 默认安装: 配置火狐代理配置代理 打开fiddler 按图操作,复制选中的链接:fiddler -> tool ->connections ...
- mac maven lombok报错
maven已导入lombok的jar包,注解@Data,但是用到getter,setter时依然出错.解决办法: 打开eclipse.ini文件,加上如下两句: -Xbootclaspath//Use ...
- NoSQL学习1
MongoDB使用C++语言编写的一个基于分布式的文件存储的开源数据库.可以在承受高负载的情况下,保证服务器的性能. MongoDB将数据存储成为一个文档,数据结构有键值对组成.类似于JSON,字段值 ...
- linux问题集
Too many authentication failures for root (code 2) 原因:服务器可能由于装了一下安全软件导致有时用ssh远程工具登陆不了,提示太多认证失败for ro ...
- uva-10125-暴力枚举
题意:给一个集合,求d=a+b+c,d最大且a,b,c,d下标不能是同一个 解题思路 a+b=d-c 另外,可以OJ看下0ms大佬们的代码. #include "pch.h" #i ...