介紹
Qt繪制二叉樹是大二時數據結構的一個實習題目,當時的功能要求如下:
- 鍵盤輸入二叉樹結點序列(前序或層次),創建一棵二叉樹
- 實現SwapTree方法,以根結點為參數,交換每個結點的左子樹和右子樹(提示:前序遞歸)
- 實現Find方法,查找值為key的結點,並輸出該結點的所有祖先結點
- 輸入一棵二叉樹的前序遍歷序列和中序遍歷序列,重構這棵二叉樹(這個序列里面是不帶空結點’#‘的)
二叉樹的前序和中序創建要求如下:
- 要求鍵盤輸入二叉樹結點序列
- 結點序列可以是前序,也可以是層次
- 空結點以#表示
由題目可知呢主要就是可視化一顆二叉樹,另外需要說清的是僅僅前序遍歷是無法確定一顆二叉樹的順序的,但是如果前序中加上空節點‘#’,是可以確定的
示例1(前序和層次的):
示例2(前序=“ABC##DE#G##F###” 或者 層次=“AB#CD##EF#G####”):
示例3(前序="ABHFDECKG"和中序="HBDFAEKCG"):
主要建樹思路
- 主要功能就前序構造、層次構造、交換節點、查找關鍵字、重新構建這幾個,所以為了圖便捷,就直接在Qt提供的ui界面上加上這幾個菜單項,可以參考下圖
- 二叉樹根據前序生成一顆樹。編寫了一個函數CreateBinTree,利用遞歸進行二叉樹的生成。思路也比較簡單可以看下面的代碼。
//前序創造節點
//i代表第幾個字母
void BinaryTree::CreateBinTree(QString &str, BinTreeNode *&Node,int &i)
{
// qDebug()<<str;
if(str[i]!='#')//說明不是空結點
{
Node=new BinTreeNode(str[i]);
Treesize++;
i++;
this->CreateBinTree(str,Node->left,i);
this->CreateBinTree(str,Node->right,i);
}
else
{
i++;
Node=nullptr;
}
}
- 二叉樹的層次遍歷生成一顆二叉樹。這個利用隊列來完成,利用隊列遍歷字符串,先將字符串第一個字符塞進隊列作為根節點,然后按順序遍歷字符串並且創建對應的孩子節點,具體如下。
int j=0;
Treesize=0;
QQueue<BinTreeNode *>Q;
BinTreeNode *p=nullptr;
if(str[j]=='#') //先創建根節點
{
Treesize=0;
return;
}
root=new BinTreeNode(str[j]);
Treesize++;
Q.enqueue(root);
j++;
while(j<(str.size()-1))
{
if(Q.isEmpty())
break;
else
p=Q.dequeue();
if(str[j]!='#') //如果字符不為‘#’,創建左結點
{
p->left=new BinTreeNode(str[j]);
Treesize++;
Q.enqueue(p->left);
}
j++;
if(str[j]!='#') //如果字符不為‘#’,創建右結點
{
p->right=new BinTreeNode(str[j]);
Treesize++;
Q.enqueue(p->right);
}
j++;
}
- 通過前序和中序建樹。前序和中序確定樹的順序思想比較簡單,利用前序的特性找到父節點,然后利用中序確定左右子樹,然后重復這樣的過程。代碼如下,借鑒一下就行,看以前的代碼自己都想吐槽。
//前序和中序建樹
//pre代表前序字符串
//in代表中序字符串
//n代表pre可以到的位置
//測試用例: 前序:"ABHFDECKG",中序:"HBDFAEKCG"
BinTreeNode *BinaryTree::creatBinaryTree(QString pre, QString in, int n)
{
qDebug()<<pre;
qDebug()<<in;
if(n==0) return nullptr;
int k=0;
while(pre[0]!=in[k]&&k<in.length())k++;
if(k>=in.length()) return nullptr; //理論上應該需要拋出異常的,
BinTreeNode *t=new BinTreeNode(pre[0]);
//以位置k分為左子樹和右子樹
t->left=creatBinaryTree(pre.mid(1),in,k);//從0-k是左子樹,所以在這里pre只能遍歷到k
t->right=creatBinaryTree(pre.mid(k+1),in.mid(k+1),n-k-1);
//由於pre和in同時都只保留右子樹部分,所以pre
return t;
}
上面建樹的例子看看就行,尤其是最后一個前中序建樹,也不知道自己當時是怎么寫出這么魔性的代碼。下面就專門介紹一下畫二叉樹的部分。
主要畫樹思路
畫樹是利用了Qt的繪圖事件,直接進行畫圖,畫圖是在建樹已經完成的基礎之上完成的。想法比較簡單,所以畫出來的比較難看,先在這里說明一下。
-
二叉樹的節點是圓形的,半徑為25。二叉樹的子節點和父節點之間x軸上相差45,y軸上100。舉個例子:父節點的坐標為(x,y),則左孩子坐標為(x-45,y+100),右孩子坐標為(x+45,y+100)。根節點的坐標設置為(500,75),這些數據都可以根據自己的需求改,不定死。
-
數據結構設計的時候,對於二叉樹的節點BinTreeNode,設計一個data(QChar類型,用於存儲數據)、point(QPoint類型,用於存儲位置)
-
數據結構設計時,對於二叉樹BinaryTree,設計Mypoints(QPoint *類型,存儲樹各個結點坐標)、My_lines( QLine *類型,存儲需要畫的線的條數)
-
寫一個函數setMyPoints,通過層次遍歷,完成各個坐標的匹配。其中對於Mypoints直接存儲節點的中心,然后畫圓;對於線段,從上面的例子也看得出來是父節點的中心向下半徑個位置作為起點,子節點中心向上半徑個位置為終點。具體代碼如下所示
//為坐標組設置應的坐標,以及得到相應的線段 void BinaryTree::setMyPoints() { //設置父節點和子節點間橫坐標相差的距離 int i=0; // int H=height(); Mypoints=new QPoint[Treesize]; //動態分配空間 My_lines=new QLine[Treesize-1]; QQueue<BinTreeNode *>Q; //調用隊列 BinTreeNode *p=root; root->setpoint(QPoint(500,75)); //為根節點設置坐標 Q.enqueue(root); Mypoints[i]=root->point; //通過層次遍歷,完成各個坐標的匹配 while(!Q.isEmpty()) { p=Q.dequeue(); if(p->left!=nullptr) { i++; int h=height(p); p->left->setpoint(p->point-QPoint(45*h,-100)); Mypoints[i]=p->left->point; My_lines[i-1].setP1(p->point+QPoint(0,25));//線 My_lines[i-1].setP2(p->left->point-QPoint(0,25)); Q.enqueue(p->left); } if(p->right!=nullptr) { i++; int h=height(p); p->right->setpoint(p->point+QPoint(45*h,100)); Mypoints[i]=p->right->point; My_lines[i-1].setP1(p->point+QPoint(0,25)); My_lines[i-1].setP2(p->right->point-QPoint(0,25)); Q.enqueue(p->right); h--; } } }
-
對於左側顯示的字符,這個就比較簡單了,直接在建樹之后進行相對應的前序、中序、后續、層次遍歷,然后將字符串保存下來即可,在這里就不展開詳細講解
總結
我畫二叉樹的思想比較簡單,所以畫出來也不是很好看,代碼雖然可以運行,但是也有一些小細節上的問題,如果有什么更好的意見歡迎指教。