力扣算法100个实际应用场景(2)-代码编辑器的撤销重做功能

项目简介

这个项目展示了100道经典LeetCode算法题在实际工程中的应用场景,帮助学员理解算法的实用价值,建立理论与实践的桥梁。

核心目标

  • 让算法学习更有意义,不再是纸上谈兵
  • 帮助面试者理解算法背后的工程价值
  • 为工程师提供算法选择的实际参考

这是栈/队列应用的第二篇:代码编辑器的撤销重做功能

视频讲解与源码领取https://www.bilibili.com/video/BV1VYtBzpEUJ/

实际应用

在代码编辑器中,程序员经常需要撤销最近的编辑操作(Ctrl+Z),或者重做被撤销的操作(Ctrl+Y)。这需要维护一个操作历史栈,记录每次编辑的详细信息,支持复杂的多步骤撤销和重做。

撤销重做示意图

对应算法

LeetCode题目: 20. 有效的括号:https://leetcode.cn/problems/valid-parentheses/

核心思想: 编辑器撤销重做系统的核心是命令模式 + 双栈架构,这是对LeetCode 20"括号匹配"思想的深度拓展:

🔑 算法精髓:

1.命令封装的数学模型:

Command接口: C = {execute(), undo(), getState()}
操作空间: Op = {Insert, Delete, Replace, Move, ...}
状态映射: φ: State × Command → State'
逆映射: φ⁻¹: State' × Command → State

2.双栈状态机设计:

  • undoStack:存储可撤销的命令序列 [c₁, c₂, ..., cₙ]
  • redoStack:存储可重做的命令序列 [r₁, r₂, ..., rₘ]
  • 不变性:undoStack.top() + redoStack.bottom() = 完整操作历史

3.操作可逆性保证:

  • 幂等性:undo(undo(op)) = op(双重撤销回到原状态)
  • 交换律约束:某些操作可交换,某些必须严格按序
  • 补偿事务:复杂操作的逆操作可能是多步骤的补偿动作

4.内存管理与压缩:

  • 操作合并:连续的同类操作自动合并(如连续字符输入)
  • 快照机制:定期保存完整状态快照,减少操作链长度
  • LRU淘汰:超出内存限制时淘汰最老的操作记录

🎯 高级算法技巧:

1.操作压缩算法:

// 智能合并策略
if (newCmd.type == INSERT && lastCmd.type == INSERT && 
    newCmd.position == lastCmd.position + lastCmd.length) {
    // 合并连续插入操作
    lastCmd.text += newCmd.text;
}

2.分层撤销机制:

  • 字符级撤销:撤销单个字符
  • 词级撤销:撤销整个单词
  • 行级撤销:撤销整行操作
  • 语义级撤销:撤销完整的语义块(函数、类等)

3.并发安全保证:

  • 操作原子性:使用事务确保命令执行的原子性
  • 版本冲突检测:多用户协作时的冲突解决
  • 操作时间戳:基于Lamport时钟的操作排序

深层技术洞察:

1.为什么选择命令模式?

  • 解耦性:操作逻辑与UI逻辑分离
  • 可扩展性:新操作类型无需修改现有代码
  • 可测试性:每个命令都是独立的可测试单元
  • 序列化:命令可持久化,支持会话恢

2.与函数式编程的关系:

  • 每个编辑操作都是纯函数:State → State'
  • 撤销操作是逆函数:State' → State
  • 操作组合符合函数组合规律

3.空间-时间权衡:

  • 全量快照:空间O(n×m),撤销时间O(1)
  • 增量命令:空间O(k),撤销时间O(k)
  • 混合策略:快照+增量的平衡方案

代码示例:参见 src/002_code_editor_undo.cpp - 完整的工业级实现

应用领域

  1. 图像编辑软件 - Photoshop、GIMP中的历史记录和撤销功能
  2. 办公软件 - Word、Excel中的编辑操作撤销和重做
  3. 数据库事务 - 数据库系统中的事务回滚和提交机制

代码示例

// 编辑器撤销重做系统 - 基于命令模式和栈结构
class EditCommand {
public:
    virtual ~EditCommand() = default;
    virtual void execute() = 0;     // 执行操作
    virtual void undo() = 0;        // 撤销操作
    virtual string getDescription() = 0;
};

class InsertTextCommand : public EditCommand {
private:
    string& document;
    int position;
    string text;
    
public:
    InsertTextCommand(string& doc, int pos, const string& txt) 
        : document(doc), position(pos), text(txt) {}
    
    void execute() override {
        document.insert(position, text);
    }
    
    void undo() override {
        document.erase(position, text.length());
    }
    
    string getDescription() override {
        return "插入文本: " + text;
    }
};

class UndoRedoManager {
private:
    stack<unique_ptr<EditCommand>> undo_stack;  // 撤销栈
    stack<unique_ptr<EditCommand>> redo_stack;  // 重做栈
    
public:
    /**
     * @brief 执行新的编辑命令
     * @param command 要执行的命令
     */
    void executeCommand(unique_ptr<EditCommand> command) {
        // 执行命令
        command->execute();
        
        // 将命令压入撤销栈
        undo_stack.push(move(command));
        
        // 清空重做栈(执行新操作后,之前的重做历史失效)
        while (!redo_stack.empty()) {
            redo_stack.pop();
        }
    }
    
    /**
     * @brief 撤销最后一个操作
     * @return 是否成功撤销
     */
    bool undo() {
        if (undo_stack.empty()) return false;
        
        // 获取最后一个命令
        auto command = move(undo_stack.top());
        undo_stack.pop();
        
        // 执行撤销操作
        command->undo();
        
        // 将命令移到重做栈
        redo_stack.push(move(command));
        
        return true;
    }
    
    /**
     * @brief 重做最后一个被撤销的操作
     * @return 是否成功重做
     */
    bool redo() {
        if (redo_stack.empty()) return false;
        
        // 获取最后一个被撤销的命令
        auto command = move(redo_stack.top());
        redo_stack.pop();
        
        // 重新执行命令
        command->execute();
        
        // 将命令移回撤销栈
        undo_stack.push(move(command));
        
        return true;
    }
    
    /**
     * @brief 检查是否可以撤销
     */
    bool canUndo() const {
        return !undo_stack.empty();
    }
    
    /**
     * @brief 检查是否可以重做
     */
    bool canRedo() const {
        return !redo_stack.empty();
    }
};

#c++##实习工作,你找得还顺利吗?##秋招##算法##牛客创作赏金赛#
全部评论

相关推荐

牛客50327486...:腾讯官方:我们没有人机对局
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务