数据结构之二叉树Python版
简述
目前来看,讲数据结构用的语言,最多的还是C++,Python版的也不是没有,但总感觉还是缺少了什么。其实像昨天的每日一题,典型的线段树用来解决的,可是我一开始想的方法并不需要用数来解决,直接求解就可以了。后来看了看洛谷中的讨论,才意识到应该用线段树来解决。所以今天想了想,就用Python再重构一下数据结构中的内容,我有正确的C和Python代码,但是Python代码我觉得写的不好,所以打算用deepseek将C的代码修改为Python代码,然后把这三个代码比照学习。当然,deepseek肯定会有问题,所以仅供参考,但是其思路、思想、方法也是值得我们去借鉴的。而我下面列出来的deepseek给的代码也是经过我自己初步判断的,而且像这种基础问题,出错的概率也不是很大,所以放心参考就行。下面的内容,我会将pta中的树对应题目全部放到这里来,后续也会补充对应题目。
一、二叉树基本操作
我只展示对应类或函数,如果需要所有源代码的可以统一复制。Python的应该是完整的,C的不完整,需要的可以联系我。不过建议还是自己敲一下。
下面题目的解决方法差不多都是链式存储解决的,所以解决方式几乎都是递归解决的,而顺序方式存储解决方式、非递归解决方法,等之后再填上,因为现在的题目大多是递归解决的。
1、二叉树结点定义
c
c
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
python
python
class BiTNode:
def __init__(self, data):
self.data = data
self.lchild = None # 左孩子指针
self.rchild = None # 右孩子指针
deepseek
python
class TreeNode:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
从上面来看,尽管命名方式不同,但是都是包含数据域、指针域这两大部分,本质上还是一样的。
2、二叉树的创建
0代表空树。
c
c
//创建二叉树各结点,输入零代表创建空树
//采用递归的思想创建
//递归边界:空树如何创建呢:直接输入0;
//递归关系:非空树的创建问题,可以归结为先创建根节点,输入其数据域值;再创建左子树;最后创建右子树。左右子树递归即可完成创建!
Status CreateBiTree(BiTree &T){
TElemType e;
scanf("%d",&e);
if(e==0)T=NULL;
else{
T=(BiTree)malloc(sizeof(BiTNode));
if(!T)exit(OVERFLOW);
T->data=e;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
return OK;
}
python
python
def BiTree_Create(nums, len, i):
if i >= len or nums[i] == 0: return None
node = BiTNode()
node.data = nums[i]
node.lchild = BiTree_Create(nums, len, 2 * i + 1)
node.rchild = BiTree_Create(nums, len, 2 * i + 2)
return node
def main():
nums = [1, 2, 3, 0, 4, 0, 5, 0, 0, 6, 0]
tree = BiTree_Create(nums, 11, 0)
# 调用主函数
main()
deepseek
python
def create_tree(it):
try:
val = next(it)
except StopIteration:
return None
if val == 0:
return None
node = TreeNode(val)
node.left = create_tree(it)
node.right = create_tree(it)
return node
def main():
data = list(map(int, input().split()))
iterator = iter(data)
root = create_tree(iterator)
if __name__ == "__main__":
main()
(1)输入区别
先不谈逻辑,c与python输入的不同上面显示的很清楚,c可以边递归边输入,而python不建议这样。在python的输入那个专栏中,我也专门说过,python的输入,建议直接读取所有数据,然后再进行对应的细分,而c是可以边循环(或者递归)边存储的。因为python如果边循环(递归)输入的话,会存在的问题是只能一个一个敲回车输入,要是直接复制粘贴数据的话会报错的,而c不存在这种情况。
基于此,我们再看上面代码,c是在函数中直接递归输入,而下面两种方法,都是在main函数中输入,然后将可迭代对象传入创建函数中的。
所以,如果不考虑输入的差异性,我们可以完全模仿c的形式,直接在main函数中创建一个二叉树跟结点的实例对象,然后直接传入构造函数中就可以了,其他的也就类似。同理,c也可以模仿python的创建方式进行创建。
(2)实现区别
c和python的实现区别就不说了,主要看python跟deepseek的区别。python给的代码需要三个参数,分别是数组、长度、下标,而且还需要找下标关系,整体上显得很冗杂。如果看了c的实现,再看python的,肯定觉得头大------还需要额外记忆下标关系,当然这在一些非递归方法中是必须的,可以用数学归纳法证明,这里不再展示。所以我就将c的代码发给deepseek之后,它给了我上面另一种写法。deepseek的方法给人一种清凉的感觉,即满足了python的输入需求,也仿照了c的节点创建方式,可以说比较完美。我们再来分析一下内部细节,看看有没有瑕疵。
首先我们先讲解一下其中main函数中是怎么输入的,以及对应方法是什么意思。下面我举了一个例子,我们来看看。
python
data = list(map(int, input().split()))
iterator = iter(data)
val=next(iterator)
print(val)
print(list(iterator))
输入输出
这里的iter()方法是将可迭代对象转换成类似数组的形式。而next()则是从头到尾逐一显示数组内容,并且删除掉,从输出结果我们可以明显看出来。不过如果iter真是数组的话,那么一直删除首元素,时间复杂度有点大吧。如果直接用列表的话,可以l[0]获取首元素,l[1:]删除首元素,这样做也可以。我又查了一下,iter()是一个迭代器,而next()内部参数只能是迭代器,其并没有删除功能,只是从首->下一个遍历。简单来说,就是iter()相当于一个指针,一开始指向首元素,我们执行next(iter())之后,会返回iter()当前指向的元素,并且iter()又指向下一个元素,因此next()并不是将首元素删除了,而是使指针iter()下移。
结合上述信息,我们再回看一下deepseek的main函数,显而易见,不多说了。再看看它是如何创建的。try...except是python的异常保护机制,这部分我没细看,感兴趣的可以去了解一下。在这里就是尝试执行val=next(it)语句,如果超出范围,直接返回。所以说,val获取了当前的值,并且指针下移了。下面就是如果当前值为0,那么直接返回。上面几句其实跟python中一开始的判断是一个意思的,可以回看一下,只不过deepseek给的更pythonic些,且容易理解。然后就是统一的赋值递归返回了。可以看到,python和deepseek的创建逻辑是一样的,排除异常、创建节点、赋值、递归、返回,可是因为参数传递的不同,可观性上又有很大的差别。
(3)分析
要说哪种方法更像人类的写法,哪种方法又满足人类的审美,我想显而易见。python的写法更像人类写的,而deepseek的写法,至少满足我的审美。为何会存在这种差异呢?
其实文章一开头我就说了,如今讲数据结构的教材仍是以c为主的,所以说,上面的python解法,写的人大概率一开始也是用c学的数据结构。这也就不难理解了:学数据结构本质上是学内在的逻辑的,语言千变万化,本质没有掌握,换了其他语言都像是重新学一样。相反,如果掌握本质,换种语言来写,虽然不一定美观,但是仍能实现目标效果。
3、二叉树求深度
很简单,整体思想是一样的,这里只给deepseek答案了。
递归
python
def get_depth(root):
if root is None:
return 0
left_depth = get_depth(root.left)
right_depth = get_depth(root.right)
return max(left_depth, right_depth) + 1
树空,返回0,否则左右分别递归,最后结果是二者最大值加1。
结构很简单,甚至直接记住就行,怎么理解呢?
结合图片来看,第一次递归,从根结点到左右两个结点,并+1,然后采用分治法的思想,再对左右两个树进行递归,满足结点不空的话,仍+1,深度的判定肯定是最下面的那个叶子,所以随着递归的进行,中间可能有的节点提前停止了,有的仍在递归,所以我们需要获取最长的就可以了。
非递归
过
4、二叉树求叶子数
(1)类型1
递归
python
def leaf_count(root):
if root is None:
return 0
if root.left is None and root.right is None:
return 1
return leaf_count(root.left) + leaf_count(root.right)
当左右分别为空的时候,返回1,并且进行左右累加。还是递归的思想。
非递归
过
(2)类型2
上面题目是求的叶子结点数,判断条件是当前节点指向下一个左右节点都不存在,就会+1,而对应的叶子结点的度为0。所以如果我们要问度=2的数量,其实就稍微改变一下判断条件就好了,本质是一样的。
c
int NodeCount ( BiTree T)
{
if (!T) return 0;
else
{
if (T->lchild && T->rchild)
return 1 + NodeCount(T->lchild) + NodeCount(T->rchild);
else
return 0 + NodeCount(T->lchild) + NodeCount(T->rchild);
}
}
其他的就不给了。自己思考一下,度为1的结点数,应该怎么求呢?
5、 二叉树统计指定取值元素节点的个数
递归
c
c
int XNodeCountOfBiTree ( BiTree T, TElemType x){
if(!T)
return 0;
else{
if(T->data==x)
return XNodeCountOfBiTree(T->lchild,x)+XNodeCountOfBiTree(T->rchild,x)+1;
else
return XNodeCountOfBiTree(T->lchild,x)+XNodeCountOfBiTree(T->rchild,x);
}
}
python
这个是我根据上面c的改的。
python
def xNodeCountOfBiTree(root,x):
if not root:
return 0
else:
if root.data==x:
return xNodeCountOfBiTree(root.left,x)+xNodeCountOfBiTree(root.right,x)+1
else:
return xNodeCountOfBiTree(root.left,x)+xNodeCountOfBiTree(root.right,x)
deepseek
deepseek给的代码仍然十分pythonic,不过用\这中换行方式还是没见过的。
python
def xNodeCountOfBiTree(root, x):
if not root:
return 0
# 当前节点的贡献 + 左子树的结果 + 右子树的结果
return (1 if root.data == x else 0) + \
xNodeCountOfBiTree(root.left, x) + \
xNodeCountOfBiTree(root.right, x)
非递归
过。
6、遍历二叉树
(1)先序遍历
c
c
void PreOrder(BiTNode *T) {
if (T == nullptr) return; // 如果结点为空,直接返回
cout << T->data; // 先访问根结点
PreOrder(T->lchild); // 再访问左子树
PreOrder(T->rchild); // 最后访问右子树
}
python
python
# 先序遍历: 根左右(NLR)
def Order_Pre(tree):
if tree is None: return
print(f"{tree.data},", end='') # 访问根树内容
Order_Pre(tree.lchild) # 递归遍历左子树
Order_Pre(tree.rchild) # 递归遍历右子树
deepseek
python
def preorder_recursive(root, result):
if root:
result.append(root.data) # 访问根节点
preorder_recursive(root.left, result)
preorder_recursive(root.right, result)
return result
deepseek是将所有数据存储到列表后,统一输出的。这一点其实跟我们之前在python的输出专栏中所说的是一致的。因为如果想上面python的边递归变输出的话,如果有格式控制问题,会十分棘手。而统一整理到列表中后,使用map转换成字符串之后,使用.join()可以十分巧妙地控制格式输出。
我也不是尬吹deepseek,其生成的代码确实符合我之前说的,而且逻辑一致。
(2)中序遍历
python
python
# 中序遍历: 左根右(LNR)
def Order_In(tree):
if tree is None: return
Order_In(tree.lchild) # 递归遍历左子树
print(f"{tree.data},", end='') # 访问根树内容
Order_In(tree.rchild) # 递归遍历右子树
deepseek
python
def inorder_recursive(root, result):
if root:
inorder_recursive(root.left, result)
result.append(root.data) # 访问根节点
inorder_recursive(root.right, result)
return result
(3)后序遍历
python
python
# 后序遍历: 左根右(LRN)
def Order_Post(tree):
if tree is None: return
Order_Post(tree.lchild) # 递归遍历左子树
Order_Post(tree.rchild) # 递归遍历右子树
print(f"{tree.data},", end='') # 访问根树内容
deepseek
python
def postorder_recursive(root, result):
if root:
postorder_recursive(root.left, result)
postorder_recursive(root.right, result)
result.append(root.data) # 访问根节点
return result
7、顺序存储的二叉树遍历
过
先到这里,后续补充。