Skip to content

Commit be11986

Browse files
committed
Update二叉树
1 parent aa70c6c commit be11986

File tree

2 files changed

+112
-9
lines changed

2 files changed

+112
-9
lines changed

data_structure/.ipynb_checkpoints/二叉树笔记-checkpoint.ipynb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,8 @@
454454
"source": [
455455
"# BFS 层次遍历\n",
456456
"\n",
457-
"## 递归(最朴素)"
457+
"## 递归(最朴素)\n",
458+
"但其实访问顺序还是前序遍历(也可以写成中序/后序遍历),只是由于depth的存在使其结果返回的是层次遍历而已"
458459
]
459460
},
460461
{
@@ -473,7 +474,7 @@
473474
" res.append([])\n",
474475
" res[depth].append(root.val)\n",
475476
" helper(root.left, depth + 1)\n",
476-
" helper(root.right, depth + 1)\n",
477+
" helper(root.right,depth + 1)\n",
477478
" helper(root, 0)\n",
478479
" return res"
479480
]

data_structure/二叉树笔记.ipynb

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
"写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。\n",
2525
"\n",
2626
"3. **确定单层递归的逻辑:**\n",
27-
"确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。\n"
27+
"确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。\n",
28+
"\n",
29+
"### 入栈前(其实也就是处理前)一定要判断是否是空节点,访问时则不需要\n"
2830
]
2931
},
3032
{
@@ -454,7 +456,8 @@
454456
"source": [
455457
"# BFS 层次遍历\n",
456458
"\n",
457-
"## 递归(最朴素)"
459+
"## 递归(最朴素)\n",
460+
"但其实访问顺序还是前序遍历(也可以写成中序/后序遍历),只是由于depth的存在使其结果返回的是层次遍历而已"
458461
]
459462
},
460463
{
@@ -473,7 +476,7 @@
473476
" res.append([])\n",
474477
" res[depth].append(root.val)\n",
475478
" helper(root.left, depth + 1)\n",
476-
" helper(root.right, depth + 1)\n",
479+
" helper(root.right,depth + 1)\n",
477480
" helper(root, 0)\n",
478481
" return res"
479482
]
@@ -554,7 +557,7 @@
554557
"\n",
555558
"适用场景\n",
556559
"\n",
557-
"- 快速排序\n",
560+
"- 快速排序(快排自顶而下不能算是分冶法吧)\n",
558561
"- 归并排序\n",
559562
"- 二叉树相关问题\n",
560563
"\n",
@@ -644,9 +647,33 @@
644647
"cell_type": "markdown",
645648
"metadata": {},
646649
"source": [
647-
"#### 思路 1:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1,\n",
648-
"(自顶而下)有了计算节点高度的函数,即可判断二叉树是否平衡。具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,首先计算左右子树的高度,如果左右子树的高度差不超过 1,再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。这是一个自顶向下的递归的过程,时间复杂度O(n^2)\n",
650+
"#### 思路 0: 自顶而下递归判断是否左右子树高度绝对值是否差1(复杂度过高,废弃)\n",
651+
"(自顶而下)有了计算节点高度的函数,即可判断二叉树是否平衡。具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,首先计算左右子树的高度,如果左右子树的高度差不超过 1,再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。这是一个自顶向下的递归的过程,时间复杂度O(n^2)(因为每一层都要重复计算height)"
652+
]
653+
},
654+
{
655+
"cell_type": "code",
656+
"execution_count": null,
657+
"metadata": {},
658+
"outputs": [],
659+
"source": [
660+
"class Solution:\n",
661+
" def isBalanced(self, root: TreeNode) -> bool:\n",
662+
" def height(root: TreeNode) -> int:\n",
663+
" if not root:\n",
664+
" return 0\n",
665+
" return max(height(root.left), height(root.right)) + 1\n",
649666
"\n",
667+
" if not root:\n",
668+
" return True\n",
669+
" return abs(height(root.left) - height(root.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)\n"
670+
]
671+
},
672+
{
673+
"cell_type": "markdown",
674+
"metadata": {},
675+
"source": [
676+
"#### 思路 1:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1,\n",
650677
"(这里是自底而上,复杂度O(n),类似于后序遍历)"
651678
]
652679
},
@@ -672,7 +699,82 @@
672699
"cell_type": "markdown",
673700
"metadata": {},
674701
"source": [
675-
"#### 思路 2:使用后序遍历实现分治法的迭代版本(过于复杂,此处不显示)"
702+
"#### 思路 1的改进:提前阻断的分治法\n",
703+
"左边平衡 && 右边平衡 && 左右两边高度 <= 1,但是是提前阻断的,只返回\n",
704+
"1. -1,代表左子树/右子树/该树本身不是平衡树,具体而言:要么左子树的结果是-1,要么右子树的结果是-1,要么左右子树高度之差>1,这种情况下提前返回-1\n",
705+
"2. 自己的高度,代表该树及其左右子树都是平衡树"
706+
]
707+
},
708+
{
709+
"cell_type": "code",
710+
"execution_count": null,
711+
"metadata": {},
712+
"outputs": [],
713+
"source": [
714+
"class Solution:\n",
715+
" def isBalanced(self, root: TreeNode) -> bool:\n",
716+
" def balance(root):\n",
717+
" if root is None:\n",
718+
" return 0\n",
719+
" left = balance(root.left)\n",
720+
" if left == -1: return -1\n",
721+
" right = balance(root.right)\n",
722+
" if right == -1: return -1\n",
723+
"\n",
724+
" return max(left,right) + 1 if abs(left - right) <= 1 else -1\n",
725+
" return balance(root) != -1"
726+
]
727+
},
728+
{
729+
"cell_type": "markdown",
730+
"metadata": {},
731+
"source": [
732+
"#### 思路 2:使用后序遍历实现分治法的迭代版本\n",
733+
"优点是即使前一个方法的提前阻断也还是需要逐层返回,而这个只要找到了不平衡的子树就可以直接停止\n",
734+
"缺点是写起来太麻烦,要维护一个三元组的栈\n",
735+
"\n",
736+
"0. s多建一个节点是为了防止迭代到根节点时out of range,没这一个节点迭代到整棵树的根节点时stack就空了,就没办法更新它的“不存在的父节点”的树深了\n",
737+
"1. -1是没有求树深的左右子树的标记\n",
738+
"2. 若左右节点为空,对应深度为0。当然这个问题是求平衡树所以无所谓,但是求深度的话如果不管结果会少1,并且-1作为没有求树深的左右的标记,如果不把空子树置0,会存在如下情况:比如某节点左子树为空,右子树深度为1,结果把这个深度归结到左子树上,这当然对平衡没有影响,但还是不够严谨"
739+
]
740+
},
741+
{
742+
"cell_type": "code",
743+
"execution_count": null,
744+
"metadata": {},
745+
"outputs": [],
746+
"source": [
747+
"class Solution:\n",
748+
" def isBalanced(self, root: TreeNode) -> bool:\n",
749+
"\n",
750+
" s = [[TreeNode(), -1, -1]] \n",
751+
" node,visit = root,None\n",
752+
" \n",
753+
" #这里是len(s)>1防止迭代到根节点时爆栈\n",
754+
" while len(s) > 1 or node is not None:\n",
755+
" if node is not None:\n",
756+
" s.append([node,-1,-1])\n",
757+
" node = node.left\n",
758+
" #若左节点为空,左子树深度为0\n",
759+
" if node is None:\n",
760+
" s[-1][1] = 0\n",
761+
" else:\n",
762+
" peak = s[-1][0]\n",
763+
" if peak.right is not None and visit != peak.right:\n",
764+
" node = peak.right\n",
765+
" else:\n",
766+
" #若右节点为空,右子树深度为0\n",
767+
" if peak.right is None:\n",
768+
" s[-1][2] = 0\n",
769+
" visit, dl, dr = s.pop()\n",
770+
" if abs(dl - dr) > 1:\n",
771+
" return False\n",
772+
" d = max(dl,dr) + 1\n",
773+
" if s[-1][1] == -1:\n",
774+
" s[-1][1] = d\n",
775+
" else:\n",
776+
" s[-1][2] = d\n",
777+
" return True"
676778
]
677779
},
678780
{

0 commit comments

Comments
 (0)