数组/链表/栈/队列/堆/优先队列/二叉树/哈希表-各类数据结构及其适用场景分析

分析对象

各类数据结构,具体包括:
1.数组
2.链表
3.栈
4.队列
5.堆
6.优先队列
7.二叉树
8.哈希表

分析

数组

定位--不受限容器

数组是容器,
提供
值查找,
索引访问,
指定位置插入,
指定位置删除,
遍历操作.

数组的定位是操作不受限的容器.
不受限体现在,可在数组的指定位置进行插入/删除,
操作的位置没有限定.
元素插入/删除时,也没有需要进行调节以满足某个循环不变式.

不可忽略的优势&劣势

数组作为容器
不可忽略的优势是支持时间复杂度为O(1)索引访问,
无法忽略的劣势是,
指定位置的插入/删除/按值查找均需要O(n)的时间复杂度.

可以忽略的优势

实现简单,
无需存储管理数据,节省内存

适合使用场景建议

频繁使用索引访问,且不怎么使用动态插入/删除场景
管理元素规模不大场景

链表

定位--不受限容器

链表也是容器.
与数组不同,容器元素通过指针串联.易于动态扩展.
正常项目中的链表应该是:
双向链表,记录链表首元素指针,尾元素指针,
记录链表有效元素个数[在对元素操作时进行维护]
PAT/考试环境下,看情况,
单链表能满足要求时,用单链表也可.
链表和数组一样作为容器,
一般要提供,
遍历,值查找,指定位置插入,指定位置删除.
链表插入,删除时,注意区分在头部/在尾部/在中间插入/删除.
链表组织的元素,不方便进行排序.

链表的不受限体现在,
可以在指定位置进行元素插入/删除,
操作的位置没有限定.
元素插入/删除时,也没有需要进行调节以满足某个循环不变式.

不可忽略的优势&劣势

链表作为容器
不可忽略的优势是,在首部插入/删除,尾部插入/删除时,
具有O(1)的时间复杂度.
双向链表下,中间位置插入在传入指针下,也有O(1)时间复杂度
不可忽略的劣势时,
只支持按值进行元素查找,且查找时间复杂度为O(N)

可以忽略的优势

所管理的元素易于实现容量动态变化.

可以忽略的劣势

每个结点除了存储数据,
还需存储指向上一结点,下一结点指针.
需要额外存储空间.

适合使用场景建议

需要频繁使用首部插入/删除,尾部插入/删除,
且不怎么用值查找场景.
需用值查找但管理的元素规模不大场景.

定位--操作受限容器

受限体现在:
栈的插入元素只能是在最后插入元素之后插入.
栈的删除元素只能是最后插入元素的删除.

可以基于数组来实现,
也可以基于链表[在头部插入/删除的单链表即可]来实现.
如果PAT/考试下,
栈的容量固定,优先用数组.
栈的容量不固定,优先用头部插入/删除的单链表.

栈作为受限的容器提供的操作包括:
栈空判断
插入元素
删除元素
查看顶部元素
满判断[仅针对容量固定的栈]

不可忽略的优势&劣势

每类操作受限容器,按其操作限定类别,
决定了其使用场景.
这类容器的不可忽略优势是,
在符合其限定要求下,应使用此类容器.
这类容器的不可忽略劣势是,
在不符合其限定要求下,不应使用此类容器.

适合使用场景建议

需要限定插入/删除,只能基于最后添加元素下.

队列

定位--操作受限容器

队列也是受限的容器.
队列的受限体现在
插入只能在最后插入元素之后进行
删除针对目前存在的最早插入元素进行.

队列可以用数组实现,也可以用链表实现.
用数组实现时,一般是固定容量数组.
队列特点时,随着入队,出队元素在数组中存放位置不断移动,
为此使用数组实现时,数组是循环可用的.
为了在固定容量数组实现下便于判断队列空,
队列满常用的一个策略是:
维护一个索引A指向尾部元素位置,
一个索引B指向队列首个元素前一位置.
这时,为了区分满,空.
容量为N的数组,最大只存储N-1个元素.
这时,
两个索引相等,代表队列空.
若指向尾部元素的索引下一位置为首部元素前一位置,
则表示队列已经满了.

[考虑若尾部下一位置为首部元素前一位置,
则表示若允许此次插入则数组N个槽全部有元素,就很好理解了]
如果允许N个槽都有元素,
则槽空,槽满时,尾部元素索引和首部元素前一位置索引均相等.
就不好判断了.

上述是书上的做法.
实际上本人更推荐
维护首部元素索引,尾部元素索引,
和一个有效元素个数[插入/删除时作维护]
这样也不用舍弃一个空间,理解起来也直观.

若是队列使用的环境是容量不确定,
用链表来实现队列.
用头部删除/尾部插入的单链表即可实现队列.
队列维护头部结点指针,尾部结点指针,推荐也维护有效元素个数.

PAT/考试下,
队列容量固定,优先考虑用数组实现.
队列容量不固定,优先考虑用单链表实现.

个人推荐,
队列只用单链表实现,因为够简单,和直观.
通过维护有效元素个数,
记录容量作限制也很容易施加固定容量的限制.

不可忽略的优势/劣势

每类操作受限容器,按其操作限定类别,
决定了其使用场景.
这类容器的不可忽略优势是,
在符合其限定要求下,应使用此类容器.
这类容器的不可忽略劣势是,
在不符合其限定要求下,不应使用此类容器.

适合使用场景建议

需要限定首部删除,尾部插入下.

堆&优先队列

定位--操作受限+满足特定性质容器

堆是有序+操作受限的容器.

操作受限体现在,对堆/优先队列执行元素删除时,
只能删除首个元素.

满足特定性质体现在,
依据最大堆/最小堆,
要求父结点与其孩子结点大小关系满足全局一致性.
最大堆下,为父结点大于孩子结点,
最小堆下,为父结点小于孩子结点

不可忽略优势/劣势

作为操作受限容器
堆的不可忽略优势是,每次从堆取出的元素是堆中最小/最大的.
这一性质可用于排序.
堆的插入/删除时间复杂度均为O(log(N)),
N为操作时堆中元素个数.
堆需要提供的接口一般为:
插入/删除/有效元素个数/满判断/空判断

构建堆,
可以逐个元素插入来构建.
也可基于一个数组进行从尾到首的调整来实现,
时间复杂度均为O(Nlog(N))

不可忽略劣势,
如果要求的性质和容器的限定不匹配,则不应使用

适合使用场景建议

使用场景,
如果是直接的排序,不要用堆,直接用数组存储集合,
然后用排序算法对其排序.
但是,
如果是针对一个动态变化的容器,
需要不断在动态变化的值中间取出最小值的情况下,
就是堆适合被使用的场景.

堆的实现主要是放入元素后的调节,
从顶部向下调节
从底部向上调节两种调节形式.
插入时,尾部插入,
然后从插入位置向上调节.[调节只涉及父子间的调节]
删除时,头部删除,尾部元素移入头部,
然后从头部向下调节.[调节只涉及父子间的调节]

二叉树

定位--满足特定性质的操作不受限容器

二叉搜索树也是容器
二叉搜索树是满足特定性质但操作不受限的容器.
操作不受限体现在,
对其执行插入/删除,不限定位置.

满足特定性质体现在,
1.搜索树的性质
左子树中所有元素均小于或等于树的根
右子树中所有元素均大于树的根

你也可以实现:
左子树中所有元素均大于或等于树的根
右子树中所有元素均小于树的根
的二叉搜索树

因为有了上述性质,对树内元素搜索时可应用二叉搜索
2.二叉树的性质
1.树中除了根节点外,每个节点有唯一一个父节点
2.树中每个节点可以有至多一个左孩子,至多一个右孩子.

不可忽略优势/劣势

二叉搜索树这种数据结构存在的意义在于:
1.它支持容器应该支持的插入/删除/元素访问,
且插入/删除/元素访问本身无限制,
所以首先它是一个不受限制的容器.
依据其性质,二叉搜索树具备以下优势:
2.对元素进行值访问时,
二叉搜索树执行的是二叉访问.效率上优于顺序访问.
3.二叉搜索树因为其性质额外支持前序/后序/中序/广度搜索
一个搜索的时间复杂度为O(N)[N为搜索时树内元素个数]
执行中序搜索,按节点访问顺序可以完成树内元素的排序.
这样即在O(N)时间复杂度下完成了元素排序
[前提:针对一个给定的二叉搜索树结构]

劣势:
二叉搜索树在树不平衡的情况下,
元素搜索/插入/删除会退化到O(N)的时间复杂度

可以忽略的劣势

1.二叉搜索实现上比数组/链表要复杂,
2.二叉搜索树在存储同样一个元素下,
因为要额外保存父结点指针,左右孩子指针,
需要更多内存.

适合使用场景建议

要使用操作不受限容器,具体体现在
可能在任意位置进行插入,可能涉及任意位置的删除等.
且所管理的元素规模较大.

一般实际项目不会使用二叉搜索树,
只会使用二叉搜索树的进化版--红黑树或二叉搜索平衡树.
因为红黑树不仅具备二叉搜索树所具备的所有优势,
额外具备平衡的性质.
平衡保证了对其插入/删除/搜索均可保证O(log(N))的时间复杂度.
所以实际项目中,需要管理规模较大的元素,且元素操作不受限时,
适合使用红黑树.

红黑树一个可以忽略的劣势是,
平衡调节的实现比较复杂.

哈希表[基于链表版本]

略.
和红黑树适合使用场景一致.
哈希表解决规模大的思想是,执行一次基于槽的划分.
来将大规模进行一次分解.
但实际一次分解在整体规模大,
桶容量有限下,仍然最终导致O(N)时间复杂度.
实际中项目中可能会有动态哈希,动态调节桶容量,来保证
散列分布均匀下,每个桶容量始终维持在常量情形.

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/113829877