二叉树的定义
:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
void visit(TreeNode* root)//对节点进行操作的函数,可自定义
{}
二叉树的建立(先序)
:
TreeNode* Preinitial()
{
int n;
TreeNode* root=new TreeNode();
if (cin >> n)
{
if (n==0) return NULL;
root->val = n;
root->left = Preinitial();
root->right = Preinitial();
}
else
return NULL;
return root;
}
这里根据先序的顺序依次输入每个节点的值,注意如果子树为空,需要输入0,最后返回数的根节点。
例如需要构造的树为:
依次输入的数据为:1 2 4 8 0 0 0 5 0 0 3 6 0 0 7 0 0 (回车换行结束)
二叉树的先序遍历(递归算法思路比较简单,不做过多介绍,下同):
递归:
void Preordertraversal(TreeNode* root)//递归算法
{
if (!root) return;
visit(root);
Preordertraversal(root->left);
Preordertraversal(root->right);
}
非递归:
void Preordertraversal(TreeNode* root)//非递归
{
TreeNode* p = root;
stack<TreeNode*> s;
while (p || !s.empty())
{
if (p)
{
if (p->right) s.push(p->right);
visit(p);
p = p->left;
}
else if (!s.empty())
{
p = s.top();
s.pop();
}
}
}
思路:
非递归的先序遍历需要借助一个栈来实现,首先p指向根节点,进入while循环,如果指针p指向的节点不为空,则访问它,再判断该节点的右子树是否为空,如果不为空则将右子树压入栈中(因为访问顺序为根节点、左子树、右子树,压入栈中的为最后需要访问的右子树)访问完该节点后,p指针指向该节点的左子树,一旦p指针赋空,则从栈中弹出元素赋给p,重复以上操作直到p为空且栈为空。
二叉树的中序遍历:
递归:
void Sequentialtraversal(TreeNode* root)//递归算法
{
if (!root) return;
Sequentialtraversal(root->left);
visit(root);
Sequentialtraversal(root->right);
}
非递归:
void Sequentialtraversal(TreeNode* root)//非递归算法
{
stack<TreeNode*> s;
TreeNode* p = root;
while (p || !s.empty())
{
while (p)
{
s.push(p);
p = p->left;
}
if (!s.empty())
{
p = s.top();
s.pop();
visit(p);
p = p->right;
}
}
}
思路:
中序遍历同样要借助一个栈,首先我们将指针p指向最初的根节点,然后p一直往左走,直到指针p赋空,把经过的节点全压入栈中。一旦p赋空,从栈中弹出元素赋给p,访问p,然后再将p指向所指节点的右子树,重复以上操作直到p为空且栈为空。
二叉树的后序遍历:
递归:
void Postordertraversal(TreeNode* root)//递归算法
{
if (!root) return;
Postordertraversal(root->left);
Postordertraversal(root->right);
visit(root);
}
非递归:
void Postordertraversal(TreeNode* root)//递归算法
{
stack<TreeNode*> s, output;
TreeNode* p = root;
while (p || !s.empty())
{
while (p)
{
s.push(p);
output.push(p);
p = p->right;
}
if (!s.empty())
{
p = s.top();
s.pop();
p = p->left;
}
}
while (!output.empty())
{
visit(output.top());
output.pop();
}
}
思路:
关于二叉树后续遍历的非递归算法,有很多种,有的需要改变二叉树的定义结构,添加线索,我这里给出了个人认为最好理解的一种算法,该算法需要借助两个栈。指针p开始指向最初的根节点,然后p一直想右走,将经过的节点全部压入栈s和栈output中,一旦p赋空,从栈s中弹出元素分给指针p,然后p指向节点的左子树,重复以上操作直到p为空且栈s为空。
最后将栈output中的节点全部弹,依次排列即为二叉树的后续遍历。
例如
二叉树求最大高度:
int max(int a, int b)
{
return a > b ? a : b;
}
int getHighth(TreeNode* root)
{
if (!root) return 0;
return 1 + max(getHighth(root->left), getHighth(root->right));
}
思路:
这里递归求树的高度比较方便,而且容易理解。首先从根节点出发,分别进入左右子树求高度,同时返回其中较大的高度加1,即为树的最大高度,递归的出口为指针赋空时,说明递归到了叶子节点。
二叉树求最大宽度:
int getWidth(TreeNode* root)
{
TreeNode* p = root;
queue<TreeNode*> q;
q.push(p);
int maxwidth = -1;
while (!q.empty())
{
int size = q.size();
if (size > maxwidth) maxwidth = size;
for (int i = 0; i < size; ++i)
{
p = q.front();
q.pop();
if (p->left) q.push(p->left);
if (p->right) q.push(p->right);
}
}
return maxwidth;
}
思路:
层序遍历二叉树,借助队列,根节点入队,然后记录下队列的长度,即为二叉树该层节点的个数,然后依次循环将该层节点全部出队,出队的同时左右子节点入队(如果不为空),再次记录下队列的长度,并且记录下队列的最大长度,即为二叉树的最大宽度。
二叉搜索树的插入:
void insert(TreeNode* root, int n)//root指向需要操作的树的根节点,n为待插入节点的值
{
if (!root) return;
if (n > root->val)
{
if (!root->right)
{
root->right = new TreeNode(n);
return;
}
insert(root->right, n);
}
else if (n < root->val)
{
if (!root->left)
{
root->left = new TreeNode(n);
return;
}
insert(root->left, n);
}
}
**注:**二叉搜索树的插入可能存在若干种正确的操作,我这里写的是最简单的一种,即在树的节点的空出的左右子树位置进行插入。
思路:
判断值大于还是小于节点的值,如果大于节点的值,则在此节点的右子树进行插入,如果节点的右子树恰好为空,则以该值新建立一个节点作为该节点的右子树。总的来说是递归的操作。
求二叉树指定节点的父亲节点:
TreeNode* getparent(TreeNode* root,TreeNode* goal)//root为树的根节点,goal为要查找父亲的节点
{
if (!root||!goal) return NULL;
if (root->left == goal || root->right == goal) return root;
TreeNode* parent = getparent(root->left, goal);
if (parent) return parent;
else return getparent(root->right, goal);
}
二叉树搜索树的删除:
void deleteNode(TreeNode* root,TreeNode* goal)
{
TreeNode* parent = getparent(root, goal);
if (!goal->left && !goal->right)//情况1
{
if (parent->left == goal) parent->left = NULL;
else parent->right = NULL;
delete goal;
return;
}
if (goal->left && !goal->right)//情况2
{
if (parent->left == goal) parent->left = goal->left;
else parent->right = goal->left;
delete goal;
return;
}
if (!goal->left&&goal->right)//情况2
{
if (parent->left == goal) parent->left = goal->right;
else parent->right = goal->right;
delete goal;
return;
}
if (goal->left&&goal->right)//情况3
{
TreeNode* p = goal->right;
if (!p->left)
{
if (parent->left == goal) parent->left = p;
else parent->right = p;
p->left = goal->left;
delete goal;
return;
}
while (p->left) p = p->left;
TreeNode* parent_p = getparent(root, p);
if (parent->left == goal) parent->left = p;
else parent->right = p;
p->left = goal->left;
parent_p->left = p->right;
p->right = goal->right;
delete goal;
return;
}
}
(getparent函数请参考上面的定义代码)
删除操作要分三种情况:
设待删除的节点为z
- 情况1:
如果z没有孩子节点,那么只是简单地将它删除,并修改它的父节点,用NULL作为孩子节点来替换z
- 情况2:
如果z只有一个孩子,那么将这个孩子提升到树中z的位置上,并修改z的父节点,用z的孩子来替换z
- 情况3:
如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树,这种情况稍微麻烦,因为还与y是否为z的右孩子相关
判断是否为完全二叉树:
bool iscompletetree(TreeNode* root)
{
if (!root) return false;
queue<TreeNode*> q;
TreeNode* p=root;
q.push(p);
while (!q.empty())
{
int size = q.size();
int flag = 0;
for (int i = 0; i < size; ++i)
{
p = q.front();
q.pop();
if (!p->left&&p->right) return false;
if (p->left && !p->right)
{
if (flag == 0) flag = 1;
else return false;
}
if (p->left&&p->right&&flag == 1) return false;
if (p->left) q.push(p->left);
if (p->right) q.push(p->right);
}
}
return true;
}
思路:
采用层序遍历的方法,依次遍历二叉树的每一层,这里我们将节点的类型分为四种,第一种为有左右子树的节点、第二种为只有左子树的节点、第三种为只有右子树的树节点、第四种为叶子节点。其中第三种为非法节点,一旦出现即返回假,第二种从左到右不能连续出现,连续出现即返回假,二、一从左到右不能连续出现,出现即返回假。这里我们用flag来记录是否连续出现。
后注:笔者不才,因为代码全部为我一个人编写和测试,所有难免会有遗漏疏忽的地方,欢迎批评指正