1. 程式人生 > >B-Tree(Balance Tree)的Java實現

B-Tree(Balance Tree)的Java實現

我就直接上自己的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的功能。

刪除節點那部分的程式碼我這裡沒貼,因為實在太複雜也太長,我的程式碼也未必能讓大家看的明白, 如果有興趣的,自己動手實現一遍更好。