二叉树
- 根节点:树中上部的节点
- 左叶子节点
- 右叶子节点
- 子树
- 完整的子树
- 一个根节点,左右叶子节点组成
- 不完整的子树
- 根节点,左叶子节点
- 根节点,右叶子节点
- 根节点
- 特点:每一个节点都可以作为某一颗子树的根节点
- 完整的子树
定义:
class Node(object):
def __init__(self,item):
self.item = item
self.left = None
self.right = None
class Tree(object):
def __init__(self): # 构建一颗空树
self.root = None # 永远指向二叉树中的根节点
def insert(self,item):
"""按照自上到下,从左到右的准则插入新的节点"""
node = Node(item)
cur = self.root
if not cur: # 这里不设防的话,如果二叉树根节点为空,下面的obj.left就会报错
self.root = node
return
q = [cur] # 列表中存放需要迭代的根节点
while q:
obj = q.pop(0) # 按顺序迭代取第一个元素,满足从上到下,从左到右
if not obj.left: # 如果存在就添加到列表中,分析他的子节点有没有空的
obj.left = node
break
if not obj.right:
obj.right = node
break
q.append(obj.left)
q.append(obj.right)
def travel(self):
"""广度遍历"""
cur = self.root
q = [cur] # [None,None]这种情况是存在的
if not cur: # 这里不设防的话,如果二叉树根节点为空,下面的obj.left就会报错
return
while q:
obj = q.pop(0)
print(obj.item)
if obj.left:
q.append(obj.left)
if obj.right:
q.append(obj.right)
使用:
tree = Tree()
tree.insert(1)
tree.insert(3)
tree.insert(2)
tree.insert(5)
tree.insert(6)
tree.insert(4)
tree.insert(7)
tree.travel()
1
3
2
5
6
4
7
tree = Tree()
tree.travel() # 不报错就行,测试下
二叉树的遍历
-
广度遍历
- 上述代码(travel)中的遍历,就是广度遍历。自上到下逐行遍历叫做广度遍历。
- 横向遍历,一层一层的输出
-
深度遍历:纵向遍历。前中后序遍历形式需要作用到子树中。前中后序中的前中后指的是子树中根节点的位置
- 前序:根左右,先遍历子树的根节点,在遍历子树的左节点然后是右节点
- 中序:左根右
- 后序:左右根
-
深度遍历的实现思路
- 深度遍历是需要作用到每一颗子树中
- 子树和子树之间的区别体现在根节点中。
- 如果写一个函数,该函数可以将一个子树中的节点进行遍历,则将该函数作用到其他子树中就可以将整棵树进行深度遍历。
定义:
class Node(object):
def __init__(self,item):
self.item = item
self.left = None
self.right = None
class Tree(object):
def __init__(self): # 构建一颗空树
self.root = None # 永远指向二叉树中的根节点
def insert(self,item):
"""按照自上到下,从左到右的准则插入新的节点"""
node = Node(item)
cur = self.root
if not cur: # 这里不设防的话,如果二叉树根节点为空,下面的obj.left就会报错
self.root = node
return
q = [cur] # 列表中存放需要迭代的根节点
while q:
obj = q.pop(0) # 按顺序迭代取第一个元素,满足从上到下,从左到右
if not obj.left: # 如果存在就添加到列表中,分析他的子节点有没有空的
obj.left = node
break
if not obj.right:
obj.right = node
break
q.append(obj.left)
q.append(obj.right)
def travel(self):
cur = self.root
q = [cur] # [None,None]这种情况是存在的
if not cur: # 这里不设防的话,如果二叉树根节点为空,下面的obj.left就会报错
return
while q:
obj = q.pop(0)
print(obj.item)
if obj.left:
q.append(obj.left)
if obj.right:
q.append(obj.right)
def forward(self,root): # 根左右
if not root:
return
print(root.item)
self.forward(root.left) # 递归
self.forward(root.right)
def middle(self,root): # 左根右
if not root:
return
self.middle(root.left)
print(root.item)
self.middle(root.right)
def back(self,root): # 左右根
if not root:
return
self.back(root.left)
self.back(root.right)
print(root.item)
使用:
tree = Tree()
tree.insert(1)
tree.insert(3)
tree.insert(2)
tree.insert(5)
tree.insert(6)
tree.insert(4)
tree.insert(7)
tree.back(tree.root)
5
6
3
4
7
2
1
排序二叉树
排序二叉树个人理解***:
- 之前一直觉得排序二叉树+中序深层遍历 会得到正序排列的元素有点神奇,但是想着想着突然有所感悟,这方法也没有那么神奇:
- 以上图为例,3右边(右子节点及其以下的),无论是哪个元素,不管怎么找,绝对都比3大;8左边的元素,绝对都比8小;5右边的元素绝对都比5大
- 这个能理解吧?以5为例,如果我右边的元素没我大,他怎么没有成为我的左子节点,或者成为左子节点下方的子节点呢?首先它在我这里就通不过
- 这样的话就好理解了吧,排序二叉树的middle(中序)方法,以左根右为排序原则:
- 遍历3,左侧有2,遍历2,左侧有1,遍历1,左空,输出1,右空,返回到2,2左侧完毕,输出2,遍历2右侧,右侧为空,返回到3,3左侧遍历完毕,输出3,遍历3右侧8,8左侧有5,遍历5,5左侧为空,输出5,遍历5右侧7,7左侧有6,遍历6,6下为空,输出6,返回到7,输出7,右侧为空,返回到5,返回到8,输出8,右侧为空,返回到3,结束
- 之前的疑问在于,5比6输出早只是因为5在上面,后来想明白了,只要6是5右边,或右边下面的,他就绝对比5大,不然,当初添加的时候就只会在5左边了!
定义:
class Node(object):
def __init__(self,item):
self.item = item
self.left = None
self.right = None
class SortTree(object):
def __init__(self):
self.root = None
def add(self,item):
node = Node(item)
cur = self.root
if not cur: # 排序二叉树根节点不存在,该元素就直接作为根节点
self.root = node
return
while 1:
if node.item > cur.item: # 如果大于你,就作为你的右子节点
if not cur.right: # 不存在就直接赋值
cur.right = node
break
else: # 如果右子节点已经存在,就与右子节点再进行比较
cur = cur.right
else:
if not cur.left:
cur.left = node
break
else:
cur = cur.left
def middle(self,root): # 左根右
if not root:
return
self.middle(root.left)
print(root.item)
self.middle(root.right)
使用:
alist = [3,8,5,7,6,2,1]
tree = SortTree()
for item in alist:
tree.add(item)
tree.middle(tree.root) # 排序二叉树+中序深层遍历 会得到正序排列的元素
# 上面的alist换成[3,8,6,7,5,2,1]结果依然是正序排列,因为如果先6后5,到时候5就会作为6的左子节点,还是会先输出5,这方法真他妈神奇
1
2
3
5
6
7
8
二分查找
- 二分查找只可以作用在有序序列中,下面定义了针对整数二分查找的函数。
整数二分查找的函数
def find(items,item):
"""
第一个参数items必须是 有序序列;
item是我们要在item中查找的元素
"""
low_index = 0
high_index = len(items) - 1
find = False
# 因为是有序排列的list,所以如果都不在最小值最大值范围内,那循环查找不是浪费时间吗?
if item < items[low_index] or item > items[high_index]:
return find
# while not find: # 这种也可以,但是没有下面的严谨
while low_index <= high_index: # 这个=很重要,不然上图中的第一种情况就会查找失败
middle_index = (high_index + low_index) // 2 # 取中间索引
if items[middle_index] > item:
high_index = middle_index - 1
# 是不是好奇为啥要-1,由于我们一直是通过中间索引对应值与目标值进行比较,这样漏掉一个值会不会导致这个数据逃过检验呢?在有序序列内的元素都是整数的情况下是不会的,考虑绝对情况,漏掉的这个数据最多也就是中间值左侧的这个数,而我们high-1也正好是那个数,而之后的中间值绝对只会<=目标值,也就是说以后变动的只会是low索引值,而我们允许最大索引值=最小索引值,那么在一次次low+1的情况下,早晚会查找到这个元素
elif items[middle_index] < item:
low_index = middle_index + 1 # 同理,如果要针对非整数,那这个+1/-1就需要做出变动
else: # 中间序列对应值正好就是目标值
find = True
break
return find
使用:
alist = [1,2,3,4,5,6,7,8,9]
print(find(alist,6))
print(find(alist,8))
True
True