一、二叉樹的遍歷:按照某種順序訪問二叉樹中的每個結點,並使每個結點被訪問一次且只被訪問一次。
二、訪問怎樣理解?
就是對結點的增加、刪除、查閱、修改或加工。(我們先簡化爲對結點數據域值的輸出)
三、遍歷的作用:將非線性結構變成線性結構
四、遍歷種類:
1、前序遍歷/先根(序)遍歷/先序遍歷
2、中序遍歷/中根(序)遍歷/中序遍歷
3、後序遍歷/後根(序)遍歷/後序遍歷
4、層次序遍歷
五、前序遍歷
1、遞歸算法:
template<class T>
void BinaryTree<T>::preOrder(BinTreeNode<T>* subTree, void(*visit)(BinTreeNode<T>* p))
{
if (subTree != nullptr)
{
visit(subTree);
preOrder(subTree->leftChild, visit);
preOrder(subTree->rightChild, visit);
}
}
2、非遞歸算法:
<1> 思路1:(如果二叉樹非空,則)
(1)將二叉樹的根結點作爲當前結點;
(2)若當前結點非空,則先訪問該節點,並將該結點進棧,再將其左孩子結點作爲當前結點,重複步驟(2),直到當前結點爲空爲止;
(3)若棧非空,則將棧頂結點出棧,並將當前結點的右孩子結點作爲當前結點;
(4)重複步驟(2)、(3),直到棧爲空且當前結點爲空爲止。
<2>實現1:
template <class T>
void BinaryTree<T>::preOrder(void(*visit)(BinTreeNode<T>*p))
{
stack<BinTreeNode<T>*> s;
BinTreeNode<T>* t = root;
while (!s.empty() || t != 0)
{
while (t != nullptr)
{
visit(t);
s.push(t);
t = t->leftChild;
}
if (!s.isEmpty())
{
t = s.top();
s.pop();
t = t->rightChild;
}
}
}
<3>思路2:(如果二叉樹非空,則)
(1)將二叉樹的根結點進棧;
(2)若棧非空,則將棧頂結點出棧並對其實施訪問;
(3)若該結點有右子樹,則將右子樹的根結點進棧;
(4)若該結點有左子樹,則將左子樹的根結點進棧;
(5)重複步驟(2),(3),(4),直到棧爲空爲止。
<4>實現2:
template<class T>
void BinaryTree<T>::preOrder(void(*visit)(BinTreeNode<T>* p))
{
stack<BinTreeNode<T>*> s;
t = root;
s.push(root);
while (!s.empty())
{
t = s.top();
s.pop();
visit(t);
if (t->rightChild != nullptr) s.push(t->rightChild);
if (t->leftChild != nullptr) s.push(t->leftChild);
}
}
六、中序遍歷
1、遞歸算法:
template<class T>
void BinaryTree<T>::inOrder(BinTreeNode<T>* subTree, void(*visit)(BinTreeNode<T>* p))
{
if (subTree != nullptr)
{
inOrder(subTree->leftChild, visit);
visit(subTree);
inOrder(subTree->rightChild, visit);
}
}
2、非遞歸算法:
<1>思路:(若二叉樹非空,則)
(1)將二叉樹的根結點作爲當前結點;
(2)若當前結點非空,則該結點進棧並將其左孩子結點作爲當前結點,重複步驟(2),直到當前結點爲空爲止;
(3)若棧非空,則將棧頂結點出棧並作爲當前結點,接着訪問當前結點,再將當前結點的右孩子結點作爲當前結點;
(4)重複步驟(2),(3),直到棧爲空且當前結點爲空爲止。
<2>實現:
template<class T>
void BinaryTree<T>::inOrder(void(*visit)(BinTreeNode<T>* p))
{
stack<BinTreeNode<T>*> s;
BinTreeNode<T>* t = root;
while (!s.empty()||t!=nullptr)
{
while (t != nullptr)
{
s.push(t);
t = t->leftChild;
}
if (!s.empty())
{
t = s.top();
s.pop();
visit(t);
t = t->rightChild;
}
}
}
七、後序遍歷
1、遞歸算法:
template<class T>
void BinaryTree<T>::postOrder(BinTreeNode<T>* subTree, void(*visit)(BinTreeNode<T>* p))
{
if (subTree != nullptr)
{
postOrder(subTree->leftChild, visit);
postOrder(subTree->rightChild, visit);
visit(subTree);
}
}
2、非遞歸算法:
<1>思路:每個結點需要進棧,出棧各兩次。爲了區別同一個結點的兩次進棧,需要設置一個標誌flag。
flag = 0,第一次進棧,表示該結點出棧後不能訪問;
flag = 1,第二次進棧,表示該結點出棧後可以訪問。
<2>實現:
template<class T>
void BinaryTree<T>::postOrder(void(*visit)(BinTreeNode<T>* p))
{
int flag;
stack<BinTreeNode<T>*> s1;
stack<int> s2;
BinTreeNode<T>* t = root;
while (!s1.empty() || t != nullptr)
{
while (t != nullptr)//"1"
{
s1.push(t);
s2.push(0);
t = t->leftChild;
}
if (!s.empty())
{
t = s1.top();
s1.pop();
flag = s2.top();
s2.pop();
if (flag == 1)
{
visit(t);
t = nullptr; //目的:跳過“1”
}
else
{
s1.push(t);
s2.push(1);
t = t->rightChild;
}
}
}
}
八、以上三種非遞歸遍歷算法的統一
1、思路:一般從代碼實現最複雜的非遞歸遍歷算法入手,然後加以改動,即對後序遍歷的非遞歸算法進行改動即可統一三種非遞歸算法;
2、實現:(只需改動後序遍歷的非遞歸算法代碼中語句visit(t)的位置)
template<class T>
void BinaryTree<T>::postOrder(void(*visit)(BinTreeNode<T>* p))
{
int flag;
stack<BinTreeNode<T>*> s1;
stack<int> s2;
BinTreeNode<T>* t = root;
while (!s1.empty() || t != nullptr)
{
while (t != nullptr)//"1"
{
//visit(t);(前序遍歷)
s1.push(t);
s2.push(0);
t = t->leftChild;
}
if (!s.empty())
{
t = s1.top();
s1.pop();
flag = s2.top();
s2.pop();
if (flag == 1)
{
//visit(t);(後序遍歷)
t = nullptr; //跳過“1”
}
else
{
//visit(t);(中序遍歷)
s1.push(t);
s2.push(1);
t = t->rightChild;
}
}
}
}
九、層次序遍歷
1、思路:
(1)遍歷之前先將二叉樹的根結點存入隊列中;
(2)然後依次從隊列中取出隊頭結點,每取出一個結點,都先訪問該結點;
(3)接着分別檢查該結點是否存在左、右孩子,若存在,則先後入列;
(4)如此反覆,直到隊列爲空爲止。
2、實現:
template<class T>
void BinaryTree<T>::levelOrder(void(*visit)(BinTreeNode<T>* p))
{
queue<BinTreeNode<T>*> q;
BinTreeNode<T>* t = root;
if (t != nullptr) q.push(t);
while (!q.empty())
{
t = q.front();
q.pop();
visit(t);
if (t->leftChild != nullptr) q.push(t->leftChild);
if (t->rightChild != nullptr) q.push(t->rightChild);
}
}
<PS>:
以上算法實現代碼中使用的是STL中的stack,便於直接使用。
以上材料的大部分內容來自本人的數據結構老師-黃煜廉老師。