44、基础数据结构:数组、栈与二叉搜索树

基础数据结构:数组、栈与二叉搜索树

在计算机科学中,选择合适的算法和数据结构对于解决计算问题至关重要。算法的效率通常取决于输入数据的存储和处理方式,特别是所选择的特定数据结构。下面将详细介绍几种基础的数据结构,包括数组、栈和二叉搜索树。

1. 算法选择与数据结构的重要性

计算问题通常可以通过多种算法来解决,选择特定算法时主要考虑两个因素:时间复杂度和实现难度,其中时间复杂度更为重要。例如,对于图上的问题,即使知道该问题可以用多项式时间复杂度的算法解决,使用时间复杂度为 $O(N^2)$ 的算法与使用 $O(N^3)$ 的算法相比,在求解时间上也会有显著差异。

算法的效率往往取决于输入数据的存储和处理方式,特别是所选择的数据结构。以计算图中所有 $N$ 个节点的度为例,如果图以 $N \times N$ 的邻接矩阵存储,计算节点度的最简单算法的时间复杂度为 $O(N^2)$;而使用稀疏矩阵表示,在某些情况下可以将时间复杂度降低到 $O(K)$ 或 $O(N)$。

2. 数组

数组是最基本的数据结构,是一块连续的内存区域,能够存储多个相同类型的变量,通常称为数组的组件或元素。在 C 语言中,数组的索引从 0 开始,例如长度为 $N$ 的数组,第一个元素的索引为 0,最后一个元素的索引为 $N - 1$。数组可以是多维的,用于表示矩阵和张量。

数组的操作时间复杂度如下:
- 读取特定元素 :已知元素在数组中的位置时,读取其值的时间复杂度为 $O(1)$,即常数时间,与数组的大小无关。
- 添加元素 :在现有大小为 $N$ 的数组末尾添加一个元素,平均时间复杂度为 $O(N)$。这是因为可能需要操作系统扩展数组的内存空间,如果内存不足,需要找到一个足够大的连续内存区域,并将现有数组的值复制到新位置。
- 删除元素 :删除数组中的一个元素,平均时间复杂度也为 $O(N)$。在最坏情况下,需要将删除元素后面的所有元素向前移动。
- 搜索元素 :使用简单的线性搜索算法在大小为 $N$ 的数组中搜索特定元素,平均时间复杂度为 $O(N)$。

为了提高搜索效率,可以对数组进行排序,然后使用二分搜索算法。二分搜索的时间复杂度为 $O(log N)$,其伪代码如下:

Algorithm 2 binary_search()
Input: x, N, v[] (sorted in ascending order)
Output: position of x, or -1 if x is not in v[]
1: high ← N - 1
2: low ← 0
3: cur ← ⌊(low + high) / 2⌋
4: while low < high do
5:
    if x > v[cur] then
6:
        low ← cur + 1
7:
    else
8:
        if x ≤ v[cur] then
9:
            high ← cur
10:
        end if
11:
    end if
12: end while
13: if x = v[cur] then
14:
    return cur
15: else
16:
    return -1
17: end if

二分搜索的基本思想是将搜索范围不断缩小一半,直到找到目标元素或确定目标元素不存在。

排序数组的常用算法是快速排序,其平均时间复杂度为 $O(N log N)$。快速排序是一个递归过程,包括以下三个步骤:
- 选择一个元素作为枢轴(pivot)。
- 将数组划分为两个子数组,一个包含小于枢轴的元素,另一个包含大于枢轴的元素。
- 递归地对两个子数组应用快速排序算法。

快速排序的伪代码如下:

Algorithm 3 quick_sort()
Input: A, low, high
Output: the array A, sorted in-place
1: if low < high then
2:
    p ← partition(A, low, high)
3:
    quick_sort(A, low, p - 1)
4:
    quick_sort(A, p + 1, high)
5: end if

数组适用于元素数量预先固定或在计算过程中变化不大的情况,并且典型操作是使用索引读取或写入元素。当数组元素不经常变化,且需要进行大量搜索操作时,使用二分搜索可以提高效率。

3. 栈

栈是一种能够容纳同类元素的数据结构,但其访问元素的方式独特。栈遵循后进先出(LIFO)的访问策略,即每次只能访问栈顶的元素。栈的操作主要有两个: push() 用于在栈顶插入新元素, pop() 用于移除栈顶元素并将其返回给用户。

栈可以通过数组和一个变量 top 来实现, top 表示栈顶的索引。当栈为空时, top 通常设置为 -1。栈的插入和删除操作的时间复杂度均为 $O(1)$,但只能访问栈顶元素,不支持随机访问和元素搜索。

栈的操作流程如下:
1. 初始化栈,将 top 设置为 -1。
2. 执行 push() 操作时,将元素放入 s[top + 1] 位置,并将 top 加 1。
3. 执行 pop() 操作时,返回 s[top] 的值,并将 top 减 1。

栈在记录算法的执行进度方面非常有用,现代操作系统使用栈来跟踪子程序和函数的调用。例如,在枚举图的所有循环和贪心模块化优化的程序中都使用了栈的实现。

4. 二叉搜索树

二叉搜索树(BST)是一种专门为高效插入、删除和搜索元素而设计的数据结构。每个元素表示为树的一个节点,并且满足以下五个属性:
1. 树的每个节点都与一个唯一的数字键相关联。
2. 树有且只有一个节点被标记为根节点。
3. 除根节点外,每个节点都有一个父节点,即离根节点更近的邻居。
4. 每个节点最多有两个子节点,分别称为左子节点和右子节点。
5. 每个节点的键必须大于其左子树中所有节点的键,并且小于其右子树中所有节点的键。

构建二叉搜索树的方法是:第一个数字作为根节点的键,后续数字根据其与根节点键的大小关系,作为左子节点或右子节点插入树中。例如,从序列 9, 5, 27, 18, 23, 1, 32, 12 构建的二叉搜索树,根节点的键为 9,5 作为左子节点,27 作为右子节点,依此类推。

在二叉搜索树中搜索元素的递归过程 bst_search() 的伪代码如下:

Algorithm 4 bst_search()
Input: node, t
Output: {TRUE | FALSE}
1: if key[node] = t then
2:
    return TRUE
3: else
4:
    if key[node] > t then
5:
        if node has left child then
6:
            bst_search(left[node], t)
7:
        else
8:
            return FALSE
9:
        end if
10:
    else
11:
        if node has right child then
12:
            bst_search(right[node], t)
13:
        else
14:
            return FALSE
15:
        end if
16:
    end if
17: end if

二叉搜索树的搜索机制与二分搜索算法类似,每次递归调用都会排除一个子树,将搜索范围缩小。然而,二叉搜索树的搜索时间复杂度很大程度上取决于树的形状。不同形状的二叉搜索树在搜索元素时所需的迭代次数不同,例如,形状更紧凑的树搜索效率更高。

综上所述,不同的数据结构适用于不同的应用场景。数组适用于元素数量固定且主要进行索引访问的情况;栈适用于需要后进先出访问模式的场景;二叉搜索树则适用于需要频繁插入、删除和搜索元素的情况。在实际应用中,应根据具体需求选择合适的数据结构,以提高算法的效率。

下面用 mermaid 流程图展示二分搜索算法的流程:

graph TD;
    A[开始] --> B[初始化 high = N - 1, low = 0, cur = ⌊(low + high) / 2⌋];
    B --> C{low < high};
    C -- 是 --> D{x > v[cur]};
    D -- 是 --> E[low = cur + 1];
    D -- 否 --> F{x ≤ v[cur]};
    F -- 是 --> G[high = cur];
    E --> H[更新 cur = ⌊(low + high) / 2⌋];
    G --> H;
    H --> C;
    C -- 否 --> I{x = v[cur]};
    I -- 是 --> J[返回 cur];
    I -- 否 --> K[返回 -1];

再用表格总结一下这几种数据结构的操作时间复杂度:
| 数据结构 | 读取元素 | 添加元素 | 删除元素 | 搜索元素 |
| ---- | ---- | ---- | ---- | ---- |
| 数组 | $O(1)$ | $O(N)$ | $O(N)$ | 线性搜索 $O(N)$,二分搜索 $O(log N)$ |
| 栈 | 仅栈顶 $O(1)$ | $O(1)$ | $O(1)$ | 不支持 |
| 二叉搜索树 | - | $O(log N)$ | $O(log N)$ | $O(log N)$(平均) |

基础数据结构:数组、栈与二叉搜索树

5. 不同数据结构的应用场景分析

在实际的编程和算法设计中,选择合适的数据结构对于提高程序的性能至关重要。下面我们将详细分析数组、栈和二叉搜索树在不同场景下的应用。

5.1 数组的应用场景
  • 图像处理 :在图像处理中,图像通常可以表示为二维数组。每个像素的颜色信息可以存储在数组的元素中。例如,一个灰度图像可以用一个二维整数数组表示,其中每个元素代表一个像素的灰度值。使用数组可以方便地对图像进行遍历和修改,如调整亮度、对比度等操作。
  • 矩阵运算 :矩阵是线性代数中的重要概念,在很多科学计算和工程领域都有广泛应用。矩阵可以用二维数组来表示,通过数组的操作可以实现矩阵的加法、乘法等运算。例如,在机器学习中,矩阵运算常用于神经网络的前向传播和反向传播过程。
5.2 栈的应用场景
  • 表达式求值 :在计算数学表达式时,栈可以用于处理运算符和操作数。例如,在计算后缀表达式(逆波兰表达式)时,我们可以使用栈来存储操作数,遇到运算符时从栈中弹出相应数量的操作数进行计算,并将结果压入栈中。以下是一个简单的后缀表达式求值的 Python 代码示例:
def evaluate_postfix(expression):
    stack = []
    for token in expression:
        if token.isdigit():
            stack.append(int(token))
        else:
            operand2 = stack.pop()
            operand1 = stack.pop()
            if token == '+':
                result = operand1 + operand2
            elif token == '-':
                result = operand1 - operand2
            elif token == '*':
                result = operand1 * operand2
            elif token == '/':
                result = operand1 / operand2
            stack.append(result)
    return stack.pop()

expression = ['3', '4', '+', '2', '*']
print(evaluate_postfix(expression))  
  • 函数调用栈 :现代操作系统使用栈来跟踪函数的调用和返回。当一个函数被调用时,系统会将当前的执行上下文(包括局部变量、返回地址等)压入栈中;当函数返回时,系统会从栈中弹出相应的执行上下文,恢复之前的执行状态。
5.3 二叉搜索树的应用场景
  • 数据库索引 :在数据库中,为了提高数据的查询效率,常常使用索引。二叉搜索树可以作为一种索引结构,通过对数据的键进行排序,使得查询操作可以在 $O(log N)$ 的时间复杂度内完成。例如,在 MySQL 数据库中,B+ 树(一种改进的二叉搜索树)被广泛用于索引的实现。
  • 文件系统 :文件系统中的目录结构可以用树来表示,而二叉搜索树可以用于快速查找文件和目录。例如,在 Linux 系统中,文件系统的目录结构可以看作是一棵多叉树,通过对文件名进行排序,可以使用二叉搜索树的思想来提高文件查找的效率。
6. 数据结构的性能比较与选择策略

为了更直观地比较数组、栈和二叉搜索树的性能,我们可以用表格来总结它们的操作时间复杂度:
| 数据结构 | 读取元素 | 添加元素 | 删除元素 | 搜索元素 |
| ---- | ---- | ---- | ---- | ---- |
| 数组 | $O(1)$ | $O(N)$ | $O(N)$ | 线性搜索 $O(N)$,二分搜索 $O(log N)$ |
| 栈 | 仅栈顶 $O(1)$ | $O(1)$ | $O(1)$ | 不支持 |
| 二叉搜索树 | - | $O(log N)$ | $O(log N)$ | $O(log N)$(平均) |

根据以上表格,我们可以得出以下选择策略:
- 如果需要随机访问元素 :数组是最好的选择,因为数组可以通过索引在 $O(1)$ 的时间复杂度内访问任意元素。
- 如果需要后进先出的访问模式 :栈是最合适的,栈的插入和删除操作都可以在 $O(1)$ 的时间复杂度内完成。
- 如果需要频繁进行插入、删除和搜索操作 :二叉搜索树是一个不错的选择,其插入、删除和搜索操作的平均时间复杂度都为 $O(log N)$。

7. 总结

本文详细介绍了数组、栈和二叉搜索树三种基础数据结构。数组是最基本的数据结构,适用于元素数量固定或变化不大的情况,通过索引可以快速访问元素;栈遵循后进先出的原则,在记录算法执行进度和处理函数调用方面非常有用;二叉搜索树则专门为高效插入、删除和搜索元素而设计,其性能取决于树的形状。

在实际应用中,我们需要根据具体的需求和场景来选择合适的数据结构。通过合理选择数据结构,可以提高算法的效率,减少程序的运行时间和空间开销。同时,我们也可以根据不同数据结构的特点,将它们组合使用,以实现更加复杂和高效的算法。

下面用 mermaid 流程图展示栈的操作流程:

graph TD;
    A[初始化栈,top = -1] --> B{执行 push() 操作?};
    B -- 是 --> C[将元素放入 s[top + 1],top 加 1];
    C --> B;
    B -- 否 --> D{执行 pop() 操作?};
    D -- 是 --> E[返回 s[top],top 减 1];
    E --> B;
    D -- 否 --> F[结束];

希望本文能够帮助读者更好地理解和应用这三种基础数据结构,在编程和算法设计中做出更明智的选择。

2022春季数据结构期中考试总结 Table of contents判断题1.若用平方探测法解决冲突,则插入新元素时,若散列表容量为质数,插入就一定可以成功。2.算法分析的两个主要方面是时间复杂度和空间复杂度的分析。3.在散列中,函数“插入”和“查找”具有同样的时间复杂度。4.在具有N个结点的单链表中,访问结点和增加结点的时间复杂度分别对应为O(1)和O(N)。5.非空二叉树的形态:一棵非空二叉树,若先序遍历与后序遍历的序列相反,则该二叉树只有一个叶子结.... 阅读详情

相关推荐

44、基础数据结构详解

本文详细介绍了基础数据结构(数组、栈和二叉搜索树)的概念、操作复杂度、应用场景及优化方法。通过分析不同数据结构的特性,帮助读者在实际问题中选择合适的数据结构以提升算法效率。同时结合实例代码与流程图,直观展示了常见算法的实现过程与性能优化策略。

Apple的专栏 58

44、二叉搜索树:原理、操作与实现

本文详细介绍了二叉搜索树(Binary Search Tree,BST)的基本原理、操作和实现方式。内容包括BST的插入、删除和搜索操作的具体算法和代码示例,并探讨了数组实现和链表实现两种方式的优缺点。此外,还介绍了树的遍历方法以及接口的改进方式,帮助读者全面理解和应用二叉搜索树,以提高程序的性能和效率。

tensorflowjs6的博客 64

44、数据结构中的树:原理、应用与实现

本文深入探讨了数据结构中的树,包括其基本概念、类型及实现方法。重点介绍了二叉树、二叉搜索树(BST)、高度平衡树(如AVL树)、堆以及B树等常见树结构的操作方式与应用场景。通过分析树在数据库索引、任务调度、表达式计算等方面的实际案例,展示了树结构在计算机科学中的重要性和广泛应用。文章还总结了不同树的特点及其适用场景,帮助读者更好地理解和选择适合的树结构以提升程序性能和系统效率。

gray5的博客 138

tkinter使用label视频播放

tkinter是python的界面开发包 使用tkinter显示视频流的一个办法是使用label来逐帧显示图片。 from Tkinter import * from PIL import ImageTk, Image import cv2 root = Tk() # Create a frame app = Frame(root, bg="white") app.grid() # Create a label in the frame lmian = Label(app) lmain.grid(

六十英里的专栏 5525

CGNS快速入门到实战(二):VS配置调用CGNS静态链接库

CGNS快速入门到实战这一系列分成三篇文章展开叙述,具体有:(一)CGNS静态链接库编译;(二)VS配置调用CGNS静态链接库;(三)CGNS文件及案例展示。本篇文章重点讲述VS配置调用CGNS静态链接库。

ahang_99的博客 2616

VMware 17+Win10 22H2全栈配置指南|UEFI优化+GPU直通实战

​VMware下载:下载官网版本的Wmare 17 Pro (点击直达链接 )企业用户可选永久许可证,个人学习也可以直接安装使用,Win10镜像:微软官网下载22H2版ISO文件(附官方纯净版渠道 )

waicsdn_haha的博客 4017

kubeadm init 运行时kebelet启动失败问题

解决k8s kubeadm init时遇到的kubelet问题

ZDPZN的博客 6072

PICO SCOPE 二次开发

基于C#开发一个简单的PICO示波器的上位机工作,与MCC600运动控制卡一起,实现数据自动采集

nixoncheng的博客 1146

《生成式AI引发的“认知污染”:社交媒体虚假信息的治理困局》

认知污染与生成式AI“认知污染”指信息环境被大量虚假、误导性内容所充斥,干扰公众对事实的认知判断。在社交媒体时代,虚假信息的生产和传播门槛大幅降低,呈现出工业化、规模化的趋势。近年兴起的生成式人工智能(Generative AI)技术,如大型语言模型(LLM)和深度伪造(deepfake),更是加剧了这一问题。AI可以自动生成看似真实的文本、图像、音视频,将虚假信息批量生产并快速散播到社交平台上。

那谁的博客 5909

Codesys电子凸轮实战:从机械替代到虚轴设计的5个关键步骤

本文详细介绍了Codesys电子凸轮技术在运动控制领域的实战应用,从机械替代到虚轴设计的5个关键步骤。通过电子凸轮技术,操作员可以快速调整切割模式,显著提升产线效率。文章还探讨了虚轴架构、CAM表设计、动态耦合技巧及调试避坑指南,为工业自动化领域提供了实用的解决方案。

tech5的博客 767

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

上一篇: 18、数据中心外部连接性设计与实现
下一篇: 45、数据结构与稀疏矩阵基础:二叉搜索树、堆与矩阵运算
随身带U盘
博客等级 码龄4年 30粉丝 1526原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值