对于一种数据结构而言,遍历是常见操做。二叉树是一种基本的数据结构,是一种每一个节点的儿子数目都很少于2的树。二叉树的节点声明以下:html
1 typedef struct TreeNode *PtrToNode; 2 typedef struct TreeNode *BinTree; 3
4 struct TreeNode 5 { 6 int Data; //为简单起见,不妨假设树节点的元素为int型
7 BinTree Left; 8 BinTree Right; 9 };
二叉树的遍历主要有先序遍历,中序遍历,后序遍历,层序遍历四种方式,下面一一介绍。算法
1. 先序遍历数据结构
在先序遍历中,对节点的访问工做是在它的左右儿子被访问以前进行的。换言之,先序遍历访问节点的顺序是根节点-左儿子-右儿子。因为树能够经过递归来定义,因此树的常见操做用递归实现经常是方便清晰的。递归实现的代码以下:ide
1 void PreOrderTraversal(BinTree BT) 2 { 3 if( BT ) 4 { 5 printf(“%d\n”, BT->Data); //对节点作些访问好比打印
6 PreOrderTraversal(BT->Left); //访问左儿子
7 PreOrderTraversal(BT->Right); //访问右儿子
8 } 9 }
由递归代码能够看出,该递归为尾递归(尾递归即递归形式在函数末尾或者说在函数即将返回前)。尾递归的递归调用须要用栈存储调用的信息,当数据规模较大时容易越出栈空间。虽然如今大部分的编译器可以自动去除尾递归,可是即便如此,咱们不妨本身去除。非递归先序遍历算法基本思路:使用堆栈函数
a. 遇到一个节点,访问它,而后把它压栈,并去遍历它的左子树;url
b. 当左子树遍历结束后,从栈顶弹出该节点并将其指向右儿子,继续a步骤;spa
c. 当全部节点访问完即最后访问的树节点为空且栈空时,中止。.net
实现代码以下:设计
void PreOrderTraversal(BinTree BT) { BinTree T = BT; Stack S = CreatStack(MAX_SIZE); //建立并初始化堆栈S
while(T || !IsEmpty(S)) { while(T) //一直向左并将沿途节点访问(打印)后压入堆栈
{ printf("%d\n", T->Data); Push(S, T); T = T->Left; } if (!IsEmpty(S)) { T = Pop(S); //节点弹出堆栈
T = T->Right; //转向右子树
} }
}
中序遍历的遍历路径与先序遍历彻底同样。其实现的思路也与先序遍历很是类似。其主要的不一样点是访问节点顺序不一样:中序遍历是访问完全部左儿子后再访问根节点,最后访问右儿子,即为左儿子-根节点-右儿子。指针
递归实现的代码以下:
void InOrderTraversal(BinTree BT) { if(BT) { InOrderTraversal(BT->Left); printf("%d\n", BT->Data); InOrderTraversal(BT->Right); } }
非递归辅助栈实现代码以下:
void InOrderTraversal(BinTree BT) { BinTree T = BT; Stack S = CreatStack(MaxSize); //建立并初始化堆栈S
while(T || !IsEmpty(S))
{ while(T) //一直向左并将沿途节点压入堆栈
{ Push(S,T); T = T->Left; } if(!IsEmpty(S)) { T = Pop(S); //节点弹出堆栈
printf("%d\n", T->Data); //(访问) 打印结点
T = T->Right; //转向右子树
}
} }
非递归不用辅助栈实现中序遍历:
试设计一个非递归算法,按中根顺序遍历非线索二叉树,但不得用任何辅助栈。在执行算法期间,容许改变左孩子指针和右孩子指针的值。
算法:右线索化+回溯
代码以下(关于二叉树的创建请戳我):
/* 输入:ABDH##I##E##CF#J##G## */ #include <cstdio> typedef struct BTNode* Position; typedef Position BTree; typedef char ElementType; struct BTNode { ElementType data; Position lChild, rChild; }; BTree CreateBTree(void); void Inorder(BTree bt); int main() { BTree bt = CreateBTree(); Inorder(bt); return 0; } void Inorder(BTree bt) { Position p = bt; while (p) { Position pLeft = p->lChild; if (pLeft) { while (pLeft->rChild && pLeft->rChild != p) //找到以p为根结点的树的最右孩子 pLeft = pLeft->rChild; if (pLeft->rChild == NULL) //线索化 { pLeft->rChild = p; p = p->lChild; continue; } else //线索化后已被访问 { pLeft->rChild = NULL; //释放指向根节点(祖先)的指针 } } printf("%c ", p->data); //打印 p = p->rChild; //向上回溯或者转向右子树 } printf("\n"); } BTree CreateBTree() //按照先序序列创建二叉树 { BTree bt = NULL; char ch; scanf("%c", &ch); if (ch != '#') //'#'表明空节点 { bt = new BTNode; bt->data = ch; bt->lChild = CreateBTree(); bt->rChild = CreateBTree(); } return bt; }
运行结果:
参考博客:http://m.blog.csdn.net/blog/Raito__/40618257
后序遍历与中序遍历,先序遍历的路径也彻底同样。主要的不一样点是后序遍历访问节点的顺序是先访问左儿子和右儿子,最后访问节点,即左儿子-右儿子-根节点。
递归实现思路与中序遍历和先序遍历类似,代码以下:
void PostOrderTraversal(BinTree BT) { if (BT) { PostOrderTraversal(BT->Left); PostOrderTraversal(BT->Right); printf("%d\n", BT->Data); } }
后序遍历的非递归实现
思路一:
对于一个节点而言,要实现访问顺序为左儿子-右儿子-根节点,能够利用后进先出的栈,在节点不为空的前提下,依次将根节点,右儿子,左儿子压栈。故咱们须要按照根节点-右儿子-左儿子的顺序遍历树,而咱们已经知道先序遍历的顺序是根节点-左儿子-右儿子,故只需将先序遍历的左右调换并把访问方式打印改成压入另外一个栈便可。最后一块儿打印栈中的元素。代码以下:
void PostOrderTraversal(BinTree BT) { BinTree T = BT; Stack S1 = CreatStack(MAX_SIZE); //建立并初始化堆栈S1
Stack S2 = CreatStack(MAX_SIZE); //建立并初始化堆栈S2
while(T || !IsEmpty(S1)) { while(T) //一直向右并将沿途节点访问(压入S2)后压入堆栈S1
{ Push(S2, T); Push(S1, T); T = T->Right; } if (!IsEmpty(S1)) { T = Pop(S1); //节点弹出堆栈
T = T->Left; //转向左子树
} } while(!IsEmpty(S2)) //访问(打印)S2中元素
{ T = Pop(S2); printf("%d\n", T->Data); } }
思路一的优势是因为利用了先序遍历的思想,代码较简洁,思路较清晰。缺点是须要用一个栈来存储树的全部节点,空间占用较大。
思路二:
要访问一个节点的条件上一个访问的节点是右儿子。咱们能够增长一个变量Prev来判断当前节点Curr的上一个节点与它的关系来执行相应的操做。
代码以下:
void PostOrderTraversal(BinTree BT) { if(BT == NULL) return ; Stack S = CreatStack(MAX_SIZE); BinTree Prev = NULL , Curr = NULL; //初始化
s.push(BT); while(!IsEmpty(S)) { Curr = Top(S); //将栈顶元素赋给Curr
if(Prev == NULL || Prev->Left == Curr || Prev->Right == Curr) //若Prev为NULL或是Curr的父节点
{ if(Curr->Left != NULL) Push(S, Curr->Left); else if(Curr->Right != NULL) Push(S, Curr->Right); } else if(Curr->Left == Prev) //若Prev是Curr的左儿子
{ if(Curr->Right != NULL) Push(S, Curr->Right); } else { printf("%d\n", Curr->Data); //访问当前节点
Pop(S); //访问后弹出
} Prev = Curr; //处理完当前节点后将Curr节点变为Prev节点
} }
二叉树遍历的核心问题是二维结构的线性化。咱们经过节点访问其左右儿子时,存在的问题是访问左儿子后,右儿子怎么访问。所以咱们须要一个存储结构保存暂时不访问的节点。前面三种遍历方式的非递归实现,咱们是经过堆栈来保存。事实上也能够经过队列来保存。
队列实现的基本思路:遍历从根节点开始,首先将根节点入队,而后执行循环:节点出队,访问(访问)根节点,将左儿子入队,将右儿子入队,直到队列为空中止。
这种遍历方式的结果是将二叉树从上到下,从左至右一层一层的遍历,即层序遍历,代码实现以下:
void LevelOrderTraversal(BinTree BT) { BinTree T; Queue Q; //声明一个队列
if (BT == NULL) return; //若是树为空,直接返回
Q = CreatQueue(MAX_SIZE); //建立并初始化队列
AddQ(Q, BT); //将根节点入队
while (!IsEmpty(Q)) { T = DeleteQ(Q); //节点出队
printf("%d\n", T->Data); //访问出队的节点
if (T->Left) AddQ(Q, T->Left); //若左儿子不为空,将其入队
if (T->Right) AddQ(Q, T->Right) //若右儿子不为空,将其入队
} }
理解上述四种二叉树遍历方式后,不妨来小试牛刀:List Leaves, Tree Traversals Again.