.数据结构与算法基础

Posted weixin_51333606

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.数据结构与算法基础相关的知识,希望对你有一定的参考价值。

目录


第六章.数据结构与算法基础(重点)

ps:上午下午都会考到且难度最高
重点:线性表、树与二叉树、排序与查找、算法基础及常见算法

第一节.数组与矩阵

数组

数据类型存储地址计算
一维数组a[n]a[i]的存储地址为:a + i * len
二维数组a[m][n]a[i][j]的存储地址(按行存储)为:a + (i * n + j) * len、a[i][j]的存储地址(按列存储)为:a + (j * m + i) * len
  • 数组的首地址是数组名,a[0] = a,因为a[0]是整个数组存储第一个元素。
  • 数组的下标从0开始。
  • len表示 每一个数组元素所占用的字节数。

例题:已知5行5列的二维数组a中的各元素占两个字节,求元素 a [ 2 ] [ 3 ] a [2] [3] a[2][3]按行优先存储的存储地址?
:元素 a [ 2 ] [ 3 ] a[2][3] a[2][3]按行优先存储的存储地址为 a + ( 2 × 5 + 3 ) × 2 = ( a + 26 ) b i t a + (2 \\times 5 + 3) \\times 2 = (a + 26)bit a+(2×5+3)×2=(a+26)bit


稀疏矩阵

常考题型:计算稀疏矩阵当中某一个元素对应的一维数组的下标。
什么是稀疏矩阵?
在矩阵中,若数值为0的元素数目远远多于非0元素的数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵;与之相反,若非0元素数目占大多数时,则称该矩阵为稠密矩阵。定义非零元素的总数比上矩阵所有元素的总数为矩阵的稠密度。

大量的元素都是零,那么我们是不是可以考虑存储矩阵的一部分内容就已经把有效数据都存进去了,如果是这样的话,这是不是就能节省出很多空间了。所以就提出这一概念来,存储稀疏矩阵一般是存储上三角或者下三角。为什么另一半不用存呢?因为另一半可能跟现在的这一半是重复的数据。什么情况是重复的呢?比方说图是可以通过矩阵的方式进行存储,如果说你要存储的图是一个无向图,那么存储下来的就是三角矩阵而已。

二维数组存入计算机中都是以一维数组顺次的形式存储下来的,这种对应关系很重要。
:数组M的下标是从1开始的,根据图片可知,先算前i行的个数,即 i ( i + 1 ) 2 \\fraci(i + 1)2 2i(i+1),又因为下标是从1开始的,所以还有一行,这一行的个数为 j + 1 j + 1 j+1,那么答案就是A。
当然代入法,也能够做,A[0, 0]对应M[1],依次将二维坐标带入到一维数组当中进行判断。


第二节.数据结构的定义

1.数据结构的概念
数据结构就是计算机存储以及组织数据的方式而已。
为什么要研究数据结构?
之所以研究数据结构是因为选择不同的数据结构可能带来的运行效率会非常之大。在同样的数据结构当中稍作调整也可以让效率有很大的改变。比方说:在数组的存储当中,行存储和列存储在既定的处理的机制之下,会有非常大的性能方面的差异,这也就是我们研究数据结构的基本价值与意义。

2.数据逻辑结构
数据结构从逻辑层面上分成

  • 线性结构(顾名思义,线性结构跟一条线一样)
  • 非线性结构(树型结构)

树和图的最大区别就是树中没有环路,图有可能存在环路。
广义的图包含树和线性结构;广义的树包含线性结构。


第三节.线性表

1.线性表的概念:线性表是线性结构的基本表现。如 ( a 1 , a 2 , … , a n ) \\left(a_1, a_2, \\ldots, a_n\\right) (a1,a2,,an)

2.线性表常见的两种存储结构

  • 顺序存储结构:顺序表
  • 链式存储结构:链表

3.顺序表(连续的空间下存储数据):开辟了连续的空间顺次的数据存储到表中,其实就是采用一维数组进行顺次存储信息。


链表详解

  • 链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个或两个域是指向其他单元的指针。这里具有一个数据域和多个指针域的存储单元通常称为节点(node)。另外,链表是在不连续的空间下存储数据。
  • 链表的第一个节点和最后一个节点,分别称为链表的头节点和尾节点。尾节点的特征是其 next 引用为空(null)。链表中每个节点的 next 引用都相当于一个指针,指向另一个节点,借助这些 next 引用,我们可以从链表的头节点移动到尾节点。

为什么会有指针域存在呢?
因为空闲的存储空间不见的都是连续的,比如说这样一种情况,磁盘上面有一个地方是空闲的,隔几个地方是空闲的,我们通过指针把这些离散的空间连接起来就形成了链表。顺序表是开辟了连续的空间顺次存储,磁盘中必须有一个能够放置10个元素的连续空间。所以两者从形态来讲是不一样的。

链表数据结构中主要包含单(向)链表、双向链表及循环链表:

  1. 单链表:只有一个指针域,在整个节点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的节点。
    单链表可以分成 有头节点和没有头节点两种类型的链表。

有头节点的链表会有什么好处呢?
头结点的数据域可以不存储任何信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。头结点的作用是使所有链表(包括空表)的头指针非空,并使对单链表的插入、删除操作不需要区分是否为空表或是否在第一个位置进行,从而与其他位置的插入、删除操作一致。

  1. 双向链表:要在单向链表中找到某个节点的前驱节点,必须从链表的头节点出发依次向后寻找,但是需要Ο(n)时间。为此我们可以扩展单向链表的节点结构,使得通过一个节点的引用,不但能够访问其后续节点,也可以方便的访问其前驱节点。扩展单向链表节点结构的方法是,在单链表节点结构中新增加一个域,该域用于指向节点的直接前驱节点。该链表称为双向链表。单向链表只能从一个方向遍历,双向链表可以从两个方向遍历。其中两个指针分别称为前驱指针和后继指针。

将尾结点指向头节点的好处是什么?
要在单向链表中找到某个节点的前驱节点,必须从链表的头节点出发依次向后寻找,但是需要Ο(n)时间。但是现在我们直接就能够取出直接前驱节点。

  1. 循环链表:头节点和尾节点被连接在一起的链表称为循环链表,这种方式在单向和双向链表中皆可实现。循环链表中第一个节点之前就是最后一个节点,反之亦然。


链表的基本操作

图注

  1. 单链表删除结点。删除a3就是将a2的指针指向a3的地址即可,但是现在不知道a3的地址,根据单链表中上一个结点的指针存储下一个结点的地址,故代码为 p − > n e x t = q − > n e x t p->next=q->next p>next=q>next
  2. 单链表插入结点。假设需要加入x结点,并且s指针指向x结点,故代码为 s − > n e x t = p − > n e x t ( 因 为 现 在 p 指 针 存 储 a 2 的 地 址 ) , p − > n e x t = s s->next=p->next(因为现在p指针存储a2的地址),p->next=s s>next=p>next(pa2)p>next=s
  3. 双向链表的操作复杂,但也大同小异。
  4. 在插入、删除节点时,要先获取到必要的指针,才能删除或添加。

链表的特点

  1. 链表可以没有表示整体的对象,只需要用节点就够了。表示一整条链表可以用“头节点”,也就是第一个节点来表示整条链
  2. 局限性:总是可以用一个节点的next指针走到下一个节点。而且大部分情况下只有这一种方法——链表没有下标,也不能直接跳转到某一环。只能从一个节点出发,一步一步往后挪
  3. 查询慢:由于链表中地址不是连续的,每次查询元素都必须从头开始
  4. 增删快:链表结构,增加/删除一个元素,对链表整体结构没有影响,所以增删快,与链表的长度无关
  5. 查询困难:链表的查找需要从头遍历,与数组类似,越长速度越慢。但由于没有下标可用,链表的遍历实际上比数组更慢一些。

总结
随着编程语言的进步,在实践中直接使用链表的情况变得越来越少。
但是强烈推荐初学者在学习编程时,掌握好链表这种基础而强大的数据结构。能够理解它的原理,举一反三,从而领悟到更多相关的知识。


顺序存储与链式存储对比

从空间性能方面对比
1.存储密度:顺序存储的存储密度为1(更优),而链式存储的密度则小于1
2.容量分配:顺序存储信息需要多少空间需要事先确定才能够分配连续的空间,而链式存储能够动态更改容量的分配(更优)
从时间性能方面对比
1.查找运算:使用顺序存储比较方便。当内容没有顺序的前提下依次查找,两种方式的时间复杂度是一样的即 O ( n 2 ) O(\\fracn2) O(2n),涉及到二分查找,顺序存储就更优一些。
2.读运算:即读取该位置的值。顺序存储时间复杂度为 O ( 1 ) O(1) O(1)(更优),因为a[5]就是数据值,链式存储时间复杂度为 O ( n + 1 2 ) O(\\fracn+12) O(2n+1),最好情况为1,最坏情况为n,因为链表的局限性。
3.插入运算:顺序存储的时间复杂度为 O ( n 2 ) O(\\fracn2) O(2n),最好情况为0,最坏情况为n,而链式存储的时间复杂度为 O ( 1 ) O(1) O(1)(最优)。
4.删除运算:顺序存储的时间复杂度为 O ( n − 1 2 ) O(\\fracn - 12) O(2n1),而链式存储的时间复杂度为 O ( 1 ) O(1) O(1)(更优)。


队列与栈


循环队列队满情况分析

  1. 规定尾指针的下一个元素是头指针表示队满。如果说所有空间都存储上信息后,那么头指针与尾指针又安置到一起,导致队空和队满的条件相同。
  2. 公式中取余是因为循环队列中会有弹出的操作,即头指针向后移动。故头指针可能不在下标为0的位置。

习题:元素按照a、b、c的次序进入栈,请尝试写出其所有可能的出栈序列。
答:实际上,以某种次序入栈的元素出栈顺序是多种多样的。因为不一定要按照前进后出的规则来进行。abc、acb、bca、bac、cba五种情况。

复杂例题

解:队列是先进先出的,那么输出序列就是在队列中排列的数据,那么根据选项进行放入,得不到输出序列是D。


第四节.广义表


补充:

  • 表头是最外层的第一个表的元素
  • 表尾是除了第一层第一个元素之外的所有元素所组成的广义表。

例1,有广义表LS1=(a,(b,c),(d,e)),则其长度为?深度为?
其长度为3层,深度为2层。
例2,有广义表LS1=(a,(b,c),(d,e)),要将其中的 b 字母取出,操作就为 ?
先取表尾再取表头,最后再取表头。即 h e a d ( h e a d ( t a i l ( L S 1 ) ) ) head(head(tail(LS1))) head(head(tail(LS1)))


第五节.树与二叉树

树的概念

  • 结点:图中的1、2、3…数字圆形都表示结点
  • 结点的度:即一个结点的所有孩子结点个数称为度(如结点1的度就是2,结点3的度就是1)
  • 树的度:即一个树当中,结点中度的最大值称为树的度
  • 根结点:即树顶部的结点
  • 叶子结点:没有孩子结点的结点(如结点4、5、7、8)
  • 分支结点:有分支的结点(如结点1、2、6)
  • 内部结点:不是根结点,也不是叶子结点的结点(如结点2、3、6)
  • 父结点和子结点是一个相对的概念(如2和4而言,2是父节点,4是子节点)
  • 兄弟结点:拥有同一个父结点的子节点之间称为兄弟结点,当然也存在当堂弟结点
  • 层次:树的层次,该树有4个层次

二叉树的分类

  • 二叉树:每个结点最多只有两个叶子结点的树
  • 满二叉树:二叉树中所有非叶子结点的度都是2,且叶子节点都在同一层次上
  • 完全二叉树:如果一个二叉树与满二叉树前m个节点的结构相同,这样的二叉树被称为完全二叉树
    也就是说,如果把满二叉树从右至左、从下往上删除一些节点,剩余的结构就构成完全二叉树
  • 不完全二叉树:不是完全二叉树的树

二叉树的重要特性


补充

  • 满二叉树每一层的结点个数能够组成等比数列
    第1、2两条的特性就是等比数列的通项公式 a n = a 1 ⋅ q n − 1 a_n=a_1 \\cdot q^n-1 an=a1qn1和求和公式 S n = a 1 ( 1 − q n ) 1 − q ( q ≠ 1 ) S_n=\\fraca_1\\left(1-q^n\\right)1-q(q \\neq 1) Sn=1qa1(1qn)(q=1)的应用。
  • 向下取整即比括号中还要小的整数。
  • 第4条特性反映了为什么要按照层序编号。因为这样就能够通过当前结点确认父节点和左右子节点。以及能够极大的简便我们的运算。

二叉树的遍历

  • 前序遍历:根结点、左子树结点、右子树结点
  • 中序遍历:左子树结点、根结点、右树子结点
  • 后序遍历:左子树结点、右子树结点、根节点
    顺序遍历关键在于根结点如何访问。
  • 层次遍历:按层序编号进行遍历

  • 图中前序遍历结果是12457836
  • 图中中序遍历结果是42785136
  • 图中后序遍历结果是48752631
  • 图中层次遍历结果是12345678

反向构造二叉树

概念:知道一定的二叉树的遍历序列,然后反向推出二叉树的构造。
注:前序和中序或者中序和后序都能够将这个二叉树推出。

例题  由前序序列为ABHFDECG; 中序序列为HBEDFAGC构造二叉树。  \\text 由前序序列为ABHFDECG; 中序序列为HBEDFAGC构造二叉树。  由前序序列为ABHFDECG 中序序列为HBEDFAGC构造二叉树。 
:根据前序序列能够知道二叉树的根结点,即为A,然后根据中序序列就能够知道左子树HBEDF和右子树GC,然后再看前序序列,左子树的根结点为B,右子树的根结点为C,那么根据中序序列可知G为左叶子结点,H也为左叶子结点,EDF在右子树上且F为右子树根结点,然后ED都为左结点。


对于普通的树其实没有太多人去做研究和分析,所以要想研究树一般的做法就是把普通的树转成二叉树进行分析和讨论。

树转二叉树

如何将普通的树转成二叉树呢?
这是有一个基本的原则。

  • 树的某个结点的孩子结点转化成二叉树的左子树结点。
  • 树的某个结点的兄弟结点转化成二叉树的右子树结点(右孩子结点)。

其实还有更简单的方法,即连线法。将兄弟结点连接起来,对于有多个孩子的只保留第一个孩子的连线,最后把树旋转就能够得到二叉树了。


查找(排序)二叉树

查找二叉树是一类特殊的二叉树,又称二叉排序树。
特点:①左孩子小于根 ②右孩子大于根

这种树的提出有什么价值和意义呢?
能够极大地提高查询的速度和效率。比方说采用顺序查找的方法需要按照顺序从前往后找到符合条件的;如果采用排序二叉树进行查找,先和根节点进行比较,然后根据比大还是比小来确定下一个比较的结点是根左子树根节点还是右子树结点。如果你构造的查找二叉树更均衡一些,查找速率就会提高。

  • 插入运算:
    ①若该键值已存在,则不再插入,如上图插入48是不行的;
    ②若查找二叉树为空树,则以新结点为查找二叉树;
    ③将要插入结点键值与插入后父结点键值比较,就能确定新结点的位置是父结点的左子结点,还是右子结点。
  • 删除结点
    ①若待删除结点是叶子结点,则直接删除;
    ②若待删除结点只有一个子结点,则将这个子结点与待删除结点的父结点直接连接,如上图删除56;
    ③若待删除结点p有两个子结点,则在其左子树上,用中序遍历寻找键值最大的结点s,用结点s的值代替结点p的值,然后删除原结点s,结点s必属于上述(1)或(2),按照这两种情况进行处理。

最优二叉树(哈夫曼树)

这种二叉树是一种工具,用于哈夫曼编码,哈夫曼树中每一个父结点的键值都等于其子结点之和(不是子树结点)。
哈夫曼编码有哪些用呢?
它是一种无损压缩编码的方式,能够将原始编码的长度变得更短一些,从而节省存储空间和传输带宽。
这种编码在多媒体领域的压缩技术当中经常使用到。

需要了解的基本概念:

  • 树的路径长度:就是从根结点到叶子结点所经过的每段路径的个数之和。如下图中左边的树,结点15到结点2的路径长度为2
  • 权:就是某个叶子结点的数值,代表着某一个字符出现的频度。
  • 带权路径长度:就是某个树的路径长度乘以该路径的权值。如结点15到结点2的带权路径长度为 2 × 2 2 \\times 2 2×2
  • 树的带权路径长度(树的代价):就是所有叶子结点的带权路径长度之和。

构造一个哈夫曼树需要达到什么样的效果呢?
要达到的效果就是让树的带权路径长度是最短的情况。就好比上图相同的几个结点(1、2、3、4、7、8、15)构成的树的带权路径长度是不相同的。

:假设有一组权值 5,29,7,8,14,23,3,11请尝试构造哈夫曼树。
首先我们应该从数列当中选择权值最小的两个数垒上去,这时权重序列发生变化,将3和5取出和将8放入,这时权值序列为29,7,8,14,23,11, 8。接着继续选择权值最小的两个数7和8,这时权重序列发生变化,将7和8取出和将15放入,这时权值序列为29,14,23,11, 8, 15。接着继续选择权值最小的两个数8和11,这时发现需要另开一个子树,权重序列发生变化,将8和11取出和将19放入,这时权

以上是关于.数据结构与算法基础的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法之深入解析“二叉搜索子树的最大键值和”的求解思路与算法示例

手撸golang 基本数据结构与算法 快速排序

基础扩展 | 04. 快速排序算法

数据结构二叉树基础oj练习2(源码+算法思路)

数据结构与算法二叉树——另一棵树的子树

算法与数据结构常用算法模板1