树经典问题

1 树的特点与性质

树(Tree)​​是一种特殊的图,满足以下条件:

        ​​连通无环​​:任意两个顶点之间有且只有一条路径相连,且没有闭环。

        ​​边数最少​​:若树有 n个顶点,则恰好有 n−1条边。

        ​​删边会不连通,加边会成环​​:这是树的脆弱性和极简性的体现。

​类比​​:树像一棵倒挂的家族树,从根向下分叉,没有“近亲结婚”(无环)。

​​树是图的子集​​:所有树都是图,但并非所有图都是树。

​图的生成树​​:如果一个连通图有环,可以通过删除某些边得到一棵覆盖所有顶点的树(称为生成树)。

​示例​​:假设一个图表示城市间的所有可能道路(含环路),其生成树就是保留部分道路,使所有城市连通且无冗余路径。

叶子节点​​:度为1的顶点(即只有一条边连接的顶点)。

​根树​​:指定一个顶点为根,可以定义父子关系和层级(如二叉树)。

​最小连通性​​:树是保证连通的前提下,边数最少的图。

2 加权无向图的最小生成树

 3 二叉树与k叉树

3.1 定义

二叉树​​:每个节点最多有2个子节点(左子节点和右子节点)

​根节点(Root)​​:树的顶端节点

​叶子节点(Leaf)​​:没有子节点的节点

​深度(Depth)​​:从根节点到该节点的最长路径长度

​高度(Height)​​:从该节点到最远叶子节点的最长路径长度

​k叉树​​:每个节点最多有 k个子节点

        节点数计算:若树高为 h,则最多有 k^h−1​/k−1 个节点(等比数列求和)

3.2 ​​性质​

        第 i 层最多有 2^i−1个节点。

        深度为 h 的二叉树最多有 2^h−1个节点(满二叉树)。

        对于任何非空二叉树,叶子节点数 = 度为2的节点数 + 1

3.3 二叉树的分类

3.3.1 满二叉树(Full Binary Tree)

每个节点都有0或2个子节点

      A
     / \
    B   C
   / \
  D   E

3.3.2 完全二叉树(Complete Binary Tree)

除最后一层外,其他层节点都填满,且最后一层节点靠左排列

      A
     / \
    B   C
   / \
  D   E

3.3.3 二叉搜索树(Binary Search Tree, BST)

对于每个节点:

  • 左子树所有节点的值 < 当前节点的值
  • 右子树所有节点的值 > 当前节点的值
      4
     / \
    2   6
   / \ / \
  1  3 5 7

3.3.4 平衡二叉树(AVL Tree)与二分法查询

节点的左右子树高度差不超过1

      3
     / \
    2   4
   /     \
  1       5

二分法查询核心特点

1.​​时间复杂度​​:始终为O(log n),得益于树的平衡性

​​2.比较规则​​:

  • 目标值 == 当前节点:返回节点

  • 目标值 < 当前节点:搜索左子树

  • 目标值 > 当前节点:搜索右子树

3.​​平衡性保障​​:为了保持BST的高效性,需要​​平衡树​​——通过旋转操作让树的高度尽可能低,确保查找、插入、删除的时间复杂度稳定在O(log n)

  • AVL树:通过旋转保持左右子树高度差≤1,通过​​旋转​​(左旋、右旋、双旋)调整树结构

  • 红黑树:通过颜色规则保持最长路径≤2*最短路径

        a.每个节点是红色或黑色

        b.根节点是黑色

        c.叶子节点(NIL空节点)是黑色

        ​​d.红色节点的子节点必须是黑色​​(即不能有连续红节点)

        e.从任意节点到其叶子节点的路径上,黑色节点数相同(称为“黑高”)

特性​

​AVL树​

​红黑树​

​平衡标准​

绝对平衡(高度差≤1)

近似平衡(黑高相同)

​插入/删除​

频繁旋转,效率较低

旋转少,效率较高

​查询效率​

更高(严格平衡)

稍低

​适用场景​

静态数据(如数据库索引)

动态数据(如Map、STL)

def search(root, target):
    """二分法查询核心逻辑"""
    if root is None or root.key == target:
        return root
    
    # 目标值小于当前节点,搜索左子树
    if target < root.key:
        return search(root.left, target)
    # 目标值大于当前节点,搜索右子树
    else:
        return search(root.right, target)

3.4 二叉树的遍历

       A
      / \
     B   C
    / \   \
   D   E   F

3.4.1 深度优先遍历(DFS)​

前序遍历(根-左-右)

def preorder(root):
    if root:
        print(root.val)
        preorder(root.left)
        preorder(root.right)
//输出​​:A B D E C F

中序遍历(左-根-右)

def postorder(root):
    if root:
        postorder(root.left)
        postorder(root.right)
        print(root.val)
//输出​​:D B E A F C

后序遍历(左-右-根)

def postorder(root):
    if root:
        postorder(root.left)
        postorder(root.right)
        print(root.val)
//输出​​:D E B F C A

​3.4.2 广度优先遍历(BFS)

from collections import deque

def bfs(root):
    queue = deque([root])
    while queue:
        node = queue.popleft()
        print(node.val)
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

//输出​​:A B C D E F

3.5 应用举例

3.5.1 表达式求值(语法树)

      *
     / \
    +   /
   / \ / \
  3  4 5 2

表示:(3+4)*(5/2)

3.5.2 文件系统目录结构

      /
     / \
   bin home
      / \
   user doc

3.5.3 简单决策系统(决策树 / 森林、机器学习分类问题、集成学习)

        天气?
       /    \
   晴朗      阴天
   / \       / \
游泳 爬山  看电影

分类/拟合,

多棵树,每棵树给系数->森林,投票法、系数法,

根据先验知识,依赖专家系统,

4 哈夫曼编码

        哈夫曼编码(Huffman Coding)是一种基于字符出现频率的变长编码方法,核心思想是​​用更短的二进制码表示高频出现的字符​​,从而减少总存储空间。由David A. Huffman于1952年提出。它是数据压缩领域最重要的算法之一,广泛应用于ZIP、JPEG、MP3等文件格式中。

4.1 核心思想

  • ​变长编码​​:高频字符用短编码,低频字符用长编码

  • ​前缀码​​:任何字符的编码都不是其他字符编码的前缀(避免解码歧义)

  • ​最优编码​​:对于给定的字符频率分布,哈夫曼编码能产生最短的平均编码长度

4.2 算法特性

  • ​贪心算法​​:每次合并频率最小的两个节点

  • ​时间复杂度​​:O(n log n)(使用优先队列实现)

  • ​空间复杂度​​:O(n)

4.3 构建步骤

  1. 统计字符频率,创建叶子节点

  2. 将节点按频率放入优先队列(最小堆)

  3. 循环取出两个最小频率的节点,合并为新节点(频率=子节点频率和)

  4. 将新节点放回队列

  5. 重复直到只剩一个节点,形成哈夫曼树

4.4 示例

import heapq
from collections import defaultdict

def build_huffman_tree(freq):
    heap = [[weight, [char, ""]] for char, weight in freq.items()]
    heapq.heapify(heap)
    
    while len(heap) > 1:
        lo = heapq.heappop(heap)
        hi = heapq.heappop(heap)
        for pair in lo[1:]:
            pair[1] = '0' + pair[1]
        for pair in hi[1:]:
            pair[1] = '1' + pair[1]
        heapq.heappush(heap, [lo[0] + hi[0]] + lo[1:] + hi[1:])
    
    return sorted(heap[0][1:], key=lambda p: (len(p[1]), p))

# 示例1:简单字母频率
print("示例1:字母频率 {'A': 50, 'B': 30, 'C': 20}")
freq1 = {'A': 50, 'B': 30, 'C': 20}
huffman1 = build_huffman_tree(freq1)
for char, code in huffman1:
    print(f"'{char}': {code}")

# 示例2:英文句子
print("\n示例2:句子 'this is an example of huffman encoding'")
text = "this is an example of huffman encoding"
freq2 = defaultdict(int)
for char in text:
    freq2[char] += 1

huffman2 = build_huffman_tree(freq2)
for char, code in huffman2:
    print(f"'{char}': {code}")
示例1:字母频率 {'A': 50, 'B': 30, 'C': 20}
'A': 0
'B': 11
'C': 10
示例2:句子 'this is an example of huffman encoding'
' ': 101
'n': 010
'a': 1100
'e': 1101
'f': 1110
'h': 0010
'i': 1111
'm': 0011
'o': 0110
's': 1000
'c': 00000
'd': 00001
'g': 00010
'l': 00011
'p': 01110
't': 01111
'u': 10010
'x': 10011

 5 哈希算法

        哈希(Hash)是一种将任意长度的输入数据转换为固定长度输出的算法,这种输出称为哈希值或散列值。

5.1 哈希的核心特性

  1. ​确定性​​:相同输入永远产生相同输出(例:"hello" → 总是生成2cf24dba5...(SHA-256)

  2. ​快速计算​​:O(1)时间复杂度(理想情况下)

  3. ​抗碰撞性​​:难以找到两个不同输入产生相同输出

  4. ​不可逆性​​:无法从哈希值还原原始数据

5.2 常见哈希算法对比

算法输出长度特点典型应用
MD5128位已不安全,易碰撞文件校验
SHA-1160位逐渐被淘汰Git版本控制
SHA-256256位当前推荐的安全哈希区块链、密码存储
CRC3232位快速但仅用于错误检测网络数据包校验

5.3 哈希冲突解决方案 

​1、​链地址法​​(Separate Chaining):

        冲突元素存储在链表中

        实现简单但需要额外内存

​2、开放寻址法​​(Open Addressing):

        线性探测:h(k,i) = (h'(k) + i) mod m

        平方探测:h(k,i) = (h'(k) + i²) mod m

        双重哈希:h(k,i) = (h₁(k) + i*h₂(k)) mod m

​3、布谷鸟哈希​​(Cuckoo Hashing):

        使用两个哈希函数

        冲突时踢出原有元素并重新插入

5.4 应用

​密码保护​

        网站存储的是哈希值而非明文密码

        加盐(随机数据)防止彩虹表攻击

​文件校验​

        下载文件后对比哈希值验证是否被篡改

        比如Linux系统ISO文件的SHA256校验

​区块链基础​

        每个区块包含前一个区块的哈希值

        形成不可篡改的链条

​数据去重​

        云存储用哈希识别重复文件

        比如Dropbox节省存储空间的原理

​快速查找​

        哈希表能在O(1)时间复杂度找到数据

        比遍历查找快数百倍

6 剪枝

        剪枝(Pruning)是优化树形结构的关键技术,主要用于消除冗余分支。 ​​通过提前终止对“无效分支”的搜索或计算,减少不必要的资源消耗​​。

6.1 剪枝的本质

        就像修剪果树多余的枝条,剪枝通过移除树结构中不必要的部分来:

  1. ​降低复杂度​​(减少计算量)

  2. ​防止过拟合​​(提升泛化能力)

  3. ​加速查询​​(缩短搜索路径)

6.2 剪枝的两种类型

6.2.1 ​​决策树剪枝​

​场景​​:机器学习中防止模型过拟合(记住训练数据但泛化能力差,模型在训练集上表现极好,但在新数据上表现很差。)
​方法​​:

  • ​预剪枝​​:生长时提前停止分裂
    (如:达到最大深度/节点样本数少于阈值)

  • ​后剪枝​​:完全生长后删除冗余分支
    (如:计算剪枝前后验证集准确率变化)

6.2.2 搜索剪枝

场景​​:算法优化(如Alpha-Beta剪枝)
​原理​​:
当发现某分支不可能优于已知解时,立即停止搜索该分支

6.3 剪枝核心

  1. ​评估当前路径​​:计算已探索部分的评估值

  2. ​对比已知最优​​:与当前全局最优解比较

  3. ​决策剪枝​​:

  • 最大化问题:当前路径≤已知最大值 → 剪枝
  • 最小化问题:当前路径≥已知最小值 → 剪枝
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值