删除一个二叉树很简单,但是通常我们是用递归的方式,若用迭代的方式则需要额外的空间来保存信息。
如何在O(N)的时间内使用O(1)的空间来完成二叉树空间的释放呢?这是我在面试中遇到的一个问题.
Morris中序遍历
利用Morris中序遍历可以在O(1)的空间O(N)的时间复杂度内遍历完成二叉树,因此可以在遍历的过程中完成树的空间的释放。我之前转载过Morris遍历二叉树的一篇文章,详细看这里:
Morris方法遍历二叉树
不断改变树的结构
如果不用Morris遍历方法,我们就不可能在O(N)时间O(1)空间内完成中序遍历,因此中序遍历这条路就走不通了。
我们考虑当要删除一个节点的时候,必须要记录下其左右子树的指针才能进行下一步的删除,这样不断删除下去,势必会需要O(N)的空间来进行存储。于是我们可以考虑改变二叉树的结构,当删除一个节点的时候,我们将其一个子树移动到另外一个位置,这样相当于只需要一个cur指针来不断的移动到要删除的节点。具体思路如下:
- 当前cur指针指向root节点,找到leftMost节点(二叉树中最左边的叶子节点)
- 若cur节点的右子树不为空,将其右子树设为leftMost节点的左子树,并重新定位leftMost节点,删除当前节点并将cur指向当前节点的左孩子节点
- 若cur右子树为空,直接删除当前节点并将cur指向当前节点的左孩子节点
- 重复上述过程,知道cur为空 也就是将二叉树全部节点删除
举一个例子更好理解,如下图:
- 当前节点指向root节点1, leftMost节点为5
1节点有右子树所以其右子树要设为节点5的左子树,并更新leftMost节点,如下图:
2节点有右子树,因此需要继续把右子树设为leftMost节点3的左子树,并调整leftMost节点为 节点6,删除节点2并设置cur节点为5
节点5没有右子树,直接删除节点5,并设置cur节点为3
同理这一步将3节点的右子树设置为6节点的左子树,leftMost节点设为4,删除节点3,并将cur节点为6
这一步很简单只需要删除节点6,将cur节点设为4
最后删除节点4,整个过程时间复杂度为O(N),空间复杂度为O(1)!
有人可能会疑惑求leftMost节点的过程时间复杂度会达到O(NlgN),但实际上是不会的,如上述几个图,求leftMost节点需要的总步数为:
2+1+1+1 =5 也就是O(N)的时间复杂度。
因此整个算法的时间复杂度仍然是O(N)。
代码实现
最终的实现代码如下:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
//获取最左边的叶节点
TreeNode* getLeftMost(TreeNode* root)
{
if(NULL == root) return root;
while(NULL != root->left)
{
root = root->left;
}
return root;
}
void deletTree(TreeNode* root)
{
TreeNode* cur = root;
TreeNode* pre =NULL;
TreeNode* leftMostNode = getLeftMost(root);
while(cur)
{
pre = cur;
if(cur->right)
{
leftMostNode->left = cur->right;
leftMostNode = getLeftMost(leftMostNode);//重新设置最左节点 总时间复杂度为O(N)
}
cur = cur->left; //当前节点指向当前节点的左孩子
delete pre;//释放节点空间
}
}