`
543089122
  • 浏览: 149645 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

简单_伸展树(Splay tree)

阅读更多
如果你注意观察你会发现,输入法就打某个字,如果你下次还打那个字,那么它的位置将在它上一次位置的前面,如果你一直打某个字,那么它每次都往前移动一格或多格。
所以不同的人使用搜狗输入法啊、紫光啊、QQ输入法啊都觉得好使,因为它会根据每个人的不同习惯把你经常打的字弄到靠近前面。
输入法也是要求查询效率的,所以输入法其实也是btree,但是为了更加的智能它使用了另一种树结构-----------伸展树。
伸展树不要求像AVL、红黑树那样追求完美的平衡(其实红黑树也没AVL树那样使劲的追求平衡),伸展树追求的是90%与10%原则,啥意思呢?这意思是说平时我们打字的时候90%的字我们都不经常去打,至少绝大多数人每天都是在打那10%还不到的字,所以它就把经常使用到的字往根节点挪动。
其实在平时应用中我们都是使用多种数据结构,也就是结构混合使用,千万别拘泥于某一种招式和动作,学数据结构就和练武一样,有时候忘记了才能跳出来才能看的更远更清晰。

package sunfa.tree;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

/**
 * 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。<br>
 * 它由Daniel Sleator和Robert Tarjan创造。它的优势在于不需要记录用于平衡树的冗余信息。<br>
 * 在伸展树上的一般操作都基于伸展操作。<br>
 * 
 * 各种查找树存在不足。比如:对于一个有n个节点的平衡树,虽然最坏情况下每次查找的时间复杂度不会超过O(logn),<br>
 * 但是如果访问模式不均匀,平衡树的效率就会受到影响。此外,它们还需要额外的空间来存储平衡信息。<br>
 * 
 * 伸展树存在的意义:
 * 假设想要对一个二叉查找树执行一系列的查找操作。为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。<br>
 * 于是想到设计一个简单方法, 在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。splay tree应运而生。<br>
 * splay tree是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。<br>
 *
 * @param <K>
 * @param <V>
 */
public class MySplayTree<K, V> {

	public static void main(String[] args) {
		MySplayTree<Integer, Integer> tree = new MySplayTree<Integer, Integer>(
				new Comparator<Integer>() {
					public int compare(Integer o1, Integer o2) {
						return o1 - o2;
					}
				});
		Random ran = new Random();
		int n = 10;
		List<Integer> list = new ArrayList<Integer>();
		int[] arr = { 10, 8, 18, 3, 9, 11, 21 };
		for (int i = 0; i < arr.length; i++) {
			int o = arr[i];// ran.nextInt(100);
			tree.put(o, o);
			list.add(o);
		}
		System.out.println("printTree:");
		tree.printTree(tree.root);
		System.out.println("search:");
		tree.search(8);
		/*
		 * for (int i = 0; i < list.size(); i++) { Entry<Integer, Integer> node
		 * = tree.search(Integer.parseInt(list .get(i).toString()));
		 * System.out.print(node.key + ","); }
		 */
	}

	private Entry<K, V> root;
	private Comparator<K> comp;

	public MySplayTree(Comparator<K> c) {
		comp = c;
	}

	public void put(K key, V value) {
		if (root == null) {
			root = new Entry<K, V>(null, key, value);
			return;
		}
		Entry<K, V> node = root;
		while (true) {
			int c = compare(key, node.key);
			if (c == 0) {
				node.value = value;
				return;
			} else if (c < 0) {
				if (node.left != null) {
					node = node.left;
				} else {
					node.left = new Entry<K, V>(node, key, value);
					return;
				}
			} else {
				if (node.right != null) {
					node = node.right;
				} else {
					node.right = new Entry<K, V>(node, key, value);
					return;
				}
			}
		}
	}

	/**
	 * 如果某个节点被访问,则旋转该节点使得该节点被提升到距离根节点进一步的位置,也就是向上提升一个级别(和他的父节点交换)
	 * @param key
	 * @return
	 */
	public Entry<K, V> search(K key) {
		Entry<K, V> node = root;
		while (true) {
			if (node == null)
				return null;
			int c = compare(key, node.key);
			if (c == 0) {
				spaly(node);
				return node;
			} else if (c < 0) {
				node = node.left;
			} else {
				node = node.right;
			}
		}
	}

	/**
	 * 旋转节点
	 * 关于伸展树的旋转:
	 * 1、被旋转的节点是左子节点
	 * 		a)被旋转节点是根节点的直接子节点
	 * 		b)被旋转节点是根节点的非直接子节点
	 * 2、被旋转的节点是右子节点
	 * 		a)被旋转节点是根节点的直接子节点
	 * 		b)被旋转节点是根节点的非直接子节点
	 * 
	 * 旋转还是很简单的,虽然这里有2个大点,左或者右,但只要写完一边的代码另一边直接copy后把left改成right并且把right改成left就可以了。
	 * @param node   查找到的节点,这个节点需要被提升到其父节点的位置
	 */
	private void spaly(Entry<K, V> node) {
		if (node != root) {
			// 被旋转的节点是左子节点
			if (node.parent.left == node) {
				if (node.parent.parent == null) {// 被旋转节点是根节点的直接子节点
					Entry<K, V> p = node.parent;
					if (node.right != null) {
						p.left = node.right;
						node.right.parent = p;
					} else {
						node.right = p;
						p.parent = node;
					}
					p.parent = node;
					node.right = p;
					node.parent = null;
					this.root = node;
				} else {// 被旋转节点是根节点的非直接子节点
					Entry<K, V> p = node.parent.parent;
					Entry<K, V> p2 = node.parent;
					if (node.right != null) {
						p2.left = node.right;
						node.right.parent = p2;
					} else {
						node.right = p2;
						p2.parent = node;
						p2.left = null;
					}
					p.left = node;
					node.parent = p;
				}
			} else {
				if (node.parent.parent == null) {// 被旋转节点是根节点的直接子节点
					Entry<K, V> p = node.parent;
					if (node.left != null) {
						p.right = node.left;
						node.left.parent = p;
					} else {
						node.left = p;
						p.parent = node;
					}
					p.parent = node;
					node.left = p;
					node.parent = null;
					this.root = node;
				} else {// 被旋转节点是根节点的非直接子节点
					Entry<K, V> p = node.parent.parent;
					Entry<K, V> p2 = node.parent;
					if (node.left != null) {
						p2.right = node.left;
						node.left.parent = p2;
					} else {
						node.left = p2;
						p2.parent = node;
						p2.right = null;
					}
					p.right = node;
					node.parent = p;
				}
			}
		}
	}

	private void printTree(Entry<K, V> node) {
		if (node == null)
			return;
		printTree(node.left);
		System.out.print(node.key + ",");
		printTree(node.right);
	}

	private int compare(K k1, K k2) {
		return comp != null ? (((Comparator<K>) comp).compare(k1, k2))
				: (((Comparable<K>) k1).compareTo(k2));
	}

	static class Entry<K, V> {
		Entry<K, V> parent;
		Entry<K, V> left;
		Entry<K, V> right;
		K key;
		V value;

		public Entry(Entry<K, V> parent, K key, V value) {
			super();
			this.parent = parent;
			this.key = key;
			this.value = value;
		}
	}
}

修正:关于节点旋转部分的代码是按照自己的逻辑写的,因为当时测试的时候没有发现问题就。看了算法导论的关于这部分的伪代码描述感觉自己的那个旋转写的不行。下面是算法道理上旋转部分的代码
/**
	 * 左旋转 <br>
	 * 1、分离旋转的子节点的(左或右)部分,分离出来的的部分称为分离集。  <br>
	 * 2、分离集的左子节点挂接到分离元素原来处的位置。 <br>
	 * 3、分离集去顶替旋转元素的位置(也起到了断开旋转元素与旋转元素父的关系)  <br>
	 * 	a)旋转元素为跟元素  <br>
	 * 	b)旋转元素是它父节点的左子节点 <br>
	 * 	c)旋转元素是它父节点的右子节点,和b)相反 <br>
	 * 4、旋转集(第3步中旋转元素部分被断开,这里称旋转集)挂接到分离集的左子节点处。 <br>
	 * 
	 * @param x
	 *            旋转的元素
	 */
	private void leftRotate(Entry<K, V> x) {
		// ①
		Entry<K, V> y = x.right;// 分离出旋转元素的右子节点
		// ②
		x.right = y.left;// 旋转元素的右子节点的左子节点挂接到旋转元素的右子节点处
		if (y.left != null) {
			y.left.parent = x;//
		}
		// ③
		y.parent = x.parent;// 分离出来的部分挂接到旋转元素的父节点下
		if (x.parent == null) {// 如果旋转元素为根节点,就让旋转元素成为根
			this.root = y;
		} else if (x == x.parent.left) {// 如果旋转元素是它父节点的左子节点,让旋转元素父节点的左指针指向分离出的节点
			x.parent.left = y;
		} else {// 如果是右子节点,就用父节点的右指针指向分离节点
			x.parent.right = y;
		}
		// ④
		y.left = x;// 分离出来的部分的左子节点指向旋转元素
		x.parent = y;// 旋转元素的父节点指向分离出的元素
	}

	/**
	 * 右旋转,参考左旋转
	 * @param x
	 */
	private void rightRotate(Entry<K, V> x) {
		// ①
		Entry<K, V> y = x.left;// 分离出旋转元素的右子节点
		// ②
		x.left = y.right;// 旋转元素的右子节点的左子节点挂接到旋转元素的右子节点处
		if (y.right != null)
			y.right.parent = x;//
		// ③
		y.parent = x.parent;// 分离出来的部分挂接到旋转元素的父节点下
		if (x.parent == null) {// 如果旋转元素为根节点,就让旋转元素成为根
			this.root = y;
		} else if (x == x.parent.left) {// 如果旋转元素是它父节点的左子节点,让旋转元素父节点的左指针指向分离出的节点
			x.parent.left = y;
		} else {// 如果是右子节点,就用父节点的右指针指向分离节点
			x.parent.right = y;
		}
		// ④
		y.right = x;// 分离出来的部分的左子节点指向旋转元素
		x.parent = y;// 旋转元素的父节点指向分离出的元素
	}
0
1
分享到:
评论

相关推荐

    top_down_splay_tree

    top_down splay_tree 伸展树

    伸展树(Splay tree)图解与实现(2020.10.22).pdf

    伸展树(Splay tree)图解与实现(2020.10.22).pdf

    伸展树(Splay Tree)

    伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。

    数据结构伸展树splay.rar

    伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由丹尼尔·斯立特Daniel Sleator 和 罗伯特·恩卓·塔扬Robert Endre Tarjan 在1985年发明的。 [1] 在伸展树上...

    splay tree C# code 伸展树的C#代码实现

    splay tree C# code 伸展树的C#代码实现 我看到没有C#实现版本,所以就把java代码转化成C#实现了一把

    splaytree.zip

    展树(Splay Tree)是一种二叉搜索树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。

    Splay Tree

    伸展树的主要特点:每次访问某个节点时,都把此节点旋转到根部。保证从空树开始任意M次操作最多花费O(MlogN)的时间,也就是说它的摊还时间为O(F(N))。 伸展数的主要难点:展开函数的实现。 基本操作插入、删除、...

    伸展树的基本操作与应用

    伸展树的基本操作与应用 伸展树的基本操作与应用

    Splay(C++)示例代码

    伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由丹尼尔·斯立特Daniel Sleator 和 罗伯特·恩卓·塔扬Robert Endre Tarjan 在1985年发明的。伸展树是一种自...

    运用伸展树解决数列维护问题.pdf

    伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由丹尼尔·斯立特Daniel Sleator 和 罗伯特·恩卓·塔扬Robert Endre Tarjan 在1985年发明的。 [1] 在伸展树上...

    伸展树的基本实现和区间操作

    给定一个长度为N的序列,每个序列的长度是一个整数。要支持以下三种操作:  将[L,R]这个区间所有数加上V.  将[L,R]这个区间翻转,例如 1234变成 4321  求[L,R]区间的最大值 能力有限,实现可能有纰漏,也没有...

    C++伸展树实现.zip

    C++伸展树实现

    数据结构之伸展树详解

    主要介绍了数据结构之伸展树详解,本文对伸展树(Splay Tree)的单旋转操作、一字型旋转、之字形旋转区间操作等理论知识做了讲解,并给出实现代码,需要的朋友可以参考下

    伸展树操作详解1

    1、 Treap:即 Tree+Heap,为每一个节点随机引入一个权值,通过维护这些权值满足堆 2、 Splay:即本文所要讲解重点——伸展树 1、 程序初始化

    二叉排序树生成

    利用伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展...

    左程云leetcode-algorithm-and-data-structure:算法+数据结构=程序https://en.wikipedia

    伸展树(Splay Tree) B-树(B-Tree) 线索二叉树(Threaded Binary Tree) 前缀树/字典树(Trie) 5. 哈希/散列(Hashing) 哈希表(Hash Table) 哈希函数(Hash Functions) 6. 优先队列(Priority Queue) 堆

    左程云leetcode-algorithm-and-data-structure:算法+数据结构=程序

    伸展树(Splay Tree) B-树(B-Tree) 线索二叉树(Threaded Binary Tree) 前缀树/字典树(Trie) 5. 哈希/散列(Hashing) 哈希表(Hash Table) 哈希函数(Hash Functions) 6. 优先队列(Priority Queue) 堆

    leetcode中国-algorithm:算法

    伸展树(splay tree 分裂树) 平衡二叉树AVL 红黑树 B树,B+,B* R树 Trie树(前缀树) 后缀树 最优二叉树(赫夫曼树) 二叉堆 (大根堆,小根堆) 二项树 二项堆 斐波那契堆(Fibonacci Heap) 图的算法 图的存储结构和基本...

    leetcode中国-MyDS:学习实现各种数据结构

    伸展树(splay tree 分裂树) 平衡二叉树AVL 红黑树 B树,B+,B* R树 Trie树(前缀树) 后缀树 最优二叉树(赫夫曼树) 二叉堆 (大根堆,小根堆) 二项树 二项堆 斐波那契堆(Fibonacci Heap) 图的算法 图的存储结构和基本...

    leetcode中国-Learn-Algorithms:学习算法

    伸展树(splay tree 分裂树) 平衡二叉树AVL 红黑树 B树,B+,B* R树 Trie树(前缀树) 后缀树 最优二叉树(赫夫曼树) 二叉堆 (大根堆,小根堆) 二项树 二项堆 斐波那契堆(Fibonacci Heap) 图的算法 图的存储结构和基本...

Global site tag (gtag.js) - Google Analytics