算法与数据结构基础

7 阅读6分钟

算法与数据结构是计算机科学的核心支柱,它们共同决定了程序的效率和性能。算法提供解决问题的步骤,而数据结构则是组织和管理数据的方式。

什么是算法?

算法(Algorithm)是一组明确定义的指令,用于在有限时间内解决特定问题或完成特定任务。算法如同菜谱,指导计算机如何一步步完成计算。例如,排序算法可以把一组杂乱的数字按顺序排列。

算法的核心特性包括:

  • 输入:算法接收一个或多个输入。
  • 输出:算法产生一个或多个输出。
  • 确定性:每一步操作都明确且无歧义。
  • 有限性:算法在有限步骤内结束。
  • 有效性:每一步都能被精确执行。

时间复杂度

时间复杂度是衡量算法效率的重要指标,表示算法运行所需时间随输入规模增长的趋势。时间复杂度通常用大O记号(Big-O Notation)表示,描述最坏情况下的时间增长。

常见的时间复杂度

  • O(1) (常数时间):无论输入规模如何,运行时间固定。例如,访问数组中的某个元素。
  • O(n) (线性时间):运行时间与输入规模成正比。例如,遍历一个长度为n的数组。
  • O(n²) (平方时间):运行时间与输入规模的平方成正比。例如,嵌套循环遍历二维矩阵。
  • O(log n) (对数时间):运行时间随输入规模的对数增长。例如,二分查找。
  • O(n log n) :常见于高效排序算法,如快速排序或归并排序。
  • O(2ⁿ) (指数时间):运行时间随输入规模呈指数增长,常见于某些递归算法。

如何分析时间复杂度?

  1. 识别基本操作:找出算法中最频繁执行的操作(如比较、赋值)。
  2. 计算操作次数:根据输入规模n,估算基本操作的执行次数。
  3. 提取主导项:忽略常数和低阶项,用大O记号表示。

例如,一个循环遍历n个元素的时间复杂度是O(n),而双重循环则是O(n²)。

TypeScript 示例展示时间复杂度

以下是通过简单 TypeScript 代码展示不同时间复杂度的示例:

  • O(1) 示例:访问数组第一个元素
function constantTime(arr: number[]): number {
    return arr[0];  // 无论数组大小,都只需一步操作
}
  • O(n) 示例:求数组和
function linearTime(arr: number[]): number {
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
        sum += arr[i];  // 循环 n 次
    }
    return sum;
}
  • O(n²) 示例:简单冒泡排序
function quadraticTime(arr: number[]): number[] {
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr.length; j++) {
            if (arr[i] < arr[j]) {
                [arr[i], arr[j]] = [arr[j], arr[i]];  // 嵌套循环,n² 次操作
            }
        }
    }
    return arr;
}
  • O(log n) 示例:二分查找(假设数组已排序)
function logTime(arr: number[], target: number): number {
    let low = 0;
    let high = arr.length - 1;
    while (low <= high) {
        const mid = Math.floor((low + high) / 2);
        if (arr[mid] === target) return mid;
        if (arr[mid] < target) low = mid + 1;
        else high = mid - 1;
    }
    return -1;  // 每次循环问题规模减半
}

空间复杂度

空间复杂度衡量算法运行时所需的额外内存空间,也用大O记号表示。它包括:

  • 固定部分:如变量、常数等,与输入规模无关。
  • 可变部分:如动态分配的数组、递归调用栈等,随输入规模变化。

常见空间复杂度

  • O(1) :只使用固定数量的变量。例如,交换两个变量。
  • O(n) :使用与输入规模成正比的额外空间。例如,复制一个长度为n的数组。
  • O(n²) :使用二维数组等。例如,存储一个n×n的矩阵。
  • O(log n) :常见于递归算法的调用栈,如二分查找。

优化空间复杂度

  • 原地算法:尽量在原数据结构上操作,减少额外空间。例如,快速排序是原地排序算法。
  • 复用空间:通过复用已分配的内存减少开销。

TypeScript 示例展示空间复杂度

  • O(1) 示例:交换两个变量。
function swap(a: number, b: number): [number, number] {
    let temp = a;
    a = b;
    b = temp;
    return [a, b];  // 只用固定空间
}
  • O(n) 示例:复制数组。
function copyArray(arr: number[]): number[] {
    const copy = new Array(arr.length);
    for (let i = 0; i < arr.length; i++) {
        copy[i] = arr[i];
    }
    return copy;  // 额外 n 个空间
}
  • O(n²) 示例:创建 n x n 矩阵。
function createMatrix(n: number): number[][] {
    const matrix: number[][] = [];
    for (let i = 0; i < n; i++) {
        matrix[i] = new Array(n).fill(0);  // n² 空间
    }
    return matrix;
}

算法设计的基本范式

学习算法时,了解常见的算法设计范式有助于快速解决问题:

  • 分治法(Divide and Conquer):将问题分解为小问题,解决后再合并。如归并排序。
  • 贪心算法(Greedy Algorithm):每一步选择局部最优解,期望得到全局最优。如最小生成树算法。
  • 动态规划(Dynamic Programming):通过记录子问题结果避免重复计算。如背包问题。
  • 回溯法(Backtracking):尝试所有可能解,逐步排除无效解。如八皇后问题。

TypeScript 示例展示算法范式

  • 分治法示例:归并排序
function mergeSort(arr: number[]): number[] {
    if (arr.length <= 1) return arr;
    const mid = Math.floor(arr.length / 2);
    const left = mergeSort(arr.slice(0, mid));
    const right = mergeSort(arr.slice(mid));
    return merge(left, right);  // 分治 + 合并
}

function merge(left: number[], right: number[]): number[] {
    const result: number[] = [];
    let i = 0, j = 0;
    while (i < left.length && j < right.length) {
        if (left[i] < right[j]) result.push(left[i++]);
        else result.push(right[j++]);
    }
    return result.concat(left.slice(i)).concat(right.slice(j));
}

数据结构基础

数据结构是组织和管理数据的方式,与算法密切相关。算法往往依赖合适的数据结构来实现高效操作。以下是常见数据结构的基础概念:

  • 数组(Array) :连续内存存储的元素集合,支持随机访问。时间复杂度:访问 O(1),插入/删除 O(n)。
  • 链表(Linked List) :由节点组成的链式结构,每个节点包含数据和指向下一个节点的指针。时间复杂度:访问 O(n),插入/删除 O(1)(若已知位置)。
  • 栈(Stack) :后进先出(LIFO)的结构,支持 push 和 pop 操作。常用于递归、表达式求值。
  • 队列(Queue) :先进先出(FIFO)的结构,支持 enqueue 和 dequeue 操作。常用于 BFS(广度优先搜索)。
  • 树(Tree) :层次结构,通常有根节点、子节点。二叉树是最简单形式,每个节点最多两个子节点。时间复杂度:搜索 O(log n)(平衡二叉树)。
  • 图(Graph) :由节点(顶点)和边组成的结构,可表示网络关系。支持有向/无向、加权/非加权。常见算法:DFS、BFS、最短路径。

TypeScript 示例展示数据结构

  • 数组示例
const arr: number[] = [1, 2, 3];
console.log(arr[1]);  // 访问 O(1)
  • 链表示例
class Node {
    value: number;
    next: Node | null = null;
    constructor(value: number) {
        this.value = value;
    }
}

const head = new Node(1);
head.next = new Node(2);  // 插入 O(1)
  • 栈示例
const stack: number[] = [];
stack.push(1);  // 入栈
const top = stack.pop();  // 出栈

总结

算法与数据结构是计算机科学的基础,时间复杂度和空间复杂度是衡量算法效率的核心指标,数据结构则是算法高效实现的关键。