左倾堆(左偏树)和是堆的一种;和普通的二叉堆不同,它是一种可合并堆。普通二叉堆是用数组存储,在进行俩个堆合并时,效率并不理想。左倾堆用的是指针链接。
typedef int Type;
// 节点结构
struct Node {
public:
Node(Type key) :Key(key), NPL(0), LC(NULL), RC(NULL) {}
Type Key; // 键值
int NPL; // 零距离 null path length
Node* LC; // 左孩子
Node* RC; // 右孩子
};
// 左倾堆(本质也是小顶堆)
class LeftistHeap {
public:
bool Insert(Type key); // 插入
bool Delete(); // 删除(只能删除堆根)
void Traverse(); // 遍历
LeftistHeap() :Root(NULL) {}
LeftistHeap(Type key) :Root(new Node(key)) {}
private:
Node* Root; //堆根
};
零路距离(null path length):
一个节点到最近一个不满节点的路径长度。不满节点是指该节点的左右孩子至少有一个为NULL。叶节点的NPL为0,NULL节点的NPL为-1。
左倾堆的性质:
1、节点键值一定小于或等于它的左右孩子的键值。(小顶堆)
2、节点的左孩子的NPL一定不小于右孩子的NPL。
3、节点的NPL等于右孩子的NPL+1。
合并:
将俩个堆合并,选择键值小的堆跟做为新的堆根,将另一堆和新堆根的右孩子合并,合并后的堆做新堆根的右孩子,再判断左右孩子的NPL,若右孩子的NPL大于左孩子的NPL,则交换左右孩子。一直向下重复操作,直到一方为空。
一张长图举例说明:
// 将俩个合并
Node* Merge(Node* node1, Node* node2) {
if (NULL == node1) return node2;
if (NULL == node2) return node1;
// 键值小的堆节点做新根(一定让 node1 为根 )
if (node1->Key > node2->Key) {
Type tem = node1->Key;
node1->Key = node2->Key;
node2->Key = tem;
}
node1->RC = Merge(node1->RC, node2);
if (NULL == node1->LC || node1->LC->NPL < node1->RC->NPL) { // 空节点的NPL为 -1;
Node*T = node1->LC; // 若左孩子的NPL小于右孩子的NPL,交换左右孩子
node1->LC = node1->RC;
node1->RC = T;
}
if (node1->RC) node1->NPL = node1->RC->NPL + 1; // 节点的NPL为右孩子的NPL +1
else node1->NPL = 0;
return node1;
}
了解了合并之后那插入和删除就很简单了,无非是俩个堆的合并。
插入:
// 插入
bool LeftistHeap::Insert(Type key) {
Node* T = new Node(key);
if (T) {
Root = Merge(Root, T);
return true;
}
return false;
}
删除:
// 删除
bool LeftistHeap::Delete() {
if (NULL == Root) return false;
Root = Merge(Root->LC, Root->RC);
}