初级程序员必备的十大技能之基础数据结构与算法(三)

简介: 教程来源 https://rvtst.cn/ 栈是后进先出(LIFO)的受限线性表,仅允许在栈顶进行插入(push)和删除(pop)操作。本质如叠盘子:最后入者最先出。常见应用包括括号匹配、浏览器历史回退、逆波兰表达式求值等,高效且原理简洁。

四、栈:后进先出(LIFO)

栈是一种受限的线性表,只允许在一端(栈顶)进行插入和删除操作。

4.1 栈的本质
就像一叠盘子:你只能从顶部取盘子,也只能在顶部放盘子。最后放上去的盘子会被最先拿走。

// 栈的操作
// push(入栈):添加元素到栈顶
// pop(出栈):移除并返回栈顶元素
// peek(查看栈顶):返回栈顶元素但不移除
// isEmpty:检查栈是否为空

4.2 栈的实现

// 使用数组实现栈(最简单)
class Stack {
    constructor() {
        this.items = [];
    }

    // 入栈 - O(1)
    push(element) {
        this.items.push(element);
    }

    // 出栈 - O(1)
    pop() {
        if (this.isEmpty()) {
            return null;
        }
        return this.items.pop();
    }

    // 查看栈顶 - O(1)
    peek() {
        if (this.isEmpty()) {
            return null;
        }
        return this.items[this.items.length - 1];
    }

    // 检查是否为空
    isEmpty() {
        return this.items.length === 0;
    }

    // 获取大小
    size() {
        return this.items.length;
    }

    // 清空栈
    clear() {
        this.items = [];
    }

    // 打印
    print() {
        console.log(this.items.join(' ← '));
    }
}

// 使用示例
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
stack.print();  // 1 ← 2 ← 3

console.log(stack.pop());  // 3
console.log(stack.peek()); // 2
console.log(stack.size()); // 2

4.3 栈的应用1:括号匹配
这是编译器检查语法错误的基础原理。

function isValidBrackets(str) {
    const stack = new Stack();
    const brackets = {
        '(': ')',
        '[': ']',
        '{': '}'
    };

    for (let i = 0; i < str.length; i++) {
        const char = str[i];

        // 如果是左括号,压入栈
        if (brackets[char]) {
            stack.push(char);
        }
        // 如果是右括号
        else if (char === ')' || char === ']' || char === '}') {
            // 栈为空,说明没有匹配的左括号
            if (stack.isEmpty()) {
                return false;
            }

            // 弹出栈顶的左括号
            const leftBracket = stack.pop();
            // 检查是否匹配
            if (brackets[leftBracket] !== char) {
                return false;
            }
        }
    }

    // 栈为空说明所有括号都匹配
    return stack.isEmpty();
}

// 测试
console.log(isValidBrackets("()"));        // true
console.log(isValidBrackets("()[]{}"));    // true
console.log(isValidBrackets("(]"));        // false
console.log(isValidBrackets("([)]"));      // false
console.log(isValidBrackets("{[]}"));      // true

// 执行过程解析:"{[]}"
// 读取 '{' → 压入栈: 栈 = ['{']
// 读取 '[' → 压入栈: 栈 = ['{', '[']
// 读取 ']' → 弹出 '[',匹配!栈 = ['{']
// 读取 '}' → 弹出 '{',匹配!栈 = []
// 结束,栈为空 → true

4.4 栈的应用2:浏览器历史记录(后退/前进)

class BrowserHistory {
    constructor() {
        this.backStack = new Stack();   // 后退栈
        this.forwardStack = new Stack(); // 前进栈
        this.current = null;
    }

    // 访问新页面
    visit(url) {
        if (this.current !== null) {
            // 将当前页面压入后退栈
            this.backStack.push(this.current);
            // 清空前栈(新访问会覆盖前进记录)
            this.forwardStack.clear();
        }
        this.current = url;
        console.log(`访问: ${url}`);
    }

    // 后退
    back() {
        if (this.backStack.isEmpty()) {
            console.log("无法后退");
            return null;
        }

        // 当前页面压入前进栈
        this.forwardStack.push(this.current);
        // 后退栈弹出页面作为当前页面
        this.current = this.backStack.pop();
        console.log(`后退到: ${this.current}`);
        return this.current;
    }

    // 前进
    forward() {
        if (this.forwardStack.isEmpty()) {
            console.log("无法前进");
            return null;
        }

        // 当前页面压入后退栈
        this.backStack.push(this.current);
        // 前进栈弹出页面作为当前页面
        this.current = this.forwardStack.pop();
        console.log(`前进到: ${this.current}`);
        return this.current;
    }

    // 查看当前页面
    getCurrent() {
        return this.current;
    }
}

// 使用示例
const browser = new BrowserHistory();
browser.visit("google.com");   // 访问: google.com
browser.visit("github.com");   // 访问: github.com
browser.visit("stackoverflow.com");  // 访问: stackoverflow.com

browser.back();     // 后退到: github.com
browser.back();     // 后退到: google.com
browser.forward();  // 前进到: github.com

4.5 栈的应用3:表达式求值(逆波兰表达式)

// 计算逆波兰表达式(后缀表达式)
// 例如:["2", "1", "+", "3", "*"] = (2+1)*3 = 9
function evalRPN(tokens) {
    const stack = new Stack();

    for (const token of tokens) {
        if (isOperator(token)) {
            // 弹出两个操作数
            const b = stack.pop();
            const a = stack.pop();
            const result = calculate(a, b, token);
            stack.push(result);
        } else {
            // 数字直接入栈
            stack.push(Number(token));
        }
    }

    return stack.pop();
}

function isOperator(token) {
    return token === '+' || token === '-' || token === '*' || token === '/';
}

function calculate(a, b, operator) {
    switch (operator) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return Math.trunc(a / b);  // 整数除法
        default: return 0;
    }
}

// 执行过程:["2", "1", "+", "3", "*"]
// 读取 "2" → 栈 = [2]
// 读取 "1" → 栈 = [2, 1]
// 读取 "+" → 弹出1和2,计算2+1=3 → 栈 = [3]
// 读取 "3" → 栈 = [3, 3]
// 读取 "*" → 弹出3和3,计算3*3=9 → 栈 = [9]
// 结果 = 9

console.log(evalRPN(["2", "1", "+", "3", "*"]));  // 9

五、队列:先进先出(FIFO)

队列就像排队买东西:先来的人先服务,后来的人排在后面。
5.1 队列的本质
队列也是一种受限的线性表,插入在一端(队尾),删除在另一端(队首)。

// 队列的操作
// enqueue(入队):添加元素到队尾
// dequeue(出队):移除并返回队首元素
// front(查看队首):返回队首元素但不移除
// isEmpty:检查队列是否为空

5.2 队列的实现

// 使用数组实现队列
class Queue {
    constructor() {
        this.items = [];
    }

    // 入队 - O(1)(均摊)
    enqueue(element) {
        this.items.push(element);
    }

    // 出队 - O(n)(因为需要移动所有元素)
    // 这是数组实现的缺点
    dequeue() {
        if (this.isEmpty()) {
            return null;
        }
        return this.items.shift();  // shift 会移动所有元素
    }

    // 查看队首 - O(1)
    front() {
        if (this.isEmpty()) {
            return null;
        }
        return this.items[0];
    }

    isEmpty() {
        return this.items.length === 0;
    }

    size() {
        return this.items.length;
    }

    print() {
        console.log(this.items.join(' ← '));
    }
}

// 使用对象实现高效队列(所有操作 O(1))
class EfficientQueue {
    constructor() {
        this.items = {};
        this.frontIndex = 0;
        this.rearIndex = 0;
    }

    enqueue(element) {
        this.items[this.rearIndex] = element;
        this.rearIndex++;
    }

    dequeue() {
        if (this.isEmpty()) {
            return null;
        }
        const element = this.items[this.frontIndex];
        delete this.items[this.frontIndex];
        this.frontIndex++;
        return element;
    }

    front() {
        if (this.isEmpty()) {
            return null;
        }
        return this.items[this.frontIndex];
    }

    isEmpty() {
        return this.frontIndex === this.rearIndex;
    }

    size() {
        return this.rearIndex - this.frontIndex;
    }

    print() {
        const values = [];
        for (let i = this.frontIndex; i < this.rearIndex; i++) {
            values.push(this.items[i]);
        }
        console.log(values.join(' ← '));
    }
}

5.3 队列的应用:任务调度系统

class TaskScheduler {
    constructor() {
        this.queue = new Queue();
    }

    // 添加任务
    addTask(task, priority = false) {
        if (priority) {
            // 优先级高的任务插到队首
            // 简化实现,实际可以用优先队列
            const temp = new Queue();
            temp.enqueue(task);
            while (!this.queue.isEmpty()) {
                temp.enqueue(this.queue.dequeue());
            }
            this.queue = temp;
        } else {
            this.queue.enqueue(task);
        }
        console.log(`添加任务: ${task.name}`);
    }

    // 执行任务
    run() {
        while (!this.queue.isEmpty()) {
            const task = this.queue.dequeue();
            console.log(`执行: ${task.name}`);
            task.execute();
        }
    }
}

// 使用示例
const scheduler = new TaskScheduler();
scheduler.addTask({ name: "任务1", execute: () => console.log("  完成1") });
scheduler.addTask({ name: "任务2", execute: () => console.log("  完成2") });
scheduler.addTask({ name: "紧急任务", execute: () => console.log("  完成紧急") }, true);

scheduler.run();
// 输出:
// 添加任务: 任务1
// 添加任务: 任务2
// 添加任务: 紧急任务
// 执行: 紧急任务
//   完成紧急
// 执行: 任务1
//   完成1
// 执行: 任务2
//   完成2

5.4 队列的应用:广度优先搜索(BFS)
广度优先搜索是图和树的遍历算法,核心就是使用队列。

// 使用 BFS 查找图中的最短路径
class Graph {
    constructor() {
        this.adjacencyList = new Map();  // 邻接表
    }

    // 添加顶点
    addVertex(vertex) {
        if (!this.adjacencyList.has(vertex)) {
            this.adjacencyList.set(vertex, []);
        }
    }

    // 添加边(无向图)
    addEdge(v1, v2) {
        this.adjacencyList.get(v1).push(v2);
        this.adjacencyList.get(v2).push(v1);
    }

    // BFS 查找最短路径
    shortestPath(start, end) {
        const queue = new EfficientQueue();
        const visited = new Set();
        const parent = new Map();  // 记录路径

        queue.enqueue(start);
        visited.add(start);
        parent.set(start, null);

        while (!queue.isEmpty()) {
            const current = queue.dequeue();

            // 找到目标
            if (current === end) {
                // 重建路径
                const path = [];
                let node = end;
                while (node !== null) {
                    path.unshift(node);
                    node = parent.get(node);
                }
                return path;
            }

            // 遍历邻居
            const neighbors = this.adjacencyList.get(current);
            for (const neighbor of neighbors) {
                if (!visited.has(neighbor)) {
                    visited.add(neighbor);
                    parent.set(neighbor, current);
                    queue.enqueue(neighbor);
                }
            }
        }

        return null;  // 没有路径
    }
}

// 创建图
const graph = new Graph();
['A', 'B', 'C', 'D', 'E', 'F'].forEach(v => graph.addVertex(v));

graph.addEdge('A', 'B');
graph.addEdge('A', 'C');
graph.addEdge('B', 'D');
graph.addEdge('C', 'E');
graph.addEdge('D', 'E');
graph.addEdge('D', 'F');
graph.addEdge('E', 'F');

console.log(graph.shortestPath('A', 'F'));  // ['A', 'B', 'D', 'F'] 或 ['A', 'C', 'E', 'F'](取决于遍历顺序)

来源:
https://xcfsr.cn/

相关文章
|
人工智能 供应链 监控
面向企业的 ChatGPT 究极手册:附录 B 到参考文献
面向企业的 ChatGPT 究极手册:附录 B 到参考文献
266 0
|
1月前
|
人工智能 供应链 安全
2026 年全球网络安全威胁态势与关键技术防御研究
本文基于Security Affairs 2026年第576期情报,系统分析Linux无文件远控(QLNX)、Dirty Frag内核提权、AI供应链投毒、Bluekit工业化钓鱼及关键基础设施混合攻击等新型威胁,揭示其内存化、智能化、武器化趋势;提出漏洞治理、供应链管控、钓鱼防御、终端加固、应急响应“五位一体”纵深防御框架,并提供可复现代码与工程化方案。(239字)
559 6
|
1月前
|
存储 人工智能 JSON
AI 应用开发的流程
AI应用开发重心转向“上下文管理”与“模型调优”,涵盖五大阶段:业务定义与选型(闭源/开源模型)、提示词工程、RAG数据增强、应用编排(LangChain/Agent)、评估迭代(LLM-as-a-judge)。强调Prompt优先、成本控制与教育场景多模态适配。
|
1月前
|
存储 弹性计算 运维
阿里云服务器怎么买?四种主要方式详解+注意事项,新手购买参考教程
本文介绍了阿里云服务器的四大购买方式的适用场景与注意事项:自定义购买支持全参数精细配置,适合有技术基础的企业用户;快速购买通过预设模板简化流程,助力新手快速上云;活动购买提供低至38元/年的限时优惠,覆盖99计划、学生300元抵扣金、百炼先用后返等多重权益;云市场镜像购买提供预装环境的开箱即用方案,适合中小企业快速建站。
|
1月前
|
人工智能 API Go
Token 到底是什么?搞懂这个“AI 最小货币单位”,省钱又省心
纯干货,用“乐高积木”比喻,3分钟讲透AI核心概念——Token:它是什么、怎么拆、为何影响输入长度、API费用和AI记忆力。附4个实测省钱技巧,助你省30%以上成本,轻松处理长文本。
|
1月前
|
人工智能 Linux API
hermes agent 安装教程:安装优化 + 模型配置 + 工具启用指南
Hermes Agent 是 Nous Research 于 2026 年发布的开源自主进化 AI 智能体框架(MIT 协议,Python 编写)。它通过任务沉淀技能、持久化记忆、原生多工具集成与并行子智能体,实现“越用越强”。支持 Linux/macOS/WSL2,安装便捷,面向个人与企业的新一代私有化 AI 助手。
|
1月前
|
人工智能 弹性计算 运维
我在阿里云 PAI 上私有化部署了 Qwen3-Coder,推理成本比公有 API 降低了 60%
本文分享Qwen3-Coder私有化部署实战:直击代码隐私、定制需求与长期成本三大痛点;选用PAI-EAS+vLLM方案,30分钟快速部署,AWQ量化降低显存40%;实测较公有API节省60%成本,兼顾安全、性能与性价比。(239字)
|
1月前
|
人工智能 弹性计算 API
Hermes Agent + Claude Code 协同编程开发:阿里云一键部署AI开发团队全教程
在AI驱动开发的新时代,单一智能体已难以覆盖从需求分析、任务拆解、代码编写到经验沉淀的全流程。Hermes Agent与Claude Code的组合,构建了一套类似“技术主管+资深工程师”的高效AI开发团队模式。前者负责统筹规划、记忆进化、任务调度,后者专注高质量编码、调试与实现,两者协同工作,可大幅提升开发效率与工程交付质量。
697 1
|
1月前
|
人工智能 运维 Rust
从Cursor、Claude Code到DeepSeek-TUI:2026年五大开源AI编程助手硬核实测
本文实测Cursor、Cline、Claude Code、Aider、DeepSeek-TUI五款AI编程工具,在相同环境(M1 Mac/1500行Rust项目)下对比任务耗时、代码质量、中文支持与资源占用。聚焦工程落地:IDE派重体验,终端Agent重流程,新锐TUI重成本与中文适配。不吹不黑,只答“哪个不坑你”。
|
1月前
|
存储 安全 网络协议
基于邮件的魔法链接认证安全性技术深度研究
本文深度剖析魔法链接(Magic Link)全生命周期安全机制,系统揭示钓鱼、令牌泄露、重放、邮件伪造等核心威胁,提出“四层防御模型”及可落地的工程化方案,涵盖高熵令牌生成、哈希存储、短时效单次消费、IP/设备绑定、SPF/DKIM/DMARC邮件认证等关键技术,并附合规实践与代码实现,助力无密码认证安全规模化落地。(239字)
101 4

热门文章

最新文章