数据结构(暂未配套 python 代码)

数据结构

声明:本博客大量内容转载自博客:【数据结构:八大数据结构分类】、【数据结构和算法】,经自己调整排版与内容而成,不是很权威(如果要初学,还是比较推荐看书的,最后有推荐书籍)

数据结构是指:相互之间存在着一种或多种关系的 数据元素的集合 和 该集合中数据元素之间的关系组成

常用的数据结构有

  • 数组 Array
  • 栈 Stack
  • 链表 Linked List
  • 队列 Queue
  • 树 Tree
  • 图 Graph
  • 堆 Heap
  • 散列表 Hash
  • ...其他数据结构...

  • 每一种数据结构都有着自己独特的数据存储方式,以及它们的优缺点

线性结构

数组

数组需要一块连续的内存空间来存储,对内存的连续性要求很高

python 中的列表 list 其实也是数组这一数据结构(做了一些特殊处理)

如果我们申请一个 100MB 大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败

优点:

  1. 按照索引查询元素速度快
  2. 按照索引遍历数组方便
  3. 存取速度快

缺点:

  1. 数组的大小固定后就无法扩容了
  2. 数组只能存储一种类型的数据
  3. 插入、删除效率非常低,因为要移动其他的元素

适用场景:

  • 频繁查询,对存储空间要求不大,很少增加和删除的情况

链表(离散存储)

链表是物理存储单元上非连续的、非顺序的存储结构

细节:数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域

分支:根据指针的指向以及节点的结构,链表能形成不同的结构,例如单链表、双向链表、循环链表、非循环链表等

结构:

  • 首节点:第一个有效节点
  • 尾结点:最后一个有效节点
  • 头结点:第一个有效节点之前的那个节点,头结点并不存储任何数据,目的是为了方便对链表的操作
  • 头指针:指向头结点的指针变量
  • 尾指针:指向尾节点的指针变量

链表的优点

  1. 不固定大小,不需要初始化容量,可以任意加减元素
  2. 添加删除节点快,添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快

缺点:

  1. 因为含有大量的指针域,占用空间较大
  2. 查找元素需要遍历链表来查找,非常耗时

适用场景:

  • 数据量较小,需要频繁增加,删除操作的场景

数组与链表的性能比较

栈是一种特殊的线性表,仅能在线性表的一端操作,每次操作的对象只能是栈顶(推荐阅读:【数据结构之线性表】、【数据结构之栈】)

特点: 先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。

栈的结构就像一个集装箱,越先放进去的东西越晚才能拿出来,所以,栈常应用于实现递归功能方面的场景,例如斐波那契数列。

分类:

  • 静态栈:静态栈的核心是数组,类似于一个连续内存的数组,我们只能操作其栈顶元素

  • 动态栈:静态栈的核心是链表

栈的应用:

  • 函数调用(特别典型的就是嵌套调用、递归)
  • 浏览器的前进后退
  • 表达式求值
  • 内存分配
  • ....

当有多个函数调用时,按照 先调用后返回 的原则,函数之间的信息传递和控制转移必须借助栈来实现

即 系统将整个程序运行时所需要的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放他的存储区,即进行出栈操作,当前运行的函数永远在栈顶的位置

队列

队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出

从一端放入元素的操作称为入队,取出元素为出队,示例图如下:

队列的分类:

  • 链式队列
  • 静态队列

使用场景:

因为队列先进先出的特点,在多线程阻塞队列管理中非常适用、生产者消费者模型(起源于操作系统,各种语言都有这个概念,也有着自己的实现方式)

非线性结构

是一种数据结构,它是由 n(n>=1)个有限节点组成一个具有层次关系的集合

把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的

  • 树就是由节点和边组成的
  • 每一个节点只能有一个父节点,但可以有多个子节点
  • 但有一个节点例外,该节点没有父节点,此节点就称为根节点

树的特点:

  • 每个节点有零个或多个子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;

与树相关的术语:

  • 节点
  • 父节点
  • 子节点
  • 子孙节点
  • 堂兄弟节点
  • 兄弟节点
  • 叶子节点:没有子节点的节点
  • 深度:从根节点到最底层节点的层数(根节点是第一层)
  • 度:某个节点的度即该节点的子节点的个数

树的分类:(这一块推荐去看看算法书,明显不止这些)

  • 普通树:每个节点的子节点的个数不受限制
  • 二叉树:任意一个节点的子节点的个数最多是两个,且子节点的位置不可更改
    • 满二叉树:在不增加层数的前提下,无法再多添加一个节点的二叉树
    • 完全二叉树:只是删除了满二叉树最底层最右边连续的若干个节点
    • 普通二叉树
  • 森林:n 个互不相交的树的集合

在日常的应用中,我们讨论和用的更多的是树的其中一种结构,就是二叉树

二叉树的特点:

  1. 每个结点最多有两颗子树,结点的度最大为2
  2. 左子树和右子树是有顺序的,次序不能颠倒
  3. 即使某结点只有一个子树,也要区分左右子树。

二叉树是一种比较有用的折中方案,它添加,删除元素都很快,并且在查找方面也有很多的算法优化,所以,二叉树既有链表的好处,也有数组的好处,是两者的优化方案,在处理大批量的动态数据方面非常有用。

扩展:
二叉树有很多扩展的数据结构,包括平衡二叉树、红黑树、B+树等,这些数据结构二叉树的基础上衍生了很多的功能,在实际应用中广泛用到,例如 mysql 的数据库索引结构用的就是 B+ 树,还有 HashMap 的底层源码中用到了红黑树。

这些二叉树的功能强大,但算法上比较复杂,想学习的话还是需要花时间去深入的。

使用场景:

  • 树是数据库中数据组织的一种重要形式(索引 B+ 树)
  • 操作系统父子进程的关系本身就是一颗树
  • 面型对象语言中类的继承关系

散列表(哈希表)

散列表,也叫哈希表,是根据关键码和值 (key 和 value) 直接进行访问的数据结构,通过 key 和 value 来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素

记录的存储位置=f(key)

这里的对应关系 f 成为散列函数,又称为哈希 ( hash 函数),而散列表就是把 Key 通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将 value 存储在以该数字为下标的数组空间里,这种存储空间可以充分利用数组的查找优势来查找元素,所以查找的速度很快

哈希表在应用中也是比较常见的,就如 Java 中有些集合类就是借鉴了哈希原理构造的

例如HashMap,HashTable 等,利用 hash 表的优势,对于集合的查找元素时非常方便的,然而,因为哈希表是基于数组衍生的数据结构,在添加删除元素方面是比较慢的,所以很多时候需要用到一种数组链表来做,也就是拉链法

拉链法是数组结合链表的一种结构,较早前的 hashMap 底层的存储就是采用这种结构,直到 jdk1.8 之后才换成了数组加红黑树的结构,其示例图如下:

从图中可以看出,左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多

我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素

注意:哈希表的应用场景很多,当然也有很多问题要考虑,比如哈希冲突的问题,如果处理的不好会浪费大量的时间,导致应用崩溃

堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象

堆的特质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值
  • 堆总是一棵完全二叉树

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

常见的堆有二叉堆、斐波那契堆等

堆的定义如下:n 个元素的序列 {k1,k2,ki,…,kn} 当且仅当满足下关系时,称之为堆:
(ki <= k2i,ki <= k2i+1) 或者 (ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2),满足前者的表达式的成为小顶堆,满足后者表达式的为大顶堆,这两者的结构图可以用完全二叉树排列出来,如下:

因为堆有序的特点,一般用来做数组中的排序,称为堆排序。

推荐阅读:【非线性数据结构——图

图是由结点的有穷集合 V 和边的集合 E 组成

其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点是顶点的有序偶对

若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系

按照顶点指向的方向可分为无向图和有向图:

图是一种比较复杂的数据结构,在存储数据上有着比较复杂和高效的算法,分别有邻接矩阵 、邻接表、十字链表、邻接多重表、边集数组等存储结构,这里不做展开,读者有兴趣可以自己学习深入

推荐书籍

如果想要系统的认识一遍,还是更推荐去看看书的,里面的讲解和案例会更好,更有助于理解的

posted @   suwanbin  阅读(196)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示