程序员必备的十大技能(进阶版)之高阶数据结构与算法(二)

简介: 教程来源 http://xcfsr.cn/ 本节深度解析四大经典树形结构:红黑树(保障O(log n)性能,支撑Java/C++有序容器);B/B+树(数据库索引核心,优化磁盘I/O与范围查询);线段树(高效处理区间查询/更新,支持懒标记);前缀树(Trie,专精字符串匹配与自动补全)。附核心代码与时间复杂度分析。

三、树形结构的深度剖析

3.1 平衡二叉搜索树——红黑树(Red-Black Tree)
红黑树是Java中TreeMap、TreeSet以及C++ STL中std::map的底层实现。其核心性质保证了树的高度控制在 2×log₂(n+1) 内。

红黑树的五大性质:

每个节点是红色或黑色

根节点是黑色

所有叶子节点(NIL)是黑色

红色节点的两个子节点必须是黑色(不存在两个连续的红色节点)

从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点(黑高相同)

左旋与右旋操作

// 左旋(以节点x为支点)
private void leftRotate(Node x) {
    Node y = x.right;        // y是x的右子节点
    x.right = y.left;        // 将y的左子树交给x的右子树

    if (y.left != nil) {
        y.left.parent = x;
    }

    y.parent = x.parent;     // 将y的父节点指向x的父节点

    if (x.parent == nil) {
        root = y;            // x是根节点,y成为新根
    } else if (x == x.parent.left) {
        x.parent.left = y;
    } else {
        x.parent.right = y;
    }

    y.left = x;
    x.parent = y;
}

// 右旋(对称操作)
private void rightRotate(Node y) {
    Node x = y.left;
    y.left = x.right;

    if (x.right != nil) {
        x.right.parent = y;
    }

    x.parent = y.parent;

    if (y.parent == nil) {
        root = x;
    } else if (y == y.parent.left) {
        y.parent.left = x;
    } else {
        y.parent.right = x;
    }

    x.right = y;
    y.parent = x;
}

插入后的修复算法

private void fixAfterInsertion(Node x) {
    x.color = RED;

    // 核心:父节点是红色时才需要修复
    while (x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            // 父节点是祖父节点的左孩子
            Node y = rightOf(parentOf(parentOf(x)));  // 叔叔节点

            if (colorOf(y) == RED) {
                // Case 1:叔叔是红色(颜色翻转)
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                // 叔叔是黑色
                if (x == rightOf(parentOf(x))) {
                    // Case 2:x是右孩子(左旋父节点)
                    x = parentOf(x);
                    leftRotate(x);
                }
                // Case 3:x是左孩子(右旋祖父节点 + 变色)
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rightRotate(parentOf(parentOf(x)));
            }
        } else {
            // 对称情况:父节点是祖父节点的右孩子
            // ... 镜像代码
        }
    }
    root.color = BLACK;
}

3.2 B树与B+树——数据库索引的基石
B树(Balanced Tree)是一种多路平衡查找树,每个节点可以存储多个键值和子节点指针。

B树性质(以阶数m表示):
每个节点最多有 m 个子节点

根节点至少有 2 个子节点(除非是叶子节点)

非根非叶节点至少有 ⌈m/2⌉ 个子节点

所有叶子节点在同一层

B+树 vs B树:
image.png
为什么数据库用B+树而不是二叉搜索树?

磁盘I/O次数:树高更矮(m=1000时,百万数据树高仅2-3层)

预读特性:节点大小匹配磁盘页(通常4KB或16KB)

范围查询:叶子节点链表提供极佳性能

// B+树节点的简化表示
class BPlusTreeNode {
    private int[] keys;              // 键值数组
    private Object[] values;         // 值数组(仅叶子节点)
    private BPlusTreeNode[] children; // 子节点数组(仅内部节点)
    private BPlusTreeNode next;      // 叶子节点链表指针
    private boolean isLeaf;          // 是否为叶子
    private int keyCount;            // 当前键值数量
}

3.3 线段树(Segment Tree)
线段树用于区间查询和区间更新,典型应用:区间和、区间最值、区间GCD。

class SegmentTree {
    private int[] tree;
    private int[] arr;
    private int n;

    public SegmentTree(int[] arr) {
        this.arr = arr;
        this.n = arr.length;
        // 线段树需要4倍空间
        this.tree = new int[4 * n];
        build(1, 0, n - 1);
    }

    // 构建线段树(递归)
    private void build(int node, int start, int end) {
        if (start == end) {
            tree[node] = arr[start];
            return;
        }
        int mid = (start + end) / 2;
        int leftChild = node * 2;
        int rightChild = node * 2 + 1;

        build(leftChild, start, mid);
        build(rightChild, mid + 1, end);

        // 合并(这里以求和为例)
        tree[node] = tree[leftChild] + tree[rightChild];
    }

    // 区间查询 [l, r]
    public int query(int l, int r) {
        return query(1, 0, n - 1, l, r);
    }

    private int query(int node, int start, int end, int l, int r) {
        // 完全无交集
        if (r < start || l > end) {
            return 0;  // 求和时返回0,最小值返回INF,最大值返回-INF
        }
        // 完全包含
        if (l <= start && end <= r) {
            return tree[node];
        }
        // 部分重叠
        int mid = (start + end) / 2;
        int leftSum = query(node * 2, start, mid, l, r);
        int rightSum = query(node * 2 + 1, mid + 1, end, l, r);
        return leftSum + rightSum;
    }

    // 单点更新
    public void update(int idx, int value) {
        update(1, 0, n - 1, idx, value);
    }

    private void update(int node, int start, int end, int idx, int value) {
        if (start == end) {
            arr[idx] = value;
            tree[node] = value;
            return;
        }
        int mid = (start + end) / 2;
        if (idx <= mid) {
            update(node * 2, start, mid, idx, value);
        } else {
            update(node * 2 + 1, mid + 1, end, idx, value);
        }
        tree[node] = tree[node * 2] + tree[node * 2 + 1];
    }

    // 延迟更新(懒标记)—— 适用于区间增加
    private int[] lazy;  // 懒标记数组

    public void rangeAdd(int l, int r, int delta) {
        rangeAdd(1, 0, n - 1, l, r, delta);
    }

    private void rangeAdd(int node, int start, int end, int l, int r, int delta) {
        if (lazy[node] != 0) {
            // 将懒标记下推
            tree[node] += lazy[node] * (end - start + 1);
            if (start != end) {
                lazy[node * 2] += lazy[node];
                lazy[node * 2 + 1] += lazy[node];
            }
            lazy[node] = 0;
        }

        if (r < start || l > end) return;

        if (l <= start && end <= r) {
            tree[node] += delta * (end - start + 1);
            if (start != end) {
                lazy[node * 2] += delta;
                lazy[node * 2 + 1] += delta;
            }
            return;
        }

        int mid = (start + end) / 2;
        rangeAdd(node * 2, start, mid, l, r, delta);
        rangeAdd(node * 2 + 1, mid + 1, end, l, r, delta);
        tree[node] = tree[node * 2] + tree[node * 2 + 1];
    }
}

时间复杂度:建树O(n),每次查询/更新O(log n)。

3.4 前缀树(Trie / 字典树)
Trie树是处理字符串集合的利器,常用于自动补全、拼写检查、IP路由。

class TrieNode {
    private TrieNode[] children;
    private boolean isEndOfWord;
    private int passCount;   // 经过该节点的单词数(用于词频统计)

    public TrieNode() {
        children = new TrieNode[26];  // 小写字母
        isEndOfWord = false;
        passCount = 0;
    }
}

public class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    // 插入单词
    public void insert(String word) {
        TrieNode node = root;
        node.passCount++;
        for (char c : word.toCharArray()) {
            int index = c - 'a';
            if (node.children[index] == null) {
                node.children[index] = new TrieNode();
            }
            node = node.children[index];
            node.passCount++;
        }
        node.isEndOfWord = true;
    }

    // 查找完整单词
    public boolean search(String word) {
        TrieNode node = searchPrefix(word);
        return node != null && node.isEndOfWord;
    }

    // 查找前缀是否存在
    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }

    private TrieNode searchPrefix(String prefix) {
        TrieNode node = root;
        for (char c : prefix.toCharArray()) {
            int index = c - 'a';
            if (node.children[index] == null) {
                return null;
            }
            node = node.children[index];
        }
        return node;
    }

    // 删除单词(需要谨慎处理,不能简单删除节点)
    public boolean delete(String word) {
        if (!search(word)) return false;
        return deleteHelper(root, word, 0);
    }

    private boolean deleteHelper(TrieNode node, String word, int depth) {
        node.passCount--;
        if (depth == word.length()) {
            node.isEndOfWord = false;
            return node.passCount == 0;  // 如果passCount为0,可以删除该节点
        }
        int index = word.charAt(depth) - 'a';
        boolean shouldDeleteChild = deleteHelper(node.children[index], word, depth + 1);
        if (shouldDeleteChild) {
            node.children[index] = null;
            return node.passCount == 0;
        }
        return false;
    }

    // 获取以prefix为前缀的所有单词(自动补全)
    public List<String> getWordsWithPrefix(String prefix) {
        List<String> result = new ArrayList<>();
        TrieNode node = searchPrefix(prefix);
        if (node == null) return result;
        collectWords(node, new StringBuilder(prefix), result);
        return result;
    }

    private void collectWords(TrieNode node, StringBuilder current, List<String> result) {
        if (node.isEndOfWord) {
            result.add(current.toString());
        }
        for (int i = 0; i < 26; i++) {
            if (node.children[i] != null) {
                current.append((char)('a' + i));
                collectWords(node.children[i], current, result);
                current.deleteCharAt(current.length() - 1);
            }
        }
    }
}

空间优化:对于大量长字符串共享前缀的场景,Trie树非常高效;但对于短字符串或随机字符串,可能不如哈希表。
来源:
http://lemci.cn/

相关文章
|
29天前
|
存储 弹性计算 小程序
阿里云最便宜云服务器怎么选?38元/99元/199元机型性能全解析
阿里云推出38元/年、99元/年、199元/年三档高性价比云服务器,分别面向个人开发者、小微初创及中小企业。本文从配置、实测性能与适用场景三维度深度对比,助力大家轻松选择低成本上云!
387 4
|
7天前
|
运维 Cloud Native 持续交付
阿里云峰会 Agent Native 基础设施专场邀您参加!
5 月 20 日杭州·西子宾馆,阿里云峰会【Agent Native 基础设施】专题论坛上,我们将围绕云原生的 Agent Infra 全栈实践,分享阿里云在一站式构建部署、多智能体治理与协作、全链路观测与持续优化、全域智能运维等方向的工程化思考和产品解决方案,助力企业打通从 Agent 开发到规模化运行的最后一公里,让智能真正成为可持续交付的生产力。
|
29天前
|
人工智能 开发工具 开发者
终端里跑 3D 老鼠,桌面窗口成摆锤;AI 大佬新公司估值百亿起
上周技术圈的信息挺杂,但有几条线索值得放在一起看。 一边,AI 产品继续往具体工作流里走:Claude Code 开始支持 Agent View,OpenAI 把 Codex 带到移动端;另一边,开发者社区继续整活:有人给 Claude Code 做实体旋钮,有人做 Claude 用量桌面仪表盘,还有人把终端做成能显示 3D 老鼠的玩具。
237 1
终端里跑 3D 老鼠,桌面窗口成摆锤;AI 大佬新公司估值百亿起
|
7天前
|
存储 程序员 调度
程序员必备的十大技能(进阶版)之底层计算机原理(一)
教程来源 http://zlpow.cn/ 本文深入计算机底层原理,涵盖体系结构、CPU微架构、内存层次、I/O、操作系统、汇编、编译链接等十大维度,揭示代码如何在硬件上高效运行,助开发者写出更可靠、高性能的程序。
|
29天前
|
程序员 开发工具 git
初级程序员必备的十大技能之规范编码与团队协作(三)
教程来源 http://qcycj.cn/ 本节系统阐述高效团队协作核心实践:从精准提问、高效会议、知识共享到冲突化解,并配套自动化工具链(Prettier/ESLint/Husky/Commitlint/GitHub Actions),全面提升研发协同质量与工程规范性。
|
29天前
|
设计模式 SQL Java
程序员必备的十大技能(进阶版)之精通主流框架与源码思想(二)
教程来源 http://qeext.cn/ 本文深入解析Spring框架中四大核心设计模式(工厂、模板方法、适配器、观察者)的实战应用,并详解Spring Boot自动配置原理:从@SpringBootApplication组合注解、spring.factories机制、条件化加载(@Conditional系列),到手写Starter实践,助你由源码理解走向约定优于配置的开发范式。
|
29天前
|
缓存 算法 搜索推荐
程序员必备的十大技能(进阶版)之高阶数据结构与算法(四)
教程来源 http://qcycj.cn/ 本节介绍海量数据与字符串匹配核心算法:布隆过滤器(高效判存、允许误报)、倒排索引(支撑搜索引擎的词→文档映射)、KMP(线性单模匹配)及AC自动机(O(n)多模匹配)。兼顾原理、代码实现与典型场景,适用于缓存穿透防护、URL去重、全文检索与敏感词识别等工业级应用。
|
1月前
|
前端开发 程序员 开发工具
初级程序员必备的十大技能之开发工具熟练使用(四)
教程来源 https://tmywi.cn/ VS Code深度集成Git:快捷键操作、冲突可视化解决;GitLens增强代码溯源与历史追踪;配合高效命令行别名与撤销技巧;辅以Node/前端多维调试方案,全面提升开发效能。
|
29天前
|
SQL Java 数据库
[019][数据模块]MyBatis-Plus 拦截器扩展设计:基于函数式接口与 Spring 自动装配
本文介绍基于函数式接口`InnerInterceptorSupplier`与Spring `ObjectProvider`的MyBatis-Plus拦截器自动装配方案,支持按`@Order`声明式排序、延迟创建及模块化扩展,提升分页、乐观锁等能力的可插拔性与框架集成友好度。(239字)
83 0
|
29天前
|
Dubbo Java 程序员
程序员必备的十大技能(进阶版)之精通主流框架与源码思想(三)
教程来源 xgmoi.cn 本文详解Java SPI机制(含原生与Dubbo增强版)及其在框架扩展中的核心作用;系统传授高效阅读源码的五步法(定骨架、带问题、写注释、对比读、重输出);并手写200行简易IoC容器,透彻还原注册、实例化、依赖注入本质。助你从使用者进阶为框架驾驭者。

热门文章

最新文章