「算法」二叉树的四种遍历方式和一些题目

二叉树的基本概念

二叉树(binary tree)是 n ( n > = 0 ) n(n>=0) n(n>=0) 个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点以及两棵互不相交的、分别称为根节点的左子树和右子树的二叉树组成。每个根节点最多只有两个子节点的就叫做二叉树。

二叉树的特点

  • 每个结点最多有两棵子树,所以二叉树中不存在度大于 2 的结点。注意不是只有两棵子树,而是最多有。没有子树或者有一棵子树都是可以的。
  • 左子树和右子树是有顺序的,次序不能任意颠倒
  • 即使树中某结点只有一棵子树,也要区分它是左子树还是右子树

完全二叉树

对于一棵具有 n 个节点的二叉树按照层序编号,如果编号为 i ( 1 < = i < = n ) i(1<=i<=n) i(1<=i<=n) 的节点与同样深度的满二叉树中编号为 i 的节点在二叉树中位置完全相同,则这棵二叉树称为”完全二叉树“。如下图:


把完全二叉树的节点与同样深度的满二叉树各个节点进行编号比较,所有节点出现的位置相同,则是完全二叉树。所以,满二叉树一定是一棵完全二叉树,而完全二叉树不一定是满二叉树。

完全二叉树的深度: h = [ l o g 2 ( n + 1 ) ] + 1 h=[log_2(n+1)]+1 h=[log2(n+1)]+1 ([ ]表示向下取整)

完全二叉树的特点:

  • 叶子节点只能出现在最下两层。
  • 最下层的叶子一定集中左部连续位置
  • 倒数第二层,若有叶子节点,一定都在右部连续位置。
  • 如果结点度为1,则该结点只有左孩子,即不存在右子树的情况。
  • 同样节点的二叉树,完全二叉树的深度最小。

二叉树的存储结构

1、二叉树顺序存储结构

二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,井且结点的存储位置,也就是数组的下标要能体现结点之间的逻辑关系。顺序存储一般只用于完全二叉树

2、二叉链表

二叉树每个结点最多有两个孩子,所以为官设计一个数据域和两个指针域,我们称这样的链表叫做二叉链表。

typedef struct BiTNode
{
    TElemType data; //结点数据
    struct BiTNode *lchild, *rchild;    //左右孩子结点
} BiTNode, *BiTree;

二叉树的遍历

二叉树的遍历(traversing binary tree)是指从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次

前序遍历

规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。

递归实现

void PreOrderTraverse(BiTree T)
 {
    if (T == NULL)
        return;
    cout << T->val;
    PreOrderTraverse(T->lchild);
    PreOrderTraverse(T->rchile);
 }

非递归实现

使用栈来实现前序遍历,首先将根节点压入栈中,然后打印栈顶元素。入栈和出栈的顺序是相反的,所以我们先将根节点的右子节点压入栈中,然后将左子节点也压入栈中,打印栈顶元素。操作完元素后让栈顶元素出栈,在压入该元素节点的右、左子节点,以此操作即可得出二叉树的前序遍历元素。

void PreOrderTraverse(BiTree T)
{
	if (T == NULL)
		return;
	stack<BiTree*> bi_tree;
	bi_tree.push(T->data);			// 先将根节点压入栈中
	while (!bi_tree.empty()) {
		BiTree* top = bi_tree.top();
		cout << top->val;			// 对栈顶元素操作
		bi_tree.pop();				// 栈顶元素出栈
		if (top->rchild != NULL)	// 压入该元素节点的右子节点
			bi_tree.push(top->rchild);
		if (top->lchild != NULL)	// 压入该元素节点的左子节点
			bi_tree.push(top->lchild);
	}
}

中序遍历

若树为空,则空操作返回。否则从根节点开始(并不是先访问根节点),中序遍历根节点的左子树,然后访问根节点,最后中序遍历右子树。

递归实现

 void InOrderTraverse(BiTree T)
 {
     if (T == NULL)
        return;
    InOrderTraverse(T->lchild);
    cout << T->val;
    InOrderTraverse(T->rchild);
 }

非递归实现

使用栈来实现二叉树的中序遍历,先将根节点压入栈中,然后一直遍历左子节点并压入栈中。完成之后将栈顶元素节点出栈并操作,再将出栈的这个节点的右子节点压入栈中,重复上述操作。

void InOrderTraverse(TreeNode* root)
{
	if (root == nullptr)
		return;
	stack<TreeNode*> bi_tree;
	while (!bi_tree.empty() || root != nullptr) {
		while (root != nullptr) {	// 遍历左子节点并压入栈中
			bi_tree.push(root);
			root = root->left;
		}
		root = bi_tree.top();	// 栈顶元素出栈
		bi_tree.pop();
		cout << root->val;		// 操作元素
		root = root->right;		// 将右子节点压入栈中
	}
}

后序遍历

规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。

递归实现

void PostOrderTraverse(BiTree T)
{
    if (T == NULL)
        return;
    PostOrderTraverse(T->lchild);
    PostOrderTraverse(T->rchild);
    cout << T->val;
}

非递归实现

使用两个栈来实现二叉树的后序遍历,我们很容易得到一种遍历方式:“根节点 -> 右子节点 -> 左子节点”,这种遍历方式是后序遍历的反序。根据栈 LIFO 的特性就可以实现后序遍历。
因此我们使用一个辅助栈来保存父节点,使用另一个栈保存我们需要的遍历结果的反序。

void postOrderTraverse(TreeNode* root)
{
	
	if (root == nullptr)
		return;
	stack<TreeNode*> res;
	stack<TreeNode*> tmp;
	tmp.push(root);
	while (!tmp.empty()) {
		TreeNode* top = tmp.top();
		tmp.pop();
		if (top->left != nullptr)
			tmp.push(top->left);
		if (top->right != nullptr)
			tmp.push(top->right);
		res.push(top);
	}
	while (!res.empty()) {
		top = res.top();
		cout << top->val;
		res.pop();
	}
}

层序遍历

规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。即对二叉树的广度优先搜索(BFS)。

对于层序遍历,我们可以借助队列 queue 容器来实现,首先压入根节点,每次遍历我们用队列首元素遍历其左子节点并入队,然后遍历其右子节点并入队,最后将队首元素打印并出队,一次迭代直到队列为空。

void levelOrderTraverse(TreeNode* root)
{
	queue<TreeNode*> level_node;
	if (root == nullptr)
		return;
	level_node.push(root);
	while (!level_node.empty()) {
		TreeNode* front = level_node.front();
		if (front->left != nullptr)
			level_node.push(front->left);
		if (front->right != nullptr)
			level_node.push(front->right);
		cout << front->val;
		level_node.pop();
	}
	cout << endl;
}

二叉树的相关题目

1. 重建二叉树

题目描述:输入二叉树的前序遍历和中序遍历结果,重建二叉树。

算法分析:如前序遍历序列 {1, 2, 4, 7, 3, 5, 6, 8} 和中序遍历序列 {4, 7, 2, 1, 5, 3, 8, 6},根据前序遍历特点,二叉树根节点为 1,在中序遍历序列中找到根节点 1,则根节点的前三个节点为左子树节点,后面的节点为右子树节点。根据中序遍历得到的结果,在前序遍历序列中根节点 1 后面的三个节点即为左子树节点。

BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
	if (preorder == nullptr || inorder == nullptr || length <= 0)
		return nullptr;
	return ConstructCore(preorder, preorder + length - 1,
			inorder, inorder + length - 1);
}
BinaryTreeNode* ConstructCore(int* stratPreorder, int* endPreorder
		int* startInorder, int* endInorder)
{
	// 前序遍历第一个数字就是根节点的值,初始化一个二叉树
	int rootValue = startPreorder;
	BinaryTreeNode* root = new BinaryTreeNode();
	root->m_value = rootValue;
	root->m_pLeft = root->m_pRight = nullptr;
	
	if (startPreorder == endPreorder) {
		if (startInorder == endInorder &&
			*startPreorder == *startInorder)
			return root;
		else
			throw std::exception("Invalid input.");
	}
	
	// 在中序遍历序列中找到根节点的值
	int* rootInorder = startInorder;
	while (rootInorder <= endInorder && *rootInorder != rootValue)
		++rootInorder;
	
	if (rootInorder == endInorder && *rootInorder != rootValue)
		throw std::exception("Invalid input.");
		
	int leftLength = rootInorder - startInorder;
	int *leftPreorderEnd = startPreorder + leftLength;
	if (leftLength > 0) {
		// 构建左子树
		root->m_pLeft = ConstructCore(startPreorder + 1,
				leftPreorderEnd, startInorder, rootInorder - 1);
	}
	if (leftLength < endPreorder - startPreorder) {
		// 构建右子树
		root->m_pRight = ConstructCore(leftPreorderEnd + 1,
				endPreorder, rootInorder + 1, endInorder);
	}
	return root;
}

2. 二叉树的下一个节点

题目描述:给定一个二叉树和其中的一个节点,找到中序遍历序列的下一个节点。树中节点有两个指向左右子节点的指针,和一个指向父节点的指针。

算法分析:分为三种情况:1)一个节点如果有右子树,则下一个节点就是它的右子树中最左子节点;2)节点没有右子树,且它是父节点的左子节点,则下一个节点就是它的父节点;3)节点没有右子树,且是父节点的右子节点,这种情况比较复杂,我们需要沿指向它父节点的指针一直向上遍历,直到找到一个节点是其父节点的左子节点,则这个节点的父节点就是下一个节点。

BinaryTreeNode* GetNext(BinaryTreeNext* pNode)
{
	if (pNode == nullptr)
		return nullptr;
		
	BinaryTreeNode* pNext = nullptr;
	// 情况1
	if (pNode->m_right != nullptr) {
		BinaryTreeNode* pRight = pNode->m_right;
		while (pRight->m_left != nullptr)
			pRight = pRight->m_left;
		pNext = pRight;
	}
	// 情况2和3
	else if(pNode->m_parent != nullptr) {
		BinartTreeNode* pCurrent = pNode;
		BinaryTreeNode* pParent = pNode->m_parent;
		while (pParent != nullptr && pCurrent == pParent->m_right) {
			pCurrent = pParent;
			pParent = pParent->m_parent;
		}
		pNext = pParent;
	}
	return pNext;
}

3. 二叉树的子结构

题目描述:输入两颗二叉树 A 和 B,判断 B 是不是 A 的子结构。

算法分析:查找树 A 中是否有和树 B 一样的子结构,可以分为两步:1)在树 A 中找到和树 B 根节点值一样的节点 R;2)判断 A 中以 R 为根节点的子树是不是包含和树 B 一样的结构。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        bool result = false;

        if (A != nullptr && B != nullptr) {
            if (Equal(A->val, B->val))
                result = DoesAHaveB(A, B);
            if (!result)
                result = isSubStructure(A->left, B);
            if (!result)
                result = isSubStructure(A->right, B);
        }
        return result;
    }
    bool DoesAHaveB(TreeNode* A, TreeNode* B) {
        if (B == nullptr)
            return true;
        if (A == nullptr)
            return false;
        if (!Equal(A->val, B->val))
            return false;
        return DoesAHaveB(A->left, B->left) 
        		&& DoesAHaveB(A->right, B->right);
    }
    bool Equal(int num1, int num2) {
        return num1 == num2 ? true : false;
    }
};

4. 二叉树的镜像

题目描述:完成一个函数,输出一棵二叉树的镜像。

算法分析:前序遍历二叉树,如果遍历的节点有子节点,就交换两个子节点的位置。

class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if (root == nullptr)
            return nullptr;
        if (root->left == nullptr && root->right)
        	return nullptr;
        TreeNode* pNode = root->left;
        root->left = root->right;
        root->right = pNode;
        if (root->left != nullptr)
            root->left = mirrorTree(root->left);
        if (root->right != nullptr)
            root->right = mirrorTree(root->right);

        return root;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 这道题目是关于如何利用二叉链表结构建立一棵二叉树,并能够实现二叉树的先序遍历、中序遍历和后序遍历三种遍历算法,能够用队列实现二叉树的层次遍历算法,并按层次输出(标出层号),并能统计树叶数、结点数、层高等。 ### 回答2: 二叉树是计算机科学中的一个重要概念,其本质上是一种树状结构,其中每个节点最多具有两个子节点。二叉树在计算机科学领域广泛应用,包括搜索树、表达式树、哈夫曼树等。下面我将就如何利用二叉链表结构来建立一棵二叉树,并递归实现它的先序遍历、中序遍历和后序遍历三种遍历算法,用队列实现层次遍历算法,并按层次输出,并能统计树叶数、节点数、层高等问题进行详细解答。 1.建立二叉树 建立二叉树可以通过二叉链表结构完成。所谓二叉链表结构,是指每个节点包含三个信息:节点值、左子节点和右子节点。以下是建立一棵二叉树的程序框架: ``` class Node: def __init__(self, val=None, left=None, right=None): self.val = val self.left = left self.right = right class BinaryTree: def __init__(self, val=None): self.root = Node(val) def insert(self, val): if not self.root: self.root = Node(val) else: self._insert(val, self.root) def _insert(self, val, node): if val < node.val: if not node.left: node.left = Node(val) else: self._insert(val, node.left) else: if not node.right: node.right = Node(val) else: self._insert(val, node.right) ``` 2.递归实现遍历算法 接下来,我们可以使用递归实现二叉树的先序遍历、中序遍历和后序遍历三种遍历算法。 (1)先序遍历,即先输出相应节点,再遍历左子树和右子树; ``` def preorder(self, node): if node: print(node.val) self.preorder(node.left) self.preorder(node.right) ``` (2)中序遍历,即先遍历左子树,再输出相应节点和右子树; ``` def inorder(self, node): if node: self.inorder(node.left) print(node.val) self.inorder(node.right) ``` (3)后序遍历,即先遍历左子树和右子树,再输出相应节点; ``` def postorder(self, node): if node: self.postorder(node.left) self.postorder(node.right) print(node.val) ``` 3.层次遍历算法 接下来,我们可以使用队列实现二叉树的层次遍历算法,并按层次输出(标出层号)。 ``` def levelorder(self): if not self.root: return [] queue = [self.root] res = [] level = 0 while queue: res.append([]) level_length = len(queue) for i in range(level_length): node = queue.pop(0) res[level].append(node.val) if node.left: queue.append(node.left) if node.right: queue.append(node.right) level += 1 return res ``` 4.统计树叶数、节点数、层高等 最后,我们可以通过递归,统计二叉树的叶数、节点数和深度。其中,深度可以使用递推的方式实现具体代码如下: ``` def count_leaves(self, node): if not node: return 0 if not node.left and not node.right: return 1 else: return self.count_leaves(node.left) + self.count_leaves(node.right) def count_nodes(self, node): if not node: return 0 return 1 + self.count_nodes(node.left) + self.count_nodes(node.right) def depth(self, node): if not node: return 0 return 1 + max(self.depth(node.left), self.depth(node.right)) ``` 以上就是关于如何利用二叉链表结构建立二叉树,并递归实现遍历算法、层次遍历算法,以及统计树叶数、节点数、层高等问题的详细解答。 ### 回答3: 二叉树特指每个节点最多只有两个子节点的树结构,节点的左右子节点顺序不同则构成不同的二叉树。二叉链表结构指利用指针来表示节点的数据结构,节点包含一个数据域和两个指向左右子节点的指针。 建立二叉树方式有多种,其中最基本的是先序遍历建立二叉树。即按照“根 - 左子树 - 右子树”的顺序从上至下递归遍历,并利用二叉链表结构生成树。先序遍历算法如下: 1. 若当前节点存在则输出该节点数据; 2. 若当前节点的左孩子节点非空,则递归遍历左子树; 3. 若当前节点的右孩子节点非空,则递归遍历右子树。 中序遍历算法按照“左子树 - 根 - 右子树”的顺序遍历,并利用递归算法实现。 1. 若当前节点的左孩子节点非空,则递归遍历左子树; 2. 若当前节点存在则输出该节点数据; 3. 若当前节点的右孩子节点非空,则递归遍历右子树。 后序遍历算法按照“左子树 - 右子树 - 根”的顺序遍历,并利用递归算法实现。 1. 若当前节点的左孩子节点非空,则递归遍历左子树; 2. 若当前节点的右孩子节点非空,则递归遍历右子树; 3. 若当前节点存在则输出该节点数据。 层次遍历算法需要利用队列数据结构实现具体算法如下: 1. 将根节点入队列; 2. 当队列非空时,弹出队头元素并输出该节点数据; 3. 若该节点的左孩子节点非空,则将其入队列; 4. 若该节点的右孩子节点非空,则将其入队列; 5. 重复2至4步直到队列为空。 统计树叶数、节点数和层高算法利用递归实现,统计的代码实现如下: //统计树叶数 int countLeaf(Tnode* root) { if (root == NULL) return 0; //空树则叶数为0 else if (root->left == NULL && root->right == NULL) return 1; //只有一个节点则叶数为1 else return countLeaf(root->left) + countLeaf(root->right); //递归计算左子树和右子树叶数之和 } //统计节点数 int countNode(Tnode* root) { if (root == NULL) return 0; //空树则节点数为0 else return countNode(root->left) + countNode(root->right) + 1; //递归计算左子树和右子树节点数之和加1 } //求层高 int getHeight(Tnode* root) { if (root == NULL) return 0; //空树则层高为0 else { int leftH = getHeight(root->left); //计算左子树的层高 int rightH = getHeight(root->right); //计算右子树的层高 return (leftH > rightH ? leftH : rightH) + 1; //返回左右子树层高较大值加1 } } 因此,利用上述算法,我们可以通过建立一棵二叉链表结构的二叉树实现递归实现二叉树的先序遍历、中序遍历和后序遍历三种遍历算法,并利用队列实现二叉树的层次遍历算法,并按层次输出(标出层号),并可以统计树叶数、节点数、层高等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值