2020考研-王道数据结构-树和二叉树-二叉树的遍历

说在开头

函数头文件定义
#include 
#include 
#include 
#include 
#include 
数据结构定义
typedef char ElemType;

typedef struct bitnode{
	ElemType data;
	struct bitnode *right, *left;
}BitNode, *PBitNode;
辅助函数
// 层序遍历 创建一个二叉树
PBitNode creatTree(string str)
{
	if (str.size() == 0) return NULL;
	queue<PBitNode> que;
	PBitNode tree = new BitNode();
	tree->data = str[0];
	tree->left = NULL;
	tree->right = NULL;
	que.push(tree);
	for (int i = 1; i < str.size(); i++)
	{
		PBitNode t = que.front();
		PBitNode l = new BitNode();
		l->left = l->right = NULL;
		l->data = str[i];
		t->left = l;
		que.push(l);
		i++;
		if (i == str.size()) break;
		PBitNode r = new BitNode();
		r->left = r->right = NULL;
		r->data = str[i];
		t->right = r;
		que.push(r);
		que.pop();
	}
	return tree;
}

第三题

题目描述

写出二叉树的后序遍历的非递归算法。下面的代码中包含了树的三种遍历方式的递归和非递归算法。

代码
// 先序遍历 递归算法
void firstOrder(PBitNode tree)
{
	if (tree){
		visit(tree);
		firstOrder(tree->left);
		firstOrder(tree->right);
	}
	return;
}

// 先序遍历 非递归算法
void firstOrder_no(PBitNode tree)
{
	if (!tree) return;
	PBitNode p = tree;
	stack<PBitNode> stk;
	stk.push(p);
	while (stk.size())
	{
		p = stk.top();
		stk.pop();
		visit(p);
		if (p->right) stk.push(p->right);
		if (p->left) stk.push(p->left);
	}
	return;
}

// 中序遍历,递归算法
void middleOrder(PBitNode tree)
{
	if (tree){
		middleOrder(tree->left);
		visit(tree);
		middleOrder(tree->right);
	}
	return;
}

// 中序遍历 非递归算法
void middleOrder_no(PBitNode tree)
{
	if (!tree) return;
	stack<PBitNode> stk;
	PBitNode p = tree;
	while (stk.size() || p)
	{
		if (p){
			stk.push(p);
			p = p->left;
		}
		else{
			PBitNode t = stk.top();
			stk.pop();
			visit(t);
			p = t->right;
		}
	}
	return;
}

// 后序遍历递归算法
void lastOrder(PBitNode tree){
	if (tree)
	{
		lastOrder(tree->left);
		lastOrder(tree->right);
		visit(tree);
	}
	return;
}

// 后序遍历非递归算法
void lastOrder_no(PBitNode tree)
{
	if (!tree) return;
	PBitNode p = tree, r = NULL;
	stack<PBitNode> stk;
	while (p || stk.size())
	{
		if (p){
			stk.push(p);
			p = p->left;
		}
		else{
			p = stk.top();
			if (p->right && p->right != r)
			{
				stk.push(p->right);
				p = p->right->left;
			}
			else
			{
				visit(p);
				r = p;
				stk.pop();
				p = NULL;
			}
		}
	}
	return;
}

第四题

题目描述

从下到上,从右到左遍历遍历二叉树。

代码思路

其实相比于从上到下,从左到右遍历二叉树的基础上增加一个栈。在从上到下,从左到右的遍历过程中,将每个节点的元素入栈,最后再将栈中元素出栈,即为从下到上,从右到左遍历二叉树。
同样的对于【从上到下,从右到左】【从下到上,从左到右】来说,只是改变左孩子节点和右孩子节点入队和出队的顺序即可。

代码
// 从下到上,从右到左 遍历
void levelOrder_reverse(PBitNode tree)
{
	if (!tree) return;
	stack<PBitNode> stk;
	queue<PBitNode> que;
	que.push(tree);
	while (que.size())
	{
		PBitNode node = que.front();
		que.pop();
		stk.push(node);
		if (node->left) que.push(node->left);
		if (node->right) que.push(node->right);
	}
	while (stk.size())
	{
		visit(stk.top());
		stk.pop();
	}
	return;
}

第五题

题目描述

用非递归算法计算二叉树的高度。下面代码中给出了递归和非递归计算的两种方式。

代码思路

课本上的代码是利用一个标记指针记录每一层的结束位置。而下面的代码是在每一层结束之后加上一个空指针用于记录每一层,和课本上的方法殊途同归。

代码
// 递归计算树的高度
int getTreeHeight(PBitNode tree)
{
	if (!tree) return 0;
	return max(getTreeHeight(tree->left), getTreeHeight(tree->right)) + 1;
}

// 非递归算法计算树的高度
int getTreeHeight_no(PBitNode tree)
{
	int level = 0;
	if (!tree) return level;
	queue<PBitNode> que;
	que.push(tree);
	que.push(NULL);
	bool ishavenode = false;
	while (que.size())
	{
		PBitNode node = que.front();
		que.pop();
		if (node == NULL){
			level++;
			if(ishavenode) 
				que.push(NULL);
			ishavenode = false;
			continue;
		}
		ishavenode = tree;
		if (node->left) que.push(node->left);
		if (node->right) que.push(node->right);
	}
	return level - 1;
}

第六题

题目描述

已知一棵树的先序遍历和中序遍历序列,建立该树的二叉链表。

题目分析

先序遍历序列中第一个节点一定是二叉树的根节点,然后通过根节点和中序遍历序列就可以知道左子树、右子树是由哪些节点构成,然后递归左序列、右序列即可。

代码
// 通过先序遍历序列和中序遍历序列创建二叉树
PBitNode creatTree(string f, string m)
{
	if (f == "" || m == "") return NULL;
	PBitNode tree = new BitNode();
	tree->data = (ElemType)f[0];
	int c = 0;
	for (c = 0; c < m.size(); c++) if (m[c] == f[0]) break;
	int lenl = c, lenr = m.size() - c - 1;
	tree->left = creatTree(string(f.begin() + 1, f.begin() + 1 + lenl), string(m.begin(), m.begin() + lenl));
	tree->right = creatTree(string(f.end() - lenr, f.end()), string(m.end() - lenr, m.end()));
	return tree;
}

第七题

题目描述

判断二叉树是否是完全二叉树。

题目分析

如果一个树是完全二叉树的话,那么按照层序遍历的话,当遇到空节点时,整棵树就遍历完了。所以我们判断的时候,当遇到空节点后,如果再次遇到非空节点,那么这棵树一定不是完全二叉树。

代码
// 判断二叉树是否为完全二叉树
bool isCompleteBinaryTree(PBitNode tree)
{
	if (!tree) return true;
	queue<PBitNode> que;
	que.push(tree);
	while(que.size())
	{
		PBitNode p = que.front();
		que.pop();
		if (p){
			que.push(p->left);
			que.push(p->right);
		}
		else
		{
			while (que.size() && que.front() == NULL) que.pop();
			if (que.size())
				return false;
		}
	}
	return true;
}

第八题

题目描述

统计二叉树中双分支节点的个数。

题目分析

和第八题是一样的,其实就是考察树的遍历吗,遍历一遍就出来结果了,只不过针对这个题目来说,不关心遍历的次序,也就是不关心先序还是后序,所以直接递归处理子树也相当于遍历了一遍,但是复杂度肯定不会少,代码确实是精简。

代码
// 统计二叉树双分支节点的个数
int countDoubleNode(PBitNode tree)
{
	if (!tree) return 0;
	bool isans = tree->left && tree->right;
	return countDoubleNode(tree->right) + countDoubleNode(tree->left) + isans;
}

第九题

题目描述

交换二叉树中的所有节点的左右子树

题目分析

和第六题是相同的思路。

代码
// 交换二叉树中的所有的左右子树
void swapBinaryTree(PBitNode tree)
{
	if (!tree) return;
	swap(tree->left, tree->right);
	swapBinaryTree(tree->left);
	swapBinaryTree(tree->right);
	return;
}

第十题

题目描述

求先序遍历序列中的第k个节点。

题目分析

我们按照先序遍历的方式去访问节点,当遇到第k个节点时直接返回第k个节点的值即可。课本上给的答案是递归遍历时的情况,下面的代码是非递归先序遍历。相比于递归遍历访问第k个节点,感觉非递归的代码比较好写。

代码
// 求先序遍历序列中的第k个节点
ElemType getBinaryTreeKNode(PBitNode tree, int k)
{
	if (!tree) return ElemType(-1);
	stack<PBitNode> stk;
	stk.push(tree);
	int c = 1;
	while (stk.size())
	{
		PBitNode p = stk.top();
		stk.pop();
		if (c == k) return p->data;
		c++;
		if (p->right) stk.push(p->right);
		if (p->left) stk.push(p->left);
	}
	return ElemType(-1);
}

第十一题

题目描述

删除树中所有值为x的节点及其子树。

题目分析

简单思路:层序遍历-》找到节点-》删除节点。
注意两个问题:

  1. 怎么递归删除一棵树。
  2. 假如我们删除的节点p,而起初树的关系是 t->left = p ,p是t的左儿子,当我们删除p后,需要把 t 的左儿子置为空,否则 t 的左儿子就变成了野指针。 解决办法也简单,就是每次只需要判断两个子树是不是应该被删除就可以了,这样的话,根节点需要特判一下。
代码
// 删除一棵树
void deleteTree(PBitNode &tree){
	if (tree){
		deleteTree(tree->left);
		deleteTree(tree->right);
		free(tree);
	}
}

// 删除所有值为x的节点及其子树
void deleteXNode(PBitNode &tree, ElemType x)
{
	if (!tree) return;
	if (tree->data == x){
		deleteTree(tree);
		return;
	}
	queue<PBitNode> que;
	que.push(tree);
	while (que.size())
	{
		PBitNode p = que.front();
		que.pop();
		if (p->left){
			if (p->left->data == x){
				deleteTree(p->left);
				p->left = NULL;
			}
			else que.push(p->left);
		}
		if (p->right)
		{
			if (p->right->data == x){
				deleteTree(p->right);
				p->right = NULL;
			}
			else que.push(p->right);
		}
	}
	return;
}

第十二题

题目描述

打印树中值为x的节点的所有祖先节点,保证值为x的节点不多于一个。

题目分析

这个题和下一个题是一种类型,下一题不再写分析。
首先我们需要了解一件事,最关键的一件事,当我们使用栈进行非递归后序遍历一棵树的时候,栈中元素保存的就是当前遍历到的节点的所有祖先节点。建议大家亲自模拟一遍非递归后序遍历的过程。
所以问题就变成了怎么用栈模拟后序遍历,代码参考前面的题目,不再赘述。

代码
// 打印值为x的节点的所有祖先节点
// 保证值为x的节点不多于一个
void printXFather(PBitNode tree, ElemType x)
{
	if (!tree) return;
	stack<PBitNode> stk;
	PBitNode p = tree, r = NULL;
	while (stk.size() || p)
	{
		if (p)
		{
			stk.push(p);
			p = p->left;
		}
		else
		{
			p = stk.top();
			if (p->right && p->right != r)
			{
				stk.push(p->right);
				p = p->right->left;
			}
			else
			{
				stk.pop();
				r = p;
				if (p->data == x) // 找到了指定节点
				{
					// 打印栈中元素
					while (stk.size())
					{
						cout << stk.top()->data << " ";
						stk.pop();
					}
					return;
				}
				p = NULL;
			}
		}
	}
}

第十三题

题目描述

找到树中两个节点的最近公共祖先。

题目分析

参考上一题。

代码
// 找到树中两个节点的最近公共祖先
PBitNode getMinFather(PBitNode tree, PBitNode t1, PBitNode t2)
{
	if (!tree) return NULL;
	PBitNode p = tree, r = NULL;
	stack<PBitNode> stk, stk1, stk2;
	while (stk.size())
	{
		if (p)
		{
			stk.push(p);
			p = p->left;
		}
		else
		{
			p = stk.top();
			if (p->right && p->right != r)
			{
				stk.push(p->right);
				p = p->right->left;
			}
			else
			{
				stk.pop();
				r = p;
				if (p == t1) stk1 = stk;
				if (p == t2) stk2 = stk;
				p = NULL;
			}
		}
	}

	while (stk1.size() && stk2.size()){
		if (stk1.top() == stk2.top()) return stk1.top();
		stk1.pop();
		stk2.pop();
	}

	return NULL;
}

第十四题

题目描述

找到树中节点数最多的一层的节点的个数。

题目分析

这个题目的关键就是把树中的每一层分离出来即可,怎么分离呢?采用层次遍历的方法,首先第一层一定只有一个根节点,所以我们可以在根节点后面加上一个空节点【标记节点】,当每次遇到标记节点时,可以传递给我们两个信息,
第一个信息是当前层次遍历的这一层已经遍历结束,
第二个信息是下一层的节点树已经全部入队,此时我们再次加入空节点用于标记下一层的结束位置。直至整棵树遍历结束。

代码
// 找到树中节点数最多的一层的节点的个数
int getMaxPointsForLevel(PBitNode tree)
{
	if (!tree) return 0;
	int res = 0, cnt = 0;
	queue<PBitNode> que;
	bool islevel = false;
	que.push(tree);
	que.push(NULL);
	while (que.size())
	{
		PBitNode p = que.front();
		que.pop();
		if (p == NULL){
			res = max(res, cnt);
			cnt = 0;
			if (islevel) que.push(NULL);
			islevel = false;
			continue;
		}
		cnt++;
		if (p->left) que.push(p->left);
		if (p->right) que.push(p->right);
		islevel = true;
	}
	return res;
}

第十五题

题目描述

已知一颗满二叉树的先序遍历序列,求满二叉树的后序遍历序列。

题目分析

满二叉树真好!
满二叉树的任何一个节点的左孩子节点的个数都和右孩子节点的个数相同。
已知的是先序遍历序列,先序遍历序列的第一个是树的根节点,并且先序遍历是先遍历的根节点的左孩子,然后再遍历的根节点的右孩子。
所以,假设序列的长度为 n n n,那么 左 孩 子 节 点 数 = 右 孩 子 节 点 数 = ( n − 1 ) / 2 左孩子节点数=右孩子节点数=(n-1)/2 ==(n1)/2, 于是我们可以轻易的把先序遍历序列分成三部分,根节点,左子树的先序遍历序列 、右子树的先序遍历序列。
然后递归进行就可以了。

代码
// 已知一颗满二叉树的先序遍历序列 求后序遍历序列
string firstOrderToLastOrder(string str)
{
	string res = "";
	if (str == "") return "";
	int mid = str.size() - 1 >> 1;
	res += firstOrderToLastOrder(string(str.begin() + 1, str.begin() + 1 + mid));
	res += firstOrderToLastOrder(string(str.end() - mid, str.end()));
	res += str[0];
	return res;
}

第十六题

题目简介

将二叉树的叶节点按照从左到右的顺序连成一个单链表,表头指针为head。二叉树按照双叉链表的方式存储,链接是二叉链表的右指针域存放单链表指针。

题目分析

问题的关键在于,我们怎么按照从左到右的顺序去访问二叉树的叶节点。
其实,不难发现,先序遍历、中序遍历、后序遍历,均是按照从左到右的顺序去遍历二叉树的。
所以我们随便选则一种遍历方式即可,当遍历到叶节点时,链接成单链表即可。

代码
// 将二叉树的叶节点从左到右链接成一个单链表
// 用二叉链表的右孩子作为单链表的next指针
PBitNode linkLeaves(PBitNode &tree)
{
	if (!tree) return NULL;
	stack<PBitNode> stk;
	PBitNode link = NULL, head = NULL;
	stk.push(tree);
	while (stk.size())
	{
		PBitNode p = stk.top();
		stk.pop();
		if (!p->right && !p->left)
		{
			if (!link){
				link = p;
				head = p;
			}
			else{
				link->right = p;
				link = p;
			}
		}
		if (p->right) stk.push(p->right);
		if (p->left) stk.push(p->left);
	}
	return head;
}

第十七题

题目简介

判断两棵二叉树是否相似。相似的定义为

  1. T 1 、 T 2 T1、T2 T1T2都是空的或者是只包含一个根节点。
  2. T 1 、 T 2 T1、T2 T1T2的左子树是相似的。
  3. T 1 、 T 2 T1、T2 T1T2的右子树是相似的。
题目分析

从相似的定义上就可以看出来一个完美的递归算法,通过条件1可以判断当前节点是不是满足相似,然后递归判断左右子树即可。

代码
// 判断两颗二叉树是否相似
bool isSimilar(PBitNode t1, PBitNode t2)
{
	if (!t1 && !t2) return true;
	if (t1 && t2) return isSimilar(t1->left, t2->left) && isSimilar(t1->right, t2->right);
	return false;
}

第十八题

参考下一篇博客,专门介绍线索二叉树的。【只不过还没有更新/哈哈哈 不过很快 3天以内】

第十九题

题目描述

二叉树的带权路径长度(WPL)指的是二叉树中所有叶节点的带权路径长度之和。
其中节点的数据结构为 (left weight right)
其中叶节点的带权路劲长度 = weight * 节点的深度
设计一个求WPL的算法。

题目分析

问题的关键在于我们遍历当根节点时,怎么获取该点的深度,其实不难?我能想到的有

  1. 先序遍历时记录一下深度。
  2. 层序遍历时记录一下层数。
  3. 非递归后序遍历时栈中元素的个数就是当前节点的深度。因为前面有一题说到了,非递归后序遍历时,栈中元素就是当前节点的祖父节点、所以祖父节点的个数就是当前节点的深度。
    所以 按照三种方式的一种去遍历二叉树,记录一下深度即可。
代码
#include 
#include 

using namespace std;

typedef struct BitNode{
	int w;
	struct BitNode *left, *right;
}BitNode, *PBitNode;

int getWPL(PBitNode tree)
{
	return dfswpl(tree, 0);
}

int dfswpl(PBitNode tree, int d)
{
	static int wpl = 0;
	if (!tree->left && !tree->right) wpl += d * tree->w;
	if (tree->left) dfswpl(tree->left, d + 1);
	if (tree->right) dfswpl(tree->right, d + 1);
	return wpl;
}

int main()
{
	return 0;
}

第二十题

题目描述

将一棵二叉树表达式树转化为等价的中缀表达式。

题目分析

课本上的思路是,利用中序遍历序列,在合适的位置加上括号就出来了。
下面这种做法采用了递归的思路,有bug,就是每个单一元素的后面也有括号,不过不影响中缀表达式的逻辑合法性。

代码
// 将给定的表达式树转化为等价的中缀表达式
string convertTo(PBitNode tree)
{
	if (!tree) return "";
	string l = convertTo(tree->left);
	string r = convertTo(tree->right);
	if (l == "" && r == "") return string() + tree->data;
	if (l != "" && r != "") return string() + "(" + l + ")" + tree->data + "(" + r + ")";
	if (l == "") return string() + tree->data + "(" + r + ")";
	return string() + "(" + l + ")" + tree->data;
}

你可能感兴趣的:(算法,2020王道数据结构)