伸展树(Splay Tree)

1. 概述

伸展树(Splay Tree)是一种能够自我平衡的二叉查找树,它能在均摊 O ( log ⁡ n ) O(\log n) O(logn) 的时间内完成基于伸展(Splay)操作的插入、查找、修改和删除操作。它是由 Daniel Sleator 和 Robert Tarjan 在1985年发明的。

在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些节点就应当经常处于靠近树根的位置。于是想到设计一个简单方法,在每次查找之后对树进行调整,把被查找的节点搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自调整的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。

1.1 优点

伸展树的自我平衡使其拥有良好的性能,频繁访问的节点会被移动到更靠近根节点的位置,进而获得更快的访问速度。

  • 可靠的性能——它的平均效率不输于其他平衡树。

  • 存储所需的内存少——伸展树无需记录额外的什么值来维护树的信息,相对于其他平衡树,内存占用要小。

1.2 缺点

  • 伸展树并不总是平衡树,在某些操作后可能变得不平衡,有可能会变成一条链。例如,在以非递减顺序访问全部 n 个节点之后,此时树的高度对应于最坏情况的时间效率,操作的实际时间效率可能很低。然而,均摊的最坏情况是对数级的—— O ( log ⁡ n ) O( \log n) O(logn)

  • 即使以“只读”方式(例如通过查找操作)访问伸展树,其结构也可能会发生变化。这使得伸展树在多线程环境下会变得很复杂。具体而言,如果允许多个线程同时执行查找操作,则需要额外的维护和操作。

2. 2种单旋转和4种双旋转

2.1 Zig Rotation

Zig Rotation 是单旋转。如果 x x x 是左子节点且 x x x 没有祖父母(即 x x x 的父节点是根节点),我们将对节点 x x x 进行 Zig Rotation。 为了完成 Zig Rotation,我们将 x x x 的父节点向右旋转。如下图所示。

在这里插入图片描述

2.2 Zag Rotation

Zag Rotation 是 Zig Rotation 的镜像。如果 x x x 是右子节点且 x x x 没有祖父母,我们将对节点 x x x 进行 Zag Rotation。为了完成 Zag Rotation,我们将 x x x 的父节点向左旋转。 如下图所示。

在这里插入图片描述

2.3 Zig-Zig Rotation

Zig-Zig Rotation 是双旋转。如果 x x x 是左子节点并且 x x x 的父节点也是左子节点,我们将对节点 x x x 进行 Zig-Zig Rotation。为了完成 Zig-Zig Rotation,我们首先将 x x x 的祖父母节点向右旋转,然后将 x x x 的父节点向右旋转。如下图所示。

在这里插入图片描述

2.4 Zag-Zag Rotation

Zag-Zag Rotation 是 Zig-Zig Rotation 的镜像。如果 x x x 是右子节点并且 x x x 的父母也是右子节点,我们将对节点 x x x 进行 Zag-Zag Rotation。为了完成 Zag-Zag Rotation,我们首先将 x x x 的祖父母节点向左旋转,然后将 x x x 的父节点向左旋转。如下图所示。

在这里插入图片描述

2.5 Zig-Zag Rotation

Zig-Zag Rotation 也是双旋转。如果 x x x 是右子节点而 x x x 的父母是左子节点,我们会对 x x x 进行 Zig-Zag Rotation。为了完成 Zig-Zag Rotation,我们首先将 x x x 的父节点向左旋转,然后将 x x x 的祖父母(新父)节点向右旋转。如下图所示。

在这里插入图片描述

2.6 Zag-Zig Rotation

Zag-Zig Rotation 是 Zig-Zag Rotation 的镜像。如果 x x x 是左子节点而 x x x 的父母是右子节点,我们将对 x x x 进行 Zag-Zig Rotation。为了完成 Zag-Zig Rotation,我们首先将 x x x 的父节点向右旋转,然后将 x x x 的祖父母(新父)节点向左旋转。如下图所示。

在这里插入图片描述

3. 操作

3.1 伸展Splay

每次双旋转都会将节点 x x x 移到其祖父母的位置,而每次单旋转都会将节点 x x x 移到其父母的位置,通过执行这些旋转,直至节点 x x x 到达树根,此过程就被称为伸展(Splay)操作。

下面给出了 Splay 操作的伪代码。

3.2 连接Join

给出两棵树 S S S T T T,且 S S S 的所有元素都比 T T T 的元素要小。下面的步骤可以把它们连接成一棵树:

  • 伸展 S S S 中最大的节点。现在这个节点变成了 S S S 的根节点,且没有右儿子。
  • T T T 的根节点变为其右儿子。

连接操作的伪代码如下。

3.3 分割Split

给出一棵树和一个节点 x x x,返回两棵树 S S S T T T,其中 S S S 中所有的元素均小于等于 x x x 的值, T T T 中所有的元素大于 x x x 的值。下面的步骤可以完成这个操作:

  • 伸展节点 x x x。这样的话,节点 x x x 就成为了这棵树的根,所以它的左子树包含了所有比 x x x 的值小的元素,右子树包含了所有比 x x x 的值大的元素。

  • x x x 的右子树从树中分割出来。

分割操作的伪代码如下。

3.4 搜索Search

如果我们要搜索 key,下面的步骤可以完成这个操作:

  • 首先,执行普通二叉查找树的搜索操作,假设在节点 x x x 中找到了 key。
  • 然后,伸展节点 x x x

搜索 key 的伪代码如下。

3.5 插入Insert

如果我们要插入 key,新建一个节点 node 保存 key 值,下面的步骤可以完成插入节点 node。

  • 首先,执行普通二叉查找树的插入操作,在适当的位置插入节点 node。
  • 然后,伸展节点 node,将其移动到树的根。

插入 key 的伪代码如下。

3.6 删除Delete

如果我们要删除 key,假设在节点 x x x 中找到了 key,下面的步骤可以完成删除节点 x x x

  • 根据节点 x x x 将整棵树分割为两棵树 S S S T T T,其中 S S S 中所有的元素均小于等于 key, T T T 中所有的元素大于 key。

  • 从 S 的根部删除节点 x x x

  • S S S 的左子树和 T T T 连接成一棵新树。

删除节点 x x x 的伪代码如下。

4. C++代码实现

// Splay tree implementation in C++
// Author: Algorithm Tutor
// Tutorial URL: http://algorithmtutor.com/Data-Structures/Tree/Splay-Trees/

#include <iostream>

using namespace std;

// data structure that represents a node in the tree
struct Node {
    
    
	int data; // holds the key
	Node *parent; // pointer to the parent
	Node *left; // pointer to left child
	Node *right; // pointer to right child
};

typedef Node *NodePtr;

// class SplayTree implements the operations in Splay tree
class SplayTree {
    
    
private:
	NodePtr root;

	void preOrderHelper(NodePtr node) {
    
    
		if (node != nullptr) {
    
    
			cout<<node->data<<" ";
			preOrderHelper(node->left);
			preOrderHelper(node->right);
		} 
	}

	void inOrderHelper(NodePtr node) {
    
    
		if (node != nullptr) {
    
    
			inOrderHelper(node->left);
			cout<<node->data<<" ";
			inOrderHelper(node->right);
		} 
	}

	void postOrderHelper(NodePtr node) {
    
    
		if (node != nullptr) {
    
    
			postOrderHelper(node->left);
			postOrderHelper(node->right);
			cout<<node->data<<" ";
		} 
	}

	NodePtr searchTreeHelper(NodePtr node, int key) {
    
    
		if (node == nullptr || key == node->data) {
    
    
			return node;
		}

		if (key < node->data) {
    
    
			return searchTreeHelper(node->left, key);
		} 
		return searchTreeHelper(node->right, key);
	}

	void deleteNodeHelper(NodePtr node, int key) {
    
    
		NodePtr x = nullptr;
		NodePtr t, s;
		while (node != nullptr){
    
    
			if (node->data == key) {
    
    
				x = node;
			}

			if (node->data <= key) {
    
    
				node = node->right;
			} else {
    
    
				node = node->left;
			}
		}

		if (x == nullptr) {
    
    
			cout<<"Couldn't find key in the tree"<<endl;
			return;
		}
		split(x, s, t); // split the tree
		if (s->left){
    
     // remove x
			s->left->parent = nullptr;
		}
		root = join(s->left, t);
		delete(s);
		s = nullptr;
	}

	void printHelper(NodePtr root, string indent, bool last) {
    
    
		// print the tree structure on the screen
	   	if (root != nullptr) {
    
    
		   cout<<indent;
		   if (last) {
    
    
		      cout<<"└────";
		      indent += "     ";
		   } else {
    
    
		      cout<<"├────";
		      indent += "|    ";
		   }

		   cout<<root->data<<endl;

		   printHelper(root->left, indent, false);
		   printHelper(root->right, indent, true);
		}
	}

	// rotate left at node x
	void leftRotate(NodePtr x) {
    
    
		NodePtr y = x->right;
		x->right = y->left;
		if (y->left != nullptr) {
    
    
			y->left->parent = x;
		}
		y->parent = x->parent;
		if (x->parent == nullptr) {
    
    
			this->root = y;
		} else if (x == x->parent->left) {
    
    
			x->parent->left = y;
		} else {
    
    
			x->parent->right = y;
		}
		y->left = x;
		x->parent = y;
	}

	// rotate right at node x
	void rightRotate(NodePtr x) {
    
    
		NodePtr y = x->left;
		x->left = y->right;
		if (y->right != nullptr) {
    
    
			y->right->parent = x;
		}
		y->parent = x->parent;
		if (x->parent == nullptr) {
    
    
			this->root = y;
		} else if (x == x->parent->right) {
    
    
			x->parent->right = y;
		} else {
    
    
			x->parent->left = y;
		}
		y->right = x;
		x->parent = y;
	}

	// splaying
	void splay(NodePtr x) {
    
    
		while (x->parent) {
    
    
			if (!x->parent->parent) {
    
    
				if (x == x->parent->left) {
    
    
					// zig rotation
					rightRotate(x->parent);
				} else {
    
    
					// zag rotation
					leftRotate(x->parent);
				}
			} else if (x == x->parent->left && x->parent == x->parent->parent->left) {
    
    
				// zig-zig rotation
				rightRotate(x->parent->parent);
				rightRotate(x->parent);
			} else if (x == x->parent->right && x->parent == x->parent->parent->right) {
    
    
				// zag-zag rotation
				leftRotate(x->parent->parent);
				leftRotate(x->parent);
			} else if (x == x->parent->right && x->parent == x->parent->parent->left) {
    
    
				// zig-zag rotation
				leftRotate(x->parent);
				rightRotate(x->parent);
			} else {
    
    
				// zag-zig rotation
				rightRotate(x->parent);
				leftRotate(x->parent);
			}
		}
	}

	// joins two trees s and t
	NodePtr join(NodePtr s, NodePtr t){
    
    
		if (!s) {
    
    
			return t;
		}

		if (!t) {
    
    
			return s;
		}
		NodePtr x = maximum(s);
		splay(x);
		x->right = t;
		t->parent = x;
		return x;
	}

	// splits the tree into s and t
	void split(NodePtr &x, NodePtr &s, NodePtr &t) {
    
    
		splay(x);
		if (x->right) {
    
    
			t = x->right;
			t->parent = nullptr;
		} else {
    
    
			t = nullptr;
		}
		s = x;
		s->right = nullptr;
		x = nullptr;
	} 

public:
	SplayTree() {
    
    
		root = nullptr;
	}

	// Pre-Order traversal
	// Node->Left Subtree->Right Subtree
	void preorder() {
    
    
		preOrderHelper(this->root);
	}

	// In-Order traversal
	// Left Subtree -> Node -> Right Subtree
	void inorder() {
    
    
		inOrderHelper(this->root);
	}

	// Post-Order traversal
	// Left Subtree -> Right Subtree -> Node
	void postorder() {
    
    
		postOrderHelper(this->root);
	}

	// search the tree for the key k
	// and return the corresponding node
	NodePtr searchTree(int k) {
    
    
		NodePtr x = searchTreeHelper(this->root, k);
		if (x) {
    
    
			splay(x);
		}
		return x;
	}

	// find the node with the minimum key
	NodePtr minimum(NodePtr node) {
    
    
		while (node->left != nullptr) {
    
    
			node = node->left;
		}
		return node;
	}

	// find the node with the maximum key
	NodePtr maximum(NodePtr node) {
    
    
		while (node->right != nullptr) {
    
    
			node = node->right;
		}
		return node;
	}

	// find the successor of a given node
	NodePtr successor(NodePtr x) {
    
    
		// if the right subtree is not null,
		// the successor is the leftmost node in the
		// right subtree
		if (x->right != nullptr) {
    
    
			return minimum(x->right);
		}

		// else it is the lowest ancestor of x whose
		// left child is also an ancestor of x.
		NodePtr y = x->parent;
		while (y != nullptr && x == y->right) {
    
    
			x = y;
			y = y->parent;
		}
		return y;
	}

	// find the predecessor of a given node
	NodePtr predecessor(NodePtr x) {
    
    
		// if the left subtree is not null,
		// the predecessor is the rightmost node in the 
		// left subtree
		if (x->left != nullptr) {
    
    
			return maximum(x->left);
		}

		NodePtr y = x->parent;
		while (y != nullptr && x == y->left) {
    
    
			x = y;
			y = y->parent;
		}

		return y;
	}

	// insert the key to the tree in its appropriate position
	void insert(int key) {
    
    
		// normal BST insert
		NodePtr node = new Node;
		node->parent = nullptr;
		node->left = nullptr;
		node->right = nullptr;
		node->data = key;
		NodePtr y = nullptr;
		NodePtr x = this->root;

		while (x != nullptr) {
    
    
			y = x;
			if (node->data < x->data) {
    
    
				x = x->left;
			} else {
    
    
				x = x->right;
			}
		}

		// y is parent of x
		node->parent = y;
		if (y == nullptr) {
    
    
			root = node;
		} else if (node->data < y->data) {
    
    
			y->left = node;
		} else {
    
    
			y->right = node;
		}

		// splay the node
		splay(node);
	}

	NodePtr getRoot(){
    
    
		return this->root;
	}

	// delete the node from the tree
	void deleteNode(int data) {
    
    
		deleteNodeHelper(this->root, data);
	}

	// print the tree structure on the screen
	void prettyPrint() {
    
    
		printHelper(this->root, "", true);
	}

};

int main() {
    
    
	SplayTree bst;
	bst.insert(33);
	bst.insert(44);
	bst.insert(67);
	bst.insert(5);
	bst.insert(89);
	bst.insert(41);
	bst.insert(98);
	bst.insert(1);
	bst.prettyPrint();
	bst.searchTree(33);
	bst.searchTree(44);
	bst.prettyPrint();
	bst.deleteNode(89);
	bst.deleteNode(67);
	bst.deleteNode(41);
	bst.deleteNode(5);
	bst.prettyPrint();
	bst.deleteNode(98);
	bst.deleteNode(1);
	bst.deleteNode(44);
	bst.deleteNode(33);
	bst.prettyPrint();
	return 0;
}

输出结果:

5. 参考资料

[1] https://en.wikipedia.org/wiki/Splay_tree

[2] https://algorithmtutor.com/Data-Structures/Tree/Splay-Trees/

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/109166949