動態展示二叉搜尋樹之實踐進行時
目錄
一、選題展示
題B5:搜尋樹操作程式
開發建立搜尋樹的程式,在程式中可以進行引數設定、資料元素輸入與操作選擇,支援搜尋樹結構中的各種資料操作(如插入、刪除、搜尋等),可使用C、C++或Java等程式語言實現。
基本要求
(1) 連續輸入若干資料元素,程式自動在畫板上畫出相應二叉搜尋樹;可以對已經生成的二叉搜尋樹進行插入、刪除和搜尋操作,程式動態顯示操作結果。
(2) 連續輸入若干資料元素,在畫板上畫出相應二叉平衡樹;可以對已經生成的二叉平衡樹進行插入、刪除和搜尋操作,程式動態顯示操作結果。
(3) 實物演示時要求演示結果正確。
(4) 程式操作友好、健壯。
提高要求:
(1) 連續輸入若干資料元素,程式能動態畫出相應B-樹。
(2) 可以對已經生成的B-樹進行插入、刪除和搜尋操作,程式動態顯示操作結果。
(3) 圖形化介面,樹形美觀對稱。
二、思考題目
個人對“動態”的理解
模仿終端的形式,在c語言的輸入框裡,通過“add XX”、“delete XX”、“find XX”進行插入、刪除和搜尋操作,每次輸入後,都會輸出二叉平衡樹/B-樹,輸入“quit”退出程式。
根據題目要求,將整個專案劃分為以下階段:
階段一:圖形顯示二叉搜尋樹之插入
階段二:圖形顯示二叉搜尋樹之查刪
階段三:動態顯示二叉搜尋樹
階段四:加入調整規則使二叉搜尋樹變為平衡樹
階段五:動態顯示二叉平衡樹
階段六:使用多種方式顯示二叉平衡樹
階段七:圖形顯示B-樹
階段八:動態顯示B-樹
圖形展示參考:二叉樹的圖形顯示
三、階段一:圖形顯示二叉搜尋樹之插入
1.圖形顯示二叉搜尋樹的要求
-
層次化輸出:同層次遍歷,但是需要在每層輸出完畢後進行換行。可由層數和孩子節點的關係2^(n-1)類推出所有層的節點總數,待輸入完畢後,進行自動按層換行。
-
確定相對位置:安排節點位置用以直觀的得到節點之間的關係,使用如下思路:在節點型別中增加成員變數int pos,用以表示節點所在位置。如果父節點為P,位置為P.pos,則可在下一層中,令左孩子L的位置滿足如下關係L.pos=P.pos-1,右孩子R的位置滿足R.pos=P.pos+1,是根節點位置為一個大小適宜的正數M,則可避免子樹中節點位置出現負數的情況。
-
輸出衝突:不同雙親節點的子節點可能出現位置重疊的情況:
對已一個節點P,定義它的左子樹中的結點pos最大值設為LMax(P),右子樹中節點pos最小值為RMin(P)。 輸出一棵樹時不會有衝突的充分條件是對於每一個結點P都有:LMax(P) < P.pos < RMin(P),也就是所有左子樹中的結點都在根節點的左方,所有右子樹中的結點都在根結點的右方。
如果某一結點不滿足LMax(P)<P.pos,就將P和右子樹中的左右結點都右移LMax(P) - P.max + 1個位置。如果不滿足P.pos < RMin(P),就將右子樹右移RMin(P)-P.pos + 1個位置。具體的處理過程是這樣的:按照層次遍歷的順序,自上到下、自左向右地檢查每個結點是否和左、右子樹存在衝突,如果存在,就移動P和右子樹。
移動之後有兩種情況,一種是結點P是其父節點(PP)的左孩子,這是P和P的右子樹都屬於PP的左子樹,移動P和P的右子樹有可能會造成條件LMax(PP) < P.pos的失敗,所以這個時候需要對PP再進行一次衝突判斷,這樣遞歸向上知道沒有衝突。第二種情況是P是PP的右孩子,這種情況下移動P和右子樹肯定不會造成PP的衝突。在P的祖父節點中,設第一個具有左孩子的節點為A,移動P和P的右子樹有可能造成A的衝突,需要再對A進行衝突處理。
2.程式碼和輸出
InfoH.h
用於定義二叉樹節點所要繼承的類,如上所說,定義了表示位置的成員變數pos。
struct InfoH {
int pos;
bool newline;
InfoH() : pos(INFI), newline(false) {}
int add_pos(int add) {
pos += add;
return pos;
}
};
PosSetter.h
引用InfoH.h檔案,初始化二叉樹中每個節點的成員變數pos,在函式VisualTree::draw()中被呼叫。
#include "InfoH.h"
template<class TreeNode>
class PosSetter
{
public:
PosSetter(TreeNode* TreeNode::* p, TreeNode *TreeNode::* l, TreeNode *TreeNode::* r) :
parent(p), left(l), right(r)
{
}
void operator() (TreeNode *node)
{
TreeNode *p = node->*parent;
if (p != NULL) {
if (node == p->*left) { node->pos = p->pos - 1; }
if (node == p->*right) { node->pos = p->pos + 1; }
}
}
private:
TreeNode *TreeNode:: *parent;
TreeNode *TreeNode:: *left;
TreeNode *TreeNode:: *right;
};
PosAdder.h
用以調整節點的pos值,調整值在例項化物件的時候進行指定,每個物件對應一個值。
#include "InfoH.h"
class PosAdder
{
public:
PosAdder(int n_) : n(n_) {}
int operator() (InfoH *node) { return node->add_pos(n); }
private:
int n;
};
ExtremumGetter.h
遍歷結點的時候, 記錄下節點中的極值。
template<class TreeNode>
class ExtremumGetter
{
public:
ExtremumGetter(TreeNode *min = 0, TreeNode *max = 0) {
init(min, max);
}
void operator() (TreeNode *p) {
if ((min_ && p->pos < min_->pos) || !min_) { min_ = p; }
if ((max_ && p->pos > max_->pos) || !max_) { max_ = p; }
}
void init(TreeNode *min, TreeNode *max) {
min_ = min; max_ = max;
if (min_ && max_ && max_->pos < min_->pos) {
std::swap(min_, max_);
}
}
TreeNode *min() const { return min_; }
TreeNode *max() const { return max_; }
private:
TreeNode *min_;
TreeNode *max_;
};
以上三個檔案中定義的三個類都過載了operator (),他們都是在呼叫VisualTree::traverse_level()的時候最為函式物件被呼叫的。
VisualTree.h
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>
#include "ExtremumGetter.h"
#include "PosAdder.h"
#include "PosSetter.h"
#include "TreePrinter.h"
struct IntExtremumPair {
int min;
int max;
IntExtremumPair(int rh1 = 0, int rh2 = 0) : min(rh1), max(rh2) {}
};
template<class TreeNode, class ValueType>
class VisualTree
{
public:
typedef TreeNode *TreeNode::* PtrToMember;
typedef ValueType TreeNode::* PtrToData;
VisualTree(PtrToMember p, PtrToMember lc, PtrToMember rc, PtrToData val);
void draw(TreeNode *node, const char *promot = NULL);
private:
void adjust_pos(TreeNode *root);
TreeNode* ancestor(TreeNode *node);
int digits(int n);
int digits(char c);
int digits(const char*s);
IntExtremumPair extreme_pos(TreeNode *p);
ExtremumGetter<TreeNode> extreme_node(TreeNode *root);
int scan_tree(TreeNode *root);
template<class VST> void traverse_level(TreeNode *p, VST &vst);
ExtremumGetter<TreeNode> getter;
TreePrinter<TreeNode, ValueType> printer;
PosSetter<TreeNode> setter;
PtrToMember parent, left, right;
ValueType TreeNode:: *value;
};
/*
* 建構函式
*/
template<class TreeNode, class ValueType>
VisualTree<TreeNode, ValueType>::VisualTree(PtrToMember p, PtrToMember lc, PtrToMember rc, PtrToData val) :
getter(), printer(p,lc,rc,val), setter(p,lc,rc), parent(p), left(lc), right(rc), value(val)
{
}
/*
* 越界調整
*/
template<class TreeNode, class ValueType>
void VisualTree<TreeNode, ValueType>::adjust_pos(TreeNode *root)
{
if (root == NULL) {
return;
}
int diff = 0;
IntExtremumPair extr;
if (root->*left) {
extr = extreme_pos(root->*left);
if (root->pos <= extr.max) { // 左子樹越界:將根節點右移及其右子樹右移
diff = extr.max - root->pos + 1;
PosAdder adder(diff);
root->add_pos(diff);
traverse_level(root->*right, adder);
adjust_pos(ancestor(root));
}
}
if (root->*right) { // 右子樹越界:將右字樹右移
extr = extreme_pos(root->*right);
if (extr.min <= root->pos) {
diff = root->pos - extr.min + 1;
PosAdder adder(diff);
traverse_level(root->*right, adder);
adjust_pos(ancestor(root));
}
}
}
/*
* 尋找節點node的第一個具有左孩子的祖先
* @return 祖先地址或者NULL
*/
template<class TreeNode, class ValueType>
TreeNode *VisualTree<TreeNode, ValueType>::ancestor(TreeNode *node)
{
TreeNode *pchild = node;
TreeNode *pparent = node->*parent;
while (pparent && pchild == pparent->*right) {
pchild = pparent;
pparent = pchild->*parent;
}
return pparent;
}
/*
* 計算一個整數n輸出時佔用的字元數
* @return 整數n輸出時所佔字元數
*/
template<class TreeNode, class ValueType>
int VisualTree<TreeNode, ValueType>::digits(int n)
{
int len = 0;
if (n < 0) {
len++;
n = -n;
}
do {
len++;
n /= 10;
} while (n);
return len;
}
/*
* 輸出一個字元所佔用的字元數
* @return 1
*/
template<class TreeNode, class ValueType>
int VisualTree<TreeNode, ValueType>::digits(char c)
{
return 1;
}
/*
* 輸出一個字串所佔用的字元數
* @return 字串長度
*/
template<class TreeNode, class ValueType>
int VisualTree<TreeNode, ValueType>::digits(const char *s)
{
return strlen(s);
}
/*
* 圖形化顯示二叉樹
* @input root:樹的根節點,promot:可選提示資訊,應以'\0'結尾
*/
template<class TreeNode, class ValueType>
void VisualTree<TreeNode, ValueType>::draw(TreeNode *root, const char *promot)
{
if (promot) {
printf("%s\n", promot);
}
if (root == NULL) {
printf("Empty tree!\n");
return;
}
int len = scan_tree(root);
printer.set_edge_length(len);
traverse_level(root, printer);
}
/*
* 獲得最靠左和最靠右的兩個節點指標
* @return std::pair, 其中first指向最左節點,second指向最右節點
*/
template<class TreeNode, class ValueType>
ExtremumGetter<TreeNode>
VisualTree<TreeNode, ValueType>::extreme_node(TreeNode *node)
{
getter.init(node, node);
traverse_level(node, getter);
return getter;
}
/*
* 獲得最靠左的節點和最靠右的節點pos
* @return 包含最小/最大位置座標的IntExtremumPair
*/
template<class TreeNode, class ValueType>
IntExtremumPair VisualTree<TreeNode, ValueType>::extreme_pos(TreeNode *node)
{
ExtremumGetter<TreeNode> nodes = extreme_node(node);
IntExtremumPair ret;
if (nodes.min()) { ret.min = nodes.min()->pos; }
if (nodes.max()) { ret.max = nodes.max()->pos; }
return ret;
}
/*
* 掃描整棵樹,設定相關資訊(換行標記/資料位數)
* @return 最大資料位數
*/
template<class TreeNode, class ValueType>
int VisualTree<TreeNode, ValueType>::scan_tree(TreeNode *root)
{
int cnt; // 當前深度已掃描節點個數
int depth; // 當前掃描深度
int max_len;
std::queue<TreeNode*> qnode;
std::vector<int> num_node; // num_node[i]: 深度為i的節點總數
num_node.push_back(1); // 一個根節點
num_node.push_back(0); // 初始化第1層
qnode.push(root);
// 將根節點位置設為0,據此算出其他節點相對位置
root->pos = 0;
traverse_level(root, setter);
// 獲取最左最右座標
IntExtremumPair extr = extreme_pos(root);
// 將最左節點座標調整為0,其他節點整體右移
PosAdder adder(root->pos - extr.min);
traverse_level(root, adder);
cnt = 0; depth = 0; max_len = 0;
for ( ; !qnode.empty(); qnode.pop()) {
TreeNode *temp = qnode.front();
adjust_pos(temp);
if (temp->*left) {
qnode.push(temp->*left);
num_node[depth+1]++;
}
if (temp->*right) {
qnode.push(temp->*right);
num_node[depth+1]++;
}
if (++cnt == num_node[depth]) {
temp->newline = true;
depth++;
num_node.push_back(cnt = 0); // 初始化下下層節點個數
} else {
temp->newline = false;
}
max_len = std::max(max_len, digits(temp->*value));
}
// 使樹和螢幕左側之間不留空隙
extr = extreme_pos(root);
if (extr.min > 0) {
PosAdder adder(-extr.min);
traverse_level(root,adder);
}
return max_len;
}
/*
* 層次遍歷二叉樹,對樹中每個節點執行操作vst
*/
template<class TreeNode, class ValueType>
template<class VST>
void VisualTree<TreeNode, ValueType>::traverse_level(TreeNode *root, VST &vst)
{
if (root == NULL) {
return;
}
TreeNode *temp;
std::queue<TreeNode*> qnode;
for (qnode.push(root); !qnode.empty(); qnode.pop()) {
temp = qnode.front();
vst(temp);
if (temp->*left) { qnode.push(temp->*left); }
if (temp->*right) { qnode.push(temp->*right); }
}
}
TreePrinter.h
template<class TreeNode, class ValueType>
class TreePrinter
{
public:
typedef TreeNode *TreeNode::*PtrToMember;
typedef ValueType TreeNode::*PtrToData;
TreePrinter(PtrToMember p, PtrToMember l, PtrToMember r, PtrToData d) :
edge_len(2), num_out(0), vec(), parent(p), left(l), right(r), value(d)
{
}
void set_edge_length(int len)
{
edge_len = std::max(len, 2); // 邊沿最小寬度為2
}
void operator() (TreeNode *node)
{
assert(node);
TreeNode *lc = node->*left;
TreeNode *rc = node->*right;
int lbl = 0, rbl = 0; // 左邊沿字元長度,右邊沿字元長度
int spaces = node->pos * edge_len - num_out; // 佔位空白字元數
if (lc) { lbl = edge_len * (node->pos-(node->*left)->pos) - 1; }
if (rc) { rbl = edge_len * ((node->*right)->pos-node->pos) - 1; }
spaces -= lbl;
assert(spaces >= 0);
while (spaces--) {
num_out += printf(" ");
}
if (node->*left) {
vec.push_back(num_out-1);
while (lbl--) {
num_out += printf("_");
}
}
num_out += out_value(node->*value);
if (node->*right) {
while (rbl--) {
num_out += printf("_");
}
vec.push_back(num_out);
}
if (node->newline) {
new_line();
}
}
private:
int out_value(char c) { return printf("%c", c); }
int out_value(int i) { return printf("%d", i); }
int out_value(const char *p) { return printf("%s", p); }
void new_line()
{
printf("\n");
if (!vec.empty()) {
int n = 0, end = vec[vec.size()-1];
for (int i = 0; i <= end && n < (int)vec.size(); ++i) {
if (i == vec[n]) {
printf("|");
n++;
} else {
printf(" ");
}
}
}
printf("\n");
num_out = 0;
vec.clear();
}
int edge_len;
int num_out; // 已輸出字元數
std::vector<int> vec;
PtrToMember parent;
PtrToMember left;
PtrToMember right;
ValueType TreeNode:: *value;
};
bst.h 為了方便測試寫的一個Binary Search Tree,在main.cc中想BST中隨機插入一定數量的結點,然後顯示:
#include <cassert>
#include <algorithm> // swap
#include "InfoH.h"
#define IS_ROOT(p) ((p)->parent == NULL)
#define IS_LEAT(p) ((p)->left == NULL && (p)->right == NULL)
#define IS_LEFT(p) (!IS_ROOT(p) && (p) == (p)->parent->left)
#define IS_RIGHT(p) (!IS_ROOT(p) && (p) == (p)->parent->right)
template<typename T>
struct TreeNode : public InfoH {
T val;
TreeNode *parent;
TreeNode *left;
TreeNode *right;
TreeNode(const T &v) : InfoH(), val(v), parent(0), left(0), right(0) {}
TreeNode(const T &v, TreeNode *p) : InfoH(), val(v), parent(p), left(0), right(0) {}
};
template<typename T>
class BSTree
{
public:
/*
* 建構函式
*/
BSTree() : root_(0) {}
/*
* 解構函式
*/
virtual ~BSTree()
{
while (!empty()) {
remove(root_);
}
}
/*
* 判斷樹是否為空
* @return true:空, false:非空
*/
bool empty()
{
return root_ == NULL;
}
/*
* 插入一個新節點
* @return 新插入節點地址
*/
TreeNode<T> *insert(const T &val)
{
if (root_ == NULL) {
return root_ = new TreeNode<T>(val);
}
TreeNode<T> *parent = root_;
TreeNode<T> *hole = 0;
hole = (root_->val < val ? root_->right : root_->left);
while (hole != NULL) {
parent = hole;
hole = (hole->val < val ? hole->right : hole->left);
}
if (parent->val < val) {
return parent->right = new TreeNode<T>(val, parent);
} else {
return parent->left = new TreeNode<T>(val, parent);
}
}
/*
* 尋找值為x的節點
* @reutrn 節點地址,如果不存在返回NULL
*/
TreeNode<T> *find(T &x)
{
TreeNode<T> *temp = root_;
while (temp != NULL) {
if (temp->val == x) {
break;
}
temp = (temp->val < x ? temp->right : temp->left);
}
// temp == NULL
return temp;
}
/*
* 刪除指定節點
*/
void remove(TreeNode<T> *p)
{
assert(p != NULL);
TreeNode<T> *temp = NULL;
if (p->left && p->right) { // 有兩個孩子
TreeNode<T> *temp = succ(p);
std::swap(p->val, temp->val); // 交換和後繼節點的內容
remove(temp); // 刪除後繼節點
} else if (p->left || p->right) { // 只有一個孩子
temp = (p->left ? p->left : p->right);
if (IS_ROOT(p)) {
root_ = temp;
root_->parent = NULL;
} else if (IS_LEFT(p)) {
p->parent->left = temp;
temp->parent = p->parent;
} else {
p->parent->right = temp;
temp->parent = p->parent;
}
delete p;
} else { // 沒有孩子
if (IS_ROOT(p)) {
root_ = NULL;
} else if (IS_LEFT(p)) {
p->parent->left = NULL;
} else {
p->parent->right = NULL;
}
delete p;
}
}
/*
* 返回根節點地址
*/
TreeNode<T> *root() const
{
return root_;
}
private:
/*
* 尋找最小節點
* @reutrn 最小節點地址
*/
TreeNode<T> *min_node(TreeNode<T> *p)
{
assert(p != NULL);
TreeNode<T> *temp = p;
while (temp->left != NULL) {
temp = temp->left;
}
return temp;
}
/*
* 尋找最大節點
* @return 最大節點地址
*/
TreeNode<T> *max_node(TreeNode<T> *p)
{
assert(p != NULL);
TreeNode<T> *temp = p;
while (temp->right != NULL) {
temp = temp->right;
}
return temp;
}
/*
* 尋找指定節點p的後繼節點(比p節點大的所有節點中的最小節點)
* @return 後繼節點地址
*/
TreeNode<T> *succ(TreeNode<T> *p)
{
assert(p != NULL);
if (p->right) { // 節點有右子樹:右子樹中最小節點即為後繼節點
return min_node(p->right);
}
// else
TreeNode<T> *child = p;
TreeNode<T> *father = p->parent;
while (father && !IS_LEFT(child)) { // 節點無右子樹:第一個有左孩子的祖先節點即為後繼節點
child = father;
father = child->parent;
}
return father;
}
TreeNode<T> *root_;
};
main.c
#include <iostream>
#include <vector>
#include "bst.h"
#include "VisualTree.h"
int main(int argc, char*argv[])
{
typedef int ValueType;
typedef TreeNode<ValueType> NodeType;
BSTree<ValueType> tree;
VisualTree<NodeType, ValueType> vtree(&NodeType::parent,
&NodeType::left,
&NodeType::right,
&NodeType::val);
int num = 15;
srand(time(NULL));
for (int i = 0; i < num; ++i) {
ValueType v = rand() % 1000; // 隨機生成一個[0,1000)的數
tree.insert(v);
std::cout << "insert : " << v << std::endl;
vtree.draw(tree.root());
}
return 0;
}
對博文的引用即到這裡,下面就是自己的思考和探索了。這位博主是很666的,想到當年輸出個*組成的大紅心就費了好大勁,不得不給他點個贊啦哈哈。
程式執行結果:
四、階段二:圖形顯示二叉搜尋樹之查刪
1.二叉搜尋樹的查詢操作
根據二叉搜尋樹的特點,將待查詢的值和根節點進行比較,小於根節點就前往左子樹查詢,大於根節點前往右子樹查詢,等於根節點即為查詢結果。返回的結果"已找到",如果不存在返回“未找到”
可在main.c進行輸出值的判斷:
ValueType search=99;
if(!tree.find(search)){
printf("未找到\n");
}
2.二叉搜尋樹的刪除操作
在查詢基礎上進行刪除,最後再次顯示:
五、階段三:動態顯示二叉搜尋樹
就我個人的理解來說,動態就是,像命令列一樣,輸入指令進行動態的增刪改查,並輸出結果。
在main.c函式中進行修改:
int t=100;//預設最多操作100次
int m=0,n=0;
while(t){
printf("-----------------------------------------------\n");
printf("--------------------請輸入指令-------------------\n");
printf("--- 插入:1,num 查詢:2,num ---\n");
printf("--- 刪除:3,num 退出:4 ---\n");
printf("--- (注:num設定為0~100) ---\n");
printf("-----------------------------------------------\n");
scanf("%d,%d",&m,&n);
printf("\n-----------------------------------------------\n");
//健壯性1
if(m!=1&&m!=2&&m!=3&&m!=4){
printf("輸入指令錯誤,請重新輸入。\n");
continue;
}
//健壯性2
if(n<0||n>100){
printf("輸入數字錯誤,請重新輸入。\n");
continue;
}
if(m==1){
ValueType v =n;
tree.insert(v);
std::cout << "insert : " << v << std::endl;
vtree.draw(tree.root());
}else if (m==2){
ValueType search =n;
if(!tree.find(search)){
printf("未找到\n");
}else{
printf("已找到\n");
}
}else if(m==3){
ValueType search=n;
if(!tree.find(search)){
printf("未找到\n");
}else{
printf("已找到,並刪除\n");
}
TreeNode<int> *p=tree.find(search);
tree.remove(p);
vtree.draw(tree.root());
}else if(m==4){
break;
}
t--;
}
return 0;
執行效果:
錯誤輸入:
異常記錄:
- 有時插入數字時會出現不斷執行迴圈的情況,儘管這種錯誤不完全因為預設執行100次,但還是後面把迴圈的指令條件該作了m!=4
- m和n的錯誤輸入如果為字母將會帶來很大的問題,在上面未修改為m!=4的時候,尚迴圈100次停止,目前需要執行錯誤行為完畢。後期還需調整。
目前的main.c為:
#include <iostream>
#include <vector>
#include "bst.h"
#include "VisualTree.h"
int main(int argc, char*argv[])
{
typedef int ValueType;
typedef TreeNode<ValueType> NodeType;
BSTree<ValueType> tree;
VisualTree<NodeType, ValueType> vtree(&NodeType::parent,
&NodeType::left,
&NodeType::right,
&NodeType::val);
int m=0,n=0;
while(m!=4){
printf("-----------------------------------------------\n");
printf("--------------------請輸入指令-------------------\n");
printf("--- 插入:1,num 查詢:2,num ---\n");
printf("--- 刪除:3,num 退出:4 ---\n");
printf("--- (注:num設定為0~100) ---\n");
printf("-----------------------------------------------\n");
scanf("%d,%d",&m,&n);
printf("\n-----------------------------------------------\n");
//健壯性1
if(m!=1&&m!=2&&m!=3&&m!=4){
printf("輸入指令錯誤,請重新輸入。\n");
continue;
}
//健壯性2
if(n<0||n>100){
printf("輸入數字錯誤,請重新輸入。\n");
continue;
}
if(m==1){
ValueType v =n;
tree.insert(v);
std::cout << "insert : " << v << std::endl;
vtree.draw(tree.root());
}else if (m==2){
ValueType search =n;
if(!tree.find(search)){
printf("未找到\n");
}else{
printf("已找到\n");
}
}else if(m==3){
ValueType search=n;
if(!tree.find(search)){
printf("未找到\n");
}else{
printf("已找到,並刪除\n");
}
TreeNode<int> *p=tree.find(search);
tree.remove(p);
vtree.draw(tree.root());
}else if(m==4){
break;
}
}
return 0;
}
可以說,已經完成了階段三的工作。
六、階段四:加入調整規則使二叉搜尋樹變為平衡樹
七、階段五:動態顯示二叉平衡樹
使用上上篇部落格裡對二叉搜尋樹轉換為二叉平衡樹的思想,時間關係找了一篇別人的博文,程式碼稍作改動,內容如下:
AVLTree.h
//
// AVLTree.h
// DrawvBinarySearchTree
//
// Created by 張立鵬 on 2018/11/1.
// Copyright © 2018年 張立鵬. All rights reserved.
//
#ifndef AVLTree_h
#define AVLTree_h
#include <iostream>
#include <algorithm>
using namespace std;
#pragma once
//平衡二叉樹結點
template <typename T>
struct AvlNode
{
T data;
int height; //結點所在高度,校驗平衡度
AvlNode<T> *left;
AvlNode<T> *right;
AvlNode<T>(const T theData) : data(theData), left(NULL), right(NULL), height(0){}
};
static int shuchu[50]={0};
static int s=0;
//AVLTree
template <typename T>
class AvlTree
{
public:
AvlTree<T>(){}
~AvlTree<T>(){}
AvlNode<T> *root;
//插入結點
void Insert(AvlNode<T> *&t, T x);
//刪除結點
bool Delete(AvlNode<T> *&t, T x);
//查詢是否存在給定值的結點
bool Contains(AvlNode<T> *t, const T x) const;
//中序遍歷
void InorderTraversal(AvlNode<T> *t);
//前序遍歷
void PreorderTraversal(AvlNode<T> *t);
//最小值結點
AvlNode<T> *FindMin(AvlNode<T> *t) const;
//最大值結點
AvlNode<T> *FindMax(AvlNode<T> *t) const;
private:
//求樹的高度
int GetHeight(AvlNode<T> *t);
//單旋轉 左
AvlNode<T> *LL(AvlNode<T> *t);
//單旋轉 右
AvlNode<T> *RR(AvlNode<T> *t);
//雙旋轉 右左
AvlNode<T> *LR(AvlNode<T> *t);
//雙旋轉 左右
AvlNode<T> *RL(AvlNode<T> *t);
};
template <typename T>
AvlNode<T> * AvlTree<T>::FindMax(AvlNode<T> *t) const
{
if (t == NULL)
return NULL;
if (t->right == NULL)
return t;
return FindMax(t->right);
}
template <typename T>
AvlNode<T> * AvlTree<T>::FindMin(AvlNode<T> *t) const
{
if (t == NULL)
return NULL;
if (t->left == NULL)
return t;
return FindMin(t->left);
}
template <typename T>
int AvlTree<T>::GetHeight(AvlNode<T> *t)
{
if (t == NULL)return -1;
else return t->height;
}
//單旋轉
//左左插入導致的不平衡
template <typename T>
AvlNode<T> * AvlTree<T>::LL(AvlNode<T> *t)
{
AvlNode<T> *q = t->left;
t->left = q->right;
q->right = t;
t = q;
t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
q->height = max(GetHeight(q->left), GetHeight(q->right)) + 1;
return q;
}
//單旋轉
//右右插入導致的不平衡
template <typename T>
AvlNode<T> * AvlTree<T>::RR(AvlNode<T> *t)
{
AvlNode<T> *q = t->right;
t->right = q->left;
q->left = t;
t = q;
t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
q->height = max(GetHeight(q->left), GetHeight(q->right)) + 1;
return q;
}
//雙旋轉
//插入點位於t的左兒子的右子樹
template <typename T>
AvlNode<T> * AvlTree<T>::LR(AvlNode<T> *t)
{
//雙旋轉可以通過兩次單旋轉實現
//對t的左結點進行RR旋轉,再對根節點進行LL旋轉
RR(t->left);
return LL(t);
}
//雙旋轉
//插入點位於t的右兒子的左子樹
template <typename T>
AvlNode<T> * AvlTree<T>::RL(AvlNode<T> *t)
{
LL(t->right);
return RR(t);
}
template <typename T>
void AvlTree<T>::Insert(AvlNode<T> *&t, T x)
{
if (t == NULL)
t = new AvlNode<T>(x);
else if (x < t->data)
{
Insert(t->left, x);
//判斷平衡情況
if (GetHeight(t->left) - GetHeight(t->right) > 1)
{
//分兩種情況 左左或左右
if (x < t->left->data)//左左
t = LL(t);
else //左右
t = LR(t);
}
}
else if (x > t->data)
{
Insert(t->right, x);
if (GetHeight(t->right) - GetHeight(t->left) > 1)
{
if (x > t->right->data)
t = RR(t);
else
t = RL(t);
}
}
else
;//資料重複
t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
}
template <typename T>
bool AvlTree<T>::Delete(AvlNode<T> *&t, T x)
{
//t為空 未找到要刪除的結點
if (t == NULL)
return false;
//找到了要刪除的結點
else if (t->data == x)
{
//左右子樹都非空
if (t->left != NULL && t->right != NULL)
{//在高度更大的那個子樹上進行刪除操作
//左子樹高度大,刪除左子樹中值最大的結點,將其賦給根結點
if (GetHeight(t->left) > GetHeight(t->right))
{
t->data = FindMax(t->left)->data;
Delete(t->left, t->data);
}
else//右子樹高度更大,刪除右子樹中值最小的結點,將其賦給根結點
{
t->data = FindMin(t->right)->data;
Delete(t->right, t->data);
}
}
else
{//左右子樹有一個不為空,直接用需要刪除的結點的子結點替換即可
AvlNode<T> *old = t;
t = t->left ? t->left: t->right;//t賦值為不空的子結點
delete old;
}
}
else if (x < t->data)//要刪除的結點在左子樹上
{
//遞迴刪除左子樹上的結點
Delete(t->left, x);
//判斷是否仍然滿足平衡條件
if (GetHeight(t->right) - GetHeight(t->left) > 1)
{
if (GetHeight(t->right->left) > GetHeight(t->right->right))
{
//RL雙旋轉
t = RL(t);
}
else
{//RR單旋轉
t = RR(t);
}
}
else//滿足平衡條件 調整高度資訊
{
t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
}
}
else//要刪除的結點在右子樹上
{
//遞迴刪除右子樹結點
Delete(t->right, x);
//判斷平衡情況
if (GetHeight(t->left) - GetHeight(t->right) > 1)
{
if (GetHeight(t->left->right) > GetHeight(t->left->left))
{
//LR雙旋轉
t = LR(t);
}
else
{
//LL單旋轉
t = LL(t);
}
}
else//滿足平衡性 調整高度
{
t->height = max(GetHeight(t->left), GetHeight(t->right)) + 1;
}
}
return true;
}
//前序遍歷
template <typename T>
void AvlTree<T>::PreorderTraversal(AvlNode<T> *t)
{
if (t)
{
shuchu[s]=t->data;
s++;
PreorderTraversal(t->left);
PreorderTraversal(t->right);
}
}
#endif /* AVLTree_h */
執行介面如下:
八、階段六:使用多種方式顯示二叉平衡樹
1.java 2D
畫板部分參考:https://blog.csdn.net/qq_40286361/article/details/78014795,這是使用java2D畫二叉樹的,後面也可以對前面階段的工作進行拓展和使用。
既然如此,我們先對找到的部落格進行研究,程式碼也放到這邊進行參考和學習。
MutableInteger.java 用以傳遞可變整數,物件是函式返回值
package Btree;
public class MutableInteger {
public int value;
public MutableInteger(int x) { value = x; }
public MutableInteger() { value = 0; }
public boolean equals(int x) {
if ( x==value )
return true;
else
return false;
}
public int getValue() { return value; }
public void setValue(int value) { this.value = value; }
}
Tnode.java
package tree;
import Btree.MutableInteger;
import java.util.LinkedList;
import java.util.List;
public class Tnode {
private String name; //該結點名字
private int layer = 0; //該結點層級
private int x = -1; //x座標
private List<Tnode> childs = null; //儲存該結點的孩子
public Tnode(String name) { this.name = name; }
public Tnode() { this.name = null; }
public void add(Tnode n) {
if (childs==null)
childs = new LinkedList<Tnode>();//這裡可以改為ArrayList
n.layer = this.layer + 1;
setChildLayer(n);
childs.add(n);
}
private void setChildLayer(Tnode n) {//遞迴設定層級,深度優先
if (n.hasChild()) {
List<Tnode> c = n.getChilds();
for (Tnode node : c) {
node.layer = n.layer + 1;
setChildLayer(node);
}
}
}
public void CoordinateProcess(MutableInteger maxX, MutableInteger maxY) { CoordinateProcess(this, maxX, maxY); }
public static void CoordinateProcess(Tnode n, MutableInteger maxX, MutableInteger maxY) {
//max其實是用來佈置畫布的大小而設定的返回值
//預設的根節點座標是(0,0),即x=0,layer=0
setx(n, new MutableInteger(0), maxX, maxY);
}
private static void setx(Tnode n, MutableInteger va, MutableInteger maxX, MutableInteger maxY) {//va其實只是用來儲存中間結果用來呼叫的
if (n.hasChild()) {
List<Tnode> c = n.getChilds();
c.get(0).x = va.value;
setx(c.get(0), va, maxX, maxY);
for (int i=1; i<c.size(); i++) {
setx(c.get(i), va, maxX, maxY);
}
n.x = c.get(0).x;//本結點的x是第一個孩子的x
} else {
n.x = va.value++;
}
//儲存最大的x,y返回
if (n.getX()>maxX.value) {
maxX.value = n.getX();
}
if (n.getLayer()>maxY.value) {
maxY.value = n.getLayer();
}
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getLayer() { return layer; }
public int getX() { return x; }
public void setLayer(int layer) { this.layer = layer; }
public List<Tnode> getChilds() { return childs; }
public void setChilds(List<Tnode> childs) { this.childs = childs; }
public boolean hasChild() { return childs==null ? false : true; }
public void printAllNode(Tnode n) {//遞迴列印所有結點,深優
System.out.println(n.toString());
if (n.hasChild()) {
List<Tnode> c = n.getChilds();
for (Tnode node : c) {
printAllNode(node);
}
}
}
public void printAllNode() { printAllNode(this); }
public String getAllNodeName(Tnode n) {
String s = n.toString()+"/n";
if (n.hasChild()) {
List<Tnode> c = n.getChilds();
for (Tnode node : c) {
s += getAllNodeName(node)+"/n";
}
}
return s;
}
public String getAllNodeName() { return getAllNodeName(this); }
public String toString() { return name; }
}
TreePanel.java
package Btree;
import java.awt.*;
import java.util.List;
import javax.swing.JPanel;
public class TreePanel extends JPanel {
private static final long serialVersionUID = 1L;
private Tnode tree; //儲存整棵樹
private int gridWidth = 170; //每個結點的寬度
private int gridHeight = 20; //每個結點的高度
private int vGap = 50; //每2個結點的垂直距離
private int hGap = 30; //每2個結點的水平距離
private int startY = 10; //根結點的Y,預設距離頂部10畫素
private int startX = 10; //根結點的X,預設距離左端10畫素
//改進之後的程式呢就不是原文的對對齊方式啦,所以下面幾行是沒用的
//private int childAlign; //孩子對齊方式
//public static int CHILD_ALIGN_ABSOLUTE = 0; //相對Panel居中
//public static int CHILD_ALIGN_RELATIVE = 1; //相對父結點居中
private Font font = new Font("微軟雅黑",Font.BOLD,14); //描述結點的字型
private Color gridColor = Color.BLACK; //結點背景顏色
private Color linkLineColor = Color.BLACK; //結點連線顏色
private Color stringColor = Color.WHITE; //結點描述文字的顏色
/*放棄了原文的內容,這是由於我們只有一種畫法,而不是中間對其或是左對齊等
public TreePanel() { this(null,CHILD_ALIGN_ABSOLUTE); }
public TreePanel(Tnode n) { this(n,CHILD_ALIGN_ABSOLUTE); }
public TreePanel(int childAlign) { this(null,childAlign); }
public TreePanel(Tnode n, int childAlign) {
super();
setTree(n);
this.childAlign = childAlign;
}
*/
public TreePanel() { this(null); }
public TreePanel(Tnode n) {
super();
setTree(n);
}
public void setTree(Tnode n) { tree = n; }
//重寫,呼叫自己的繪製方法
public void paintComponent(Graphics g) {
//startX = (getWidth()-gridWidth)/2;//這是居中方式的設定,放棄原文方法
super.paintComponent(g);
g.setFont(font);
drawAllNode(tree, g);
}
/**
* 遞迴繪製整棵樹
* n 被繪製的Node
* xPos 根節點的繪製X位置
* g 繪圖上下文環境
*/
public void drawAllNode(Tnode n, Graphics g) {
int y = n.getLayer()*(vGap+gridHeight)+startY;
int x = n.getX()*(hGap+gridWidth)+startX;
int fontY = y + gridHeight - 5; //5為測試得出的值,你可以通過FM計算更精確的,但會影響速度
g.setColor(gridColor);
g.fillRoundRect(x, y, gridWidth, gridHeight, 10, 10); //畫結點的格子
g.setColor(stringColor);
g.drawString(n.toString(), x+5, fontY); //畫結點的名字
if (n.hasChild()) {
g.setColor(linkLineColor);
g.drawLine(x+gridWidth/2, y+gridHeight, x+gridWidth/2, y+gridHeight+vGap/2);
List<Tnode> c = n.getChilds();
int i = 0;
for (Tnode node : c) {
int newX = node.getX()*(hGap+gridWidth)+startX; //孩子結點起始X
g.setColor(linkLineColor);
g.drawLine(newX+gridWidth/2, y+gridHeight+vGap/2, newX+gridWidth/2, y+gridHeight+vGap);
drawAllNode(node, g);
i++;
if (i==c.size()) {
g.setColor(linkLineColor);
g.drawLine(x+gridWidth/2, y+gridHeight+vGap/2, newX+gridWidth/2, y+gridHeight+vGap/2);
}
}
}
}
public Color getGridColor() { return gridColor; }
public void setGridColor(Color gridColor) { this.gridColor = gridColor; }
public Color getLinkLineColor() { return linkLineColor; }
public void setLinkLineColor(Color gridLinkLine) { this.linkLineColor = gridLinkLine; }
public Color getStringColor() { return stringColor; }
public void setStringColor(Color stringColor) { this.stringColor = stringColor; }
public int getStartY() { return startY; }
public void setStartY(int startY) { this.startY = startY; }
public int getStartX() { return startX; }
public void setStartX(int startX) { this.startX = startX; }
public int getGridWidth() { return gridWidth; }
public void setGridWidth(int gridWidth) { this.gridWidth = gridWidth; }
public int getGridHight() { return gridHeight; }
public void setGridHeight(int gridHeight) { this.gridHeight = gridHeight; }
public int getVGap() { return vGap; }
public void setVGap(int vGap) { this.vGap = vGap; }
public int getHGap() { return hGap; }
public void setHGap(int hGap) { this.hGap = hGap; }
}
原博主的實現效果如下:
可見是可以實現的。
目前我要做兩件事情:
1.使用Java實現同樣的二叉樹輸出;
2.使用畫板實現二叉樹的輸出。
因為畫板是在使用Java窗體的基礎上定義各種畫面元素的位置,而位置的判斷常常伴隨著裂點等操作,因此是畫板程式碼和B-tree的結合。
首先我找來了graph2D列印二叉樹的程式碼(看來我有時間得把前面的工作拓展一下咯),並作以研究,經本人改動之後如下:
TreeNode.java
package gg;
public class TreeNode {
String data;
TreeNode left;
TreeNode right;
int layerNo, x;
/*三個建構函式*/
public TreeNode(String v) {
data = v;
left = null;
right = null;
}
public TreeNode(String v, TreeNode l, TreeNode r) {
data = v;
left = l;
right = r;
}
public TreeNode() {
// 預設建構函式
}
}
Mytree.java
package gg;
import gg.TreeNode;
public class Mytree {
public int cx, cy;
public int i = 0, j = 0;
TreeNode root; //根節點
int maxDepth; //最大深度
Mytree(String v) { //建構函式
insert(v); //自定義insert函式
}
void insert(String v) {
TreeNode p = new TreeNode(), head; //TreeNode
TreeNode st[];
st = new TreeNode[v.length() + 10];
int top = -1, k = 0, j = 0; //j為String v中字母的索引位置
char ch;
head = root; // 建立的二叉樹初始時為空
ch = v.charAt(j);//harAt(int index)方法是一個能夠用來檢索特定索引下的字元的String例項的方法.charAt()方法返回指定索引位置的char值。索引範圍為0~length()-1
if (head == null) { // 頭結點相當於一個虛擬的頭ROOT.
head = new TreeNode(""+ch);
j++;
ch = v.charAt(j);
}
int i = v.length();
while (j < v.length() - 1) // str未掃描完時迴圈
{
switch (ch) {
case '(':
top++;
k = 1;
break;
case ')':
head = st[top - k];
top -= k;
k = 1;
break;
case ',':
head = st[top];
top++;
k = 2;
break; // 上述選擇一個變數來確定有幾個節點
default:
p = new TreeNode("" + ch);
// 已建立二叉樹根結點
{
switch (k) {
case 1:
head.left = p;
st[top] = head;
head = head.left;
break;
case 2:
head.right = p;
st[top] = head;
head = head.right;// 插入,並且儲存一個數組中
break;
}
}
}
j++;
ch = v.charAt(j);
}
root = st[0];
}
public void calcX(TreeNode tn, int x) {
tn.x = x;
if (tn.left != null)
calcX(tn.left, x - 1);
if (tn.right != null)
calcX(tn.right, x + 1);
}
public void calcX2(TreeNode tn, int x) {
tn.x = x;
int dx = (int) Math.pow(2, maxDepth - tn.layerNo - 1);
if (tn.left != null)
calcX2(tn.left, x - dx);
if (tn.right != null)
calcX2(tn.right, x + dx);
}
// 計算每個結點的x座標,採用前序遍歷
// *******************************************************************
public void calcDepth(TreeNode tn, int d) {
tn.layerNo = d;
if (maxDepth < d)
maxDepth = d;
d += 1;
if (tn.left != null)
calcDepth(tn.left, d);
if (tn.right != null)
calcDepth(tn.right, d);
}
// 計算每個結點的層號及樹的最大深度,採用前序遍歷
// *************************************************************************
public void printLayer() {
calcDepth(root, 1); // 先計算出每個結點的層號及樹的最大深度maxDepth
calcX2(root, (int) Math.pow(2, maxDepth - 1));
}
}
study.java
package gg;
import java.awt.Color;
import java.awt.Event;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import gg.TreeNode;
import gg.Mytree;
public class study extends Frame {
String temp = " ";// 下面交換字串有用。
static String tree = "a(b(l,m),e(f(h,j),i(k,g)))";//自定義二叉樹
Toolkit toolkit = Toolkit.getDefaultToolkit();
public static void main(String args[]) {
Frame f = new study();
f.setTitle("二叉樹的圖形展示 ———WittPeng");
f.setBackground(Color.LIGHT_GRAY);
f.setBounds(1000, 500, 1000, 800);//x,y,width,height
f.setVisible(true); //窗體可視
f.setResizable(false);
}
// 類似於資料結構中的連結串列
public boolean handleEvent(Event evt) {
if (evt.id == Event.WINDOW_DESTROY)
System.exit(0);
return super.handleEvent(evt);
}
public void paint(Graphics g) {
int i = 0, j = 0;
Mytree bt = new Mytree(tree);
TreeNode Node, tNode[];
int cx[], cy[];
Node = bt.root;
bt.printLayer();// 首先遍歷一下,得到座標初始化。
int top = -1;
tNode = new TreeNode[bt.maxDepth];// 為了下層遍歷出座標定義的一個數組
cx = new int[tree.length() + 1];
cy = new int[tree.length() + 1];
cx[0] = 1;
cy[0] = 1;
if (bt.root != null) {
top++;
tNode[top] = bt.root;
while (top > -1) {
Node = tNode[top];
top--;
g.setColor(Color.PINK);//節點背景顏色
g.fillOval(100 + Node.x * 40, Node.layerNo * 50, 40, 20);//x,y,width,height
g.setColor(Color.BLACK);//節點字型顏色
g.drawString(Node.data, 120 + Node.x * 40,
Node.layerNo * 50 + 13);//String Str,x,y
cx[i] = 120 + Node.x * 40;
cy[i] = Node.layerNo * 50;// cx cy 兩座標統計節點的座標值,相同下標對應的XY座標為節點座標。
i++;
if (Node.right != null) {
top++;
tNode[top] = Node.right;
}
if (Node.left != null) {
top++;
tNode[top] = Node.left;
}
}
}
// 非遞迴遍歷出樹的結點
g.setColor(Color.black);//線條顏色
top = 0;
int k = 1, s = 0, a = 0, b = i;
s = cx[1] - c