伸展树
与平衡树(AVL)一样,伸展树(splay tree)也是二叉树的一种,它能在O(log n)内完成插入、删除和查找操作。
伸展树的特点:
1. 假设当前节点X的关键值为key,则X左子树的节点y的键值key[y] < key. 右子树节点z的键值key[z] > key.
2. 当某个节点被访问时,伸展树会通过旋转使得该节点成为树根(root)。使得下次访问该节点时,能够迅速访问到节点。
伸展树的实现
1.节点定义
与二叉树的节点定义一样:
typedef int Type;
typedef struct SplayTreeNode
{
Type key; //关键字
struct SplayTreeNode *left; //左子树
struct SplayTreeNode *right; //右子树
}Node, *SplayTree;
2.伸展树的遍历
作为二叉树的一种,同样具有前序遍历、中序遍历、后序遍历三种遍历树的方法,这里巩固一下三种方法的实现:
//前序遍历伸展树
void preorder_splaytree(SplayTree tree)
{
if (tree)
{
printf("%d ",tree->key);
preorder_splaytree(tree->left);
preorder_splaytree(tree->right);
}
}
//中序遍历伸展树
void inorder_splaytree(SplayTree tree)
{
if (tree)
{
inorder_splaytree(tree->left);
printf("%d ",tree->key);
inorder_splaytree(tree->right);
}
}
//后序遍历伸展树
void poster_splaytree(SplayTree tree)
{
if (tree)
{
poster_splaytree(tree->left);
poster_splaytree(tree->right);
printf("%d ", tree->key);
}
}
3. 查找给的关键值key的节点
//(递归实现)查找伸展树中键值为key的节点
Node *splaytree_search(SplayTree x, Type key)
{
if(x == NULL || x->key == key)
return x;
if(key < x->key)
return splaytree_search(x->left, key);
if(key > x->key)
return splaytree_search(x->right, key);
}
//(非递归实现)查找伸展树中键值为key的节点
Node *iterative_splaytree_search(SplayTree x, Type key)
{
while ((x!= NULL)&&(x->key != key))
{
if(key < x->key)
x = x->left;
else
x = x->right;
}
return x;
}
4.查找伸展树中的最小关键值、最大关键值的节点
//查找最小节点:返回tree为根节点的伸展树的最小节点
Node *splaytree_minimum(SplayTree tree)
{
if (tree == NULL)
{
printf("splay tree is empty!\n");
return NULL;
}
while (tree->left != NULL)
tree = tree->left;
return tree;
}
//查找最大节点:返回tree为根节点的伸展树的最大节点
Node *splaytree_maximum(SplayTree tree)
{
if (tree == NULL)
{
printf("splay tree is empty!\n");
return NULL;
}
while (tree->right != NULL)
tree = tree->right;
return tree;
}
5.伸展树的旋转
旋转key对应的节点为根节点
注意:
(a)伸展树中存在键值为key的节点, 将该节点旋转为根节点
(b)伸展树中不存在键值为key的节点,并且key < tree->key.
b-1:键值为key的节点的前驱节点存在的话,将前驱节点旋转为根节点
b-2:键值为key的节点存在前驱节点的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点
(c): 伸展树中不存在键值为key的节点,并且key > tree->key。
c-1:键值为key的节点的后继节点存在的话,将后继节点旋转为根节点
c-2:键值为key的后继节点存在的话,则意味着,key比树中任何键值都打,那么此时,将最大节点旋转为根节点。
Node *splaytree_splay(SplayTree tree, Type key)
{
Node N, *l, *r, *c;
if(tree == NULL)
return NULL;
N.left = N.right = NULL;
l = r = &N;
for (;;)
{
if (key < tree->key)
{
if(tree->left == NULL)
break;
if (key < tree->left->key) //01,rotate right
{
c = tree->left;
tree->left = c->right;
c->right = tree;
tree = c;
if(tree->left == NULL)
break;
}
r->left = tree; //02,link right
r = tree;
tree = tree->left;
}
else if(key > tree->key)
{
if(tree->right == NULL)
break;
if(key > tree->right->key) //03,rotate left
{
c = tree->right;
tree->right = c->left;
c->left = tree;
tree = c;
if(tree->right == NULL)
break;
}
l->right = tree; //04,link left
l = tree;
tree = tree->right;
}
else break;
}
l->right = tree->left; //05, assemble
r->left = tree->right;
tree->left = N.right;
tree->right = N.left;
return tree;
}
6.插入节点
//将节点插入到伸展树中(不旋转),并返回根节点
Node *splaytree_insert(SplayTree tree, Node *z)
{
Node *y = NULL;
Node *x = tree;
//查找z的插入位置
while (x != NULL)
{
y = x;
if(z->key < x->key)
x = x->left;
else if(z->key > x ->key)
x = x->right;
else
{
printf("节点已经存在!\n");
free(z);
return tree;
}
}
if(y == NULL)
tree = z;
else if(z->key < y->key)
y->left = z;
else
y->right = z;
return tree;
}
7.创建伸展树节点
//创建并返回伸展树节点
//key:键值,parent:父节点, left:左孩子,right:右孩子
static Node *create_spalytree_node(Type key, Node *left, Node *right)
{
Node *p;
p = (Node *)malloc(sizeof(Node));
if(p == NULL)
{
printf("out of space!\n");
return NULL;
}
p->key = key;
p->left = left;
p->right = right;
return p;
}
8.创建节点key,并旋转为根节点
//新建节点key,并将其插入到伸展树中,将插入节点旋转为根节点
Node *insert_splaytree(SplayTree tree, Type key)
{
Node *z; //新建节点
if((z = create_spalytree_node(key, NULL, NULL))== NULL) //新建节点失败,则返回
return tree;
//插入节点
tree = splaytree_insert(tree, z);
//将节点key旋转为根节点
tree = splaytree_splay(tree, key);
}
9.删除key节点
//删除key节点,返回根节点(根节点是被删除节点的前驱节点)
Node *delete_splaytree(SplayTree tree, Type key)
{
Node *x;
if(tree == NULL)
return NULL;
//查找键值为key的节点,找不到的话直接返回
if(splaytree_search(tree,key) == NULL)
return tree;
//将key对应的节点旋转为根节点
tree = splaytree_splay(tree, key);
if (tree->left)
{
//将tree的前驱节点旋转为根节点
x = splaytree_splay(tree->left, key);
//移除tree节点
x->right = tree->right;
}
else
x = tree->right;
free(tree);
return x;
}
其实伸展树的大部分操作还是二叉树的操作,只是在访问节点时,将节点旋转至跟节点,留意该旋转操作。
完整的测试代码
#include <stdio.h>
#include <stdlib.h>
typedef int Type;
typedef struct SplayTreeNode
{
Type key; //关键字
struct SplayTreeNode *left; //左子树
struct SplayTreeNode *right; //右子树
}Node, *SplayTree;
//前序遍历伸展树
void preorder_splaytree(SplayTree tree)
{
if (tree)
{
printf("%d ",tree->key);
preorder_splaytree(tree->left);
preorder_splaytree(tree->right);
}
}
//中序遍历伸展树
void inorder_splaytree(SplayTree tree)
{
if (tree)
{
inorder_splaytree(tree->left);
printf("%d ",tree->key);
inorder_splaytree(tree->right);
}
}
//后序遍历伸展树
void poster_splaytree(SplayTree tree)
{
if (tree)
{
poster_splaytree(tree->left);
poster_splaytree(tree->right);
printf("%d ", tree->key);
}
}
//(递归实现)查找伸展树中键值为key的节点
Node *splaytree_search(SplayTree x, Type key)
{
if(x == NULL || x->key == key)
return x;
if(key < x->key)
return splaytree_search(x->left, key);
if(key > x->key)
return splaytree_search(x->right, key);
}
//(非递归实现)查找伸展树中键值为key的节点
Node *iterative_splaytree_search(SplayTree x, Type key)
{
while ((x!= NULL)&&(x->key != key))
{
if(key < x->key)
x = x->left;
else
x = x->right;
}
return x;
}
//查找最小节点:返回tree为根节点的伸展树的最小节点
Node *splaytree_minimum(SplayTree tree)
{
if (tree == NULL)
{
printf("splay tree is empty!\n");
return NULL;
}
while (tree->left != NULL)
tree = tree->left;
return tree;
}
//查找最大节点:返回tree为根节点的伸展树的最大节点
Node *splaytree_maximum(SplayTree tree)
{
if (tree == NULL)
{
printf("splay tree is empty!\n");
return NULL;
}
while (tree->right != NULL)
tree = tree->right;
return tree;
}
//旋转key对应的节点为根节点
//注意:(a)伸展树中存在键值为key的节点, 将该节点旋转为根节点
//(b)伸展树中不存在键值为key的节点,并且key < tree->key.
// b-1:键值为key的节点的前驱节点存在的话,将前驱节点旋转为根节点
// b-2:键值为key的节点存在前驱节点的话,则意味着,key比树中任何键值都小,那么此时,将最小节点旋转为根节点
//(c): 伸展树中不存在键值为key的节点,并且key > tree->key。
// c-1:键值为key的节点的后继节点存在的话,将后继节点旋转为根节点
// c-2:键值为key的后继节点存在的话,则意味着,key比树中任何键值都打,那么此时,将最大节点旋转为根节点。
Node *splaytree_splay(SplayTree tree, Type key)
{
Node N, *l, *r, *c;
if(tree == NULL)
return NULL;
N.left = N.right = NULL;
l = r = &N;
for (;;)
{
if (key < tree->key)
{
if(tree->left == NULL)
break;
if (key < tree->left->key) //01,rotate right
{
c = tree->left;
tree->left = c->right;
c->right = tree;
tree = c;
if(tree->left == NULL)
break;
}
r->left = tree; //02,link right
r = tree;
tree = tree->left;
}
else if(key > tree->key)
{
if(tree->right == NULL)
break;
if(key > tree->right->key) //03,rotate left
{
c = tree->right;
tree->right = c->left;
c->left = tree;
tree = c;
if(tree->right == NULL)
break;
}
l->right = tree; //04,link left
l = tree;
tree = tree->right;
}
else break;
}
l->right = tree->left; //05, assemble
r->left = tree->right;
tree->left = N.right;
tree->right = N.left;
return tree;
}
//将节点插入到伸展树中(不旋转),并返回根节点
Node *splaytree_insert(SplayTree tree, Node *z)
{
Node *y = NULL;
Node *x = tree;
//查找z的插入位置
while (x != NULL)
{
y = x;
if(z->key < x->key)
x = x->left;
else if(z->key > x ->key)
x = x->right;
else
{
printf("节点已经存在!\n");
free(z);
return tree;
}
}
if(y == NULL)
tree = z;
else if(z->key < y->key)
y->left = z;
else
y->right = z;
return tree;
}
//创建并返回伸展树节点
//key:键值,parent:父节点, left:左孩子,right:右孩子
static Node *create_spalytree_node(Type key, Node *left, Node *right)
{
Node *p;
p = (Node *)malloc(sizeof(Node));
if(p == NULL)
{
printf("out of space!\n");
return NULL;
}
p->key = key;
p->left = left;
p->right = right;
return p;
}
//新建节点key,并将其插入到伸展树中,将插入节点旋转为根节点
Node *insert_splaytree(SplayTree tree, Type key)
{
Node *z; //新建节点
if((z = create_spalytree_node(key, NULL, NULL))== NULL) //新建节点失败,则返回
return tree;
//插入节点
tree = splaytree_insert(tree, z);
//将节点key旋转为根节点
tree = splaytree_splay(tree, key);
}
//删除key节点,返回根节点(根节点是被删除节点的前驱节点)
Node *delete_splaytree(SplayTree tree, Type key)
{
Node *x;
if(tree == NULL)
return NULL;
//查找键值为key的节点,找不到的话直接返回
if(splaytree_search(tree,key) == NULL)
return tree;
//将key对应的节点旋转为根节点
tree = splaytree_splay(tree, key);
if (tree->left)
{
//将tree的前驱节点旋转为根节点
x = splaytree_splay(tree->left, key);
//移除tree节点
x->right = tree->right;
}
else
x = tree->right;
free(tree);
return x;
}
//销毁伸展树
void destroy_splaytree(SplayTree tree)
{
if(tree == NULL)
return ;
if(tree->left != NULL)
destroy_splaytree(tree->left);
if(tree->right != NULL)
destroy_splaytree(tree->right);
free(tree);
}
//打印伸展树
void print_splaytree(SplayTree tree, Type key, int direction)
{
if (tree)
{
if(direction == 0) //tree是根节点
printf("%2d is root\n", tree->key);
else
printf("%2d is %2d's %6s child\n", tree->key, key, direction == 1?"right" :"left");
print_splaytree(tree->left, tree->key, -1);
print_splaytree(tree->right, tree->key, 1);
}
}
//测试
int main()
{
int a[] = {10, 50, 40, 30, 20, 60};
int i;
SplayTree root = NULL;
printf("===依次添加===\n");
for (i = 0; i < 6; i ++)
{
printf("%d ", a[i]);
root = insert_splaytree(root, a[i]);
}
printf("\n 前序遍历\n");
preorder_splaytree(root);
printf("\n中序遍历\n");
inorder_splaytree(root);
printf("\n后续遍历\n");
poster_splaytree(root);
printf("\n最大值:%d\n",splaytree_maximum(root)->key);
printf("最小值:%d\n",splaytree_minimum(root)->key);
printf("树的详细信息:\n");
print_splaytree(root, root->key, 0);
i = 30;
printf("旋转节点为%d为根节点\n", i);
root = splaytree_splay(root, i);
printf("树的详细信息\n");
print_splaytree(root, root->key, 0);
destroy_splaytree(root);
system("pause");
return 0;
}
reference:http://www.cnblogs.com/skywang12345/p/3604238.html?utm_source=tuicool