7.二叉树 - 算法
二叉树
1.简介
根节点:树最上层的节点
子节点:
- 左叶子节点
- 右叶子节点
树的高度:树的层数
子树:
- 完全子树
- 根节点+左右叶子节点
- 不完全子树
- 根节点+左叶子节点
- 根节点+右叶子节点
- 结论:二叉树中的每一个节点都可以作为另一颗子树的根节点。
- 可以根据根节点来区分不同子树
二叉树的遍历方法:
- 广度遍历:逐层遍历(简单)
- 深度遍历:纵向遍历(按照如下三种方式将二叉树中的每一颗子树进行遍历即可)
- 前序:根左右
- 中序:左根右
- 后序:左右根
2.普通二叉树结构实现:
# 节点
class Node():
def __init__(self,item):
self.item = item
self.left = None
self.right = None
# 二叉树 -- 只有添加和遍历两种方法
class Tree():
# 创建一个根
def __init__(self):
self._root = None
# 判断是否为空
def is_empty(self):
return self._root is None
# 添加节点 -- 从左到右逐层添加
def add(self,item):
node = Node(item)
if self.is_empty():
self._root = node
return
cur = self._root
# 使用列表,把每一个子节点当做另一个树的根节点
lis = [cur]
while lis:
# 判断每个节点的左右叶子节点,没值添加,有值扔入列表下次循环
pop_node = lis.pop(0)
if pop_node.left is not None:
lis.append(pop_node.left)
else:
pop_node.left = node
if pop_node.right is not None:
lis.append(pop_node.right)
else:
pop_node.right = node
# 广度遍历 -- 逐层遍历
def traversal(self):
if self.is_empty():
print('二叉树中没有数据')
return
cur = self._root
# 使用列表,把所有的跟和叶子节点进行遍历
lis = [cur]
while lis:
pop_node = lis.pop(0)
print(pop_node.item)
if pop_node.left is not None:
lis.append(pop_node.left)
if pop_node.right is not None:
lis.append(pop_node.right)
# 深度遍历(递归) -- 前序:根左右
def traversal_front(self,_root): # 调用时传入参数根节点
if _root is None:
return
print(_root.item) # 根
self.traversal_front(_root.left) # 左
self.traversal_front(_root.right) # 右
# 深度遍历(递归) -- 中序:左根右 -- 二叉树排序会用到
def traversal_middle(self,_root):
if _root is None:
return
self.traversal_middle(_root.left) # 左
print(_root.item) # 根
self.traversal_middle(_root.right) # 右
# 深度遍历(递归) -- 后序:左右根
def traversal_rear(self,_root):
if _root is None:
return
self.traversal_rear(_root.left) # 左
self.traversal_rear(_root.right) # 右
print(_root.item) # 根
# 测试
tree = Tree()
tree.add(1)
tree.add(2)
tree.add(3)
tree.add(4)
# tree.traversal()
tree.traversal_middle(tree._root)
3.堆
最大堆: 各根节点不小于子叶节点
最小堆: 各等阶段不大于子叶节点
# python heapq模块实现堆的特性
from heapq import heapify,heappop,heappush,heapreplace,nlargest,nsmallest
'''
函 数 描 述
heappush(heap, x) 将x压入堆中
heappop(heap) 从堆中弹出最小的元素
heapify(heap) 让列表具备堆特征
heapreplace(heap, x) 弹出最小的元素,并将x压入堆中
heappushpop(heap, x) 先将x压入堆中, 再弹出最小的元素
nlargest(n, iter) 返回iter中n个最大的元素
nsmallest(n, iter) 返回iter中n个最小的元素
'''
lis = [2,5,8,1,33]
# 1.使用heapify函数实现列表的堆特性
heapify(lis)
print(heapreplace(lis,10)) # 1 [2, 5, 8, 10, 33]
print(heappop(lis), '->', lis) # 2 -> [5, 10, 8, 33]
# 2.遍历列表,使用heappush函数实现堆特性
heap = []
for i in lis:
heappush(heap,i)
算法
1.二叉树排序
问题: 给定一组无序列表,按照从小到大(从大到小)进行排序
二叉树解决思路(从小到大):
遍历列表,与每个节点比较,小于的放在左叶子节点,大于的放在右叶子节点.
再深度遍历二叉树 -- 中序:左根右
代码实现
# 节点
class Node():
def __init__(self,item):
self.item = item
self.left = None
self.right = None
# 排序二叉树 -- 从小到大
class Tree():
def __init__(self):
self._root = None
# 判断二叉树是否为空
def is_empty(self):
return self._root is None
# 添加数据 -- 小于的放左叶子节点,大于的放右叶子节点
def add(self,item):
node = Node(item)
if self.is_empty():
self._root = node
return
cur = self._root
lis = [cur]
while lis:
pop_node = lis.pop(0)
# 判断,小于的放左叶子节点
if item < pop_node.item:
if pop_node.left is not None:
lis.append(pop_node.left)
else:
pop_node.left = node
else: # 大于的放右叶子节点
if pop_node.right is not None:
lis.append(pop_node.right)
else:
pop_node.right = node
# 深度遍历 -- 中序:左根右
def traversal_middle(self,_root):
if _root is None:
return
self.traversal_middle(_root.left)
print(_root.item)
self.traversal_middle(_root.right)
# 测试
tree = Tree()
lis = [4,2,6,9,1,7]
for i in lis:
tree.add(i)
tree.traversal_middle(tree._root)
2.二分查找
1.概念:
二分查找也称折半查找,它是一种效率较高的查找方法。但是,只可以作用在有序序列中
2.查找过程:
首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;
否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
代码实现:
# 传入列表和想要查找的数据
def Binary_search(lis,item):
lis = sorted(lis) # 从小到大排序
first = 0 # 起始位置
last = len(lis)-1 # 末尾位置
found = False # 返回值 -- 真:数据在列表中 - 假:数据不在列表中
while first < last:
middle = (first + last) // 2 # 中间位置
# 比较
if item < lis[middle]: # 查找左半数据
last = middle - 1
elif item > lis[middle]: # 查找右半边
first = middle + 1
else: # 查找成功
found = True
break
return found
# 测试
lis = [3,5,6,2,7]
item = 9
print(Binary_search(lis,item))
3.冒泡排序
实现步骤:
1.元素两两比较,然后将较大的元素逐步向后偏移(将最大值逐步移动到最后位置)
2.循环上一步操作即可
代码
# 冒泡排序 -- 从小到大
def maopao(lis):
for j in range(len(lis)-1):
# 第一步,把最大的元素放到最后面
# -j每次循环都确定了最大值位置,减少1次遍历
for i in range(len(lis)-1-j):
if lis[i] > lis[i+1]:
# 元素交换
lis[i],lis[i+1] = lis[i+1],lis[i]
return lis # 返回排序后的列表
# 测试
lis = [3,4,9,6,2,7,4]
print(maopao(lis)) # [2, 3, 4, 4, 6, 7, 9]
4.选择排序
选择排序改进了冒泡排序,每次遍历列表只做一次交换
实现步骤:
1.将乱序序列中的最大值找出,直接让其和最后一个元素交换位置即可
2.循环第一步操作即可
代码
# 选择排序 -- 从小到大
def Select(lis):
for j in range(len(lis)-1):
max_index = 0 # 最大元素索引
# 第一步,把最大的元素与最后一个元素交换
# -j每次循环都确定了最大值位置,减少1次遍历
for i in range(len(lis)-1-j):
if lis[i+1] > lis[max_index]:
# 更新最大索引
max_index = i+1
# 最大元素交换 -- 每循环1次,最后元素提前1位
lis[max_index],lis[len(lis)-1-j] = lis[len(lis)-1-j],lis[max_index]
return lis
# 测试
lis = [3,4,9,6,2,7,4]
print(Select(lis))
5.插入排序
插入排序:将无序部分的元素依次插入到有序部分即可
- 把乱序序列假设拆分成两部分
- 有序部分:默认将第一个元素
- 无序部分:剩下元素
i:表示有序部分元素的个数,
alist[i]:无序部分的第一个元素,
alist[i-1]:有序部分的最后一个元素
i = 1
if alist[i-1] > alist[i]: # 前一个数比后一个数大就交换
alist[i],alist[i-1] = alist[i-1],alist[i]
# 前面有序部分进行冒泡排序
i = 2
while i >= 1:
if alist[i-1] > alist[i]:
alist[i],alist[i-1] = alist[i-1],alist[i]
i -= 1
代码
# 插入排序 -- 从小到大
def insert_sort(lis):
# i 是有序元素个数
for i in range(1,len(lis)):
# 每次把无序部分第一个元素添加到有序部分
# 前面有序部分再进行冒泡排序
while i >=1:
if lis[i-1] > lis[i]:
lis[i-1],lis[i] = lis[i],lis[i-1]
i -= 1
return lis
# 测试
lis = [3,4,9,6,2,7,4]
print(insert_sort(lis))
6.希尔排序
1.概念:
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本,
2.该方法的基本思想是:
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量(gap)”的元素组成的)分别进行直接插入排序,
然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。
因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率比直接插入排序有较大提高
3.- gap:初始值就是序列个数整除2
- gap的作用:
- gap表示间隔
- gap表示组数
- 插入排序就是增量为1的希尔排序
代码
# 希尔排序 -- 从大到小
def xier(lis):
# 初始增量
gap = len(lis) // 2
while gap >= 1:
for i in range(gap,len(lis)):
while i >= gap:
# 每组数有两个值,当前一个大于后一个时,交换
if lis[i-gap] > lis[i]:
lis[i-gap],lis[i] = lis[i],lis[i-gap]
i -= gap
# 减小增量
gap //= 2
return lis
# 测试
lis = [3,4,9,6,2,7,4,8]
print(xier(lis))
7.快速排序
思想:
1.将列表中第一个元素设定为基准数字,赋值给mid变量,然后将整个列表中比基准小的数值放在基准的左侧,比基准到的数字放在基准右侧。然后将基准数字左右两侧的序列在根据此方法进行排放。
2.定义两个指针,left指向最左侧,right指向最右侧
3.然后对最右侧指针进行向左移动,移动法则是,如果指针指向的数值比基准小,则将指针指向的数字移动到基准数字原始的位置,然后停止移动,否则继续移动指针。
4.如果最右侧指针指向的数值移动到基准位置时,开始移动最左侧指针,将其向右移动,如果该指针指向的数值大于基准则将该数值移动到最右侧指针指向的位置,然后停止移动,否则继续移动指针。
5.如果左右侧指针重复则,将基准放入左右指针重复的位置,则基准左侧为比其小的数值,右侧为比其大的数值。
代码
# 快速排序 -- 从小到大
# 参数 lis列表,start列表第一个元素索引,end最后一个元素索引
def quick(lis,start,end):
left = start # 最左侧指针
right = end # 最右侧指针
# 递归结束条件
if left > right:
return
# 基数
mid = lis[left]
while left < right:
# 向左移动右侧指针
while left < right:
if lis[right] >= mid:
right -= 1
else:
# 右侧指针位置元素赋给最左侧指针位置
lis[left] = lis[right]
break
# 向右移动左侧指针
while left < right:
if lis[left] <= mid:
left += 1
else:
# 左侧指针位置元素赋给最右侧指针位置
lis[right] = lis[left]
break
# 如果左右指针相等,赋值基数
if left == right:
lis[left] = mid
# 递归,重复上述步骤
quick(lis,start,left-1)
quick(lis,right+1,end)
return lis
# 测试
lis = [3,4,9,6,2,7,8,3]
print(quick(lis,0,len(lis)-1))