批量建堆(二叉堆完全二叉堆)~~批量建堆
Posted 一乐乐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了批量建堆(二叉堆完全二叉堆)~~批量建堆相关的知识,希望对你有一定的参考价值。
批量建堆
1,逻辑:局部建立堆---》整体建立堆
2,其实就是一个调整范围的确定 + 考虑当前结点的身份(作为子结点或父结点)而已。
■ 上滤(自上而下的上滤【本质就是添加】)—----当前结点作为子节点,考虑它作为子结点在当前位置是否合适。 ■ 下滤(自下而上的下滤【本质就是删除】)------当前结点作为父结点,考虑它作为父结点在当前位置是否合适。 |
❀ 上滤建立堆-----逻辑就是添加时的上滤操作-是添加到数组的最后一个位置,然后不断地往上比(比较的终点是根),当前结点与它的父结点比较【直到找到合适位置】
❀ 下滤建立堆----逻辑就是符合删除操作~从第一个非叶子结点开始(比较的终点是根),当前结点不断地与它的最大子结点比较【直到找到合适位置】
■ 最大堆----批量建堆-效率对比:上滤与下滤比较----同线比较
例如:拿1号线作比较,可以看到上滤与下滤比较:
上滤可能移动的结点比较多(往上跑的结点数量比较多),
下滤可能移动的结点比较少(往下跑的结点数量比较少)
● 添加代码(咱使用的数据结构是数组):
@Override public void add(E element) { //检查元素是否具有可比较性【排除null】 elementNotFoundCheck(element); // 扩容检查、扩容操作 ensureCapacity(size + 1); //按数组添加特点【每次都是添加到最后的位置,添加完数组长度++】 elements[size++] = element; //维持二叉堆的特点【大根堆特点】~ 上滤 siftUp(size -1); }
● 上溢代码:
/** * 上滤【从最后一个位置开始往上 】 * 【插入到原逻辑的优化,插入到最后一个位置时,比较当前结点的父结点是否还是大于
* 【插入结点,若是满足,即找到合适位置,否则,当前结点的父结点要变成子结点啦,然后爬升到父结点的位置继续比较】】 */ private void siftUp(int index) { // 插入element 随着比较不断的上移【选择一个合适的位置(插入的当前结点比父结点小)】,而是最终确定位置后,放一下] E element = elements[index]; while (index > 0) { int parentIndex = (index - 1) >> 1; E parent = elements[parentIndex]; if (compare(element, parent) <= 0) break; //让比较小的父元素放到子元素位置 elements[index] = parent; // 重新赋值index index = parentIndex; } elements[index] = element;//找到最终的位置index }
● 删除代码:
@Override public E remove() { emptyCheck();//检查数组是否为空 E root = elements[0]; int lastIndex = --size; elements[0] = elements[lastIndex]; elements[lastIndex] = null; //下滤 siftDown(0); //根据索引进行下滤操作 return root; }
● 下溢代码:
/** * 下滤:删除操作【从第 index 位置开始,往下】 * 即从当前结点开始往下比(跟最大孩子结点比),直到找到合适的位置【满足 当前结点的值(作为父结点)大于子结点的值】 * 当出现了最大子结点大于父结点时,子结点的值覆盖到父节点的位置 * @param index */ private void siftDown(int index) { /** * 非叶子结点个数: n1 + n2 = floor(n /2);【最后一个非叶子结点索引即:(size/2) - 1】 * 叶子结点个数:n0 = floor((n + 1) /2); *结论:第一个叶子结点的索引就是非叶子结点的数量【从上到下,从左到右,非叶子-》叶子】 */ // while(index < 第一个叶子结点的索引)【即保证index 都是非叶子结点】 int half = size >> 1; E element = elements[index]; while(index < half) { //完全二叉树:只能有两种情况: //1,只有 左结点 //2,有左,有右 //默认假设左结点是最大结点 int childIndex = (index << 1) + 1;//左结点的索引要注意【位移符号的书写的方向】 E childElement = elements[childIndex]; //rightIndex < size [说明右结点存在,处于合理区间|,即存在] int rightIndex = childIndex + 1; if(rightIndex < size && compare(elements[rightIndex], childElement) > 0) { childIndex = rightIndex; childElement = elements[rightIndex]; //优化一下代码写法: // childElement = elements[childIndex = rightIndex]; } //用当前结点的最大子结点和当前结点进行比较【当前大的话】 //bug:if(compare(elements[index], childElement) >= 0) //错误:咱是要将element 找到合适位置呀,element 是固定的呀,不是 elements[index] if(compare(element, childElement) >= 0) break; //子结点大的话 elements[index] = childElement; index = childIndex; } elements[index] = element; }
左式堆
1. 堆合并
两个完全二叉堆的合并算法:
1.1 A.insert( B.del_max() )
完全二叉堆是基于vector实现的,故若采用合并算法,只需将堆A(n)作为一个基本堆,进而不断的将堆B(m)的元素进行删除取出,重新插入A中。
每次迭代为一次删除和一次插入操作,共进行m次迭代。
复杂度为 m * { O(logm)+ O(log(n + m)) } — O(mlogn)
1.2 Floyd建堆算法
将两个堆的元素任意混合为一个新的向量,只需O(min(n,m))时间,再调用建堆算法,其复杂度为O(n + m)
以上两种算法的复杂度都不尽人意,没有考虑两个堆已经是偏序的堆
2. 空节点路径长度
在二叉搜索树中,在逻辑上定义内部节点和外部节点,外部节点为度为0/1节点的孩子,从而实现建立一棵度均为偶数的树。
Null Path Length — npl(x)
1) 当前节点为外部节点npl(x) = 0
2) 当前节点为内部节点,npl(x) = 1 + min( npl(lc), npl(lc) ),左右孩子中空节点长度的最小值加1
3) npl(x) = x到外部节点的最短路径
4) 也是以x为根的最大满子树的树高
3. 左倾性
要满足左倾性,内部节点x的左孩子的npl不小于右孩子的npl
npl(x->lc) >= npl(x->rc) 由此可知,npl(x) = 1 + npl(x->rc)
但是并不意味着左孩子的规模和高度也大于其右孩子
4. 右侧链
从x出发,一直沿着其右侧孩子路径出发,到达外部节点时,其路径为右侧链rPath(x),显然由于npl(x) = 1 + npl(x->rc),每个节点的npl值都是由右侧子孙决定的。
根节点的右侧通路的终点必然是全堆深度最小的外部节点,由此构成一棵以r为根,d为rPath(root)的满二叉树。
计算可知,其右侧链长度不过O(logn)
5. 左式堆
左式堆是优先级队列的另外实现形式,能够在O(logn)的复杂度实现两堆的合并,其需要调整的节点数只在O(logn)下实现。
5.1 左式堆的合并
合并A堆和B堆,只需将A堆的右子堆与B堆进行合并,从而递归的实现。
设置A>=B
Merge(A,B)
其细节在于:
1. 每次调整前需比较A,B的大小,始终保持A>=B,进行交换即可
2. 合并完成后,需要更新A的右子堆子父关系A->rc->parent = A
3. 更为重要的是,需要比较A的新右子堆和左子堆的npl关系,保证左孩子的npl不小于右孩子npl,为此进行交换即可
以上是关于批量建堆(二叉堆完全二叉堆)~~批量建堆的主要内容,如果未能解决你的问题,请参考以下文章