数据结构与算法简介

在计算机科学中,数据结构和算法是构建高效、可靠软件的核心基础,它们是程序员解决复杂问题的工具箱,决定了程序的运行速度和资源消耗。

想象一下,你正在整理一个杂乱无章的书架。如果只是把书随意堆放,找一本特定的书会非常困难。但如果你按照书名首字母排序,或者按照类别(如小说、历史、科学)分类,查找效率就会大大提高。数据结构就像是整理书架的方法,它定义了数据应该如何组织和存储。而算法则是查找、插入或删除一本书的具体步骤。

数据结构与算法是计算机科学的底层能力,用来解决据如何组织、问题如何高效求解这两件事。

数据结构
研究数据在内存或存储中的组织方式,以及如何高效访问和修改。核心目标是降低时间和空间成本。
常见类型:

  • 线性结构:数组、链表、栈、队列
  • 非线性结构:树、图、堆、哈希表

算法
研究解决问题的步骤和策略,本质是对计算过程的抽象。评价标准只有两个:时间复杂度、空间复杂度。
常见分类:

  • 基础算法:遍历、递归、分治
  • 排序与查找:快速排序、归并排序、二分查找
  • 高级策略:贪心、动态规划、回溯

关系
数据结构决定怎么存,算法决定怎么算。同一个问题,结构不同,算法复杂度可能天差地别。


数据结构

数据结构是计算机存储、组织数据的方式。

数据结构旨在实现一种或多种特定关系的数据元素的集合,以及定义在这些数据上的一组操作。

核心概念

  • 数据: 计算机可以识别、存储和处理的符号集合,如数字、字符等。
  • 数据元素: 数据的基本单位,在程序中通常作为一个整体进行考虑和处理。
  • 数据项: 构成数据元素的、有独立含义的、不可分割的最小单位。
  • 数据结构: 相互之间存在一种或多种特定关系的数据元素的集合。

数据结构的分类

数据结构主要分为两大类:逻辑结构物理结构

逻辑结构描述数据元素之间的抽象关系,与计算机如何实现无关。它主要分为:

  • 线性结构: 数据元素之间存在"一对一"的线性关系。如数组、链表、栈、队列。
  • 非线性结构: 数据元素之间存在"一对多"或"多对多"的复杂关系。如树、图、集合。

物理结构(又称存储结构)描述数据在计算机内存中的实际存储方式。它主要分为:

  • 顺序存储: 把逻辑上相邻的元素存储在物理位置也相邻的存储单元中。如数组。
  • 链式存储: 不要求逻辑上相邻的元素在物理位置上也相邻,元素间的逻辑关系通过附加的指针来表示。如链表。

算法

算法是解决特定问题的一系列清晰、有限的指令步骤。

一个有效的算法应该具备以下五个特性:

  1. 有穷性: 算法必须在执行有限步之后结束。
  2. 确定性: 算法的每一步都必须有确切的定义,不会产生二义性。
  3. 可行性: 算法中的每一步都可以通过已经实现的基本运算执行有限次来实现。
  4. 输入: 一个算法有零个或多个输入。
  5. 输出: 一个算法有一个或多个输出,输出是与输入有特定关系的量。

算法的评价标准:时间复杂度与空间复杂度

我们如何判断一个算法的好坏?通常从时间空间两个维度来衡量。

  • 时间复杂度: 定性描述该算法的运行时间。它表示随着问题规模 n 的增大,算法执行时间的增长趋势。常用大 O 符号表示。
  • 空间复杂度: 定性描述该算法在运行过程中临时占用存储空间的大小。同样用大 O 符号表示。

常见时间复杂度对比

下表展示了不同时间复杂度随数据规模增长的趋势:

复杂度表示 名称 举例(n=10 vs n=1000) 说明
O(1) 常数阶 1 步 vs 1 步 效率最高,与问题规模无关
O(log n) 对数阶 ~3 步 vs ~10 步 效率非常高,如二分查找
O(n) 线性阶 10 步 vs 1000 步 效率良好,如遍历数组
O(n log n) 线性对数阶 ~30 步 vs ~10000 步 效率较好,如快速排序
O(n²) 平方阶 100 步 vs 1,000,000 步 效率较低,如简单选择排序
O(2ⁿ) 指数阶 1024 步 vs 天文数字 效率极低,应尽量避免

简单理解: 我们追求的是随着数据量 n 变大,耗时增长越慢的算法。O(1) 和 O(log n) 是优秀的,O(n) 是可接受的,而 O(n²) 及以上在大数据量时就需要优化了。


经典数据结构与算法示例

让我们通过一个具体问题来感受数据结构和算法的应用。

问题: 在一个无序的数字列表中,快速找到是否存在某个目标数字。

方案一:线性查找(使用数组)

这是一种直观但低效的方法。

  • 数据结构: 数组(顺序存储的线性表)。
  • 算法: 从第一个元素开始,逐个与目标值比较,直到找到或遍历完所有元素。
  • 时间复杂度: O(n)。最坏情况下需要检查所有 n 个元素。

实例

# 线性查找算法示例
def linear_search(arr, target):
    """
    在数组 arr 中线性查找目标值 target。
    参数:
        arr: 待查找的列表
        target: 要查找的目标值
    返回:
        如果找到,返回其索引;否则返回 -1。
    """

    for i in range(len(arr)):  # 遍历数组的每个元素
        if arr[i] == target:   # 如果当前元素等于目标值
            return i           # 返回当前索引
    return -1                  # 遍历完毕未找到,返回 -1

# 测试数据
test_data = [12, 45, 67, 89, 34, 23, 90, 11]
target_number = 34

# 执行查找
result = linear_search(test_data, target_number)

# 输出结果
if result != -1:
    print(f"目标数字 {target_number} 在数组中的索引是:{result}")
else:
    print(f"在数组中未找到目标数字 {target_number}")

输出结果:

目标数字 34 在数组中的索引是:4

方案二:二分查找(使用有序数组)

这是一种高效的方法,但前提是数据必须有序。

  • 数据结构: 有序数组。
  • 算法
    1. 比较目标值与数组中间的元素。
    2. 如果相等,则找到。
    3. 如果目标值更小,则在数组的左半部分重复步骤1。
    4. 如果目标值更大,则在数组的右半部分重复步骤1。
    5. 如果搜索区间为空,则未找到。
  • 时间复杂度: O(log n)。每次比较都将搜索范围缩小一半。

实例

# 二分查找算法示例 (假设输入数组已排序)
def binary_search(sorted_arr, target):
    """
    在已排序的数组 sorted_arr 中二分查找目标值 target。
    参数:
        sorted_arr: 已排序的列表(升序)
        target: 要查找的目标值
    返回:
        如果找到,返回其索引;否则返回 -1。
    """

    left, right = 0, len(sorted_arr) - 1  # 初始化搜索区间为整个数组

    while left <= right:                  # 当区间有效时继续搜索
        mid = (left + right) // 2         # 计算中间索引
        if sorted_arr[mid] == target:    # 找到目标值
            return mid
        elif sorted_arr[mid] < target:   # 目标值在右半部分
            left = mid + 1
        else:                            # 目标值在左半部分
            right = mid - 1
    return -1                             # 区间无效,未找到

# 测试数据 (必须是已排序的)
test_data = [12, 45, 67, 89, 34, 23, 90, 11]
sorted_test_data = sorted(test_data)  # 对测试数据排序
print(f"有序数组:{sorted_test_data}")
target_number = 34

# 执行查找
result = binary_search(sorted_test_data, target_number)

# 输出结果
if result != -1:
    print(f"目标数字 {target_number} 在有序数组中的索引是:{result}")
else:
    print(f"在有序数组中未找到目标数字 {target_number}")

输出结果:

有序数组:[11, 12, 23, 34, 45, 67, 89, 90]
目标数字 34 在有序数组中的索引是:3

对比与思考

  • n=1000 时,线性查找最多需要 1000 次比较,而二分查找最多只需要约 10 次比较(因为 2^10 ≈ 1024)。
  • 二分查找的效率远高于线性查找,但它要求数据预先排序。排序本身需要成本,因此选择哪种算法取决于具体场景(例如,数据是静态的频繁查找,还是动态的频繁插入)。

如何开始学习?

学习数据结构与算法是一个循序渐进的过程,建议按以下路径进行:

  1. 掌握一门编程语言: 首先你需要一个工具来实现想法,Python、Java 或 C++ 都是不错的选择。Python 语法简洁,更适合初学者聚焦算法逻辑本身。
  2. 从基础数据结构开始
    • 数组 / 列表: 最基础、最常用的顺序存储结构。
    • 链表: 理解指针/引用和动态内存分配的关键。
    • 栈与队列: 理解"先进后出"和"先进先出"的操作受限的线性表。
  3. 学习基础算法思想
    • 排序算法: 冒泡排序、选择排序、插入排序(理解 O(n²)),然后学习归并排序、快速排序(理解 O(n log n) 和分治思想)。
    • 查找算法: 顺序查找、二分查找。
  4. 挑战非线性结构
    • : 重点学习二叉树,特别是二叉搜索树。理解树的遍历(前序、中序、后序)。
    • : 理解图的表示方法(邻接矩阵、邻接表)和基础遍历算法(深度优先搜索 DFS、广度优先搜索 BFS)。
  5. 实践与刷题: 在 LeetCode、牛客网等平台从简单题目开始练习,将理论应用于实践,这是巩固知识、锻炼思维的最佳方式。