二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历)
轉(zhuǎn)載:二叉樹遍歷(前序、中序、后序、層次、深度優(yōu)先、廣度優(yōu)先遍歷)
【數(shù)據(jù)結(jié)構(gòu)和算法】全面剖析樹的各類遍歷方法
二叉樹
概念
二叉樹是一種非常重要的數(shù)據(jù)結(jié)構(gòu),非常多其他數(shù)據(jù)結(jié)構(gòu)都是基于二叉樹的基礎(chǔ)演變而來的。對于二叉樹,有深度遍歷和廣度遍歷,深度遍歷有前序、中序以及后序三種遍歷方法,廣度遍歷即我們尋常所說的層次遍歷。由于樹的定義本身就是遞歸定義,因此採用遞歸的方法去實現(xiàn)樹的三種遍歷不僅easy理解并且代碼非常簡潔,而對于廣度遍歷來說,須要其他數(shù)據(jù)結(jié)構(gòu)的支撐。比方堆了。所以。對于一段代碼來說,可讀性有時候要比代碼本身的效率要重要的多。
四種基本的遍歷思想
前序遍歷:根結(jié)點 ---> 左子樹 ---> 右子樹
中序遍歷:左子樹--->根結(jié)點---> 右子樹
后序遍歷:左子樹 ---> 右子樹---> 根結(jié)點
層次遍歷:僅僅需按層次遍歷就可以
比如。求以下二叉樹的各種遍歷
前序遍歷:1 2 4 5 7 8 3 6
中序遍歷:4 2 7 5 8 1 3 6
后序遍歷:4 7 8 5 2 6 3 1
層次遍歷:1 2 3 4 5 6 7 8
一、前序遍歷
1)依據(jù)上文提到的遍歷思路:根結(jié)點 ---> 左子樹 ---> 右子樹,非常easy寫出遞歸版本號:
public void preOrderTraverse1(TreeNode root) {
if (root != null) {
System.out.print(root.val+" ");
preOrderTraverse1(root.left);
preOrderTraverse1(root.right);
}
}
2)如今討論非遞歸的版本號:
依據(jù)前序遍歷的順序,優(yōu)先訪問根結(jié)點。然后在訪問左子樹和右子樹。所以。對于隨意結(jié)點node。第一部分即直接訪問之,之后在推斷左子樹是否為空,不為空時即反復(fù)上面的步驟,直到其為空。若為空。則須要訪問右子樹。注意。在訪問過左孩子之后。須要反過來訪問其右孩子。所以,須要棧這樣的數(shù)據(jù)結(jié)構(gòu)的支持。對于隨意一個結(jié)點node,詳細(xì)過程例如以下:
a)訪問之,并把結(jié)點node入棧。當(dāng)前結(jié)點置為左孩子;
b)推斷結(jié)點node是否為空,若為空。則取出棧頂結(jié)點并出棧,將右孩子置為當(dāng)前結(jié)點;否則反復(fù)a)步直到當(dāng)前結(jié)點為空或者棧為空(能夠發(fā)現(xiàn)棧中的結(jié)點就是為了訪問右孩子才存儲的)
代碼例如以下:
public void preOrderTraverse2(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode pNode = root;
while (pNode != null || !stack.isEmpty()) {
if (pNode != null) {
System.out.print(pNode.val+" ");
stack.push(pNode);
pNode = pNode.left;
} else { //pNode == null && !stack.isEmpty()
TreeNode node = stack.pop();
pNode = node.right;
}
}
}
二、中序遍歷
1)依據(jù)上文提到的遍歷思路:左子樹--->根結(jié)點---> 右子樹,非常easy寫出遞歸版本號:
public void inOrderTraverse1(TreeNode root) {
if (root != null) {
inOrderTraverse1(root.left);
System.out.print(root.val+" ");
inOrderTraverse1(root.right);
}
}
2)非遞歸實現(xiàn),有了上面前序的解釋,中序也就比較簡單了。同樣的道理。僅僅只是訪問的順序移到出棧時。代碼例如以下:
public void inOrderTraverse2(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode pNode = root;
while (pNode != null || !stack.isEmpty()) {
if (pNode != null) {
stack.push(pNode);
pNode = pNode.left;
} else { //pNode == null && !stack.isEmpty()
TreeNode node = stack.pop();
System.out.print(node.val+" ");
pNode = node.right;
}
}
}
三、后序遍歷
1)依據(jù)上文提到的遍歷思路:左子樹 ---> 右子樹---> 根結(jié)點。非常easy寫出遞歸版本號:
public void postOrderTraverse1(TreeNode root) {
if (root != null) {
postOrderTraverse1(root.left);
postOrderTraverse1(root.right);
System.out.print(root.val+" ");
}
}
2)
后序遍歷的非遞歸實現(xiàn)是三種遍歷方式中最難的一種。由于在后序遍歷中,要保證左孩子和右孩子都已被訪問而且左孩子在右孩子前訪問才干訪問根結(jié)點,這就為流程的控制帶來了難題。以下介紹兩種思路。
第一種思路:對于任一結(jié)點P,將其入棧,然后沿其左子樹一直往下搜索。直到搜索到?jīng)]有左孩子的結(jié)點,此時該結(jié)點出如今棧頂,可是此時不能將其出棧并訪問,因此其右孩子還為被訪問。
所以接下來依照同樣的規(guī)則對其右子樹進行同樣的處理,當(dāng)訪問完其右孩子時。該結(jié)點又出如今棧頂,此時能夠?qū)⑵涑鰲2⒃L問。這樣就保證了正確的訪問順序。能夠看出,在這個過程中,每一個結(jié)點都兩次出如今棧頂,僅僅有在第二次出如今棧頂時,才干訪問它。因此須要多設(shè)置一個變量標(biāo)識該結(jié)點是否是第一次出如今棧頂。
void postOrder2(BinTree *root) //非遞歸后序遍歷
{
stack<BTNode*> s;
BinTree *p=root;
BTNode *temp;
while(p!=NULL||!s.empty())
{
while(p!=NULL) //沿左子樹一直往下搜索。直至出現(xiàn)沒有左子樹的結(jié)點
{
BTNode *btn=(BTNode *)malloc(sizeof(BTNode));
btn->btnode=p;
btn->isFirst=true;
s.push(btn);
p=p->lchild;
}
if(!s.empty())
{
temp=s.top();
s.pop();
if(temp->isFirst==true) //表示是第一次出如今棧頂
{
temp->isFirst=false;
s.push(temp);
p=temp->btnode->rchild;
}
else //第二次出如今棧頂
{
cout<<temp->btnode->data<<" ";
p=NULL;
}
}
}
}
另外一種思路:要保證根結(jié)點在左孩子和右孩子訪問之后才干訪問,因此對于任一結(jié)點P。先將其入棧。假設(shè)P不存在左孩子和右孩子。則能夠直接訪問它;或者P存在左孩子或者右孩子。可是其左孩子和右孩子都已被訪問過了。則相同能夠直接訪問該結(jié)點。若非上述兩種情況。則將P的右孩子和左孩子依次入棧。這樣就保證了每次取棧頂元素的時候,左孩子在右孩子前面被訪問。左孩子和右孩子都在根結(jié)點前面被訪問。
void postOrder3(BinTree *root) //非遞歸后序遍歷
{
stack<BinTree*> s;
BinTree *cur; //當(dāng)前結(jié)點
BinTree *pre=NULL; //前一次訪問的結(jié)點
s.push(root);
while(!s.empty())
{
cur=s.top();
if((cur->lchild==NULL&&cur->rchild==NULL)||
(pre!=NULL&&(pre==cur->lchild||pre==cur->rchild)))
{
cout<<cur->data<<" "; //假設(shè)當(dāng)前結(jié)點沒有孩子結(jié)點或者孩子節(jié)點都已被訪問過
s.pop();
pre=cur;
}
else
{
if(cur->rchild!=NULL)
s.push(cur->rchild);
if(cur->lchild!=NULL)
s.push(cur->lchild);
}
}
}
四、層次遍歷
層次遍歷的代碼比較簡單。僅僅須要一個隊列就可以。先在隊列中增加根結(jié)點。之后對于隨意一個結(jié)點來說。在其出隊列的時候,訪問之。同一時候假設(shè)左孩子和右孩子有不為空的。入隊列。代碼例如以下:
public void levelTraverse(TreeNode root) {
if (root == null) {
return;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
System.out.print(node.val+" ");
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
五、深度優(yōu)先遍歷
事實上深度遍歷就是上面的前序、中序和后序。可是為了保證與廣度優(yōu)先遍歷相照顧,也寫在這。代碼也比較好理解,事實上就是前序遍歷,代碼例如以下:
public void depthOrderTraverse(TreeNode root) {
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
System.out.print(node.val+" ");
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
六、后序遍歷的簡單思路
前序遍歷的非遞歸版本,訪問順序依次是根節(jié)點->左子樹->右子樹,如果將壓棧順序改動一下,可以很容易得到根節(jié)點->右子樹->左子樹,觀察這個順序和后序遍歷左子樹->右子樹->根節(jié)點正好反序。
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ret;
if(root==NULL) return ret;
stack<TreeNode*> st;
st.push(root);
while(!st.empty())
{
TreeNode* tmp = st.top();
ret.push_back(tmp->val);//先訪問根節(jié)點
st.pop();
if(tmp->left!=NULL) st.push(tmp->left);//再訪問左子樹
if(tmp->right!=NULL) st.push(tmp->right);//最后訪問右子樹
}
reverse(ret.begin(),ret.end());//將結(jié)果反序輸出
return ret;
}
public void postOrderTraverse(Node node) {
if (node == null) {
return;
}
Node temp = node;
Stack<Node> stack = new Stack<>();
List<Node> nodes = new ArrayList<>();
while (temp != null || !stack.isEmpty()) {
if (temp != null) {
nodes.add(temp);
stack.push(temp);
temp = temp.rightChild;
} else {
Node popNode = stack.pop();
temp = popNode.leftChild;
}
}
for (int i = nodes.size() - 1; i >= 0; i--) {
System.out.print(nodes.get(i).data + " ");
}
}
總結(jié)
以上是生活随笔為你收集整理的二叉树遍历(前序、中序、后序、层次、深度优先、广度优先遍历)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 牛顿法、拟牛顿法、共轭梯度法
- 下一篇: 怎么创建具有真实纹理的CG场景岩石?