# 本章的内容是树形dp,非常重要,熟练掌握后可以解决大部分二叉树问题。
# 以及大数据问题的补充点,以及之前学习过程中没有完成的二进制运算(感谢Golang)
class TreeNode(object):
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def __repr__(self):
return str(self.value)
# 以上是本章所需的二叉树节点定义
# 求一颗二叉树的最大距离
# 解题思路:最大距离的可能性有三种:1.头节点不在最大距离内,最大距离由左树贡献; 2.头节点不在最大距离内,最大距离由右树贡献; 3.头节点在最大距离内,最大距离左树高度+右树高度+1贡献
def max_Distance(head):
if head == None:
return [0, 0]
leftdata = max_Distance(head.left)
rightdata = max_Distance(head.right)
distance = max(leftdata[0], rightdata[0], leftdata[1] + rightdata[1] + 1)
height = max(leftdata[1], rightdata[1]) + 1
return [distance, height]
'''
由上题引出,树形dp第一步:
以某个节点为头节点的子树中,先分析有哪些可能性,分析尽可能的朝着向左右树要信息的思路去想
第二步:
根据第一步的分析。列出所有需要的信息
第三步:
合并第二步得到信息,对所有树提出同样的要求,找到return值
第四步:
设置basecase(多半是head==None)返回的值,得到并处理左右树的信息,返回处理后的信息
'''
# 员工有一个属性,快乐值;一个列表,表示下级。类似于多叉树
class Employee(object):
def __init__(self, happy):
self.happy = happy
self.subordinates = []
def add(self, *subordinates):
for subordinate in subordinates:
self.subordinates.append(subordinate)
def __repr__(self):
return str(self.happy)
# 现在公司要办party,可以决定那些员工来,哪些不来。
# 如果某个员工来了,他的下级都不能来
# 怎样让party的hapy最高
# 解题思路:
# 1.根节点来:
# root+ a(a不来)+ b(b不来)+ c(c不来)
# 2.根节点不来
# 0+ a(max(来,不来))+ b(max(来,不来))+ c(max(来,不来))
def party_happy(root):
if root.subordinates == []:
return [root.happy, 0]
come = root.happy
uncome = 0
for subordinate in root.subordinates:
res = party_happy(subordinate)
come += res[1]
uncome += max(res[0], res[1])
return [come, uncome]
# Morris遍历
# 一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)通过利用原树中大量空闲指针的方式,达到节省空间的目的
# 用叶子节点指针移动的方法模拟递归的过程,
def morris(root):
if root == None:
return
cur = root
while cur != None:
leftmostRight = cur.left
# 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
# 如果指向空,说明是第一次来到这个节点
# 如果指向自己,说明是第二次
if leftmostRight != None:
while leftmostRight.right != None and leftmostRight.right != cur:
leftmostRight = leftmostRight.right
if leftmostRight.right == None:
leftmostRight.right = cur
cur = cur.left
continue
else:
leftmostRight.right = None
# 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
cur = cur.right
def preorder_morris(root):
if root == None:
return
cur = root
while cur != None:
leftmostRight = cur.left
# 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
# 如果指向空,说明是第一次来到这个节点
# 如果指向自己,说明是第二次
if leftmostRight != None:
while leftmostRight.right != None and leftmostRight.right != cur:
leftmostRight = leftmostRight.right
if leftmostRight.right == None:
# 说明是第一次来到cur,往cur左移动的同时,让leftmostRight和cur练成环,方便以后回到cur.
leftmostRight.right = cur
print(cur)
cur = cur.left
continue
else:
# 说明是第二次来到cur,说明左树都走完了.断开环,往右走
leftmostRight.right = None
cur = cur.right
else:
# 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
# 如果没有左树,所以这是第一次来到
print(cur)
cur = cur.right
def inorder_morris(root):
if root == None:
return
cur = root
while cur != None:
leftmostRight = cur.left
# 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
# 如果指向空,说明是第一次来到这个节点
# 如果指向自己,说明是第二次
if leftmostRight != None:
while leftmostRight.right != None and leftmostRight.right != cur:
leftmostRight = leftmostRight.right
if leftmostRight.right == None:
leftmostRight.right = cur
cur = cur.left
continue
else:
leftmostRight.right = None
# 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
print(cur)
cur = cur.right
def postorder_morris(root):
if root == None:
return
cur = root
while cur != None:
leftmostRight = cur.left
# 利用左子树最右节点的右指针是否指向自己,来判断是否是第一次来到整个节点
# 如果指向空,说明是第一次来到这个节点
# 如果指向自己,说明是第二次
if leftmostRight != None:
while leftmostRight.right != None and leftmostRight.right != cur:
leftmostRight = leftmostRight.right
if leftmostRight.right == None:
leftmostRight.right = cur
cur = cur.left
continue
else:
leftmostRight.right = None
reversed(root.left)
# 如果没有左树,往右走,如果自己是叶子节点,在之前就已经被和之前的某个节点连成环了
cur = cur.right
def reversed(root):
pre = None
while root != None:
thenext = root.right
root.right = pre
pre = root
root = thenext
tail = pre
cur = tail
while cur != None:
print(cur)
cur = cur.right
pre = None
while root != None:
thenext = root.right
root = pre
pre = root
root = thenext
'''
二叉树遍历问题用Morris
需要合并两个分支的用递归方法
大数据问题补充
'''
# 大数据题目的解题技巧
# 1. 哈希函数可以把数据按照种类均匀分类
# 2. 布隆过滤器用于合计的建立和查询,并可以节省大量空间
# 3. 一致性哈希解决数据服务器的负载管理问题
# 4. 利用并查集结构解决岛问题的并行计算
# 5. 位图解决某一范围数字的出现情况
# 6. 利用分段统计思想、并进一步节省大量空间
# 7. 利用堆和外排序来做多个处理单元的结果合并
# 32位无符号整数(uint32)的范围是 0~4294967295,现在有一个正好包含40亿个无符号整数的文件,所以文件中一定有没有出现过的数.
# 使用最多1GB的内存,找到所有没出现的数? 放到位图中描黑: 2^32/8≈500M,每1比特代表一个数
# 如果只是用3KB内存,找到一个没出现的数? 把所有的空间尽可能的申请位int类型的数组,3kb可以申请700+的数组,去向下取最近的2的指数512, 2^32/512=8388608.
# 用arr统计词频,每一个单元代表把8388608范围,扫描一遍文件,必定至少有一个单元小于其他的,说明这个范围内的数比较少.
# 假设是arr[0]比较少,说明0~8388607的数比较少,那么这时候用arr统计0~8388607的数,arr[0]代表0~16383.找到较少的那个范围,用位图描黑统计出现情况,只需要16384bit=2KB
# 有一个包含100亿个URL的大文件,假设灭个URL占用64B,找出其中所有重复的URL
# 布隆过滤器: 每读取一个就标记,重复就记录
# 哈希函数: 用哈希函数分流,保证每中URL都能进出同一个组.再在每个组统计重复URL
# 设计一种可行方法在亿级数据求出Top100的词汇.
# 思路: 先把数据分流统计词频把放入一个大根堆中,再把每个小文件的堆顶拿出来再组成一个大根堆.
# 每次弹出一个词就从它的源文件中获取一个继续比较.就是不停的让每个小文件的最大词频比较
# 32位无符号整数(uint32)的范围是 0~4294967295,现在有一个正好包含40亿个无符号整数的文件
# 使用1GB的内存的内存,找出所有出现了两次的数:
# 1.哈希函数分流; 2. 用位图: 位图只能表示一个数是否出现过,但是可以用两位来表示更多的信息;
# 因为我们只需要考虑0、1、2、更多次,一共四种情况,所以可以用00\01\10\11来表示.需要正好4294967296/8/2=1GB
# 使用10KB的内存,找到这40亿个整数的中位数:
# 同理找没出现,10KB申请2560个数组,去向下取的2^11=2048. 2^32/2048=2097152,arr[0]代表0~209751数的词频.
# 找到中位数所在的范围,再统计那个范围的词频。arr[]代表1024个数,找到中位数所在的那个范围,最后直接那个范围统计每个数的词频
# 有一个无序的10G的无序int文件,用有限的内存,把它变成有序的int文件。
# 解题思路:
# 方法一:
# 一条记录(value,出现的次数count),16btye.
# 把文件按照之前的方法分流,用所有的内存申请一个小根堆,每次值排序一个范围内的数
# 记录下来,直到把所有范围都排序完.
# 方法二:
# 用所有内存申请一个大根堆排序,每一轮排序完,把阈值设定为上一轮最大的数,周而复始
'''
下面是位运算的一些方法,因为python变量类型变化过于诡异,使用的语言是Golang语言
'''
# 位运算
# 给定两个有符号的32位整数a和b,不做任何判断返回a和b中较大的
'''
func sign(n int32) int32 {
// 取符号位,正数为0负数为1
return ((n >> 31) & 1) ^ 1
}
func getmax(a, b int32) int32 {
c := a - b
sign_A := sign(a)
sign_B := sign(b)
sign_C := sign(c)
// 如果a、b符号不一样,为1;一样为0
diffSab := sign_A ^ sign_B
// 如果a、b符号一样,为1;不一样为0
sameSab := diffSab ^ 1
// 当a、b符号不一样,且a是非负数,返回a
// 当a、b符号一样,且a-b是非负数,返回a
return_A := diffSab*sign_A + sameSab*sign_C
// 否则返回b
return_B := return_A ^ 1
return a*return_A + b*return_B
}
func whoisbigest(a, b int32) string {
who := getmax(a, b)
switch who {
case a:
return fmt.Sprintf("A is the bigest one")
case b:
return fmt.Sprintf("B is the bigest one")
default:
return fmt.Sprintf("err")
}
}
'''
# 判断一个数是不是2的幂、4的幂
# 2的幂就是只有一个1,4的幂的出了是2的幂以外,它的1还只在偶数位上
'''
func getrightone1(n uint64) uint64 {
// n取反,那么n最右边的1就变成了n2最右的0,n2+1最右的0就变成了最右的1且其他位置肯定不一样,再和n做与运算就能得到最右的1了
n2 := ^n
n2 += 1
return n2 & n
}
func isPowerofTwo1(n uint64) bool {
// 如果n是2的幂,则n一定只有一个1,那么去掉了最右的那个1之后,n就变成0了
switch getrightone1(n) - n {
case 0:
return true
default:
return false
}
}
func isPowerofTwo2(n uint64) bool {
// 如果n是2的幂,则n一定只有一个1,n+1就会打乱那个1,再和n做与运算,1的位置肯定不重合
n2 := n - 1
switch n & n2 {
case 0:
return true
default:
return false
}
}
func isPowerofFour(n uint64) bool {
// 在n是2的幂的前提下,如果n与上10101010101...不等于0,说明1在基数位上
if isPowerofTwo2(n) && n&0x5555555555555555 != 0 {
return true
}
return false
}
'''
# 位运算做到加减乘除法
# 异或运算(相同为1,不同为0)就是无进位相加的结果,与运算(同1为1)再左移一位就是进位信息
'''
func add(a, b int32) int32 {
if b == 0 {
return a
}
OR := a ^ b
AND := (a & b) << 1
res := add(OR, AND)
return res
}
func getnegative(n int32) int32 {
return add(^n, 1)
}
func sub(a, b int32) int32 {
return add(a, getnegative(b))
}
func mul(a, b int32) int32 {
var n int32
if b>>31 != 0 {
b = ^b + 1
a = ^a + 1
}
for {
if b == 0 {
return n
}
// 如果左移一位再右移一位值不变,就不相加
if b&1 == 1 {
n = add(n, a)
}
a = a << 1
b = b >> 1
}
}
func div(a, b int32) int32 {
var needed bool = false
if b>>31 != 0 {
if a>>31 != 0 {
b = getnegative(b)
a = getnegative(a)
} else {
b = getnegative(b)
needed = true
}
} else if a>>31 != 0 {
a = getnegative(a)
needed = true
}
n := 32
var res int32
for {
if n == -1 {
if needed {
res = ^res + 1
}
return res
}
if (a >> n) >= b {
a = sub(a, (b << n))
res = add(res, (1 << n))
}
n -= 1
}
}
'''
dp怎么使用Python
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章