数据结构---树

Posted Nireus_LOVE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构---树相关的知识,希望对你有一定的参考价值。

1. 二叉树

可以采用顺序存储数组和链式存储二叉链表两种方法来存储二叉树。经常使用的二叉链表方法,因为其非常灵活,方便二叉树的操作。二叉树的二叉链表存储结构如下所示:

typedef struct binary_tree_node
{
    int elem;
    struct binary_tree_node *left;
    struct binary_tree_node *right;
}binary_tree_node,*binary_tree;

2. 判断树二叉树是不是平衡二叉树序

问题描述:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
思路:对于树的题目,第一反应就是用递归。对于以某个结点为根的树,只需计算出它的左右子树的深度,如果深度相差小于等于1,则递归判断它的左右子树是不是平衡树;否则肯定不是平衡二叉树。这个问题的关键是要计算树的深度,如果是自顶向下,会有很多重复的计算。计算以1为根的树的深度,会牵涉到以2为根、以3为根的子树。计算以2为根的树的深度,会牵涉到以4为根、以5为根的子树。由于要遍历每个结点,判断以该结点为根的树是不是平衡二叉树。所以计算以1为根的树的深度,与计算以2为根的树的深度,会重复计算以4为根、以5为根的子树的深度。
消除重复办法,当时是能记录下之前计算过的子树的深度,下次使用就不用重新计算。这就需要自底向上的计算深度。庆幸的是递归解决树的问题,就是自底向上的过程。因为我们在递归求解中,先要得出子树的解,子树的解最终会转换为叶结点的解。可以利用后序遍历的方法,遍历每个结点时,先判断它的左右子树是不是平衡二叉树,同时记录下左右子树的深度,然后判断该结点为根的树是不是平衡二叉树,至于该树的深度计算很方便,取左右子树中较大的深度+1就可以了。这里左右子树的深度在递归求解中已经计算出来,不需要重复计算了。

1.  struct BinaryTreeNode  
2.  {  
3.      int data;  
4.      BinaryTreeNode *pLeft;  
5.      BinaryTreeNode *pRight;  
6.  };  
7.  //函数功能 : 判断二叉树是不是平衡的  
8.  //函数参数 : pRoot为根结点,pDepth为根结点的深度。  
9.  //返回值 :   是否平衡的  
10. bool IsBalanced(BinaryTreeNode *pRoot, int *pDepth)  
11. {  
12.     if(pRoot == NULL)  
13.     {  
14.         *pDepth = 0;  
15.         return true;  
16.     }  
17.     int leftDepth, rightDepth; //左右子树的深度  
18.     if(IsBalanced(pRoot->pLeft, &leftDepth)&&  
19.         IsBalanced(pRoot->pRight, &rightDepth))  
20.     {  
21.         int diff = leftDepth - rightDepth;  
22.         if(diff == 0 || diff == 1 || diff == -1)  //相差为0或1或-1  
23.         {  
24.             *pDepth = 1 + (leftDepth > rightDepth ? leftDepth: rightDepth);   
25.             return true;  
26.         }  
27.         else  
28.             return false;  
29.     }  
30.     return false;  
31. }  
32. 
33. #include<iostream>  
34. #define N 7  
35. using namespace std;  
36.   
37. typedef struct node  
38. {  
39.     struct node *leftChild;  
40.     struct node *rightChild;  
41.     int data;  
42. }BiTreeNode, *BiTree;  
43.   
44. //生成一个结点  
45. BiTreeNode *createNode(int i)  
46. {  
47.     BiTreeNode * q = new BiTreeNode;  
48.     q->leftChild = NULL;  
49.     q->rightChild = NULL;  
50.     q->data = i;  
51.   
52.     return q;  
53. }  
54.   
55. BiTree createBiTree()  
56. {  
57.     BiTreeNode *p[N] = {NULL};  
58.     int i;  
59.     for(i = 0; i < N; i++)  
60.         p[i] = createNode(i + 1);  
61.   
62.     // 把结点连接成树  
63.     for(i = 0; i < N/2; i++)  
64.     {  
65.         p[i]->leftChild = p[i * 2 + 1];  
66.         p[i]->rightChild = p[i * 2 + 2];  
67.     }  
68.   
69.     return p[0];  
70. }  
71. void createBiTree(BiTree &T)  
72. {  
73.     char c;  
74.     cin >> c;  
75.     if('#' == c)  
76.         T = NULL;  
77.     else  
78.     {  
79.         T = new BiTreeNode;  
80.         T->data = c;  
81.         createBiTree(T->leftChild);  
82.         createBiTree(T->rightChild);  
83.     }  
84. }  
85. void PrintTreeByLevel(TreeNode *pHead)
86. {
87.     if (NULL == pHead)
88.     {
89.         return;
90.     }
91.     vector<TreeNode*> vec;
92.     vec.push_back(pHead);int cur = 0;
93.     int last = 0;
94.     while(cur < vec.size())
95.     {
96.         last = vec.size();
97.         while (cur < last)
98.         {
99.             cout<<vec[cur]->m_nValue<<"  ";
100.                if (NULL != vec[cur]->m_pLeft)
101.                {
102.                    vec.push_back(vec[cur]->m_pLeft);
103.                }
104.                if (NULL != vec[cur]->m_pRight)
105.                {
106.                    vec.push_back(vec[cur]->m_pRight);
107.                }
108.                cur++;
109.            }
110.            cout<<endl;
111.        }
112.    }

3. 输入一颗二元查找树,将该树转换为它的镜像

问题描述:输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点。用递归和循环两种方法完成树的镜像转换。例如输入:
8
/ /
6 10
// //
5 7 9 11
输出:
8
/ /
10 6
// //
11 9 7 5
思路:题目要求用两种方法,递归和循环,其实质是一样的。
解法一:用递归。假设当前结点为pNode,只需交换该结点的左右子女,然后分别递归求解左子树和右子树即可。代码极为简单。
解法二:用循环,需要一个辅助栈完成,每次取栈顶元素交换左右子女,然后将左右子女分别压入辅助栈,当栈中元素为空时,结束循环。其实不论是递归也好,循环也好,都是利用栈的特性完成。

1.  //函数功能 : 输入一颗二元查找树,将该树转换为它的镜像  
2.  //函数参数 : pRoot为根结点  
3.  //返回值 :   根结点  
4.  BSTreeNode * Mirror_Solution1(BSTreeNode * pRoot)  
5.  {  
6.      if(pRoot != NULL)  
7.      {  
8.          BSTreeNode * pRight = pRoot->right;  
9.          BSTreeNode * pLeft = pRoot->left;  
10.         pRoot->left = Mirror_Solution1(pRight);  //转化右子树  
11.         pRoot->right = Mirror_Solution1(pLeft);  //转化左子树  
12.     }  
13.     return pRoot;  
14. }  
1.  BSTreeNode * Mirror_Solution2(BSTreeNode * pRoot)  
2.  {  
3.      if(pRoot != NULL)  
4.      {  
5.          stack<BSTreeNode *> stk;   //辅助栈  
6.          stk.push(pRoot);           //压入根结点  
7.          while(stk.size())  
8.          {  
9.              BSTreeNode *pNode = stk.top(); //top有返回值
10.             BSTreeNode *pLeft = pNode->left;  
11.             BSTreeNode* pRight = pNode->right;  
12.             stk.pop();  //没有返回值
13.   
14.             if(pLeft != NULL)  
15.                 stk.push(pLeft);  
16.             if(pRight != NULL)  
17.                 stk.push(pRight);  
18.             pNode->left = pRight;  //交换左右子女  
19.             pNode->right = pLeft;  
20.         }  
21.     }  
22.     return pRoot;  
23. }   

4. 判断整数序列是不是二元查找树的后序遍历结果

二元查找树: 它首先要是一棵二元树,在这基础上它或者是一棵空树;或者是具有下列性质的二元树: (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3)左、右子树也分别为二元查找树
问题描述:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。如果是返回true,否则返回false。例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果:
8
/ /
6 10
/ / / /
5 7 9 11
因此返回true。如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。
思路:分析后序遍历的特点,序列的最后一个数应该是根结点,剩余的节点分为两个连续的子序列,前一子序列的值小于最后一个数,后一子序列的值大于最后一个数。然后递归求解这两个子序列。
如果是判断是前序遍历也很简单,只不过根节点变为了第一个数,剩余的节点也是分为两个连续的子序列。如果判断是中序遍历,更方便,只需扫描一遍,检查序列是不是排好序的,如果没有排好序,就不是中序遍历的结果。

bool verifySquenceOfBST(int squence[], int length)
{
      if(squence == NULL || length <= 0)//数组名当指针用
            return false;

      // root of a BST is at the end of post order traversal squence
      int root = squence[length - 1];

      // the nodes in left sub-tree are less than the root
      int i = 0;
      for(; i < length - 1; ++ i)
      {
            if(squence[i] > root) //找到序列中右子树的第一个结点
                  break;
      }
       //找到序列中右子树的第一个结点,并以此为分隔点判断左右部分是否满足
      // the nodes in the right sub-tree are greater than the root
      int j = i;
      for(; j < length - 1; ++ j)
      {
            if(squence[j] < root)
                  return false;
      }

      // verify whether the left sub-tree is a BST
      bool left = true;
      if(i > 0)
            left = verifySquenceOfBST(squence, i);

      // verify whether the right sub-tree is a BST
      bool right = true;
      if(i < length - 1)
            right = verifySquenceOfBST(squence + i, length - i - 1);

      return (left && right);
}  

5. 在二元树中找出和为某一值的所有路径(树)

问题描述:输入一个整数和一棵二元树。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。
例如输入整数22和如下二元树
10
/ /
5 12
/ /
4 7
则打印出两条路径:10, 12和10, 5, 7。
二元树节点的数据结构定义为:
struct BinaryTreeNode
{
int data;
BinaryTreeNode *pLeft;
BinaryTreeNode *pRight;
};
思路:递归的思想。很多树的题目都是用递归解决的,例如把二元查找树转变成排序的双向链表(树)。递归的终止条件为当前为空结点或当前结点的值大于剩余和。如果当前结点的值等于剩余和,并且是叶结点,那么打印路径。否则,将剩余和减去当前结点的值,递归求解。至于路径的记录,可以利用栈的思想来实现。

1.  void FindPath(BinaryTreeNode *pNode,int sum,vector<int> &path)  
2.  {  
3.      //结点为空或值大于当前和  
4.      if(pNode == NULL || pNode->data > sum)  
5.          return;  
6.      path.push_back(pNode->data);  
7.      //判断是不是叶结点  
8.      bool isLeaf = (pNode->pLeft == NULL && pNode->pRight == NULL)? true: false;  
9.      //找到一条路径,打印  
10.     if(pNode->data == sum && isLeaf)  
11.     {  
12.         vector<int>::iterator iter = path.begin();  
13.         for(; iter != path.end(); iter++)  
14.             cout<<*iter<<' ';  
15.         cout<<endl;  
16.     }  
17.     else  
18.     {  
19.         //求剩余和  
20.         sum = sum - pNode->data;   
21.         //递归求解  
22.         FindPath(pNode->pLeft, sum, path);   
23.         FindPath(pNode->pRight, sum, path);  
24.     }  
25.     path.pop_back();  //走不到这里???
26. } 

6. 把二元查找树转变成排序的双向链表

问题描述:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求不能创建任何新的结点,只调整指针的指向。
10
/ /
6 14
/ / / /
4 8 12 16
转换成双向链表4=6=8=10=12=14=16。
思路:利用递归的思想求解,分别调整某结点的左右子树,调整完后,将该结点的左指针指向左子树的最大节点,右指针指向右子树的最小节点。

1.  BSTreeNode * Convert(BSTreeNode *node)  
2.  {  
3.      if(node == NULL)  
4.          return NULL;  
5.      BSTreeNode *leftMax,*rightMin;  
6.      leftMax = node->left;      
7.      rightMin = node->right;  
8.      //找到左子树的最大结点  
9.      while(leftMax != NULL && leftMax->right != NULL)  
10.         leftMax = leftMax->right;  
11.     //找到右子树的最小结点  
12.     while(rightMin != NULL && rightMin->left != NULL)  
13.         rightMin = rightMin->left;  
14.     //递归求解  在此之前未改变树的结构,
15.     //先递归左右子树,再改变node的左右指针
16.     Convert(node->right);   
17.     Convert(node->left);  
18.     //将左右子树同根结点连起来,只不过是以兄弟的关系  
19.     if(leftMax != NULL)  
20.         leftMax->right = node;  
21.     if(rightMin != NULL)  
22.         rightMin->left = node;  
23.     node->left = leftMax;  
24.     node->right = rightMin;  
25.     return node;  
26. }  
1.  测试当中,需要建立二叉搜索树,下面给出建立及遍历二叉树的代码。
2.  struct BSTreeNode  
3.  {  
4.      int value;  
5.      BSTreeNode *left;  
6.      BSTreeNode *right;  
7.  };  
8.  BSTreeNode * Insert(BSTreeNode *p, int x)  
9.  {  
10.     if(p == NULL)  
11.     {  
12.         p = new BSTreeNode;  
13.         p->value = x;  
14.         p->left = NULL;  
15.         p->right = NULL;  
16.     }  
17.     else  
18.     {  
19.         if(p->value > x)  
20.             p->left = Insert(p->left, x);  
21.         if(p->value < x)  
22.             p->right = Insert(p->right, x);  
23.     }  
24.     return p;  
25. }  
26. void Traverse(BSTreeNode *p) //中序遍历  
27. {  
28.     if(p == NULL)  
29.         return;  
30.     Traverse(p->left);  
31.     cout<<p->value<<' ';  
32.     Traverse(p->right);  
33. }   

此题实质就是考察递归的使用以及树的遍历。中序遍历二元查找数的结果就是有序的目标节点顺序。只需按中序遍历的顺序把节点链接到链表尾端即可。

 1. template<typename T>  
2.  struct TreeNode  
3.  {  
4.      T data;  
5.      TreeNode* pLChild;  
6.      TreeNode* pRChild;  
7.  };  
8.    
9.  // 要求两个输出参数要初始化为NULL  
10. template<typename T>  
11. void ConvertBSTree2List(TreeNode<T>* pTreeRoot/*树的根节点*/, TreeNode<T>*& pListHead/*双向链表的头指针*/, TreeNode<T>*& pListLast/*双向链表的尾指针*/)  
12. {  
13.     if(pTreeRoot == NULL)  
14.     {  
15.         return;  
16.     }  
17.   
18.     // 中序遍历左子树  
19.     ConvertBSTree2List(pTreeRoot->pLChild, pListHead, pListLast);    
20.     // 处理当前节点,把节点链到双向链表尾部    
21.     // 修改当前节点左指针,指向双向链表尾部  
22.     pTreeRoot->pLChild = pListLast;  
23.     if(pListLast)       // 非第一个节点  
24.     {  
25.         pListLast->pRChild = pTreeRoot;  
26.     }  
27.     else                // 第一个节点  
28.     {  
29.         pListHead = pTreeRoot;  只赋值一次 
30.     }  
31.   
32.     pListLast = pTreeRoot; //始终指向最后一个元素
33.   
34.     // 中序遍历右子树  
35.     ConvertBSTree2List(pTreeRoot->pRChild, pListHead, pListLast);  
36. }  

7. 二元树的深度。输入一棵二元树的根结点,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

例如:输入二元树:
10
/ \\
6 14
/ / \\
4 12 16
输出该树的深度3。
思路:这是一道关于树的题目。很多树的问题都是用递归解决的。这个问题也不例外,只要递归求结点的左右子树的深度,树的深度为 = 1 + max{ 根节点左子树深度,根结点右子树深度}。

struct SBinaryTreeNode // a node of the binary tree
{
      int               m_nValue; // value of node
      SBinaryTreeNode  *m_pLeft;  // left child of node
      SBinaryTreeNode  *m_pRight; // right child of node
};
1.  //函数功能 : 二元树的深度  
2.  //函数参数 : pNode指向树的结点  
3.  //返回值 :   深度  
4.  int BinaryTreeDepth(BinaryTreeNode *pNode)  
5.  {  
6.      if(pNode == NULL)  
7.          return 0;  
8.      else  
9.      {  
10.         int leftDepth = BinaryTreeDepth(pNode->m_pLeft);  
11.         int rightDepth = BinaryTreeDepth(pNode->m_pRight);  
12.         return (leftDepth > rightDepth) ? leftDepth+1 : rightDepth+1;  
13.     }  
14. }

8. 按层次遍历二元树

问题描述:输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。
例如输入
8
/ /
6 10
/ / / /
5 7 9 11
输出8 6 10 5 7 9 11。
思路:利用队列的先进先出,很容易实现。每次取出队列的首元素,然后将其左右子女放入队列中。直至队列为空即可。按这种方式进出队列,正好是按层遍历二元树。

1.  struct BSTreeNode    
2.  {    
3.      int value;    
4.      BSTreeNode *left;    
5.      BSTreeNode *right;    
6.  };
7.  1.  //函数功能 : 按层次遍历二元树  
2.  //函数参数 : pRoot指向根结点  
3.  //返回值 :   无  
4.  void LevelReverse(BSTreeNode *pRoot)  
5.  {  
6.      if(pRoot == NULL)  
7.          return;  
8.    
9.      queue<BSTreeNode *> nodeQueue;  
10.     nodeQueue.push(pRoot);  
11.     while(nodeQueue.size())  
12.     {  
13.         BSTreeNode * pNode = nodeQueue.front(); //取队首元素  
14.         nodeQueue.pop(); //必须出队列  
15.         if(pNode->left)  //左子女  
16.             nodeQueue.push(pNode->left);  
17.         if(pNode->right) //右子女  
18.             nodeQueue.push(pNode->right);  
19.   
20.         cout<<pNode->value<<' ';  
21.     }  
22. }   

扩展一:上文给出的代码,所有结点都输出在同一行。如果希望仅仅同层结点输出在同一行,该如何修改代码呢?
思路:如果我们能知道每层的最后一个结点,那么就方便多了,输出每层最后一个结点的同时,输出一个换行符。因此,关键在于如何标记每层的结束。可以考虑在每层的最后一个点之后,插入一个空结点。比如队列中先放入根结点,由于第0层只有一个结点,因此放入一个空结点。然后依次取出队列中的结点,将其子女放入队列中,如果遇到空结点,表明当前层的结点已遍历完了,而队列中放的恰恰是下一层的所有结点。如果当前队列为空,表明下一层无结点,也就说是所有结点已遍历好了。如果不为空,那么插入一个空结点,用于标记下一层的结束。

1.  void LevelReverse(BSTreeNode *pRoot)  
2.  {  
3.      if(pRoot == NULL)  
4.          return;  
5.      queue<BSTreeNode *> nodeQueue;    
6.      nodeQueue.push(pRoot);    
7.      nodeQueue.push(NULL);   //放入空结点,作为层的结束符  
8.      while(nodeQueue.size())  
9.      {  
10.         BSTreeNode * pNode = nodeQueue.front(); //取队首元素    
11.         nodeQueue.pop(); //必须出队列   
12.         if(pNode)  
13.         {  
14.             if(pNode->left)  //左子女    
15.                 nodeQueue.push(pNode->left);    
16.             if(pNode->right) //右子女    
17.                 nodeQueue.push(pNode->right);  
18.             cout<<pNode->value<<' ';    
19.         }  
20.         else if(nodeQueue.size()) //如果结点为空并且队列也为空,那么所有结点都已访问  
21.         {  
22.             nodeQueue.push(NULL);  
23.             cout<<endl;  
24.         }  
25.     }  
26. } 

扩展二:之前讨论的都是从上往下、从左往右遍历二叉树,那么如果希望自下往上、从左右往右遍历二叉树,该如何修改代码呢?
思路:比较简单的方法,首先遍历二叉树,将所有结点保存在一个数组中,遍历的同时记录每一层在数组中的起止位置。然后根据起止位置,就可以自下往上的打印二叉树的结点。

1.  //每层的起止位置  
2.  struct Pos  
3.  {  
4.      int begin;  
5.      int end;  
6.      Pos(int b, int e): begin(b),end(e) {}  
7.  };  
8.  void LevelReverse(BSTreeNode *pRoot)  
9.  {  
10.     if(pRoot == NULL)  
11.         return;  
12.   
13.     vector<BSTreeNode*> vec;   //用以存放所有结点  
14.     vector<Pos> pos;           //用以记录每层的起止位置  
15.     vec.push_back(pRoot);  
16.   
17.     int level = 0;    //树的层数  
18.     int cur = 0;  
19.     int last = 1;  
20.   
21.     while(cur < vec.size())  
22.     {  
23.         last = vec.size();  
24.         pos.push_back(Pos(cur, last)); //记录当前层的起止位置  
25.   
26.         while(cur < last) //遍历当前层的结点,将子女放入数组中  
27.         {  
28.             if(vec[cur]->left) //先是左然后是右。如果希望自由向左,交换一下顺序即可  
29.                 vec.push_back(vec[cur]->left);  
30.             if(vec[cur]->right)  
31.                 vec.push_back(vec[cur]->right);  
32.             cur++;  
33.         }  
34.         level++; //层数加1  
35.     }  
36.   
37.     for(int i = level - 1; i >= 0; i--) //自下往上遍历  
38.     {  
39.         for(int j = pos[i].begin; j < pos[i].end; j++)  
40.             cout<<vec[j]->value<<' ';  
41.         cout<<endl;  
42.     }  
43. }

以上是关于数据结构---树的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 中绘制回归树

LeetCode810. 黑板异或游戏/455. 分发饼干/剑指Offer 53 - I. 在排序数组中查找数字 I/53 - II. 0~n-1中缺失的数字/54. 二叉搜索树的第k大节点(代码片段

使用 Apollo 客户端的片段组合:约定和样板

Discord.py 如何制作干净的对话树?

VSCode自定义代码片段5——HTML元素结构

VSCode自定义代码片段5——HTML元素结构