左偏树(Leftist Tree)是一种可并堆(Mergeable Heap) ,它除了支持优先队列的三个基本操作(插入,删除堆顶,取最小节点),还支持一个很特殊的操作——合并操作。左偏树一般不是平衡树。
左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有左右子树指针( left, right)外,还有两个属性:键值和距离(dist)。键值是用于比较节点的大小。距离则是如下定义的:
节点i称为外节点(externalnode),当且仅当节点i的左子树或右子树为空( left(i) = NULL或right(i) = NULL );节点i的距离(dist(i))是节点i到它的后代中,最近的外节点所经过的边数。特别的,如果节点i本身是外节点,则它的距离为0;而空节点的距离规定为-1 (dist(NULL) =-1)。在本文中,有时也提到一棵左偏树的距离,这指的是该树根节点的距离。
左偏树满足左偏性质:
节点的左子节点的距离不小于右子节点的距离
还有一个性质:左偏树的根节点的距离值不超过log(|V|),操作的复杂度也为log(|V|)
合并操作:
采用递归的方式合并两棵左偏树:
1.如果有一棵树为空,返回另外一棵树
2.将根节点键值较小的树与较大树的右子树合并
3.如果合并后违反了左偏规则,即右儿子距离大于左儿子距离,交换两个儿子,并且更新当前节点距离值
插入操作:
可视为与一棵只有一个节点的左偏树合并
删除操作:
删除根,合并其左右子树
普通的二叉堆,由于完全二叉树的性质以及数组的实现,不支持堆合并。出于堆合并的需求,提出了左偏树,这里的左偏以及具体规则,目的是快速访问最小节点以及在对树修改后快速的恢复堆性质。
问题来了,为何不直接用二叉树直接用来实现可合并堆,直接不把堆实现为完全二叉树就可以了......原因是,如果不提出一个规则,怎么合并,和左儿子还是右儿子?而且直接用二叉树的话,在插入过程中,树的形状不可知,复杂度可能会变的很大。左偏树明确提出了合并规则,将根节点键值较小的树与较大树的右子树合并,而且其规则:节点的左子节点的距离不小于右子节点的距离 就是基于合并规则提出的,为了递归的去和右子树合并的过程中,走过的路程少,递归层数少,从而复杂度低。至于为何用距离而不是用平常意义上的深度,原因也和不断和右子树合并有关,考虑深度的话,没啥意义
代码:
可以指针实现和数组实现
指针实现:写了人基于C++类封装的,未封装的略
// 左偏树.cpp
//以最大堆为例
#include "stdafx.h"
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
class TreeNode {
private:
int data;
int dist;
TreeNode *left, *right;
public:
TreeNode(int te):data(te),dist(0),left(NULL),right(NULL){}
static TreeNode *Merge(TreeNode *p,TreeNode *w);
static void Insert(TreeNode *&root,int te);
void Delete(TreeNode *&root);
void postOrder();
};
TreeNode *TreeNode::Merge(TreeNode *p,TreeNode *w) {
if (p == NULL) {
return w;
}
else if (w == NULL) {
return p;
}
TreeNode *x = p->data > w->data ? p : w;
TreeNode *y = x == p ? w : p;
x->right = TreeNode::Merge(x->right, y);
if (x->left == NULL ||x->left->dist < x->right->dist) {
TreeNode *temp = x->left;
x->left = x->right;
x->right = temp;
}
if (x->right == NULL) {
x->dist = 0;
}
else {
x->dist = x->right->dist + 1;
}
return x;
}
void TreeNode::Insert(TreeNode *&root,int te) {
TreeNode *p = new TreeNode(te);
if (root == NULL) {
root = p;
}
else {
root = TreeNode::Merge(root, p);
}
}
void TreeNode::Delete(TreeNode *&root) {
TreeNode *te = root;
root = Merge(root->left, root->right);
delete te;
}
void TreeNode::postOrder() {
if (this == NULL) {
return;
}
this->left->postOrder();
this->right->postOrder();
cout << " "<<this->data;
}
int main(){
int te,n;
TreeNode *leftistTree = NULL;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> te;
TreeNode::Insert(leftistTree,te);
}
leftistTree->postOrder();
cout << endl;
leftistTree->Delete(leftistTree);
leftistTree->postOrder();
cout << endl;
return 0;
}
上面不好,修改了一下:
#include "stdafx.h"
#include <cstdio>
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
class TreeNode {
private:
int data;
int dist;
TreeNode *left, *right;
public:
TreeNode(int te) :data(te), dist(0), left(NULL), right(NULL) {}
TreeNode *&merge(TreeNode *&p, TreeNode *&w);
static void insert(TreeNode *&root, int te);
TreeNode *&pop(TreeNode *&root);
int top();
void postOrder();
};
TreeNode *&TreeNode::merge(TreeNode *&p, TreeNode *&w) {
if (p == NULL) {
return w;
}
else if (w == NULL) {
return p;
}
TreeNode *&x = p->data < w->data ? p : w;
TreeNode *&y = x == p ? w : p;
x->right = TreeNode::merge(x->right, y);
if (x->left == NULL || x->left->dist < x->right->dist) {
swap(x->left,x->right);
}
if (x->right == NULL) {
x->dist = 0;
}
else {
x->dist = x->right->dist + 1;
}
p = x;
return p;
}
void TreeNode::insert(TreeNode *&root, int te) {
TreeNode *p = new TreeNode(te);
if (root == NULL) {
root = p;
}
else {
root = root->merge(root, p);
}
}
TreeNode *&TreeNode::pop(TreeNode *&root) {
TreeNode *te = root;
root = merge(root->left, root->right);
delete te;
return root;
}
int TreeNode::top() {
return this->data;
}
void TreeNode::postOrder() {
if (this == NULL) {
return;
}
this->left->postOrder();
this->right->postOrder();
cout << " " << this->data;
}
class LeftistTree {
private:
TreeNode *root;
public:LeftistTree():root(NULL){}
void insert(int te) {
TreeNode::insert(root, te);
}
void pop() {
if (root == NULL) {
cerr << "ERROR" << endl;
}
root = root->pop(root);
}
void top() {
if (root == NULL) {
cerr << "ERROR" << endl;
return;
}
cout << root->top() << endl;
}
void merge(LeftistTree *&tree2) {
root = root->merge(root,tree2->root);
}
void postOrder() {
if (root == NULL) {
return;
}
root->postOrder();
}
};
int main() {
int te, n;
LeftistTree a;
cin >> n;//5
for (int i = 0; i < n; i++) {//8 4 5 6 2
cin >> te;
a.insert(te);
}
a.postOrder();//8 6 5 4 2
cout << endl;
a.pop();
a.postOrder();//8 6 5 4
cout << endl;
return 0;
}
指针实现的,一个左偏树的类,对应一个左偏树,而数组实现的,可有多个左偏树,节点不为其他节点的孩子的,即为一个左偏树的树根。以下的insert,top,pop,merge都要求以左偏树堆顶的编号代表这一左偏树。
数组实现:写的也是基于C++类封装的,未封装的略
#include "stdafx.h"
#include <cstdio>
#include <cstring>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
#define MAXN 100005
class LeftistTree {
private:
int len;
int data[MAXN], left[MAXN], right[MAXN], dist[MAXN];
public:
LeftistTree():len(0) {
memset(left, -1, sizeof(left));
memset(right, -1, sizeof(right));
memset(dist, -1, sizeof(dist));
}
int merge(int x, int y) {
if (x == -1) {
return y;
}
if (y == -1) {
return x;
}
int a = data[x] < data[y] ? x : y;
int b = a == x ? y : x;
right[a] = merge(right[a], b);
if (dist[left[a]] < dist[right[a]]) {
swap(left[a], right[a]);
}
dist[a] = dist[right[a]] + 1;
return a;
}
int insert(int x, int value) {//x指的是左偏树的堆顶
data[len] = value;
dist[len] = 0;
return merge(x, len++);
}
int pop(int x) {
return merge(left[x], right[x]);
}
int top(int x) {
return data[x];
}
void postOrder(int x) {
if (x == -1) {
return;
}
postOrder(left[x]);
postOrder(right[x]);
cout << " " << data[x];
}
};
int main() {
int n,te,root = -1;
LeftistTree *tree = new LeftistTree();
cin >> n;//5
for (int i = 0; i < n; i++) {//8 4 5 6 2
cin >> te;
root = tree->insert(root, te);
}
tree->postOrder(root);//8 6 5 4 2
cout << endl;
root = tree->pop(root);
tree->postOrder(root);//8 6 5 4
cout << endl;
return 0;
}