数据结构
数据结构是研究如何存储数据的学科
存储结构
-
线性表
1.1 顺序表
1.2 链表
1.3 栈
1.4 队列
-
树结构
2.1 普通树
2.2 二叉树
2.3 线索二叉树
-
图存储结构
-
递归
线性表
同小朋友手拉手一样。每个元素都有且仅有一个与之相邻的元素(除首尾以外)。具备这种关系就可以使用线性表来存储。
注意
线性表不是一种具体的存储结构,是顺序表
和链表
的统称
顺序表
顺序表可以简单的理解为数组
链表
链表可以想象成用链子连接成的数据条。
链表详解
使用顺序表时,需要提前申请一定的空间,这块存储空间的物理地址是连续的。使用链表则完全不同。链表是随用随申请。也就是说他的位置是随机的。
单链表
为了给各个数据块建立“依次排列”的关系,链表给各数据块增设一个指针,每个数据块的指针都指向下一个数据块(最后一个数据块的指针指向 NULL),就如同一个个小学生都伸手去拉住下一个小学生的手,这样,看似毫无关系的数据块就建立了“依次排列”的关系,也就形成了链表。
单链表中有两个节点比较特殊,分别是第一个结点和最后一个结点。我们通常把第一个结点叫作头结点,把最后一个结点叫作尾结点。其中,头结点用来记录链表的基地址,有了它,我们就可以遍历得到整条链表。而尾结点特殊的地方是:指针不是指向下一个结点,而是指向一个空地址 NULL,表示这是链表上最后一个结点。对单链表而言,理论上来说,插入和删除节点的时间复杂度是 O(1),查询节点的时间复杂度是 O(n)。
循环链表
然后还有在单链表的基础上扩展还有循环链表,循环链表和单链表的区别是尾节点指向了头结点,从而首尾相连,有点像贪吃蛇。
双向链表
即用两个链子连起来的链表。一根链子指向下一个节点,另一根链子指向上一个节点。这样找到上一个节点只需要O(1)个复杂度。
正是因为这个节点,使得双向链表在插入和删除操作时比单向链表更高效。
前面已经提到单链表插入、删除时间复杂度已经是 O(1) 了,但是这没有考虑还只是针对插入、删除操作本身而言,
以删除为例,删除某个节点后,需要将其前驱节点的指针指向被删除节点的下一个节点,这样,我们还需要获取其前驱节点,
在单链表中获取前驱节点的时间复杂度是 O(n),所以综合来看单链表的删除、插入操作时间复杂度也是 O(n),而双向链表则不然,
它有一个指针指向上一个节点,所以其插入和删除时间复杂度才是真正的 O(1)。
双向循环链表
其实就是将双向链表和循环链表组合起来
栈和队列
栈和队列隶属于线性表,是特殊的线性表,因为它们对线性表中元素的进出做了明确的要求。
栈
中的元素只能从线性表的一端进出(另一端封死),且要遵循“先入后出”的原则,即先进栈的元素后出栈。
队列
中的元素只能从线性表的一端进,从另一端出,且要遵循“先入先出”的特点,即先进队列的元素也要先出队列。
树存储结构
树存储结构适合存储具有“一对多”关系的数据。
图存储结构
图存储结构适合存储具有“多对多”关系的数据。
时间复杂度和空间复杂度
算法
即解决问题的办法。同一个问题,使用不同的算法,耗费的时间和资源是不同的。
算法VS程序
算法是解决问题的思路;程序是在心中有想法的前提下编写出来可以运行的代码。
好算法的标准
对于一个问题的算法来说,之所以称之为算法,首先它必须能够解决这个问题(称为准确性)。其次,通过这个算法编写的程序要求在任何情况下不能崩溃(称为健壮性)。
如果准确性和健壮性都满足,接下来,就要考虑最重要的一点:通过算法编写的程序,运行的效率怎么样。
运行效率体现在两方面:
- 算法的运行时间 (时间复杂度)
- 运行算法所需要的内存空间大小 (空间复杂度)
存储结构和数据的关系
-
线性表 用于存储 一对一 逻辑关系的数据
-
树结构 用于存储 一对多 逻辑关系的数据
-
图结构 用于存储 多对多 逻辑关系的数据
存储结构(物理结构)
如果选择集中存储,就使用顺序存储结构;反之,就使用链式存储。至于如何选择,主要取决于存储设备的状态以及数据的用途。
集中存储(底层实现使用的是数组)需要使用一大块连续的物理空间,假设要存储大小为 1G 的数据,
若存储设备上没有整块大小超过 1G 的空间,就无法使用顺序存储,此时就要选择链式存储,
因为链式存储是随机存储数据,占用的都是存储设备中比较小的存储空间,因此有一定几率可以存储成功。
什么是线性表
就是把所有的数据用一根线串起来,放到一个物理空间中
两种线性存储结构
- 数据集中存放(顺序存储结构 简称 顺序表)
- 数据分散存放(链式存储结构 简称 链表)
***重点
****:数据存储的成功与否;取决于能否将数据完整的还原成它原本的样子
前驱和后继
另外,对于具有“一对一”逻辑关系的数据,我们一直在用“某一元素的左侧(前边)或右侧(后边)”这样不专业的词,其实线性表中有更准确的术语:
某一元素的左侧相邻元素称为“直接前驱”,位于此元素左侧的所有元素都统称为“前驱元素”;
某一元素的右侧相邻元素称为“直接后继”,位于此元素右侧的所有元素都统称为“后继元素”;
顺序表及初始化过程详解
顺序表存储数据时,会提前申请一块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝空隙。
递归
递归算不上数据结构和算法,只是一种编程技巧。很多算法会通过递归来实现,比如归并排序、快速排序、二分查找法等。递归,简单来讲就是在函数定义中调用函数自身
判断一个问题是否可以通过递归来解决,主要看它是否满足以下三个条件:
- 一个问题的解可以分解为几个子问题的解
- 这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样
- 存在递归终止条件
递归一定要有终止条件,否则会导致函数被无限调用最终致使内存溢出。