PTA 7-29 Self-printable B+ Tree(Java實現B+樹)(第一次用Java寫數據結構就翻車

        因爲一些原因,前段時間被迫好好研究了一下B+樹,做了PTA的Self-printable B+ Tree(好叭其實是上學期的數據結構沒好好學
        今天突發奇想,爲什麼不用Java再來實現一下呢,還能 水一篇博客 提高Java的代碼水平。B+樹的原理就不詳細解釋了,網上的博客很多,但是實現的代碼很冗雜,質量不是很高。(我個人認爲我的代碼還是比較高效易懂的)
        所以下面我們直接上題目和代碼:https://pintia.cn/problem-sets/16/problems/691

7-29 Self-printable B+ Tree

題意大概就是根據輸入的數據,輸出一個3階B+樹的結構。

In this project, you are supposed to implement a B+ tree of order 3, with the following operations: initialize, insert (with splitting) and search. The B+ tree should be able to print out itself.

Input Specification:
Each input file contains one test case. For each case, the first line contains a positive number N (≤10^4​​ ), the number of integer keys to be inserted. Then a line of the N positive integer keys follows. All the numbers in a line are separated by spaces.
Output Specification:
For each test case, insert the keys into an initially empty B+ tree of order 3 according to the given order. Print in a line Key X is duplicated where X already exists when being inserted. After all the insertions, print out the B+ tree in a top-down lever-order format as shown by the samples.

Sample Input 1:

6
7 8 9 10 7 4

Sample Output 1:

Key 7 is duplicated
[9]
[4,7,8][9,10]

Sample Input 2:

10
3 1 4 5 9 2 6 8 7 0

Sample Output 2:

[6]
[2,4][8]
[0,1][2,3][4,5][6,7][8,9]

Sample Input 3:

3
1 2 3

Sample Output 3:

[1,2,3]

下面是我根據之前AC的C++代碼“翻譯”出來的Java代碼:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;

public class Main {
	static Node tree_root = null; // 樹根,初始爲null
	static int[] input = new int[10009]; // 輸入的數組
	
	// 節點類
	static class Node{
		Node left, mid, right, fa; // 分別對應該節點的左子樹,中子數,右子樹和父節點
		List<Integer> num = new ArrayList<>(); // 記錄節點元素的數組
		boolean isLeave; // 是否爲葉子節點
				
		public Node() {
			left = null; mid = null; right = null; fa = null;
			isLeave = true;
		}
	}
	
	// 查找x的放置位置
	static Node find(Node root, int x) {
		// 如果該節點是葉子節點
		if (root.isLeave == true) {
			for (int n: root.num) {
				if (n == x) return null; // n == x說明已經存在了相同數值的節點,不能加入,所以返回null
			}
			return root;
		}
		
		int k = root.num.size(), len = k;
		for (int i = 0; i < len; ++i) {
			if (root.num.get(i) == x) return null;
			if (root.num.get(i) < x) continue;
			k = i; break;
		}
		
		if (k == 0) return find(root.left, x);
		else if (k == 1) return find(root.mid, x);
		else return find(root.right, x);
	}
	
	// 將x插入root中
	static void insert(Node root, int x) {
		// 插入第一個節點時需要用到,初始化tree_root		
		if (root == null) {
			tree_root = new Node();			
			tree_root.num.add(x);
			return;
		}
		
		root.num.add(x);
		Collections.sort(root.num);
		if (root.num.size() <= 3) return; // 如果加入root後,該節點的元素個數符合要求的話,即可直接返回
		
		// 否則需要更新樹結構,將root的後兩個元素分給一個新的節點new_root
		Node new_root = new Node();
		new_root.num.add(root.num.get(2)); new_root.num.add(root.num.get(3)); root.num.remove(3);
		
		int up_num = root.num.remove(2); // 需要上調到上一層的數字
		// 需要持續進行樹結構的更新,直到符合要求爲止
		while (true) {
			// 如果到了樹根,需要單獨處理
			if (root == tree_root) {
				tree_root = new Node(); tree_root.isLeave = false;
				tree_root.left = root; tree_root.mid = new_root;
				tree_root.num.add(up_num);
				root.fa = tree_root; new_root.fa = tree_root;
				break;
			}			
			
			root.fa.num.add(up_num);
			Collections.sort(root.fa.num);

			// 更新後如果符合條件,則調整完成後跳出
			if (root.fa.num.size() == 2) {
				if (root.fa.left == root) {
					root.fa.right = root.fa.mid;
					root.fa.mid = new_root;
					new_root.fa = root.fa;
				}
				else {
					root.fa.right = new_root;
					new_root.fa = root.fa;
				}
				break;
			}
			
			// 否則需要繼續調整
			// 思想其實就是把root分爲兩個節點,從而root.fa就分爲了Tmp1, Tmp2, Tmp3, Tmp4這四個節點
			up_num = root.fa.num.get(1);
			int left_num = root.fa.num.get(0), right_num = root.fa.num.get(2);
			Node Tmp1, Tmp2, Tmp3, Tmp4;
			if (root.fa.left == root) {
			    Tmp1 = root; Tmp2 = new_root; Tmp3 = root.fa.mid; Tmp4 = root.fa.right;
	        }
	        else if (root.fa.mid == root){
	            Tmp1 = root.fa.left; Tmp2 = root; Tmp3 = new_root; Tmp4 = root.fa.right;
	        }
	        else{
	            Tmp1 = root.fa.left; Tmp2 = root.fa.mid; Tmp3 = root; Tmp4 = new_root;
	        }
			
			root = root.fa; root.left = Tmp1; root.mid = Tmp2; root.right = null; 
			root.num.clear(); root.num.add(left_num);
			Tmp1.fa = root; Tmp2.fa = root;
			new_root = new Node();
			new_root.left = Tmp3; new_root.mid = Tmp4; new_root.isLeave = false;
			new_root.num.add(right_num);
			Tmp3.fa = new_root; Tmp4.fa = new_root;
		}
		return;
	}
		
	static class inqueue{
		Node tree;
		int height;
		
		public inqueue(Node t, int h) {
			tree = t;
			height = h;
		}
	}
	
	// 層序遍歷,很簡單的隊列實現
	static void levelorder() {
		Queue<inqueue> q = new LinkedList<inqueue>();
		q.add(new inqueue(tree_root, 0));
		
		int cur_height = 0;
		while (!q.isEmpty()) {
			inqueue a = q.remove();
			if (a.height != cur_height) {
				System.out.println();
				cur_height = a.height;
			}
			
			System.out.print("[");
			Iterator<Integer> it = a.tree.num.iterator();
	        while(true) {
	            System.out.printf("%d", it.next());
	            if (it.hasNext()) System.out.print(",");
	            else {
	            	System.out.print("]");
	            	break;
	            }
	        }
			
			if (a.tree.left != null) {
				q.add(new inqueue(a.tree.left, a.height+1));
			}
			if (a.tree.mid != null) {
				q.add(new inqueue(a.tree.mid, a.height+1));
			}
			if (a.tree.right != null) {
				q.add(new inqueue(a.tree.right, a.height+1));
			}
		}
		System.out.println();
	}
	
	// 主函數
	public static void main(String[] args) {
		Scanner sn = new Scanner(System.in);		
		int n = sn.nextInt();
		for (int i = 0; i < n; ++i) input[i] = sn.nextInt();
		for (int i = 0; i < n; ++i) {
			int x = input[i];
			if (i == 0) insert(tree_root, x); // 第一次插入數字時,單獨處理
			else {
				Node root = find(tree_root, x); // 先找到插入的位置
				if (root == null) System.out.printf("Key %d is duplicated\n", x); // 如果返回null說明無法插入
				else insert(root, x); // 否則正常insert
			}
		}
		levelorder(); //輸出層序遍歷的結果
		sn.close();
	}

}

大體的思路就是先找數字插入的位置,插入後再調整樹型。唯一需要解釋一下的就是調整樹型的時候用到的Tmp1, Tmp2, Tmp3和Tmp4;我們來看下面這張圖,以root爲其父節點的左子樹爲例解釋一下。
在這裏插入圖片描述 在這裏插入圖片描述
左圖爲初始狀態,如果root中的元素個數超過了3,就要從root中分裂出右圖的節點new_root,並將root和new_root傳遞到上層

所以,我們就可以去PTA提交代碼了,第一次在OJ上提交Java代碼,還有點小激動呢:)
然鵝,超時了???:(
在這裏插入圖片描述
分明我以前用C++提交的代碼輕鬆過百萬來着(不對,什麼百萬,又不是n^2
在這裏插入圖片描述
        於是,我第一次嘗試用Java寫數據結構就翻車了,翻在了TLE上。但是這一次嘗試還是很有意義的,不論是編寫代碼的方式還是最後呈現的結果,都能直觀感受到Java和C++之間的差別。在解決具體問題的時候,C++的時間效率還是要高很多的;但是如果在一個大型項目中,時間效率顯得不如開發效率重要時,Java的優勢就展現出來了。所以說這一次翻車還是很有意義的嘗試。
(另外,C++的代碼我也有,但是因爲涉及到代碼查重的問題不能貼在博客裏,需要的可以私信問我要

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章