非递归实现前序遍历思路:非递归的前序遍历就比较简单,借助一个栈,将根节点入栈,然后通过循环,取栈顶元素,出栈就可访问当前栈顶元素(也就是当前树的根节点(前序遍历的顺序是VLR)),有先序遍历的顺序规则,在将其右子树入栈,在将其左子树入栈,只需要注意一点,左右子树的入栈顺序一定是先入栈右子树在入栈左子树。(因为我们先序遍历是访问了根节点之后就访问左子树在访问右子树,而栈的特点是后进先出,因此我们要想左子树的节点先于右子树的节点被访问到,左子树必须要在右子树入栈之后再入栈。)
//非递归前序遍历二叉树
void TreePreOrderByLoop(TreeNode *root)
{
if(root == NULL)
{
//空树
return;
}
Stack stack;
StackInit(&stack);
//根节点入栈
StackPush(&stack,root);
TreeNode *cur = NULL;
//循环的去栈顶元素对其操作,栈为空时结束循环
while(StackGetTop(&stack,&cur))
{
//出栈
StackPop(&stack);
//访问当前栈顶元素
printf("%c ",cur->data);
//将当前元素的右子树入栈
if(cur->rchild != NULL)
{
StackPush(&stack,cur->rchild);
}
//再将当前元素的左子树入栈(一定是先右在左)
if(cur->lchild != NULL)
{
StackPush(&stack,cur->lchild);
}
}
return;
}
非递归实现中序遍历思路:
//非递归中序遍历二叉树
void TreeInOrderByLoop(TreeNode *root)
{
if(root == NULL)
{
//空树
return;
}
Stack stack;
StackInit(&stack);
TreeNode *cur = root;
while(1)
{
//该循环结束cur就到了一个空节点处
//他的前一个指向的节点就是当前树的最左的节点
while(cur != NULL)
{
StackPush(&stack,cur);
cur = cur->lchild;
}
//此时cur为空
//取栈顶元素
TreeNode *tmp = NULL;
int ret = StackGetTop(&stack,&tmp);
if(ret == 0)
{
//取栈顶元素失败,说明栈空了
//也就说明遍历完成
return;
}
//访问
printf("%c ",tmp->data);
//出栈
StackPop(&stack);
if(tmp->rchild != NULL)
{
cur = tmp->rchild;
}
}
return;
}
非递归实现后序遍历思路:后序遍历我们都知道是LRV的顺序,所以我们在找到当前树的最左的节点之后,并不能立即进行访问,还要找到右节点,并且找到以后需要判断,该有节点是否被访问过或者是否为空,如果为空或者当前有节点被访问过,满足任意一个条件才能访问其根节点。因此我们还需要一个变量来保存我们最后访问的一个元素用于判断。
//非递归后序遍历二叉树
void TreePostOrderByLoop(TreeNode *root)
{
if(root == NULL)
{
//空树
return;
}
//定义一个栈并且初始化
Stack stack;
StackInit(&stack);
//定义cur指针指向根节点
TreeNode *cur = root;
//定义一个指针保存我们最新访问过的最后一个元素
TreeNode *pre = NULL;
while(1)
{
//该循环结束cur就到了一个空节点处
//他的前一个指向的节点就是当前树的最左的节点
while(cur != NULL)
{
StackPush(&stack,cur);
cur = cur->lchild;
}
//此时cur为空
//取栈顶元素
TreeNode *tmp = NULL;
int ret = StackGetTop(&stack,&tmp);
if(ret == 0)
{
//取栈顶元素失败,说明栈空了
//也就说明遍历完成
return;
}
//对当前栈顶元素进行判定
//1、如果当前栈顶元素的右子树为空
//2、或者右子树已经访问过(和访问的上一个元素是同一个元素)
//此时才能够对该栈顶元素进行访问,然后出栈
if(tmp->rchild == NULL || tmp->rchild == pre)
{
printf("%c ",tmp->data);
StackPop(&stack);
//更新pre的指向,用于下一次的栈顶元素的判断
pre = tmp;
}
//如果都不满足,则就让cur指向该栈顶元素的右子树
//重复之前的动作
else
{
cur = tmp->rchild;
}
}
}
//以下为3个非递归遍历函数的测试函数
void TestTraverse()
{
Test_Header;
TreeNode *a = CreateNode('a');
TreeNode *b = CreateNode('b');
TreeNode *c = CreateNode('c');
TreeNode *d = CreateNode('d');
TreeNode *e = CreateNode('e');
TreeNode *f = CreateNode('f');
TreeNode *g = CreateNode('g');
a->lchild = b;
a->rchild = c;
c->rchild = f;
b->lchild = d;
b->rchild = e;
e->lchild = g;
printf("递归先序遍历结果:");
TreePreOrder(a);
printf("非递归先序遍历结果:");
TreePreOrderByLoop(a);
printf("\n");
printf("递归中序遍历结果:");
TreeInOrder(a);
printf("非递归中序遍历结果:");
TreeInOrderByLoop(a);
printf("\n");
printf("递归后序遍历结果:");
TreePostOrder(a);
printf("非递归后序遍历结果:");
TreePostOrderByLoop(a);
printf("\n");
}
测试结果:
递归与非递归求二叉树的镜像其实就像我们日常生活中照镜子一样,我们看到的镜子里面的自己总是和现实的自己的左右手颠倒的。
//交换左右子树的辅助函数
void swap(TreeNode **a,TreeNode **b)
{
if(a == NULL || b == NULL)
{
return;
}
TreeNode *tmp = *a;
*a = *b;
*b = tmp;
return;
}
//求二叉树的镜像(递归)
void TreeMirror(TreeNode *root)
{
if(root == NULL)
{
//空树
return;
}
//以先序的顺序,只是这里的访问动作是交换节点的左右子树
swap(&root->lchild,&root->rchild);
//调用函数本身,递归的实现每一个节点的左右子树的交换(不重不漏)
TreeMirror(root->lchild);
TreeMirror(root->rchild);
}
//求二叉树的镜像(非递归,借助队列层序遍历)
void TreeMirror2(TreeNode *root)
{
if(root == NULL)
{
//空树
return;
}
//定义一个队列并且初始化
Queue queue;
QueueInit(&queue);
//将根节点入队列
QueuePush(&queue,root);
TreeNode *top = NULL;
//循环的取队首元素
while(QueueGetTop(&queue,&top))
{
//访问动作是交换左右子树
swap(&top->lchild,&top->rchild);
//将当前栈顶元素出栈
QueuePop(&queue);
//将当前栈顶元素的左子树入队列
if(top->lchild != NULL)
{
QueuePush(&queue,top->lchild);
}
//将当前栈顶元素的右子树入队列
if(top->rchild != NULL)
{
QueuePush(&queue,top->rchild);
}
}
return;
}
//测试函数
void TestTraverse()
{
Test_Header;
TreeNode *a = CreateNode('a');
TreeNode *b = CreateNode('b');
TreeNode *c = CreateNode('c');
TreeNode *d = CreateNode('d');
TreeNode *e = CreateNode('e');
TreeNode *f = CreateNode('f');
TreeNode *g = CreateNode('g');
a->lchild = b;
a->rchild = c;
c->rchild = f;
b->lchild = d;
b->rchild = e;
e->lchild = g;
TreeMirror(a);
//TreeMirror2(a);
printf("先序遍历结果:");
TreePreOrder(a);
printf("非递归先序遍历结果:");
TreePreOrderByLoop(a);
printf("\n");
printf("中序遍历结果:");
TreeInOrder(a);
printf("非递归中序遍历结果:");
TreeInOrderByLoop(a);
printf("\n");
printf("后序遍历结果:");
TreePostOrder(a);
printf("非递归后序遍历结果:");
TreePostOrderByLoop(a);
printf("\n");
}
测试结果:
判断一棵二叉树是否为一个完全二叉树思路:首先我们需要借助队列层序的遍历该二叉树的每一个元素,对每一个元素做相应的判断。
1、将根节点入队列
2、循环的取队首元素进行判断
(2.1)如果当前栈顶元素的左右子树都存在,则将其左右子树入栈进入下一个元素的判断。
(2.2)如果当前栈顶元素只有右子树,则该二叉树一定不是二叉树
(2.3)如果当前栈顶元素只有左子树,则需要进入第二个阶段进行更深层次的判断。并且将其左子树入队列。
(2.4)如果当前栈顶元素的左右子树都没有,则也需要进入第二个阶段进行判断。
3、进入第二阶段的判断,判断每一个元素是否都没有左右子树
(3.1)若每一个元素都没有左右子树,说明是完全二叉树
(3.2)若不满足该情况,说明不是完全二叉树
//判断是否为完全二叉树
int IsCompleteTree(TreeNode *root)
{
if(root == NULL)
{
//空树
return 0;
}
//定义一个队列并且初始化
Queue queue;
QueueInit(&queue);
//将根节点初始化
QueuePush(&queue,root);
//定义一个变量用来表示是否进入第二阶段
int Is_start_step_two = 0;
TreeNode *top = NULL;
//循环取队首元素
while(QueueGetTop(&queue,&top))
{
//将当前队首元素出栈
QueuePop(&queue);
//第一阶段的判定
if(Is_start_step_two == 0)
{
if(top->lchild != NULL && top->rchild != NULL)
{
QueuePush(&queue,top->lchild);
QueuePush(&queue,top->rchild);
}
else if(top->lchild == NULL && top->rchild != NULL)
{
//此时没有左子树,只有右子树,那么一定不是完全二叉树
return 0;
}
else if(top->lchild != NULL && top->rchild == NULL)
{
//此时只有左子树没有右子树,就需要进入下一个阶段再进行判断
Is_start_step_two = 1;
QueuePush(&queue,top->lchild);
}
else
{
//此时左右子树都没有,需要进入下一阶段在进行判断
Is_start_step_two = 1;
}
}
//第二阶段的判定
else
{
//节点没有子树
if(top->lchild == NULL && top->rchild == NULL)
{
;
}
else
{
//节点有任意一棵子树,或者两棵子树都有,一定不是完全二叉树
return 0;
}
}
}//此处循环结束
//所有的节点都遍历完了,而又没有return 0从而走到这一步
//说明该树一定是完全二叉树
return 1;
}
//测试函数
void TestIsCompleteTree()
{
Test_Header;
TreeNode *a = CreateNode('a');
TreeNode *b = CreateNode('b');
TreeNode *c = CreateNode('c');
TreeNode *d = CreateNode('d');
TreeNode *e = CreateNode('e');
TreeNode *f = CreateNode('f');
TreeNode *g = CreateNode('g');
a->lchild = b;
a->rchild = c;
c->rchild = f;
b->lchild = d;
b->rchild = e;
e->lchild = g;
//对一棵非完全二叉树进行判断测试
int ret = IsCompleteTree(a);
printf("expected ret = 0,actual ret = %d\n",ret);
//将树修改为完全二叉树再次测试
c->rchild = NULL;
e->lchild = NULL;
ret = IsCompleteTree(a);
printf("expected ret = 1,actual ret = %d\n",ret);
}
测试结果:
由先序和中序遍历结果创建一棵二叉树
//由前序和中序结果创建树
//辅助查找终于结果中当前根节点的左右子树区间的函数
int Find(TreeDataType to_find,TreeDataType in_order[],int left,\
int right)
{
int i = left;
for(;i < right;i++)
{
if(to_find == in_order[i])
{
return i;
}
}
return -1;
}
TreeNode *_TreeRebuild(TreeDataType pre_order[],\
int *pre_order_index,int size,TreeDataType in_order[],\
int left,int right)
{
if(left >= right)
{
//区间无效,说明为一棵空树
return NULL;
}
if(pre_order_index == NULL)
{
//非法输入
return NULL;
}
if(*pre_order_index >= size)
{
//遍历结束
return NULL;
}
//以先序结果取当前值构建一个新节点
//相当于当前子树的根节点
TreeNode *new_node = CreateNode(pre_order[*pre_order_index]);
//找到当前节点在中序遍历结果中的位置
int cur_root_in_order_index = Find(new_node->data,in_order,\
left,right);
assert(cur_root_in_order_index != -1);
//更新index值,指向下一个即将被创建为节点的值
++(*pre_order_index);
//左子树区间[left,cur_root_in_order_index)
//递归的创建当前根节点的左子树
new_node->lchild = _TreeRebuild(pre_order,pre_order_index,\
size,in_order,left,cur_root_in_order_index);
//右子树区间[cur_root_in_order_index+1,right)
//递归的创建当前根节点的右子树
new_node->rchild = _TreeRebuild(pre_order,pre_order_index,\
size,in_order,cur_root_in_order_index+1,right);
return new_node;
}
//辅助创建二叉树的函数
TreeNode *TreeRebuild(TreeDataType pre_order[],\
TreeDataType in_order[],int size)
{
int pre_order_index = 0;
int in_order_left = 0;
int in_order_right = size;
return _TreeRebuild(pre_order,&pre_order_index,\
size,in_order,in_order_left,in_order_right);
}
//由前序和中序结果创建树的函数测试
void TestRebuild()
{
Test_Header;
//先序结果
TreeDataType pre_order[] = "abdegcf";
//中序结果
TreeDataType in_order[] = "dbgeacf";
//调用创建函数
TreeNode *new_node = TreeRebuild(pre_order,in_order,7);
printf("递归先序遍历结果:");
TreePreOrder(new_node);
printf("非递归先序遍历结果:");
TreePreOrderByLoop(new_node);
printf("\n");
printf("递归中序遍历结果:");
TreeInOrder(new_node);
printf("非递归中序遍历结果:");
TreeInOrderByLoop(new_node);
printf("\n");
printf("递归后序遍历结果:");
TreePostOrder(new_node);
printf("非递归后序遍历结果:");
TreePostOrderByLoop(new_node);
printf("\n");
}
测试结果:观察遍历结果均正确,说明创建成功