一、基本概念
每个结点最多有两棵子树,左子树和右子树,次序不可以颠倒。
性质:
1、非空二叉树的第n层上至多有2^(n-1)个元素。
2、深度为h的二叉树至多有2^h-1个结点。
3、对任何一棵二叉树T,如果其终端结点数(即叶子结点数)为n0,度为2的结点数为n2,则n0 = n2 + 1。
满二叉树:所有终端都在同一层次,且非终端结点的度数为2。
在满二叉树中若其深度为h,则其所包含的结点数必为2^h-1。
完全二叉树:除了最大的层次即成为一颗满二叉树且层次最大那层所有的结点均向左靠齐,即集中在左面的位置上,不能有空位置。
对于完全二叉树,设一个结点为i则其父节点为i/2,2i为左子节点,2i+1为右子节点。
二、二叉树的遍历
遍历二叉树的所有结点且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。
前序遍历:根节点->左子树->右子树(根节点在前面)
中序遍历:左子树->根节点->右子树(根节点在中间)
后序遍历:左子树->右子树->根节点(根节点在后边)
例如:求下面树的三种遍历
前序遍历:abdefgc
中序遍历:debgfac
后序遍历:edgfbca
广度遍历:abcdfeg
三、代码实现
struct Node
{
int val;
Node * left;
Node * right;
Node(int x) :val(x), left(nullptr), right(nullptr){};
};
1、前序遍历:递归与非递归
//递归
void preOrder(Node * root)
{
if (root != nullptr)
{
visit(root);
preOrder(root->left);
preOrder(root->right);
}
}
//迭代
void preOrderF(Node * root)
{
if (root == nullptr)
return;
stack<Node *> s;
s.push(root);
Node *p = nullptr;
while (!s.empty())
{
p = s.top();
s.pop();
cout << p->val << " ";
if (p->right)
s.push(p->right);
if (p->left)
s.push(p->left);
}
}
2、中序遍历
//递归
void inOrder(Node * root)
{
if (root != nullptr)
{
inOrder(root->left);
visit(root);
inOrder(root->right);
}
}
//迭代
void inOrderF(Node * root)
{
if (root == nullptr)
return;
stack<Node *> s;
Node *p = root;
while (p||!s.empty())
{
if (p)
{
s.push(p);
p = p->left;
}
else
{
p = s.top();
s.pop();
cout << p->val << " ";
p = p->right;
}
}
}
3、后序
递归:
void postOrder(Node * root)
{
if (root != nullptr)
{
postOrder(root->left);
postOrder(root->right);
visit(root);
}
}
三种非递归遍历中,后序遍历是比较难理解的。后序遍历必须保证左子树和右子树都访问完以后才能访问根节点,而能访问根节点的可能是有两种:右子树已经被访问或者当前根节点的右子树为NULL。
给出几种方法:
A、申请一个栈用于存放节点,申请一个容器vector存放数据(存放倒序的数据),此法空间消耗大点
void postOrderF(Node * root)
{
if (root == nullptr)
return;
stack<Node *> s;
vector<int> rs;
s.push(root);
Node *p = nullptr;
while (!s.empty())
{
p = s.top();
s.pop();
rs.insert(rs.begin(),p->val);
if (p->left)
s.push(p->left);
if (p->right)
s.push(p->right);
}
for (int i = 0; i < rs.size(); i++)
cout << rs[i] << " ";
}
B、一个标记位pos,当当前根节点右子树和pos相等时,说明已经可以访问该根节点了。
void _PostOrder_NonR(Node* root) //非递归--后序
{
Node* cur = root;
Node* pos = root;
stack<Node*> s;
while (cur || !s.empty())
{
while (cur)
{
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
if (top->_right == NULL || top->_right == pos)
//top存的是当前的根节点,当top的右子树为空或者top的右子树为pos,说明右子树已经遍历过,
//这时就可以访问当前的根节点了
{
cout<<top->_data<<" ";
pos = top;
s.pop();
}
else
{
cur = top->_right;
}
}
cout<<endl;
}
C、只用一个栈,两个指针(个人觉得这个好)
对于任一结点p,先将其入栈。若p不存在左孩子和右孩子,则可以直接访问它。或者p存在左孩子或者右孩子,但是左孩子和右孩子都已经被访问过了,则可以直接访问该结点。
若非上述两种情况,则将右孩子和左孩子依次入栈。这样可以保证每次取栈顶元素时,左孩子在右孩子前面被访问,根结点在左孩子和右孩子访问之后被访问。
//后序遍历的非递归法
void postOrder(BinaryTreeNode* pRoot)
{
stack<BinaryTreeNode*> s;
BinaryTreeNode *cur;
BinaryTreeNode *pre=NULL;
s.push(pRoot);//根结点入栈
while(!s.empty())
{
cur=s.top();
if((cur->left==NULL&&cur->right==NULL)||(pre!=NULL&&(pre==cur->left||pre==cur->right)))
{
//左孩子和右孩子同时为空,或者当前结点的左孩子或右孩子已经遍历过了
cout<<cur->value<<" ";
s.pop();
pre=cur;
}
else
{
if(cur->right!=NULL)
s.push(cur->right);
if(cur->left!=NULL)
s.push(cur->left);
}
}
}
4、广度遍历,剑指offer上
题目:从上到下打印二叉树的每个节点,同层的节点按照从左向右打印。
解析:即分层遍历二叉树。利用广度优先遍历的思想,遍历树或者有向图,都可在队列中完成。
step1:把起始节点放入队列。
step2:每次从队头取出节点,遍历(输出)。接下来,把从该节点能到达的节点(子树或者其他)都依次放入队列。
重复step2,直到队列中的节点为空。
void PrintFromTopToBottom(Node* pRoot)
{
if(pRoot == NULL)
return;
std::deque<Node *> deque;
deque.push_back(pRoot);
while(deque.size())
{
Node *pNode = deque.front();
deque.pop_front();
printf("%d ", pNode->m_nValue);
if(pNode->m_pLeft)
deque.push_back(pNode->m_pLeft);
if(pNode->m_pRight)
deque.push_back(pNode->m_pRight);
}
}
参考:
https://blog.youkuaiyun.com/u014465639/article/details/71076092
https://blog.youkuaiyun.com/qq_33951180/article/details/52687692
https://blog.youkuaiyun.com/xiaominkong123/article/details/51567437