Tree 2-3 平衡搜索树
与2-3-4树相似,2-3 平衡树是一种搜索树。
但由于每个节点最多有两个数据,分裂算法需要新插入数据的参与,这导致算法与2-3-4树有一定的差异。
每个节点可能会有2,3,4个子节点,对应可能会有1,2,3个数据。子节点数=数据数 + 1,不可能有空节点。插入数据时,树向下遍历以得到恰当的叶子节点时,数据均插入叶子节点,如果子节点需要分裂,则进行节点分裂,分裂产生的新数据向父节点插入,如果父节点是满的,则向上递归调用此过程。当根节点分裂,所有叶子节点的层高都增加一层,以此原理来保证树的平衡。因为分裂需要第三个数据,所以是在插入之后分裂节点的,这与2-3-4树的插入算法不同,此处采用添加临时数据,临时子节点的做饭以简化算法。但每个节点需要额外的空间来保存临时数据,临时子节点。
与2-3-4树不同,分裂的主体算法移至Node,这样编程相对简单。其他的平衡数可以参见红黑树
此树没有实现删除数据的算法,如果需要,可考虑将数据标志为作废的方式,以避免真正的删除时的复杂。插入的数据为了简便起见,假设均为Integer,且不会重复。
方法ordinal是升序打印,为的是测试。
Tree.main 函数提供一个简短的测试。
class Node { private Integer[] values = new Integer[3]; //装数据 private Node[] children = new Node[4]; //装子节点 private Node parent; private int size; //当前的有效数据的个数 Node() {} Node(Integer value) { values[0] = value; size++; } Node getParent() { return parent; } int find(Integer value) { //得到当前节点下要查找的数据的下标索引 for(int i=0; i<size; i++) if(values[i].equals(value)) return i; return -1; } Node getNextChild(Integer value) { //在当前的节点下根据关键数值查找合适的子节点 for(int i=0; i<size; i++) if(value < values[i]) return children[i]; return children[size]; } Node getChild(int index) { return children[index]; } boolean isLeaf() { return children[0] == null; } boolean needSplit() { return size > 2; } //返回当前节点是否需要分裂 int size() { return size; } Integer getValue(int index) { return values[index]; } int insert(Integer value) { //将数据插入当前节点的恰当位置,以保持数据的升序排序 int pos = size ; while(pos > 0 && values[pos -1 ] > value) { values[pos] = values[pos-1]; pos--; } values[pos] = value; size++; return pos; } void insertChild(int index, Node child) { //在指定的位置之后插入子节点,之后的子节点依次向后移动 for(int i=size; i>index + 1; i--) children[i] = children[i-1]; children[index + 1] = child; if(child != null) child.parent = this; } Node disConnect(int index) { //断开指定位置的子节点 Node result = children[index]; if(result != null) result.parent = null; children[index] = null; return result; } void connect(int index, Node child) { //在指定位置连接子节点 children[index] = child; if(child != null) child.parent = this; } Node split() { assert needSplit(); Node left = this; //左节点 Integer middle = values[1]; //中间数值,将要提升至父节点 Node right = new Node(values[2]); //右节点 //调整左节点中的数据与有效数据大小 left.values[1] = null; left.values[2] = null; left.size = 1; //如果没有父节点(此时当前节点一定为根节点),则创建新的父节点 if(parent == null) { parent = new Node(middle); parent.connect(0,left); parent.connect(1,right); } else { //否则在父节点中插入要提升的数值,并将新的子节点放入插入合适的位置 int index = parent.insert(middle); parent.insertChild(index,right); } if(!isLeaf()) { //当前节点有子节点,则将子节点平分至两个分裂后两个新节点中 Node child1 = disConnect(2); Node child2 = disConnect(3); right.connect(0,child1); right.connect(1,child2); } return parent; } } class Tree { private Node root = new Node(); //根节点 void insert(Integer value) { Node current = root; //从根节点开始寻找恰当的叶子节点 while(!current.isLeaf()) current = current.getNextChild(value); current.insert(value); //将数值插入叶子节点 if(current.needSplit()) split(current); //如果需要,则分裂叶子节点 } void ordinal() { ordinal(root); //打印 } private void ordinal(Node node) { //按照升序遍历并打印数据 for(int i=0; i<node.size(); i++) { if(!node.isLeaf()) ordinal(node.getChild(i)); System.out.println(node.getValue(i)); } if(!node.isLeaf()) ordinal(node.getChild(node.size())); } boolean contains(Integer value) { //判断树中是否包含指定的关键值的字段 Node current = root; while(!current.isLeaf()) { if(current.find(value) != -1) return true; else current = current.getNextChild(value); } return current.find(value) != -1; } private void split(Node node) { Node parent = node.split(); //分裂当前的节点,并得到其父节点 if(node == root) root = parent; //如果当前节点是根节点,则重置根结点 if(parent.needSplit()) split(parent); //如果父节点需要分裂,则递归调用 } public static void main(String[] args) { Tree t = new Tree(); t.insert(50); t.insert(51); t.insert(52); t.insert(53); t.insert(54); t.insert(55); t.insert(56); t.insert(57); t.insert(17); t.insert(87); t.insert(27); t.insert(67); t.insert(97); t.ordinal(); assert t.contains(67); assert t.contains(17); assert !t.contains(100); } }