B-Tree(Balance Tree)的Java实现

具体算法思想及应用介绍的文章网上有很多,这篇推荐给大家http://blog.csdn.net/hbhhww/article/details/8206846

我就直接上自己的Java代码,之前写的时候也参考了一些其它语言的实现 。

/**
 * <code>B-Tree</code> : <p />
 * 假设B树的度为 m(m>=2),则B树满足如下要求:(参考算法导论)
 * <br />
 * (1)每个非根节点至少包含m-1个关键字,m个指向子节点的指针;至多包含2m-1个关键字,2m个指向子女的指针(叶子节点的子女为空)
 * <br />
 * (2)节点的所有key按非降序存放,假设节点的关键字分别为K[1], K[2] … K[n], 指向子女的指针分别为P[1], P[2]…P[n+1],
 * 其中n为节点关键字的个数。则有:P[1] <= K[1] <= P[2] <= K[2] …..<= K[n] <= P[n+1] // 这里P[n]也指其指向的关键字
 * <br />
 * (3)若根节点非空,则根节点至少包含两个子女;
 * <br />
 * (4)所有的叶子节点都在同一层
 * <p/>
 * 
 * 
 * 查询:<br />
 * 从<code>root</code>出发,对每个节点,找到大于或等于target关键字中最小的K[i]
 *  <br />
 * (1)如果K[i]与target相等,则查找成功,返回一个标识true,和target下标i
 *  <br />
 * (2)否则查找失败, 返回一个标识false,和一个下标指向其子节点位置,递归search新的子节点,如果是叶节点则表示不存在该关键字
 * 
 * <p/>
 * 插入:
 * <br />
 * B树的插入需要沿着搜索的路径从<code>root</code>一直到叶节点,根据B树的规则,每个节点的关键字个数在[m-1, 2m-1]之间,
 * 当target要加入到某个叶子时,如果该叶子节点已经有2m-1个关键字,则再加入target就违反了B树的定义,
 * 这时就需要对该叶子节点进行分裂,将叶子以中间节点为界,分成两个包含m-1个关键字的子节点,
 * 同时把中间节点提升到该叶子的父节点中,如果这样使得父节点的关键字个数超过2m-1,
 * 则要继续向上分裂,直到根节点,根节点的分裂会使得树加高一层
 * <br />
 * 关键:在下降的过程中,一旦遇到已满的节点(关键字个数为2m-1),就就对该节点进行分裂,这样就保证在叶子节点需要分裂时,
 * 其父节点一定是非满的,从而不需要再向上回溯
 * 
 * <p/>
 * 删除:
 * <br />
 * B树的删除同样需要沿着搜索的路径从<code>root</code>一直到叶节点
 * 1,根节点只有一个实例,且起子节点都只包含<code>minEntrySize</code>个实例时,树降高(树降高的唯一情形)
 * <br />
 * 2,如果在内部节点node中找到关键字key下标i,观察该节点的第i与i+i个子节点,在它俩中找到实例数大于<code>minEntrySize</code>的
 *    记为tmpNode,中转tmpKey到当前node,在tmpNode中递归删除tmpKey,如果上述两个子节点实例数均不大于<code>minEntrySize</code>
 *    则借当前key合并节点
 * <br />  
 * 3,与插入时避免回溯的思想一样,保证(2)中合并节点时,借走父节点的 一个实例,而不须继续向上回溯
 */
public class BTree<K extends Comparable<K>, V> {
	private static final int M = 4;
	private int minEntrySize;
	private int maxEntrySize;
	private Node root;
	private int size;
	private int depth;
	
	public BTree() {
		this(M);
	}

	public BTree(int m) {
		this.minEntrySize = m - 1;
		this.maxEntrySize = (m * 2) - 1;
		this.root = new Node(true);
	}
	
	/**
	  * @return 树中不存在当前key时返回null, 否则返回上次key保存的值
	  */
	public V put(K key, V value) {
		if (root.isFull()) {// 创建新root
			Node newRoot = new Node(false);
			newRoot.children.add(root);
			
			splitFullNode(root, newRoot, 0);
			root = newRoot;
			depth++;
		}
		return insert(root, key, value);
	}
	
	public V get(K key) {
		
		return serach(root, key);
	}
	
	private V serach(Node current, K key) {
		SerachResult sr = current.serach(key);
		if (sr.serached) {
			return current.entrys.get(sr.wudindex).value;
		} else {
			if (!current.isLeaf) {
				return serach(current.children.get(sr.wudindex), key);
			}
			//递归到叶节点中仍没有查询到
			return null;
		}
	}

	public int size() {
		return size;
	}
	
	public boolean isEmpty() {
		return size == 0;
	}
	
	public int depth() {
		return depth;
	}
	
	private V insert(Node current, K key, V value) {
		if (current.isFull()) {
			throw new UnsupportedOperationException("只能为非满节点插入实例...");
		}
		SerachResult sr = current.serach(key);
		
		if (sr.serached) {
			V oldValue = current.entrys.get(sr.wudindex).value;
			current.entrys.get(sr.wudindex).value = value;
			return oldValue;
		}
		if (!current.isLeaf) {
			Node wudNode = current.children.get(sr.wudindex);
			if (wudNode.isFull()) {
				// 回溯过程中每遇到一个满实例节点就立刻分裂
                // 保证在叶节点需要分裂时,其父节点一定是非满的,从而不需要再向上回溯
				splitFullNode(wudNode, current, sr.wudindex);
				
				if (key.compareTo(current.entrys.get(sr.wudindex).key) > 0) {//需要插入到分裂出的兄弟节点中
					wudNode = current.children.get(sr.wudindex + 1);
				}
			}
			
			return insert(wudNode, key, value);
		} else {//所有节点都在叶节点加入
			current.entrys.add(sr.wudindex, new Entry(key, value));
			size++;
		}
		return null;
	}

	/**
	  * 将一个满实例节点分割, 中间实例提取至父节点中, 分隔出的部分((m - 1)个)实例放入新生成的兄弟节点中
	  * @param parent 父节点
	  * @param child 满实例节点
	  * @param i 满实例节点在父节点中的位置(提取出中间实例应在父节点中插入的位置)
	  */
	private void splitFullNode(Node fullNode, Node parent, int index) {
		if (!fullNode.isFull()) {
			throw new UnsupportedOperationException("不能对非满节点经行分裂!");
		}
		int middleIndex = (fullNode.entrys.size() - 1) / 2;
		Entry middleEntry = fullNode.entrys.get(middleIndex);
		//此处已保证parent加入新节点后仍满足B-tree特性
		parent.entrys.add(index, middleEntry);
		
		Node sibling = new Node(fullNode.isLeaf);
		//采用向右分割
		for (int i = maxEntrySize - 1; i > middleIndex; i--) {
			sibling.entrys.add(0, fullNode.entrys.get(i));
			fullNode.entrys.remove(i);
		}
		fullNode.entrys.remove(middleIndex);
		parent.children.add(index + 1, sibling);
		
		//如果分割的不是叶节点, 则fullNode的后半部分(m个)子节点将成为sibling的子节点
		if (!fullNode.isLeaf) {
			int cs = fullNode.children.size();
			for (int i = cs - 1; i >= (cs / 2); i--) {
				sibling.children.add(0, fullNode.children.get(i));
				fullNode.children.remove(i);
			}
		}
	}

	private class Node {
		List<Entry> entrys;
		List<Node> children;
		boolean isLeaf;
		
		Node(boolean isLeaf) {
			this.entrys = new ArrayList<Entry>();
			this.children = new ArrayList<Node>();
			this.isLeaf = isLeaf; 
		}

		/**
		  * 在当前节点中查询
		  * @param key 搜索关键字
		  * @return {@link SerachResult}
		  */
		SerachResult serach(K key) {
			int left = 0;
			int right = entrys.size() - 1;
			int middle = 0;
			
			while (left <= right) {
				middle = (left + right) / 2;
				
				K ck = entrys.get(middle).key;
				if (key.compareTo(ck) < 0) {
					right = middle - 1;
				} else if(key.compareTo(ck) > 0) {
					left = middle + 1;
				} else {
					break;
				}
			}
			
			boolean flag = true;
			if (left > right) {//未找到
				flag = false;
			}
			
			return new SerachResult(flag, left);
		}

		boolean isFull() {
			return entrys.size() == maxEntrySize;
		}
	}
	
	private class Entry {
		K key;
		V value;
		
		Entry(K key, V value) {
			this.key = key;
			this.value = value;
		}
		
		@Override
		public String toString() {
			return "{key:" + key + ", value:" + value + "}";
		}
	}
	
	/**
	 * 在节点中单次查询结果
	 */
	private class SerachResult {
		/**
		 * 是否查询到
		 */
		boolean serached;
		/**
		 * 查到时为在node实例集中的位置
		 * 未查到时表示在node子节点集对应节点中
		 */
		int wudindex;
		
		SerachResult(boolean serached, int wudindex) {
			this.serached = serached;
			this.wudindex = wudindex;
		}
	}
}
我的实现里主要加入了泛型,提供了put(key, value); get(key); 两个操作。 大体上看就是一个HashMap的功能。

删除节点那部分的代码我这里没贴,因为实在太复杂也太长,我的代码也未必能让大家看的明白, 如果有兴趣的,自己动手实现一遍更好。 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BPlusTree_Java实现 package bplustree; import java.util.*; import com.xuedi.IO.*; import com.xuedi.maths.*; ////// DisposeRoot ///////中的key参数有些问题 public class BTree { //用于记录每个节点中的键值数量 public int keyAmount; //树的根节点 public Node root; public BTree(int keyAmount) { this.keyAmount = keyAmount; this.root = new Node(keyAmount); } //在B树中插入叶节点///////////////////////////////////////////////////////////// public void insert(long key,Object pointer) { //找到应该插入的节点 Node theNode = search(key,root); //在叶节点中找到空闲空间,有的话就把键放在那里 if( !isFull(theNode) ) { putKeyToNode(key,pointer,theNode); }else{ //如果在适当的叶节点没有空间,就把该叶节点分裂成两个,并正确分配键值 Node newNode = separateLeaf(key,pointer,theNode); //如果分裂的是根节点,就新建一个新的根节点将新建的节点作为他的字节点 if( isRoot(theNode) ) { DisposeRoot(theNode,newNode,newNode.keys[0]); }else{ //将新建立的节点的指针插入到上层节点 insertToInnerNode(theNode.parent,newNode,newNode.keys[0]); } } } //lowerNode是下级节点分离后新建立的那个节点/////////////////////////////////////// //upperNode是lowerNode的上层节点 private void insertToInnerNode(Node upperNode,Node lowerNode,long key) { //上层节点有空位就直接插入 if( !isFull(upperNode) ) { putKeyToNode(key,lowerNode,upperNode); //重置父节点指针 pointerRedirect(upperNode); return; }else{ //如果分裂的是根节点,就新建一个新的根节点将新建的节点作为他的子节点 Node newNode; if( isRoot(upperNode) ) { newNode = separateInnerNode(key,lowerNode,upperNode); Node newRoot = new Node(this.keyAmount); newRoot.pointer[0] = upperNode; newRoot.pointer[1] = newNode; upperNode.parent = newRoot; newNode.parent = newRoot; newRoot.keyAmount = 1; newRoot.keys[0] = key; root = newRoot; //重置父节点指针 pointerRedirect(upperNode); return; }else{ //上层非根节点没有空位进行分裂和插入操作 newNode = separateInnerNode(key,lowerNode,upperNode); //重置父节点指针 pointerRedirect(upperNode); //记录要向上插入的键值在源节点中的位置(该键值在separateInnerNode()被保留在srcNode中) int keyToUpperNodePosition = upperNode.keyAmount; //向上递归插入 insertToInnerNode(upperNode.parent,newNode,upperNode.keys[keyToUpperNodePosition]); //重置父节点指针 pointerRedirect(newNode); } } } //将对应的内部节点进行分裂并正确分配键值,返回新建的节点 private Node separateInnerNode(long key,Object pointer,Node srcNode) { Node newNode = new Node(this.keyAmount); //因为我在Node中预制了一个位置用于插入,而下面的函数(putKeyToLeaf())不进行越界检查 //所以可以将键-指针对先插入到元节点,然后再分别放到两个节点中 putKeyToNode(key,pointer,srcNode); //先前节点后来因该有(n+1)/2取上界个键-值针对 int ptrSaveAmount = (int)com.xuedi.maths.NumericalBound.getBound(0,(double)(this.keyAmount+1)/2); int keySaveAmount = (int)com.xuedi.maths.NumericalBound.getBound(0,(double)(this.keyAmount)/2); int keyMoveAmount = (int)com.xuedi.maths.NumericalBound.getBound(1,(double)(this.keyAmount)/2); //(n+1)/2取上界个指针和n/2取上界个键留在源节点中 //剩下的n+1)/2取下界个指n/2取下界个键留在源节点中 for (int k = ptrSaveAmount; k < srcNode.keyAmount; k++) { newNode.add(srcNode.keys[k], srcNode.pointer[k]); } newNode.pointer[newNode.keyAmount] = srcNode.pointer[srcNode.pointer.length-1]; srcNode.keyAmount = keySaveAmount; return newNode; } //将对应的叶节点进行分裂并正确分配键值,返回新建的节点/////////////////////////////// private Node separateLeaf(long key,Object pointer,Node srcNode) { Node newNode = new Node(this.keyAmount); //兄弟间的指针传递 newNode.pointer[this.keyAmount] = srcNode.pointer[this.keyAmount]; //因为我在Node中预制了一个位置用于插入,而下面的函数(putKeyToLeaf())不进行越界检查 //所以可以将键-指针对先插入到元节点,然后再分别放到两个节点中 putKeyToNode(key,pointer,srcNode); //先前节点后来因该有(n+1)/2取上界个键-值针对 int oldNodeSize = (int)com.xuedi.maths.NumericalBound.getBound(0,(double)(this.keyAmount+1)/2); for(int k = oldNodeSize; k <= this.keyAmount; k++) { newNode.add(srcNode.keys[k],srcNode.pointer[k]); } srcNode.keyAmount = oldNodeSize; //更改指针--让新节点成为就节点的右边的兄弟 srcNode.pointer[this.keyAmount] = newNode; return newNode; } //把键值放到叶节点中--这个函数不进行越界检查//////////////////////////////////////// private void putKeyToNode(long key,Object pointer,Node theNode) { int position = getInsertPosition(key,theNode); //进行搬迁动作--------叶节点的搬迁 if( isLeaf(theNode) ) { if(theNode.keyAmount <= position) { theNode.add(key,pointer); return; } else{ for (int j = theNode.keyAmount - 1; j >= position; j--) { theNode.keys[j + 1] = theNode.keys[j]; theNode.pointer[j + 1] = theNode.pointer[j]; } theNode.keys[position] = key; theNode.pointer[position] = pointer; } }else{ //内部节点的搬迁----有一定的插入策略: //指针的插入比数据的插入多出一位 for (int j = theNode.keyAmount - 1; j >= position; j--) { theNode.keys[j + 1] = theNode.keys[j]; theNode.pointer[j + 2] = theNode.pointer[j+1]; } theNode.keys[position] = key; theNode.pointer[position+1] = pointer; } //键值数量加1 theNode.keyAmount++; } //获得正确的插入位置 private int getInsertPosition(long key,Node node) { //将数据插入到相应的位置 int position = 0; for (int i = 0; i < node.keyAmount; i++) { if (node.keys[i] > key) break; position++; } return position; } //有用的辅助函数//////////////////////////////////////////////////////////////// //判断某个结点是否已经装满了 private boolean isFull(Node node) { if(node.keyAmount >= this.keyAmount) return true; else return false; } //判断某个节点是否是叶子结点 private boolean isLeaf(Node node) { //int i = 0; if(node.keyAmount == 0) return true; //如果向下的指针是Node型,则肯定不是叶子节点 if(node.pointer[0] instanceof Node) return false; return true; } private boolean isRoot(Node node) { if( node.equals(this.root) ) return true; return false; } //给内部节点中的自己点重新定向自己的父亲 private void pointerRedirect(Node node) { for(int i = 0; i <= node.keyAmount; i++) { ((Node)node.pointer[i]).parent = node; } } //新建一个新的根节点将新建的节点作为他的字节点 private void DisposeRoot(Node child1,Node child2,long key) { Node newRoot = new Node(this.keyAmount); newRoot.pointer[0] = child1; newRoot.pointer[1] = child2; newRoot.keyAmount = 1; newRoot.keys[0] = key; root = newRoot; //如果两个孩子是叶节点就让他们两个相连接 if( isLeaf(child1) ) { //兄弟间的指针传递 child2.pointer[this.keyAmount] = child1.pointer[this.keyAmount]; child1.pointer[this.keyAmount] = child2; } pointerRedirect(root); return; } /////////////////////////////////////////////////////////////////////////////// //用于寻找键值key所在的或key应该插入的节点 //key为键值,curNode为当前节点--一般从root节点开始 public Node search(long key,Node curNode) { if (isLeaf(curNode)) return curNode; for (int i = 0; i < this.keyAmount; i++) { if (key < curNode.keys[i]) //判断是否是第一个值 return search(key, (Node) curNode.pointer[i]); else if (key >= curNode.keys[i]) { if (i == curNode.keyAmount - 1) //如果后面没有值 { //如果key比最后一个键值大,则给出最后一个指针进行递归查询 return search(key,(Node) curNode.pointer[curNode.keyAmount]); } else { if (key < curNode.keys[i + 1]) return search(key, (Node) curNode.pointer[i + 1]); } } } //永远也不会到达这里 return null; } }
平衡二叉树(AVL树)是一种自平衡的二叉搜索树,它的左子树和右子树的高度差不超过1。在Java中,可以通过以下步骤实现平衡二叉树: 1. 定义节点类:首先定义一个节点类,包含节点值、左子节点和右子节点等属性。 ```java class Node { int value; Node left; Node right; public Node(int value) { this.value = value; this.left = null; this.right = null; } } ``` 2. 实现平衡二叉树类:创建一个平衡二叉树类,包含插入节点、删除节点、旋转操作等方法。 ```java class AVLTree { private Node root; // 插入节点 public void insert(int value) { root = insertNode(root, value); } private Node insertNode(Node root, int value) { if (root == null) { return new Node(value); } if (value < root.value) { root.left = insertNode(root.left, value); } else if (value > root.value) { root.right = insertNode(root.right, value); } else { // 如果存在相同值的节点,可以根据需求进行处理 return root; } // 更新节点的高度 root.height = 1 + Math.max(getHeight(root.left), getHeight(root.right)); // 平衡操作 int balance = getBalance(root); // 左左情况,进行右旋操作 if (balance > 1 && value < root.left.value) { return rightRotate(root); } // 右右情况,进行左旋操作 if (balance < -1 && value > root.right.value) { return leftRotate(root); } // 左右情况,先左旋再右旋 if (balance > 1 && value > root.left.value) { root.left = leftRotate(root.left); return rightRotate(root); } // 右左情况,先右旋再左旋 if (balance < -1 && value < root.right.value) { root.right = rightRotate(root.right); return leftRotate(root); } return root; } // 删除节点 public void delete(int value) { root = deleteNode(root, value); } private Node deleteNode(Node root, int value) { // 空树或未找到节点 if (root == null) { return root; } if (value < root.value) { root.left = deleteNode(root.left, value); } else if (value > root.value) { root.right = deleteNode(root.right, value); } else { // 找到要删除的节点 // 节点只有一个子节点或无子节点 if (root.left == null || root.right == null) { Node temp = null; if (temp == root.left) { temp = root.right; } else { temp = root.left; } // 无子节点的情况 if (temp == null) { temp = root; root = null; } else { // 一个子节点的情况 root = temp; } } else { // 节点有两个子节点,找到右子树中最小的节点 Node temp = minValueNode(root.right); // 将右子树中最小节点的值赋给要删除的节点 root.value = temp.value; // 删除右子树中最小的节点 root.right = deleteNode(root.right, temp.value); } } // 更新节点的高度 root.height = 1 + Math.max(getHeight(root.left), getHeight(root.right)); // 平衡操作 int balance = getBalance(root); // 左左情况,进行右旋操作 if (balance > 1 && getBalance(root.left) >= 0) { return rightRotate(root); } // 左右情况,先左旋再右旋 if (balance > 1 && getBalance(root.left) < 0) { root.left = leftRotate(root.left); return rightRotate(root); } // 右右情况,进行左旋操作 if (balance < -1 && getBalance(root.right) <= 0) { return leftRotate(root); } // 右左情况,先右旋再左旋 if (balance < -1 && getBalance(root.right) > 0) { root.right = rightRotate(root.right); return leftRotate(root); } return root; } // 获取节点的高度 private int getHeight(Node node) { if (node == null) { return 0; } return node.height; } // 获取节点的平衡因子 private int getBalance(Node node) { if (node == null) { return 0; } return getHeight(node.left) - getHeight(node.right); } // 右旋操作 private Node rightRotate(Node y) { Node x = y.left; Node T2 = x.right; x.right = y; y.left = T2; y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; return x; } // 左旋操作 private Node leftRotate(Node x) { Node y = x.right; Node T2 = y.left; y.left = x; x.right = T2; x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; return y; } // 获取最小值节点 private Node minValueNode(Node node) { Node current = node; while (current.left != null) { current = current.left; } return current; } } ``` 以上是一个简单的平衡二叉树的Java实现,包括插入节点、删除节点、旋转操作等方法。你可以根据需要进行调整和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值