1.大根堆 1.1 定义 大根树: 树中的每一个节点的值都大于或等于其子节点的值
大根堆: 既是大根树又是完全二叉树(增加了完全二叉树的限制条件)所以下图中只有(a)和(c)是大根堆
1.2 大根堆的插入(数组实现) 假设在下面大根堆中插入一个元素9,插入步骤如下,时间复杂度为O(height)=O(logn)
尝试插入在6号位置,如果新的元素小于3号位置,则插入;否则把3号位置的元素向下移动到6号位置
尝试插入在3号位置,如果新的元素小于1号元素,则插入;否则把1号位置的元素向下移动到3号位置,循环终止
1 2 3 4 5 6 7 8 9 int currentNode = ++heapSize;while (currentNode != 1 && heap[currentNode / 2 ] < theElement){ heap[currentNode] = heap[currentNode / 2 ]; currentNode /= 2 ; } heap[currentNode] = theElement;
1.3 大根堆的出队 出队元素是最大的元素,也就是根节点,我们首先将1号元素删除,然后拿到最后一个元素,从上向下做插入的操作,时间复杂度为O(height)=O(logn)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int currentNode = 1 ; int child = 2 ; while (child <= heapSize){ if (child < heapSize && heap[child] < heap[child + 1 ]) child++; if (lastElement >= heap[child]) break ; heap[currentNode] = heap[child]; currentNode = child; child *= 2 ; } heap[currentNode] = lastElement;
1.4 大根堆的初始化 自下而上: 从最后一个元素开始对比,到第一个元素结束,具体步骤如下
①找到孩子节点 2 和 7 中更大的元素,接下来与其双亲节点对比,7 更大所以交换 5 和 7 的位置
①找到孩子节点 7 和 6 中更大的元素,接下来与其双亲节点对比,7 更大所以交换 1 和 7 的位置;②找到孩子节点 2 和 5 中更大的元素,接下来与其双亲节点对比,5 更大所以交换 1 和 5 的位置
这里的循环包括两个步骤,第一个步骤是从下到上交换一次,第二个步骤是从上到下再检查一遍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for (int root = heapSize / 2 ; root >= 1 ; root--){ T rootElement = heap[root]; int child = 2 * root; while (child <= heapSize) { if (child < heapSize && heap[child] < heap[child + 1 ]) child++; if (rootElement >= heap[child]) break ; heap[child / 2 ] = heap[child]; child *= 2 ; } heap[child / 2 ] = rootElement; }
1.5 完整代码 1.5.1 优先级队列抽象父类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #pragma once using namespace std;template <class T >class maxPriorityQueue { public : virtual ~maxPriorityQueue () {} virtual bool empty () const = 0 ; virtual int size () const = 0 ; virtual const T& top () = 0 ; virtual void pop () = 0 ; virtual void push (const T& theElement) = 0 ; };
1.5.2 大根堆的数组实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 #pragma once #include "maxPriorityQueue.h" #include <iostream> #include <algorithm> using namespace std;template <class T >class maxHeap : public maxPriorityQueue<T>{ private : int heapSize; int arrayLength; T* heap; public : maxHeap (int initialCapacity = 10 ); ~maxHeap () { delete [] heap; } bool empty () const { return heapSize == 0 ; } int size () const { return heapSize; } const T& top () { if (heapSize == 0 ) { cout << "队列为空" << endl; return NULL ; } return heap[1 ]; } void pop () ; void push (const T&) ; void initialize (T*, int ) ; void deactivateArray () { heap = NULL ; arrayLength = heapSize = 0 ; } void output () const ; }; template <class T>void changeLength (T*& a, int oldLength, int newLength) { if (newLength < 1 )return ; T* temp = new T[newLength]; int number = min (oldLength, newLength); copy (a, a + number, temp); delete [] a; a = temp; } template <class T >maxHeap<T>::maxHeap (int initialCapacity) { if (initialCapacity < 1 ) { cout << "构造大根堆的数量必须大于1" << endl; return ; } arrayLength = initialCapacity + 1 ; heap = new T[arrayLength]; heapSize = 0 ; } template <class T >void maxHeap<T>::push (const T& theElement){ if (heapSize == arrayLength - 1 ) { changeLength (heap, arrayLength, 2 * arrayLength); arrayLength *= 2 ; } int currentNode = ++heapSize; while (currentNode != 1 && heap[currentNode / 2 ] < theElement) { heap[currentNode] = heap[currentNode / 2 ]; currentNode /= 2 ; } heap[currentNode] = theElement; } template <class T >void maxHeap<T>::pop (){ if (heapSize == 0 ) return ; heap[1 ].~T (); T lastElement = heap[heapSize--]; int currentNode = 1 ; int child = 2 ; while (child <= heapSize) { if (child < heapSize && heap[child] < heap[child + 1 ]) child++; if (lastElement >= heap[child]) break ; heap[currentNode] = heap[child]; currentNode = child; child *= 2 ; } heap[currentNode] = lastElement; } template <class T >void maxHeap<T>::initialize (T* theHeap, int theSize){ delete [] heap; heap = theHeap; heapSize = theSize; for (int root = heapSize / 2 ; root >= 1 ; root--) { T rootElement = heap[root]; int child = 2 * root; while (child <= heapSize) { if (child < heapSize && heap[child] < heap[child + 1 ]) child++; if (rootElement >= heap[child]) break ; heap[child / 2 ] = heap[child]; child *= 2 ; } heap[child / 2 ] = rootElement; } } template <class T >void maxHeap<T>::output () const { for (int i = 1 ; i < heapSize; i++) { cout << this ->heap[i] << " " ; } cout << endl; }
2.左高树 2.1 高度优先左高树和重量优先左高树 左高树合并操作的时间复杂度更低,适合需要合并操作较多的数据
定义一类特殊的节点为外部节点 来代替空子树,其余节点为内部节点 。增加了外部节点的二叉树称为扩充二叉树 。
定义距离s: s 是该节点到外部节点的最短路径 。
定义重量w: w 是该节点重量的和 ,节点本身重量为1,节点的重量为自身重量与孩子重量的和。
高度优先左高树 (height-biased leftist tree,HBLT ):这个二叉树任何一个内部节点左孩子的 s 值都大于等于右孩子的 s 值。其中上图(a)就不是一棵左高树,因为它左侧子树的左孩子值为0,而右孩子值为1,下图是2个HBLT。
重量优先左高树 (weight-biased leftist tree,WBLT ):这个二叉树任何一个内部节点左孩子的 w 值都大于或等于右孩子的 w 值。
2.2 HBLT的性质
堆的性质:任意节点值的大小 ≤ 其孩子节点的大小(小根堆,也可以是 ≥ )
左偏性质:左孩子的距离 s ≥ 右孩子的距离 s
任意节点的距离 s 都等于其右孩子的距离 + 1
一棵有 n 个节点的二叉树,根的距离dis ≤ long(n+1) - 1(距离最远就是满二叉树的情况,否则一定有更短路径出去)
2.3 HBLT的插入和删除 插入相当于将已有的树和一棵仅有一个元素的树进行合并操作
删除相当于将左右两棵HBLT进行合并操作
2.4 HBLT的合并(链式实现) 用一张图解释合并的过程,我们需要把两棵树合并到 x 上
对比 x 指向节点数值和 y 指向节点数值的大小
如果节点数值 x < y,则将 x 指向的地址和 y 指向的地址交换
将 x 指针移动到 x 的右孩子位置
这就是图左完整的递归过程,递归的过程实际上就是在比较两个左高树右侧孩子链的大小,并来回交换来保证堆的性质,所以整个递归应该是O(logm)+O(logn)的时间复杂度
首先 x 移动到下一个位置指向元素 7,对比元素 7 和元素 8,元素 8 更大,所以指针指向的地址互换,元素 8 移动到右孩子的位置
然后 x 移动到下一个位置指向元素 6,对比元素 6 和元素 7,元素 7 更大,所以指针指向的地址呼唤,元素 7 移动到右孩子的位置
然后 x 移动到下一个位置指向 NULL,因为 x 指向 NULL,所以直接将 x 指向 y 所指向的地址,递归结束
最后一步是回溯,已经完成元素的插入后,需要维护左高树的性质,回溯的过程就是根据距离不断交换孩子节点的位置,保证左孩子的距离 ≥ 右孩子的距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 template <class T >void maxHblt<T>::merged (binaryTreeNode<pair<int , T> >*& x, binaryTreeNode<pair<int , T> >*& y){ if (y == NULL ) return ; if (x == NULL ) { x = y; return ; } if (x->element.second < y->element.second) swap (x, y); merged (x->rightChild, y); if (x->leftChild == NULL ) { x->leftChild = x->rightChild; x->rightChild = NULL ; x->element.first = 1 ; } else { if (x->leftChild->element.first < x->rightChild->element.first) swap (x->leftChild, x->rightChild); x->element.first = x->rightChild->element.first + 1 ; } }
2.5 HBLT的初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 template <class T >void maxHblt<T>::initialize (T* theElements, int theSize){ arrayQueue<binaryTreeNode<pair<int , T> >*> q (theSize); for (int i = 1 ; i <= theSize; i++) q.push (new binaryTreeNode<pair<int , T> > (pair <int , T>(1 , theElements[i]))); for (int i = 1 ; i <= theSize - 1 ; i++) { binaryTreeNode<pair<int , T> >* b = q.front (); q.pop (); binaryTreeNode<pair<int , T> >* c = q.front (); q.pop (); merged (b, c); q.push (b); } if (theSize > 0 ) this ->root = q.front (); this ->treeSize = theSize; }
Author:
mxwu
Permalink:
https://mingxuanwu.com/2023/10/12/202310121708/
License:
Copyright (c) 2023 CC-BY-NC-4.0 LICENSE