二叉树的基本操作(遍历、删除、插入、求最大高度、求最大宽度等)

二叉树的定义

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
情况1

  • 情况2:

如果z只有一个孩子,那么将这个孩子提升到树中z的位置上,并修改z的父节点,用z的孩子来替换z
情况2

  • 情况3:

如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树,这种情况稍微麻烦,因为还与y是否为z的右孩子相关
情况3

判断是否为完全二叉树:

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来记录是否连续出现。

后注:笔者不才,因为代码全部为我一个人编写和测试,所有难免会有遗漏疏忽的地方,欢迎批评指正

发布了90 篇原创文章 · 获赞 7 · 访问量 2165

猜你喜欢

转载自blog.csdn.net/weixin_43784305/article/details/103102032
今日推荐