本文將記錄自己學習過程中的理解。將按順序介紹先序、中序、及後序的遞歸及非遞歸實現,其實非遞歸是可以在對遞歸的理解上寫出來的。“樹的深度優先遍歷”分爲先、中、後序用的是棧,“樹的廣度優先遍歷”即層次遍歷,用的是隊列,下一篇會講。樹不用判重,就是因爲樹比較特殊,不像是圖,要考慮重複遍歷。
目錄
0.寫在前面
對一個函數而言,其實就可以用棧的方式理解。比如如下一個簡單函數:準備執行這個函數時,其實“我是棧底”這個動作其實是先入棧的,然後是“我是棧頂”這個動作再進棧,因此函數真正執行時,按照出棧的方式,就是先打印“我是棧頂”,再打印“我是棧底”。後面理解遞歸的運行會用到這裏的理解方式
void my_printf()
{
printf("我是棧頂\n");
printf("我是棧底\n");
}
1.生成本文例子中的樹
本文中創建的樹如下:
其先、中、後序的遍歷結果如下
//一棵樹的先、中、後序遍歷結果如下
int pre_value[] = {1,2,3,5,4,6,7,8};
int mid_value[] = {5,3,2,4,1,7,6,8};
int after_value[] = {5,3,4,2,7,8,6,1};
創建樹的代碼,這裏比較簡單直接賦值的形式了,執行完tree就是一顆如上圖的樹
void Create_Tree(TREE_NODE_S* tree)
{
TREE_NODE_S* poll = (TREE_NODE_S*)malloc(8*sizeof(TREE_NODE_S));
int i = 0;
memset(poll,0,8*sizeof(TREE_NODE_S));
for(i = 0; i <= 7;i++)
{
poll[i].data = i+1;
}
memcpy(tree,&poll[0],sizeof(sizeof(TREE_NODE_S)));
tree->leftchild = &poll[1];
tree->rightchild = &poll[5];
poll[1].leftchild = &poll[2];
poll[1].rightchild = &poll[3];
poll[2].leftchild = &poll[4];
poll[5].leftchild = &poll[6];
poll[5].rightchild = &poll[7];
}
2.先序遍歷遞歸方式
代碼如下。
void Pre_Print_Tree(TREE_NODE_S* tree)
{
if(tree == NULL)
return;
printf("%d ",tree->data);
Pre_Print_Tree(tree->leftchild);
Pre_Print_Tree(tree->rightchild);
}
運用本文目錄0處的理解,對着樹看的話,其實簡單。
開始執行時,棧如下(右邊是棧底):打印1,1的左孩2,1的右孩6
1.執行即出棧,打印1
此時棧:1的左孩2,1的右孩6
2.遍歷1的左孩2
此時棧:打印2,2的左孩3,2的右孩4,1的右孩6
3.打印2
此時棧:2的左孩3,2的右孩4,1的右孩6
如上是詳細版的過程,寫着會很多,如下也貼上簡寫的其實也很繁雜,可見遞歸其實效率不高。
右是棧底
1.打印1,會對1的左孩2遍歷,此時“遍歷1的右孩6”的動作入棧
此時棧:遍歷1的右孩6
2.對2遍歷時,打印2,會對2的左孩3遍歷,此時右孩4遍歷的動作入棧
此時棧:遍歷2的右孩4,遍歷1的右孩6
3.對2的左孩3遍歷時,打印3,會對3的左孩5遍歷,此時“遍歷3的右孩子空”入棧
此時棧:遍歷3右孩空,遍歷2的右孩4,遍歷1的右孩6
4.對3的左孩5遍歷,打印5,會對5的左孩空遍歷,此時“遍歷5的右孩空”入棧
此時棧:遍歷5右孩空,遍歷3右孩空,遍歷2的右孩4,遍歷1的右孩6
5.對5的左孩空遍歷,直接返回,並出棧
此時棧:遍歷5右孩空,遍歷3右孩空,遍歷2的右孩4,遍歷1的右孩6
6.對5右孩空遍歷,直接返回
此時棧:遍歷3右孩空,遍歷2的右孩4,遍歷1的右孩6
7.對3右孩空遍歷,直接返回
此時棧:遍歷2的右孩4,遍歷1的右孩6
8.對4遍歷,會對4的左孩空遍歷,打印4,此時“遍歷4右孩空”入棧
此時棧:,遍歷4右孩空,遍歷1的右孩6
9.遍歷4左孩空,直接返回
此時棧:遍歷4右孩空,遍歷1的右孩6
10. 遍歷4左孩空,直接返回
此時棧:遍歷1的右孩6
11.對6遍歷,會對6的左孩 7遍歷,此時“打印6”及“遍歷6右孩8”入棧
此時棧:打印6,遍歷6右孩8
12.對7遍歷,會對7左孩空遍歷,此時“打印7”及“遍歷7右孩空”入棧
此時棧:打印7,遍歷7右孩空,打印6,遍歷6右孩8
13.對7左孩空遍歷,直接返回,開始出棧
此時棧:打印7,遍歷7右孩空,打印6,遍歷6右孩8
14.打印7
此時棧:遍歷7右孩空,打印6,遍歷6右孩8
15.對7右孩空,直接返回,繼續出棧
此時棧:打印6,遍歷6右孩8
16.打印6
此時棧:遍歷6右孩8
17.遍歷8,會對8的左孩空遍歷,“打印8”及“遍歷8右孩空”入棧
此時棧:打印8,遍歷8右孩空
18.遍歷8的左孩空,直接返回,並開始出棧
此時棧:打印8,遍歷8右孩空
19.打印8
此時棧:遍歷8右孩空
20.遍歷8右孩空,直接返回
21.至此,順序:1,2,3,5,4,6,7,8
3.先序的非遞歸方式
代碼如下:
void Pre_Printf_Tree_2_v1(TREE_NODE_S* tree)
{
STACK_S stack = {0};
TREE_NODE_S topnode = {0};
TREE_NODE_S* topnode_p = NULL;
Stack_Init(&stack,50);
//1.頂點入棧
(void)Stack_Pushback(&stack,tree);
while(Stack_Is_Empty(&stack)!=1)
{
//2.對於棧中的元素,出棧就調用操作函數
topnode_p = Stack_GetTop(&stack);//不判斷爲空是因爲下面入棧的節點保證了不是空的,如果是下面註釋的方法那麼就這裏需要判空
printf("%d ",topnode_p->data);
Stack_Pop(&stack);
//3.如果頂點元素有右孩子,右孩子優先入棧,因爲棧的先入後出
if(topnode_p->rightchild != NULL)
{
(void)Stack_Pushback(&stack,topnode_p->rightchild);
}
if(topnode_p->leftchild != NULL)
{
(void)Stack_Pushback(&stack,topnode_p->leftchild);
}
/*if(topnode_p != NULL)
{
(void)Stack_Pushback(&stack,topnode_p->rightchild);
(void)Stack_Pushback(&stack,topnode_p->leftchild);
}*/
}
}
知道遞歸的執行方式,藉助棧結構寫出非遞歸的方式也不是很難。看節點1、2、3,聯繫先序遞歸寫法,可以知道其實出棧即可以打印,同時並先入棧右節點、再入棧左節點。
4.中序遞歸
代碼如下:
void mid_print_tree(TREE_NODE_S* tree)
{
if(tree == NULL)
return;
mid_print_tree(tree->leftchild);
printf("%d ",tree->data);
mid_print_tree(tree->rightchild);
}
同理,也可以知道其的遞歸運行方式
右是棧底
1.首先1開始,會對1的左孩子2進行遍歷,此時“打印1”動作,及對“1的右孩子6遍歷”的動作入棧,後者先壓入。
此時棧:打印1,遍歷6
2.對2繼續,同理,會對2的左孩子3進行遍歷,此時“打印2”動作及對“2的右孩子4遍歷”動作入棧,後者先壓入
此時棧:打印2,遍歷4,打印1,遍歷6
3.對3遍歷,會對3的左孩子5進行遍歷,此時,“打印3”動作及“對3的右孩子(空)遍歷”動作入棧,後者先壓入
此時棧:打印3,遍歷3的右孩空,打印2,遍歷4,打印1,遍歷6
4.對5遍歷,會繼續對5的左孩子空遍歷,此時“打印5”動作及對“5的右孩子(空)遍歷”動作入棧,後者先壓入
此時棧:打印5,遍歷5的右孩空,打印3,遍歷3的右孩空,打印2,遍歷4,打印1,遍歷6
5.對5的左孩子空遍歷,則返回並開始出棧
6.打印5,
此時棧:遍歷5的右孩空,打印3,遍歷3的右孩空,打印2,遍歷4,打印1,遍歷6
7.遍歷5的右孩空,直接返回
此時棧:打印3,遍歷3的右孩空,打印2,遍歷4,打印1,遍歷6
8.打印3
此時棧:遍歷3的右孩空,打印2,遍歷4,打印1,遍歷6
9.遍歷3的右孩子空,直接返回
此時棧:打印2,遍歷4,打印1,遍歷6
10.打印2
此時棧:遍歷4,打印1,遍歷6
11.遍歷4時,會對4的左孩子空遍歷,此時“打印4”動作和“對4的右孩子空遍歷”動作入棧
此時棧:打印4,遍歷4的右空,打印1,遍歷6
12.打印4
此時棧:遍歷4的右空,打印1,遍歷6
13.遍歷4的右空,直接返回
此時棧:打印1,遍歷6
14.打印1
此時棧:遍歷6
15:對6遍歷,會對6的左孩子7遍歷,此時“打印6”動作及對6的右孩8 壓棧,後者先壓入
此時棧:打印6,遍歷8
16:對7遍歷,會對7的左孩子空遍歷,此時“打印7”及遍歷7的右孩空入棧
此時棧:打印7,遍歷7右孩空,打印6,遍歷8
17.打印7
此時棧:遍歷7右孩空,打印6,遍歷8
18.遍歷7右孩空,直接返回
此時棧:打印6,遍歷8
19:打印6
此時棧:遍歷8
20:遍歷8時,會對8的左孩子空遍歷,此時“打印8”及遍歷8的右孩空入棧
此時棧:打印8,遍歷8右孩空
21.打印8
此時棧:遍歷8右孩空
22:遍歷8右孩空,直接返回
23.至此打印順序是:5,3,2,4,1,7,6,8
5.中序非遞歸方式
代碼如下:
void mid_print_tree_2(TREE_NODE_S* tree)
{
STACK_S stack = {0};
TREE_NODE_S topnode = {0};
TREE_NODE_S* topnode_p = NULL;
int i = 0;
Stack_Init(&stack,50);
//1.頂點入棧
(void)Stack_Pushback(&stack,tree);
while(Stack_Is_Empty(&stack)!=1)
{
//2.把左孩子全部壓入
topnode_p = Stack_GetTop(&stack);
while(topnode_p != NULL)
{
Stack_Pushback(&stack,topnode_p->leftchild);
topnode_p = Stack_GetTop(&stack);
}
//把最後一個左孩子空的彈走
Stack_Pop(&stack);
topnode_p = Stack_GetTop(&stack);
if(topnode_p != NULL)//避免棧中最後一個空的被彈出後,此時棧已經空了再獲取棧頂時就是空的
{
printf("%d ",topnode_p->data);
Stack_Pop(&stack);
Stack_Pushback(&stack,topnode_p->rightchild);
}
}
}
知道遞歸的寫法,並用原理,可以看到其實中序需要對節點的左節點遍歷到底,才能對節點進行打印。打印之後,其右節點又入棧當做左節點方式繼續遍歷。
6.後序遞歸遍歷
代碼如下:
void after_print_tree(TREE_NODE_S* tree)
{
if(tree == NULL)
return ;
after_print_tree(tree->leftchild);
after_print_tree(tree->rightchild);
printf("%d ",tree->data);
}
遞歸運行的方式其實是一樣的,有興趣的可以自行寫出,然後寫道評論區共同討論(哈哈)。
7.後序非遞歸方式
代碼如下:
void after_print_tree_2(TREE_NODE_S* tree)
{
STACK_S stack = {0};
TREE_NODE_S* topnode_p = NULL;
TREE_NODE_S* pre = NULL;
Stack_Init(&stack,50);
Stack_Pushback(&stack,tree);
while(Stack_Is_Empty(&stack)!=1)
{
topnode_p = Stack_GetTop(&stack);
//要打印節點:條件是該節點左右孩子爲空,或者上次遍歷的節點不是空的而且恰好是該節點的左孩子或者右孩子
if((topnode_p->leftchild == NULL && topnode_p->rightchild ==NULL) || (pre!=NULL && (pre == topnode_p->leftchild || pre == topnode_p->rightchild)))
{
printf("%d ",topnode_p->data);
Stack_Pop(&stack);
pre = topnode_p;
}
else
{
if(topnode_p->rightchild != NULL)
Stack_Pushback(&stack,topnode_p->rightchild);
if(topnode_p->leftchild != NULL)
Stack_Pushback(&stack,topnode_p->leftchild);
}
}
}
通過後序遞歸的寫法,及遞歸運行方式,對着圖,可以知道打印節點的條件是該節點的左右孩子爲空,或者上一次的節點是該節點的左孩子或者右孩子;
8.棧結構代碼如下
//樹節點
typedef struct Tree_node
{
int data;
struct Tree_node* leftchild;
struct Tree_node* rightchild;
}TREE_NODE_S;
//棧結構
typedef struct Stack
{
int cap;
int top;
TREE_NODE_S** treenode;
}STACK_S;
void Stack_Init(STACK_S* stack,int cap)
{
if(cap <= 0)
return;
stack->treenode = (TREE_NODE_S**)malloc(sizeof(TREE_NODE_S*)*cap);
memset((TREE_NODE_S*)stack->treenode,0,sizeof(TREE_NODE_S*)*cap);
stack->cap = cap;
stack->top = -1;
}
int Stack_Is_Full(STACK_S* stack)
{
return stack->cap == stack->top ? 1 :0;
}
int Stack_Is_Empty(STACK_S* stack)
{
return stack->top == -1 ? 1 : 0;
}
void Stack_Pushback(STACK_S* stack,TREE_NODE_S* treenode)
{
if(Stack_Is_Full(stack)!=1)
{
stack->top++;
stack->treenode[stack->top] = treenode;
}
return;
}
TREE_NODE_S* Stack_GetTop(STACK_S* stack)
{
if(Stack_Is_Empty(stack)!=1)
return stack->treenode[stack->top];
else
return NULL;
}
void Stack_Pop(STACK_S* stack)
{
if(Stack_Is_Empty(stack)!=1)
stack->top--;
}
9.所有代碼的鏈接,一個.c文件
https://pan.baidu.com/s/1eolitj44OgsgowM2T5aPOQ