一、二叉树概念
1. 二叉树的定义
二叉树是有序树,有左右之分,可为空。每个结点至多有俩子树。
- 满二叉树:高度
h
h
h,有2h- 1个结点,
- 每层含有最多的结点,即:每行都是满的。
- 结点i的双亲若存在,则为 ⌊ i 2 ⌋ \left\lfloor \frac { i }{ 2 } \right\rfloor ⌊2i⌋。
- 完全二叉树:高度
h
h
h,有
n
n
n个结点,
- 前n-1行都满,最后一行从左开始排,可不满。
- 若 i ≤ ⌊ n 2 ⌋ i\le \left\lfloor \frac { n }{ 2 } \right\rfloor i≤⌊2n⌋ ,则结点 i i i为分支结点,否则为叶结点。
- 二叉排序树 - BST(又名:二叉搜索树、二叉查找树):
- 空二叉树 or 每个“根节点”的左子树所有结点 < 该根节点 < 右子树所有结点的二叉树。
- 平衡二叉树 - AVL:
- 树上任一结点左右子树深度之差<1。(树的深度:树中最大的结点层)
2. 二叉树的存储结构
- 顺序存储结构:一维数组下标从1开始存,适合存储满2叉or完全2叉。
- 链式存储结构:含有n个结点的二叉链表中,有n+1个空链域。
typedef struct Tree{
int value;
Tree* pLeft;
Tree* pRight;
}Tree,*binaryTree;
二、二叉树的遍历
前序、中序、后序以及层次遍历(递归与非递归)
1. 如何遍历一棵树
有两种通用的遍历树的策略:
- 深度优先搜索(DFS)
- 在这个策略中,我们采用深度作为优先级,以便从跟开始一直到达某个确定的叶子,然后再返回根到达另一个分支。
- 深度优先搜索策略又可以根据根节点、左孩子和右孩子的相对顺序被细分为:先序遍历,中序遍历和后序遍历。
- 广度优先搜索(BFS)
- 我们按照高度顺序,一层一层的访问整棵树,高层次的节点将会比低层次的节点先被访问到。
2. 二叉树遍历的递归实现
二叉树的先序遍历NLR
/* 递归实现前序遍历 */
void preOrderRecur(binaryTree T){
if(T == NULL)
return;
printf("%d ",T->value);
preOrderRecur(T->pLeft);
preOrderRecur(T->pRight);
}
二叉树的中序遍历LNR
/* 递归实现中序遍历 */
void inOrderRecur(binaryTree T){
if(T == NULL)
return;
inOrderRecur(T->pLeft);
printf("%d ", T->value);
inOrderRecur(T->pRight);
}
二叉树的后序遍历LRN
/* 递归实现后续遍历 */
void posOrderRecur(binaryTree T){
if(T == NULL)
return;
posOrderRecur(T->pLeft);
posOrderRecur(T->pRight);
printf("%d ", T->value);
}
3. 二叉树遍历的非递归实现
二叉树的先序遍历NLR
(1)将根节点压入栈;
(2)弹出栈顶元素,打印栈顶元素的值,如果该元素的右子树不为空,则先将右子数压入栈,如果该元素的左子树也不为空,则将该元素的左子树也压入栈;
(3)重复步骤2直到栈为空。
注:假设有结点root-L(LL、LR)-R(RL、RR)
弹出 | 根节点root | L | LL | LR | R | RL | RR | |
---|---|---|---|---|---|---|---|---|
栈顶 | 根节点root | 根节点的左子树 L | L的左子树 LL | LR | R | RL | RR | |
根节点的右子树 R | L的右子树 LR | R | RR | |||||
R |
/* 非递归实现先序遍历,用栈,LeetCode144 */
void preOrderUnRecur(binaryTree T){
if(T == NULL)
return;
stack<binaryTree> S; //定义一个栈
S.push(T); //压入根节点
while(!S.empty()){
//弹出栈顶元素
binaryTree tempNode = S.top();
S.pop();
printf("%d ", tempNode->value);
//先压入右子树,后压入左子树
if(tempTree != NULL)
S.push(tempNode->pRight);
if(tempTree != NULL)
s.push(tempNode->pLeft);
}
}
二叉树的中序遍历LNR
(1)定义一个当前节点,将根节点指针赋给当前节点;
(2)如果当前节点不为空,就将当前节点压入栈,并将当前节点更新为其左子树;
(3)如果当前节点为空,就弹出栈顶元素,并打印该元素值。然后将当前节点更新为该元素值的右子树;
(4)一直重复(2)或(3)直到栈为空而且当前元素值也为空。
例如:root - L(LL, LR) - R
弹出 | LL | L | LR | root | R | |||||
---|---|---|---|---|---|---|---|---|---|---|
当前节点 | root | L | LL | 空(LLL) | 空(LLR) | LR | 空(LRL) | 空(LRR) | R | 空(RL) |
栈顶 | root | L | LL | L | root | LR | root | R | ||
root | L | root | root | |||||||
root |
/* 非递归实现中序遍历 LeetCode94*/
void inOrderUnrecur(binaryTree T){
if(T == NULL)
return;
stack<binaryTree> S;
binaryTree tempNode = T; // tempNode是遍历指针,定义一个当前节点,将根节点指针赋给当前节点;
while(!S.empty() || tempNode != NULL) // 栈不空或temp不空时循环
{ // 先把temp压入栈中,依次把左边界压入栈中,即不停的令cur=cur.left,重复步骤2
if(tempNode != NULL)
{
S.push(tempNode);
tempNode = tempNode->pLeft;
}
else//不断重复2直到为null,从栈中弹出栈顶节点,打印它的值,并令temp=node.right,重复步骤2
{
tempNode = S.top();
printf("%d ",tempNode->value);//第二次遇见的时候输出
S.pop();
tempNode = tempNode->pRight;
}
}
}
二叉树的后序遍历LRN
迭代 莫里斯遍历
/*非递归实现后序遍历 LeetCode145*/
/*** Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
//迭代,利用莫里斯遍历,核心模板如下:利用一个pre指针保存前一个结点的情况来判断是否重复计数
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> mystack;
vector<int> ans;
TreeNode* curr = root;
TreeNode* pre = NULL;
while(curr || !mystack.empty()){
while(curr){
mystack.push(curr);
curr = curr->left;
}
curr = mystack.top();
//若右节点已经访问过或者没有右节点 则输出该节点值
if(!curr->right || pre == curr->right){
mystack.pop();
ans.push_back(curr->val);
pre = curr;
curr = NULL;
}else{ // 右节点存在且没有访问过
curr = curr->right;
pre = NULL;
}
}
return ans;
}
};
双栈法
/* 双栈法,栈1从左至右依次层次遍历,栈2作为记录栈,最后将栈2中结点值逆序输出(正常出栈)即可 */
// 出栈2:左右根,那么入2:根右左,那么1出:根右左,1入就是先左后右
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk1,stk2;
if(root==NULL)
return res;
stk1.push(root);
TreeNode* temp;
while(!stk1.empty()){
temp=stk1.top();
stk1.pop();
stk2.push(temp);
if(temp->left!=NULL)
stk1.push(temp->left);
if(temp->right!=NULL)
stk1.push(temp->right);
}
while(!stk2.empty()){
res.push_back(stk2.top()->val);
stk2.pop();
}
return res;
}
} ;
4. 二叉树的层序遍历
按层遍历就是二叉树的广度优先搜索
(1)将二叉树的根从头部压入队列
(2)从队列尾部弹出一个元素,打印该元素值,然后将如果该元素左子树不为空,则将该元素的左子树从头部压入队列,右子树同左子树的操作;
(3)重复过程2直到队列为空。
/* 按层遍历二叉树 */
//用一个队列保存数据
void floorOrder(binaryTree T){
if(T == NULL)
return;
deque<int> Q; //辅助队列Q
Q.push_back(T); //根节点入队列
while(!Q.empty()){ // 队列不空循环
binaryTree tempNode = Q.front();// 队列头
printf("%d ", tempNode->value); // 访问当前节点,打印该元素值
if(tempNode->pLeft != NULL)
Q.push_back(tempNode.pLeft); // 左子树不空,左子树入队列
if(tempNode->pRight != NULL)
Q.push_back(tempNode.pRight);
Q.pop_front(); // 队列头出队
}
}
递归是解决二叉树相关问题的神级方法;