資料結構開發(25):二叉樹中屬性操作、層次遍歷與典型遍歷
阿新 • • 發佈:2018-12-23
0.目錄
1.二叉樹的比較與相加
2.二叉樹的線索化實現
3.二叉樹的經典面試題分析
4.小結
1.二叉樹的比較與相加
二叉樹的克隆操作:
SharedPointer< BTree<T> > clone() const
- 克隆當前樹的一份拷貝
- 返回值為堆空間中的一棵新二叉樹 ( 與當前樹相等 )
二叉樹的克隆:
- 定義功能:clone(node)
- 拷貝 node 為根結點的二叉樹 ( 資料元素在對應位置相等 )
在BTree.h中實現二叉樹的克隆操作:
protected: BTreeNode<T>* clone(BTreeNode<T>* node) const { BTreeNode<T>* ret = NULL; if( node != NULL ) { ret = BTreeNode<T>::NewNode(); if( ret != NULL ) { ret->value = node->value; ret->left = clone(node->left); ret->right = clone(node->right); if( ret->left != NULL ) { ret->left->parent = ret; } if( ret->right != NULL ) { ret->right->parent = ret; } } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new node ..."); } } return ret; } public: SharedPointer< BTree<T> > clone() const { BTree<T>* ret = new BTree<T>(); if( ret != NULL ) { ret->m_root = clone(root()); } else { THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new tree ..."); } return ret; }
二叉樹比較操作的定義:
- 判斷兩棵二叉樹中的資料元素是否對應相等
bool operator == (const BTree<T>& btree)
bool operator != (const BTree<T>& btree)
二叉樹的比較:
- 定義功能:equal(lh, rh)
- 判斷 Ih 為根結點的二叉樹與 rh 為根結點的二叉樹是否相等
在BTree.h中實現二叉樹的比較操作:
protected: bool equal(BTreeNode<T>* lh, BTreeNode<T>* rh) const { if( lh == rh ) { return true; } else if( (lh != NULL) && (rh != NULL) ) { return (lh->value == rh->value) && equal(lh->left, rh->left) && equal(lh->right, rh->right); } else { return false; } } public: bool operator == (const BTree<T>& btree) { return equal(root(), btree.root()); } bool operator != (const BTree<T>& btree) { return !(*this == btree); }
統一mian.cpp測試:
#include <iostream>
#include "BTree.h"
using namespace std;
using namespace StLib;
int main()
{
BTree<int> bt;
BTreeNode<int>* n = NULL;
bt.insert(1, NULL);
n = bt.find(1);
bt.insert(2, n);
bt.insert(3, n);
n = bt.find(2);
bt.insert(4, n);
bt.insert(5, n);
n = bt.find(4);
bt.insert(8, n);
bt.insert(9, n);
n = bt.find(5);
bt.insert(10, n);
n = bt.find(3);
bt.insert(6, n);
bt.insert(7, n);
SharedPointer< BTree<int> > btClone = bt.clone();
int a[] = {8, 9, 10, 6, 7};
cout << "Clone: " << endl;
for(int i=0; i<5; i++)
{
TreeNode<int>* node = btClone->find(a[i]);
while( node )
{
cout << node->value << " ";
node = node->parent;
}
cout << endl;
}
cout << endl;
cout << "Old BTree: " << endl;
for(int i=0; i<5; i++)
{
TreeNode<int>* node = bt.find(a[i]);
while( node )
{
cout << node->value << " ";
node = node->parent;
}
cout << endl;
}
cout << endl;
cout << "bt == *btClone : " << (bt == *btClone) << endl;
return 0;
}
執行結果為:
Clone:
8 4 2 1
9 4 2 1
10 5 2 1
6 3 1
7 3 1
Old BTree:
8 4 2 1
9 4 2 1
10 5 2 1
6 3 1
7 3 1
bt == *btClone : 1
二叉樹的相加操作:
SharedPointer< BTree<T> > add(const BTree<T>& btree) const
- 將當前二叉樹與引數 btree 中的資料元素在對應位置處相加
- 返回值 ( 相加的結果 ) 為堆空間中的一棵新二叉樹
二叉樹的加法:
- 定義功能:add(Ih, rh)
- 將 Ih 為根結點的二叉樹與 rh 為根結點的二叉樹相加
在BTree.h中實現二叉樹的相加操作:
protected:
BTreeNode<T>* add(BTreeNode<T>* lh, BTreeNode<T>* rh) const
{
BTreeNode<T>* ret = NULL;
if( (lh == NULL) && (rh != NULL) )
{
ret = clone(rh);
}
else if( (lh != NULL) && (rh == NULL) )
{
ret = clone(lh);
}
else if( (lh != NULL) && (rh != NULL) )
{
ret = BTreeNode<T>::NewNode();
if( ret != NULL )
{
ret->value = lh->value + rh->value;
ret->left = add(lh->left, rh->left);
ret->right = add(lh->right, rh->right);
if( ret->left != NULL )
{
ret->left->parent = ret;
}
if( ret->right != NULL )
{
ret->right->parent = ret;
}
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new node ...");
}
}
return ret;
}
public:
SharedPointer< BTree<T> > add(const BTree<T>& btree) const
{
BTree<T>* ret = new BTree<T>();
if( ret != NULL )
{
ret->m_root = add(root(), btree.root());
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create new tree ...");
}
return ret;
}
mian.cpp測試:
#include <iostream>
#include "BTree.h"
using namespace std;
using namespace StLib;
int main()
{
BTree<int> bt;
BTreeNode<int>* n = NULL;
bt.insert(1, NULL);
n = bt.find(1);
bt.insert(2, n);
bt.insert(3, n);
n = bt.find(2);
bt.insert(4, n);
bt.insert(5, n);
n = bt.find(4);
bt.insert(8, n);
bt.insert(9, n);
n = bt.find(5);
bt.insert(10, n);
n = bt.find(3);
bt.insert(6, n);
bt.insert(7, n);
BTree<int> nbt;
nbt.insert(0, NULL);
n = nbt.find(0);
nbt.insert(6, n);
nbt.insert(2, n);
n = nbt.find(2);
nbt.insert(7, n);
nbt.insert(8, n);
SharedPointer< BTree<int> > r = bt.add(nbt);
int a[] = {8, 9, 10, 13, 5};
cout << "Add result: " << endl;
for(int i=0; i<5; i++)
{
TreeNode<int>* node = r->find(a[i]);
while( node )
{
cout << node->value << " ";
node = node->parent;
}
cout << endl;
}
cout << endl;
SharedPointer< Array<int> > tr = r->traversal(PreOrder);
cout << "先序遍歷:" << endl;
for(int i=0; i<(*tr).length(); i++)
{
cout << (*tr)[i] << " ";
}
cout << endl;
return 0;
}
執行結果為:
Add result:
8 1
9 4 8 1
10 5 8 1
13 5 1
5 8 1
先序遍歷:
1 8 4 8 9 5 10 5 13 15
2.二叉樹的線索化實現
什麼是線索化二叉樹?
- 將二叉樹轉換為雙向連結串列的過程 ( 非線性 → 線性 )
- 能夠反映某種二叉樹的遍歷次序 ( 結點的先後訪問次序 )
- 利用結點的 right 指標指向遍歷中的後繼結點
- 利用結點的 left 指標指向遍歷中的前驅結點
如何對二叉樹進行線索化?
思維過程:
二叉樹的線索化:
本節目標:
- 新增功能函式
traversal(order, queue)
- 新增遍歷方式
BTTraversal::LevelOrder
- 新增公有函式
BTreeNode<T>* thread(BTTraversal order)
- 消除遍歷和線索化的程式碼冗餘 ( 程式碼重構 )
層次遍歷演算法小結:
- 將根結點壓入佇列中
- 訪問隊頭元素指向的二叉樹結點
- 隊頭元素彈出,將隊頭元素的孩子壓入佇列中
- 判斷佇列是否為空 ( 非空:轉 2,空:結束 )
層次遍歷演算法示例:
函式介面設計:
BTreeNode<T>* thread(BTTraversal order)
- 根據引數 order 選擇線索化的次序 ( 先序,中序,後序,層次 )
- 返回值線索化之後指向連結串列首結點的指標
- 線索化執行結束之後對應的二叉樹變為空樹
線索化流程:
佇列中結點的連線演算法 [ connect(queue) ]:
列舉中加入層次遍歷:
enum BTTraversal
{
PreOrder,
InOrder,
PostOrder,
LevelOrder
};
二叉樹的線索化:
protected:
void levelOrderTraversal(BTreeNode<T>* node, LinkQueue<BTreeNode<T>*>& queue) // 層次遍歷
{
if( node != NULL )
{
LinkQueue<BTreeNode<T>*> tmp;
tmp.add(node);
while( tmp.length() > 0 )
{
BTreeNode<T>* n = tmp.front();
if( n->left != NULL )
{
tmp.add(n->left);
}
if( n->right != NULL )
{
tmp.add(n->right);
}
tmp.remove();
queue.add(n);
}
}
}
void traversal(BTTraversal order, LinkQueue<BTreeNode<T>*>& queue)
{
switch (order)
{
case PreOrder:
preOrderTraversal(root(), queue);
break;
case InOrder:
inOrderTraversal(root(), queue);
break;
case PostOrder:
postOrderTraversal(root(), queue);
break;
case LevelOrder:
levelOrderTraversal(root(), queue);
break;
default:
THROW_EXCEPTION(InvalidParameterException, "Parameter order is invalid ...");
break;
}
}
BTreeNode<T>* connect(LinkQueue<BTreeNode<T>*>& queue)
{
BTreeNode<T>* ret = NULL;
if( queue.length() > 0 )
{
ret = queue.front();
BTreeNode<T>* slider = queue.front();
queue.remove();
slider->left = NULL;
while( queue.length() > 0 )
{
slider->right = queue.front();
queue.front()->left = slider;
slider = queue.front();
queue.remove();
}
slider->right = NULL;
}
return ret;
}
public:
SharedPointer< Array<T> > traversal(BTTraversal order)
{
DynamicArray<T>* ret = NULL;
LinkQueue<BTreeNode<T>*> queue;
traversal(order, queue);
ret = new DynamicArray<T>(queue.length());
if( ret != NULL )
{
for(int i=0; i<ret->length(); i++, queue.remove())
{
ret->set(i, queue.front()->value);
}
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException, "No memory to create return array ...");
}
return ret;
}
BTreeNode<T>* thread(BTTraversal order)
{
BTreeNode<T>* ret = NULL;
LinkQueue<BTreeNode<T>*> queue;
traversal(order, queue);
ret = connect(queue);
this->m_root = NULL;
m_queue.clear();
return ret;
}
main.cpp測試:
#include <iostream>
#include "BTree.h"
using namespace std;
using namespace StLib;
int main()
{
BTree<int> bt;
BTreeNode<int>* n = NULL;
bt.insert(1, NULL);
n = bt.find(1);
bt.insert(2, n);
bt.insert(3, n);
n = bt.find(2);
bt.insert(4, n);
bt.insert(5, n);
n = bt.find(4);
bt.insert(8, n);
bt.insert(9, n);
n = bt.find(5);
bt.insert(10, n);
n = bt.find(3);
bt.insert(6, n);
bt.insert(7, n);
SharedPointer< Array<int> > tr = bt.traversal(LevelOrder);
for(int i=0; i<(*tr).length(); i++)
{
cout << (*tr)[i] << " ";
}
cout << endl;
BTreeNode<int>* head = bt.thread(LevelOrder);
while( head != NULL )
{
cout << head->value << " ";
head = head->right;
}
cout << endl;
return 0;
}
執行結果為:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
3.二叉樹的經典面試題分析
準備程式碼:
#include <iostream>
#include "BTree.h"
using namespace std;
using namespace StLib;
template < typename T >
BTreeNode<T>* createTree()
{
static BTreeNode<int> ns[9];
for(int i=0; i<9; i++)
{
ns[i].value = i;
ns[i].parent = NULL;
ns[i].left = NULL;
ns[i].right = NULL;
}
ns[0].left = &ns[1];
ns[0].right = &ns[2];
ns[1].parent = &ns[0];
ns[2].parent = &ns[0];
ns[1].left = &ns[3];
ns[1].right = NULL;
ns[3].parent = &ns[1];
ns[2].left = &ns[4];
ns[2].right = &ns[5];
ns[4].parent = &ns[2];
ns[5].parent = &ns[2];
ns[3].left = NULL;
ns[3].right = &ns[6];
ns[6].parent = &ns[3];
ns[4].left = &ns[7];
ns[4].right = NULL;
ns[7].parent = &ns[4];
ns[5].left = &ns[8];
ns[5].right = NULL;
ns[8].parent = &ns[5];
return ns;
}
template < typename T >
void printInOrder(BTreeNode<T>* node)
{
if( node != NULL )
{
printInOrder(node->left);
cout << node->value <<" ";
printInOrder(node->right);
}
}
template < typename T >
void printDualList(BTreeNode<T>* node)
{
BTreeNode<T>* g = node;
cout << "head -> tail: " << endl;
while( node != NULL )
{
cout << node->value << " ";
g = node;
node = node->right;
}
cout << endl;
cout << "tail -> head: " << endl;
while( g != NULL )
{
cout << g->value << " ";
g = g->left;
}
cout << endl;
}
int main()
{
BTreeNode<int>* ns = createTree<int>();
printInOrder(ns);
cout << endl;
return 0;
}
執行結果為:
3 6 1 0 7 4 2 8 5
3.1 單度結點刪除
面試題一:
- 單度結點刪除
- 編寫一個函式用於刪除二叉樹中的所有單度結點
- 要求:結點刪除後,其唯一的子結點替代它的位置
結點中包含指向父結點的指標:
- 定義功能:delOdd1(node)
- 刪除 node 為根結點的二叉樹中的單度結點
單度結點刪除(結點中包含指向父結點的指標):
template < typename T >
BTreeNode<T>* delOdd1(BTreeNode<T>* node)
{
BTreeNode<T>* ret = NULL;
if( node != NULL )
{
if(((node->left != NULL) && (node->right == NULL)) ||
((node->left == NULL) && (node->right != NULL)) )
{
BTreeNode<T>* parent = dynamic_cast<BTreeNode<T>*>(node->parent);
BTreeNode<T>* node_child = (node->left != NULL) ? node->left : node->right;
if( parent != NULL )
{
BTreeNode<T>*& parent_child = (parent->left == node) ? parent->left : parent->right;
parent_child = node_child;
node_child->parent = parent;
}
else
{
node_child->parent = NULL;
}
if( node->flag() )
{
delete node;
}
ret = delOdd1(node_child);
}
else
{
delOdd1(node->left);
delOdd1(node->right);
ret = node;
}
}
return ret;
}
int main()
{
BTreeNode<int>* ns = createTree<int>();
printInOrder(ns);
cout << endl;
ns = delOdd1(ns);
printInOrder(ns);
cout << endl;
int a[] = {6, 7, 8};
for(int i=0; i<3; i++)
{
TreeNode<int>* n = ns + a[i];
while( n != NULL )
{
cout << n->value << " ";
n = n->parent;
}
cout << endl;
}
return 0;
}
執行結果為:
3 6 1 0 7 4 2 8 5
6 0 7 2 8
6 0
7 2 0
8 2 0
結點中只包含左右孩子指標:
- 定義功能:delOdd2(node) // node為結點指標的引用
- 刪除 node 為根結點的二叉樹中的單度結點
單度結點刪除(結點中只包含左右孩子指標):
template < typename T >
void delOdd2(BTreeNode<T>*& node)
{
if( node != NULL )
{
if(((node->left != NULL) && (node->right == NULL)) ||
((node->left == NULL) && (node->right != NULL)) )
{
BTreeNode<T>* node_child = (node->left != NULL) ? node->left : node->right;
if( node->flag() )
{
delete node;
}
node = node_child;
delOdd2(node);
}
else
{
delOdd2(node->left);
delOdd2(node->right);
}
}
}
int main()
{
BTreeNode<int>* ns = createTree<int>();
printInOrder(ns);
cout << endl;
delOdd2(ns);
printInOrder(ns);
cout << endl;
return 0;
}
執行結果為:
3 6 1 0 7 4 2 8 5
6 0 7 2 8
3.2 中序線索化二叉樹
面試題二:
- 中序線索化二叉樹
- 編寫一個函式用於中序線索化二叉樹
- 要求:不允許使用其它資料結構
解法一:在中序遍歷的同時進行線索化
- 思路:
- 使用輔助指標,在中序遍歷時指向當前結點的前驅結點
- 訪問當前結點時,連線與前驅結點的先後次序
定義功能:inOrderThread(node, pre)
- node:根結點,也是中序訪問的結點
- pre:為中序遍歷時的前驅結點指標
中序線索化二叉樹:
template < typename T >
void inOrderThread(BTreeNode<T>* node, BTreeNode<T>*& pre)
{
if( node != NULL )
{
inOrderThread(node->left, pre);
node->left = pre;
if( pre != NULL )
{
pre->right = node;
}
pre = node;
inOrderThread(node->right, pre);
}
}
template < typename T >
BTreeNode<T>* inOrderThread1(BTreeNode<T>* node)
{
BTreeNode<T>* pre = NULL;
inOrderThread(node, pre);
while( (node != NULL) && (node->left != NULL) )
{
node = node->left;
}
return node;
}
int main()
{
BTreeNode<int>* ns = createTree<int>();
printInOrder(ns);
cout << endl;
delOdd2(ns);
printInOrder(ns);
cout << endl;
ns = inOrderThread1(ns);
printDualList(ns);
return 0;
}
執行結果為:
3 6 1 0 7 4 2 8 5
6 0 7 2 8
head -> tail:
6 0 7 2 8
tail -> head:
8 2 7 0 6
解法二:中序遍歷的結點次序正好是結點的水平次序
- 思路:
- 使用輔助指標,指向轉換後雙向連結串列的頭結點和尾結點
- 根結點與左右子樹轉換的雙向連結串列連線,成為完整雙向連結串列
定義功能:inOrderThread(node, head, tail)
- node:根結點,也是中序訪問的結點
- head:轉換成功後指向雙向連結串列的首結點
- tail:轉換成功後指向雙鏈表的尾結點
中序線索化二叉樹:
template < typename T >
void inOrderThread(BTreeNode<T>* node, BTreeNode<T>*& head, BTreeNode<T>*& tail)
{
if( node != NULL )
{
BTreeNode<T>* h = NULL;
BTreeNode<T>* t = NULL;
inOrderThread(node->left, h, t);
node->left = t;
if( t != NULL )
{
t->right = node;
}
head = (h != NULL) ? h : node;
h = NULL;
t = NULL;
inOrderThread(node->right, h, t);
node->right = h;
if( h != NULL )
{
h->left = node;
}
tail = (t != NULL) ? t : node;
}
}
template < typename T >
BTreeNode<T>* inOrderThread2(BTreeNode<T>* node)
{
BTreeNode<T>* head = NULL;
BTreeNode<T>* tail = NULL;
inOrderThread(node, head, tail);
return head;
}
int main()
{
BTreeNode<int>* ns = createTree<int>();
printInOrder(ns);
cout << endl;
ns = inOrderThread2(ns);
printDualList(ns);
return 0;
}
執行結果為:
3 6 1 0 7 4 2 8 5
head -> tail:
3 6 1 0 7 4 2 8 5
tail -> head:
5 8 2 4 7 0 1 6 3
4.小結
- 比較操作判斷兩棵二叉樹中的資料元素是否對應相等
- 克隆操作將當前二叉樹在堆空間中進行復制
- 相加操作將兩棵二叉樹中的資料元素在對應位置處相加
- 相加操作的結果儲存在堆空間的一棵二叉樹中
- 線索化是將二叉樹轉換為雙向連結串列的過程
- 線索化之後結點間的先後次序符合某種遍歷次序
- 線索化操作將破壞原二叉樹結點間的父子關係
- 線索化之後二叉樹將不再管理結點的生命期