二叉树常见面试题
二叉树的基本操作实现见博客
点击打开链接
本文将每个函数的测试代码也附在函数后面
1. 非递归实现先序遍历
实现思想:
实现代码:
void TreePreOrderByLoop(TreeNode* root)//1.非递归先序遍历 {//利用栈辅助实现 if(root == NULL) return; //1.先将根结点入栈 SeqStack stack; SeqStackInit(&stack); SeqStackPush(&stack,root); //2.循环开始 while(1) { //取栈顶元素为当前元素 TreeNode* cur = NULL; int ret = SeqStackGetTop(&stack,&cur); //栈空代表遍历结束 if(ret == 0) break; //出栈当前元素,并访问当前元素 SeqStackPop(&stack); printf("[%c] ",cur->data); //把当前元素的右子树入栈,再将当前元素的左子树入栈(为空不入栈) if(cur->rchild != NULL) SeqStackPush(&stack,cur->rchild); if(cur->lchild != NULL) SeqStackPush(&stack,cur->lchild); } printf("\n"); } void TestPreOrderByLoop() { SHOW_NAME; TreeNode* root; TreeInit(&root); TreeNodeType data[] = "abd##eg###c#f##"; size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点 TreeNodeType null_node = '#'; TreeNode* new_node = TreeCreate(data,size,null_node); TreePreOrderByLoop(new_node); }
2. 非递归实现中序遍历
实现思想:
实现代码:
void TreeInOrderByLoop(TreeNode* root)//2.非递归中序遍历 {//利用栈辅助实现 if(root == NULL) return; SeqStack stack; SeqStackInit(&stack); //1.定义cur指针,指向根节点,根节点不为空时,将根节点入栈,且cur=cur->lchild TreeNode* cur = root; //2.循环开始 while(1) { //直至左子树为空时才结束循环,因为中序遍历是先访问左子树 while(cur != NULL) { SeqStackPush(&stack,cur); cur = cur->lchild; } //取栈顶元素为当前元素 TreeNode* top = NULL; int ret = SeqStackGetTop(&stack,&top); //栈空表示访问结束 if(ret == 0) break; //访问当前元素,并出栈 printf("[%c] ",top->data); SeqStackPop(&stack); //3.让cur指向栈顶元素的右子树,重复2的判断过程 cur = top->rchild; } printf("\n"); } void TestInOrderByLoop() { SHOW_NAME; TreeNode* root; TreeInit(&root); TreeNodeType data[] = "abd##eg###c#f##"; size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点 TreeNodeType null_node = '#'; TreeNode* new_node = TreeCreate(data,size,null_node); TreeInOrderByLoop(new_node); }
3. 非递归实现后续遍历
实现思想:
实现代码:
void TreePostOrderByLoop(TreeNode* root)//3.非递归后序遍历 {//利用栈辅助实现 if(root == NULL) return; SeqStack stack; SeqStackInit(&stack); //1.定义cur指针,指向根节点,根节点不为空时,将根节点入栈,且cur=cur->lchild TreeNode* cur = root; TreeNode* pre = NULL;//用来保存上一个访问的元素 //2.循环开始 while(1) { //直至左子树为空时才结束循环,因为中序遍历是先访问左子树 while(cur != NULL) { SeqStackPush(&stack,cur); cur = cur->lchild; } //取栈顶元素为当前元素 TreeNode* top = NULL; int ret = SeqStackGetTop(&stack,&top); //栈空表示访问结束 if(ret == 0) break; //判断当前元素,若当前元素的右子树为空或刚刚已访问过,则可以访问当前元素并出栈 if(top->rchild==NULL || top->rchild==pre) { printf("[%c] ",top->data); SeqStackPop(&stack); //要注意更新pre指针 pre = top; } //3.当前元素不符合刚刚判断当前元素的规则,则让cur指向栈顶元素的右子树,重复2的判断过程 else cur = top->rchild; } printf("\n"); } void TestPostOrderByLoop() { SHOW_NAME; TreeNode* root; TreeInit(&root); TreeNodeType data[] = "abd##eg###c#f##"; size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点 TreeNodeType null_node = '#'; TreeNode* new_node = TreeCreate(data,size,null_node); TreePostOrderByLoop(new_node); }
4. 树的镜像
实现思想:
将树的每个结点的左右子树交换,生成的新树即是实现了树的镜像的新树。这里写了两种方法,
实现代码:
void Swap(TreeNode** a, TreeNode** b)//4.树的镜像实现的交换函数 { TreeNode* tmp = *a; *a = *b; *b = tmp; } void TreeMirror1(TreeNode* root)//树的镜像(方法1) {//树的镜像即将树对称的画出来,即左右子树都交换 if(root == NULL) return; //访问动作即交换左右子树 Swap(&root->lchild,&root->rchild); TreeMirror1(root->lchild); TreeMirror1(root->rchild); return; } void TreeMirror2(TreeNode* root)//树的镜像(方法2) {//层序遍历、利用队列实现 if(root == NULL) return; SeqQueue q; SeqQueueInit(&q); //1.将根节点入队 SeqQueuePush(&q,root); TreeNode* front = NULL; //2.循环开始,若当前队首元素不为空,交换当前元素的左右子树并出队 while(SeqQueueGetFront(&q,&front)) { //此处的访问即交换左右子树 Swap(&front->lchild,&front->rchild); SeqQueuePop(&q); //3.若当前元素的左右子树不为空,则入队,继续循环判断 if(front->lchild != NULL) SeqQueuePush(&q,front->lchild); if(front->rchild != NULL) SeqQueuePush(&q,front->rchild); } return; } void TestMirror() { SHOW_NAME; TreeNode* root; TreeInit(&root); TreeNodeType data[] = "abd##eg###c#f##"; size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点 TreeNodeType null_node = '#'; TreeNode* new_node = TreeCreate(data,size,null_node); TreePreOrderByLoop(new_node); TreeMirror1(new_node); TreePreOrderByLoop(new_node); TreeMirror2(new_node); TreePreOrderByLoop(new_node); }
5.判断一棵树是否为完全二叉树
实现思想:
利用队列通过层序遍历的思想,判断,主要分为两个阶段:
1.任何一个结点应该同时具有左右两个子树,若哪个结点不是同时具备,则:
(1)若当前结点只有右子树,不是完全二叉树;
(2)若当前结点只有左子树,进入第二阶段;
(3)若当前结点无子树,进入第二阶段;
2.进入第二阶段的结点,均必须无子树,若有子树说明不是完全二叉树
当以上全部判断过,函数还未返回,说明该树是完全二叉树。
实现代码:
int IsCompleteTree(TreeNode* root)//5.判断当前树是否为完全二叉树 { if(root == NULL)//空树 return 0; SeqQueue q; SeqQueueInit(&q); SeqQueuePush(&q,root); int if_start_step_two_flag = 0;//是否开始第二阶段的标志 TreeNode* cur = NULL; while(SeqQueueGetFront(&q,&cur)) { SeqQueuePop(&q); //一.第一阶段 if(if_start_step_two_flag == 0) { //1.当前结点同时具有左右子树,将左右子树入队 if(cur->lchild!=NULL && cur->rchild!=NULL) { SeqQueuePush(&q,cur->lchild); SeqQueuePush(&q,cur->rchild); } //2.当前结点只有右子树,但无左子树,说明不是完全二叉树,直接返回 else if(cur->lchild==NULL && cur->rchild!=NULL) { return 0; } //3.当前结点只有左子树无右子树,将左子树入队,进入第二阶段 else if(cur->lchild!=NULL && cur->rchild==NULL) { if_start_step_two_flag = 1; SeqQueuePush(&q,cur->lchild); } //4.当前结点无左右子树,进入第二阶段 else { if_start_step_two_flag = 1; } } //二.第二阶段 else { //任何一个结点都应该没有子树,否则不是完全二叉树 if(cur->lchild==NULL && cur->rchild==NULL) ; else return 0; } } //所有条件都满足,时一个完全二叉树 return 1; } void TestIsCompleteTree() { SHOW_NAME; TreeNode* root; TreeInit(&root); TreeNodeType data[] = "abd##eg###c#f##"; size_t size = sizeof(data) - 1;//这里用sizeof比strlen效率高一点 TreeNodeType null_node = '#'; TreeNode* new_node = TreeCreate(data,size,null_node); int ret = IsCompleteTree(new_node); printf("expected is 0, actual is %d\n",ret); TreeNode* root1; TreeInit(&root1); TreeNodeType data1[] = "abd##e##cf###"; size_t size1 = sizeof(data1) - 1;//这里用sizeof比strlen效率高一点 TreeNodeType null_node1 = '#'; TreeNode* new_node1 = TreeCreate(data1,size1,null_node1); int ret1 = IsCompleteTree(new_node1); printf("expected is 1, actual is %d\n",ret1); }
6. 给定先序遍历序列和中序遍历序列,还原一棵树
实现思想:
先序序列:ABDFGCH,中序序列:DBFGAHC。因为先序是:根节点->左子树->右子树,中序是:左子树->根节点->右子树。所以,在上述序列中:定义index为先序遍历序列中的下标,left为中序序列中某棵树所在范围左侧的下标,right为中序序列中某棵树所在范围右侧的下标。该范围是一左闭右开区间,如树A的范围为:[0,7)
(1)根据下标范围判断该树是否为空(为空:left >= right)
(2)不为空,则根据index处的元素创建结点,作为当前树的当前根节点
(3)找到当前根节点在中序序列中的下标,从而确定当前根节点的左右子树的范围。然后index++遍历到之后的结点。
(4)如果左子树不为空,则index处的元素必为左子树的根节点,此时可以根据index创建新的结点作为左子树的根结点(此处的操作与(1)~(3)相同,因此可以递归的创建左子树)。否则转至(5)
(5)如果右子树不为空,则index处的元素必为右子树的根节点,此时可以根据index处创建新的结点作为右子树的根节点(此处的操作与(1)~(3)相同,因此可以递归的创建右子树)。否则根节点的左右子树均创建完毕,直接返回根节点即可。
实现代码:
size_t Find(TreeNodeType arr[], size_t left, size_t right, TreeNodeType to_find)//6.还原一棵树的查找当前结点在中序序列中的位置 { size_t i = 0; for(i=left; i<right; i++) { if(arr[i] == to_find) return i; } return -1; } TreeNode* _TreeReBulid(TreeNodeType pre_order[], size_t pre_order_size, size_t* pre_order_index,\ TreeNodeType in_order[], size_t in_order_left, size_t in_order_right)//真正还原一棵树的代码 { if(in_order_left >= in_order_right)//无效区间 return NULL; if(pre_order_index == NULL)//非法输入 return NULL; if(*pre_order_index >= pre_order_size)//遍历结束 return NULL; //根据先序遍历结果取出当前值,基于该值构建一个结点 TreeNode* new_node = CreateTreeNode(pre_order[*pre_order_index]); //查找当前结点在中序遍历序列中的位置 size_t cur_root_in_order_index = Find(in_order,in_order_left,in_order_right,new_node->data); //避免找不到当前结点在中序序列中的位置 assert(cur_root_in_order_index != (size_t)-1); //左子树区间 [in_order_left, cur_root_in_order_index); //右子树区间 [cur_root_in_order_index+1, in_order_right); ++(*pre_order_index); new_node->lchild = _TreeReBulid(pre_order, pre_order_size, pre_order_index, in_order, in_order_left,cur_root_in_order_index); new_node->rchild = _TreeReBulid(pre_order, pre_order_size, pre_order_index, in_order, cur_root_in_order_index+1, in_order_right); return new_node; } TreeNode* TreeReBulid(TreeNodeType pre_order[], TreeNodeType in_order[], size_t size)//给定先序和中序遍历结果,还原一棵树 { size_t pre_order_index = 0; //前闭后开区间,用来表示对当前子树中序遍历的结果 size_t in_order_left = 0; size_t in_order_right = size; return _TreeReBulid(pre_order,size,&pre_order_index,in_order,in_order_left,in_order_right); } void TestReBuild() { SHOW_NAME; TreeNode* root; TreeInit(&root); TreeNodeType pre_order[] = "abdegcf"; TreeNodeType in_order[] = "dbgeacf"; size_t size = sizeof(pre_order) - 1;//这里用sizeof比strlen效率高一点 TreeNode* new_node = TreeReBulid(pre_order, in_order, size); printf("pre_order# %s\n", pre_order); TreePreOrderByLoop(new_node); printf("in_order# %s\n", in_order); TreeInOrderByLoop(new_node); }