【0】README

0.1) 本文文字描述部分转自 数据结构与算法分析, 旨在理解 优先队列——二项队列(binominal queue) 的基础知识; 
0.2) 本文核心的剖析思路均为原创(insert,merge和deleteMin的操作步骤图片示例), 源代码均为原创; 
0.3) for original source code, please visit https://github.com/pacosonTang/dataStructure-algorithmAnalysis/tree/master/chapter6/p152_binominal_queue


【1】二项队列相关

1.0)Attention: 二项队列中不允许有高度相同的二项树存在该队列中; 

1.1)problem+solution:

  • 1.1.1)problem:虽然左式堆和斜堆每次操作花费O(logN)时间, 这有效地支持了合并, 插入和deleteMin, 但还是有改进的余地,因为我们知道, 二叉堆以每次操作花费常数平均时间支持插入。
  • 1.1.2)solution: 二项队列支持所有这三种操作(merge + insert + deleteMin), 每次操作的最坏情形运行时间为O(logN), 而插入操作平均花费常数时间; (干货——优先队列的三种基本操作——merge + insert + deleteMin)

1.2)相关定义

  • 1.2.1) 二项队列定义: 二项队列不同于我们看到的所有优先队列的实现之处在于, 一个二项队列不是一颗堆序的树, 而是堆序树的集合,称为森林;(干货——二项队列的定义和构成,二项队列是二项树的集合,而二项树是一颗堆序树)
  • 1.2.2)二项树定义: 堆序树中的每一颗都是有约束的形式。 (干货——二项树的定义)
  • 1.2.3)二项树的构成:每一个高度上至多存在一颗二项树, 高度为0的二项树是一颗单节点树; 高度为k 的二项树Bk 通过将一颗二项树 Bk-1 附接到另一颗二项树Bk-1 的根上而构成;(干货——二项树的构成) 

对上图的分析(Analysis):

  • A1)二项树的性质:

    • A1.1)从图中看到, 二项树Bk 由一个带有儿子B0, B1, …, Bk-1的根组成;
    • A1.2)高度为k 的二项树恰好有2^k 个节点;
    • A1.3) 而在深度d 的节点数是 二项系数 。
  • A2)如果我们把堆序添加到二项树上, 并允许任意高度上最多有一颗二项树,那么我们能够用二项树的集合唯一地表示任意大小的优先队列;


【2】二项队列操作(merge + insert + deleteMin)

2.1)合并操作(merge) (干货——合并操作的第一步就是查看是否有高度相同的二项树,如果有的话将它们merge)

  • step1) H1 没有高度为0的二项树而H2有,所以将H2中高度为0的二项树直接作为H3的一部分;(直接的意思==中间不需要merge);
  • step2) H1 和 H2 中都有高度为1的二项树,将它们进行merge, 得到高度为2的二项树(根为12);
  • step3)现在存在三颗高度为2的二项树(根分别为12, 14, 23),将其中两个进行merge(如merge根为12 和 根为14 的二项树),得到高度为3的二项树;
  • step4)所以,最后,我们得到二项队列, 其集合包括:高度为0的二项树(根为13), 高度为1的二项树(根为23),高度为3的二项树(高度为12);

Attention)

  • A1)显然,merge操作是按照高度升序依次进行的;
  • A2)最后得到的二项队列不存在高度相同的二项树,即使存在,也要将高度相同的二项树进行merge;
  • A3)二项队里中的二项树的高度不必囊括所有的升序实数,即不必一定是0, 1, 2, 3,4 等等; 也可以是0, 1, 3 等;
  • A4)单节点树的高度为0; (干货——树高度从零起跳) 

2.2)插入操作(insert) (干货——insert操作是merge操作的特例,而merge操作的第一步就是查看是否有高度相同的二项树,如果有的话将它们merge)

  • 2.2.1)插入操作实际上: 就是特殊情形的合并, 我们只需要创建一颗单节点树并执行一次merge;
  • 2.2.2)更准确地说: 如果元素将要插入的那个优先队列中不存在的最小的二项树是Bi, 那么运行时间与 i + 1 成正比; 

对上图的分析(Analysis):

  • A1) 4 插入之后,与B0(根为3)进行merge, 得到一颗高度为1的树B1’(根为3);
  • A2)将B1’ 与 B1(根为1) 进行merge 得到高度为2 的树B2’(根为1), 它是新的优先队列;
  • A3)在插入7之后的下一次插入又是一个坏情形, 因为需要三次merge操作;

2.3)删除最小值操作(deleteMin)

  • step1)找出一颗具有最小根的二项树来完成, 令该树为Bk, 令原始序列为H;
  • step2)从H中除去Bk, 形成新的二项队列H’;
  • step3)再除去Bk的根, 得到一些二项树B0, B1, …, Bk-1, 它们共同形成优先队列H”;
  • step4) 合并H’ 和 H” , 操作结束; 

【3】 source code and printing results

3.1)source code at a glance 
Attention)二项队列的实现源代码用到了 儿子兄弟表示法

#include "binominal_queue.h" 

#define MINIMAL 10000

int minimal(BinominalQueue bq)
{
int capacity;
int i;
int minimal;
int miniIndex; minimal = MINIMAL;
capacity = bq->capacity;
for(i=; i<capacity; i++)
{
if(bq->trees[i] && bq->trees[i]->value < minimal)
{
minimal = bq->trees[i]->value;
miniIndex = i;
}
} return miniIndex;
} // initialize the BinominalQueue with given capacity.
BinominalQueue init(int capacity)
{
BinominalQueue queue;
BinominalTree* trees;
int i; queue = (BinominalQueue)malloc(sizeof(struct BinominalQueue));
if(!queue)
{
Error("failed init, for out of space !");
return queue;
}
queue->capacity = capacity; trees = (BinominalTree*)malloc(capacity * sizeof(BinominalTree));
if(!trees)
{
Error("failed init, for out of space !");
return NULL;
}
queue->trees = trees; for(i=; i<capacity; i++)
{
queue->trees[i] = NULL;
} return queue;
} // attention: the root must be the left child of the binominal tree.
int getHeight(BinominalTree root)
{
int height;
if(root == NULL)
{
return ;
} height = ;
while(root->nextSibling)
{
height++;
root = root->nextSibling;
} return height;
} // merge BinominalQueue bq2 into bq1.
void outerMerge(BinominalQueue bq1, BinominalQueue bq2)
{
int height;
int i; for(i=; i<bq2->capacity; i++)
{
height = -;
if(bq2->trees[i])
{
height = getHeight(bq2->trees[i]->leftChild);
// attention for the line above
// height = height(bq2->trees[i]->leftChild); not height = height(bq2->trees[i]);
merge(bq2->trees[i], height, bq1);
}
}
} // merge tree h1 and h2 = bq->trees[height],
// who represents the new tree and old one respectively.
BinominalTree merge(BinominalTree h1, int height, BinominalQueue bq)
{
if(h1 == NULL)
{
return h1;
} if(bq->trees[height] == NULL) // if the queue don't has the B0 tree.
{
bq->trees[height] = h1;
return bq->trees[height];
}
else // otherwise, compare the new tree's height with that of old one.
{
if(h1->value > bq->trees[height]->value) // the new should be treated as the parent of the old.
{
innerMerge(bq->trees[height], height, h1, bq);
}
else // the old should be treated as the parent of the new.
{
innerMerge(h1, height, bq->trees[height], bq);
}
} return h1;
} BinominalTree lastChild(BinominalTree root)
{
while(root->nextSibling)
{
root = root->nextSibling;
} return root;
} // merge tree h1 and h2 = bq->trees[height],
// who represents the new tree and old one respectively.
BinominalTree innerMerge(BinominalTree h1, int height, BinominalTree h2, BinominalQueue bq)
{
if(h1->leftChild == NULL)
{
h1->leftChild = h2;
}
else
{
lastChild(h1->leftChild)->nextSibling = h2;
// attention for the line above
// lastChild(h1->leftChild)->nextSibling = h2 not lastChild(h1)->nextSibling = h2
}
height++;
bq->trees[height-] = NULL;
merge(h1, height, bq); return h1;
} // insert an element with value into the priority queue.
void insert(ElementType value, BinominalQueue bq)
{
TreeNode node; node = (TreeNode)malloc(sizeof(struct TreeNode));
if(!node)
{
Error("failed inserting, for out of space !");
return ;
}
node->leftChild= NULL;
node->nextSibling = NULL;
node->value = value; merge(node, , bq);
} // analog print node values in the binominal tree, which involves preorder traversal.
void printPreorderChildSibling(int depth, BinominalTree root)
{
int i; if(root) {
for(i = ; i < depth; i++)
printf(" ");
printf("%d\n", root->value);
printPreorderChildSibling(depth + , root->leftChild);
printPreorderChildSibling(depth, root->nextSibling);
}
else
{
for(i = ; i < depth; i++)
printf(" ");
printf("NULL\n");
}
} // print Binominal Queue bq
void printBinominalQueue(BinominalQueue bq)
{
int i; for(i=; i<bq->capacity; i++)
{
printf("bq[%d] = \n", i);
printPreorderChildSibling(, bq->trees[i]);
}
} void deleteMin(BinominalQueue bq)
{
int i;
BinominalTree minitree;
BinominalTree sibling; i = minimal(bq);
minitree = bq->trees[i]->leftChild; //minitree->value=51
free(bq->trees[i]);
bq->trees[i] = NULL; while(minitree)
{
sibling = minitree->nextSibling;
minitree->nextSibling = NULL;
merge(minitree, getHeight(minitree->leftChild), bq);
minitree = sibling;
}
} int main()
{
BinominalQueue bq, bq1, bq2;
int data[] = {, , , , , , , , , , };
int data1[] = {, , , , , };
int data2[] = {, , , , , , };
int i;
int capacity; // creating the binominal queue bq starts.
capacity = ;
bq = init(capacity);
for(i=; i<capacity; i++)
{
insert(data[i], bq);
}
printf("\n=== after the binominal queue bq is created ===\n");
printBinominalQueue(bq);
// creating over. // creating the binominal queue bq1 starts.
capacity = ;
bq1 = init(capacity);
for(i=; i<capacity; i++)
{
insert(data1[i], bq1);
}
printf("\n=== after the binominal queue bq1 is created ===\n");
printBinominalQueue(bq1);
// creating over. // creating the binominal queue bq2 starts.
capacity = ;
bq2 = init(capacity);
for(i=; i<capacity; i++)
{
insert(data2[i], bq2);
}
printf("\n=== after the binominal queue bq2 is created ===\n");
printBinominalQueue(bq2);
// creating over. // merge bq2 into the bq1
outerMerge(bq1, bq2);
printf("\n=== after bq2 is merged into the bq1 ===\n");
printBinominalQueue(bq1);
// merge over. // executing deleteMin opeartion towards binominal queue bq1
printf("\n=== after executing deleteMin opeartion towards binominal queue bq1 ===\n");
deleteMin(bq1);
printBinominalQueue(bq1);
// deleteMin over!
return ;
}

3.2) printing results

优先队列——二项队列(binominal queue)的更多相关文章

  1. 【python】-- 队列(Queue)、生产者消费者模型

    队列(Queue) 在多个线程之间安全的交换数据信息,队列在多线程编程中特别有用 队列的好处: 提高双方的效率,你只需要把数据放到队列中,中间去干别的事情. 完成了程序的解耦性,两者关系依赖性没有不大 ...

  2. jQuery源代码学习之七—队列模块queue

    一.jQuery种的队列模块 jQuery的队列模块主要是为动画模块EFFECTS提供支持,(不过到现在为了支持动画队列的inprogress的出入队还是搞不太清楚),单独抽取出一个命名空间是为了使程 ...

  3. python-Day3-set 集合-counter计数器-默认字典(defaultdict) -可命名元组(namedtuple)-有序字典(orderedDict)-双向队列(deque)--Queue单项队列--深浅拷贝---函数参数

    上节内容回顾:C语言为什么比起他语言块,因为C 会把代码变异成机器码Pyhton 的 .pyc文件是什么python 把.py文件编译成的.pyc文件是Python的字节码, 字符串本质是 字符数组, ...

  4. Javascript数据结构与算法--队列(顺序队列、优先队列、循环队列)的实现与用法

    前言 队列和栈非常类似,前面已经讲过了栈的实现与用法,现在我们来说说队列. 队列介绍 队列遵循FIFO(First In First Out,先进先出)原则的一组有序的项. 队列是一种特殊的线性表,特 ...

  5. C# 堆栈(Stack)和队列(Queue)

    一.什么是堆?(Heap)      堆是无序的,是一片不连续的内存域,由用户自己来控制和释放,如果用户自己不释放的话,当内存达到一定的特定值时,通过垃圾回收器(GC)来回收.      是程序运行期 ...

  6. [Leetcode]双项队列解决滑动窗口最大值难题

    这道题是从优先队列的难题里面找到的一个题目.可是解法并不是优先队列,而是双项队列deque 其实只要知道思路,这一道题直接写没有太大的问题.我们看看题 给定一个数组 nums,有一个大小为 k 的滑动 ...

  7. STL之heap与优先级队列Priority Queue详解

    一.heap heap并不属于STL容器组件,它分为 max heap 和min heap,在缺省情况下,max-heap是优先队列(priority queue)的底层实现机制.而这个实现机制中的m ...

  8. JS数据结构及算法(二) 队列

    队列是遵循先进先出的一种数据结构,在尾部添加新元素,并从顶部移除元素. 1.普通队列 function Queue() { this.items = []; } Queue.prototype = { ...

  9. C# 编程中的堆栈(Stack)和队列(Queue)

    一.什么是堆?(Heap)      堆是无序的,是一片不连续的内存域,由用户自己来控制和释放,如果用户自己不释放的话,当内存达到一定的特定值时,通过垃圾回收器(GC)来回收.      是程序运行期 ...

随机推荐

  1. Intel haxm安装失败问题解决

    在安装Intel haxm为安卓模拟器加速时,会遇到提示VT-X未开启问题,问题提示如下图 工具/原料   Intel haxm 安卓模拟器 方法/步骤   确认你的处理器是否是Intel的,如果是A ...

  2. Android Studio使用过程中Java类突然报红,但项目可运行解决方案

    1.点击File->Invalidate Caches / Restart... 2.重启Gradle,清除缓存 3.Clean Project

  3. Tomcat环境下配置数据源

    两种方式,图形化和字符型配置,图形化需要部署一个应用,字符型配置如下: 需要文件 mysql-connector-java-5.1.16-bin.jar Oracle需要classes12.jar文件 ...

  4. leetcode练习之No.1------ 两数之和Two Sum

    github地址:git@github.com:ZQCard/leetcode.git 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 你可以假设每个输入只对应一种答案,且同样的元素不能 ...

  5. infer 编译代码审查命令记录

    infer -- xcodebuild -target <target name> -configuration <build configuration> -sdk ipho ...

  6. [TypeScript] Create Explicit and Readable Type Declarations with TypeScript mapped Type Modifiers

    Using the optional “+” sign together with mapped type modifiers, we can create more explicit and rea ...

  7. proto3 中的 map 类型

    .proto syntax = "proto3"; option optimize_for = SPEED; message TestStruct { map<int32,s ...

  8. EJB学习笔记之十(BMT事务和CMT事务)

     1.前言 前两篇博客主要介绍了与事务相关的知识.比如事务的一些特性,以及并发产生的问题.本篇来解说一下EJB中两种处理事务的方式.一种是以生命式方式来管理事务(CMT):还有一种则是在EJB内部使用 ...

  9. gdb调试多进程和多线程命令(转)

    1. 默认设置下,在调试多进程程序时GDB只会调试主进程.但是GDB(>V7.0)支持多进程的分别以及同时调试,换句话说,GDB可以同时调试多个程序.只需要设置follow-fork-mode( ...

  10. CentOS 7上安装Zabbix(高速安装监控工具Zabbix)

    前提要求(optional) 安装Zabbix监控工具前,先安装必要的执行工具包 yum install gcc gcc-c++ make openssl-devel curl wget net-sn ...