数据结构必知必会(面试必备)

数据结构   面试   p7  
发布于 Jul 23, 2024

本文旨在罗列常见的数据结构,注意是罗列,大家知道个大概就行,不至于别人问起来一窍不通。

数据结构主要研究各种数据对象在计算机内存中的组织方式和存储结构,以及对这些数据对象进行操作的算法和方法。

在计算机程序设计中,数据结构是指数据元素之间的关系和操作方式的抽象描述,是程序设计中最基本的概念之一。

常见的数据结构,大家记一记:

  • 数组,一种线性结构,由一组具有相同类型的元素组成,这些元素按照一定的顺序排列并占据连续的存储空间,可以通过下标快速访问数组中的任意元素。
  • 链表,一种线性结构,它由一系列结点组成,每个结点包含数据域和指向下一个结点的指针,结点之间通过指针相连,可以实现动态的插入、删除等操作。
  • ,一种特殊的线性结构,它具有“后进先出”的特点,栈可以用数组或链表实现。
  • 队列,也是一种特殊的线性结构,它具有“先进先出”的特点,队列可以用数组或链表实现。
  • 散列表(哈希表),一种根据关键字直接访问内存存储位置的数据结构,它通过哈希函数将关键字映射到一个内存地址,并在该地址中存储对应的数据。哈希表可以用数组实现。
  • ,一种非线性结构,它由若干个结点组成,每个结点可以有若干个子结点,结点之间通过边相连。树可以用链表或数组实现。
  • ,堆是二叉树的一个特例,其特点是某个节点的值总是不大于或不小于其父节点的值。
  • ,一种非线性结构,它由若干个结点和若干个边组成,结点之间通过边相连。图可以用邻接矩阵或邻接表等方式实现。

数组

数组是用一组连续的内存空间,来存储一组具有相同类型的数据的结构,该空间具有固定的大小。所以其特点就是空间连续数据类型相同空间大小固定可以进行随机访问

因为数组的大小是固定的,所以数组中插入和删除元素是不能直接完成的,必须要先分配一个新的数组空间。

以插入一个元素到数组中为例,首先创建一个新的数组,该新数组的元素个数比原来数组的元素个数多1个。

然后将原来数组中的元素依次拷贝到新数组中,最后将要插入的元素放到新数组的最后一个位置。

删除操作也是同理。

链表

链表相较于数组,除了数据域,还增加了指针域用于构建链式的数据存储。

链表中每一个节点都包含此节点的数据和指向下一节点地址的指针。

由于是通过指针进行下一个数据元素的查找和访问,使得链表的自由度更高。

在链表中需有以下几个术语:

  • 链表中的每个元素称为节点
  • 每个节点包含一个key和一个指向下一个节点的指针。key是该节点用来存放数据的,也叫数据区域。下一个节点指针通常用next表示,用来指向下一个节点的地址。
  • 有一个head的指针,该指针指向链表的第一个节点。
  • 有一个tail指针,该指针指向链表的最后一个节点。

一个单链表如下图所示:

链表对节点进行增加和删除时,只需要对上一节点的指针地址进行修改,而无需变动其它的节点。

不过事物皆有两极,指针带来高自由度的同时,自然会牺牲数据查找的效率和多余空间的使用。

与数组的区别

另外对指针域进行反向链接,还可以形成双向链表或者循环链表。

  • 双向链表:可以从前后两个方向进行遍历。每个节点有两个指针:prev和next。分别指向自己的前驱和后继节点。
  • 循环链表:循环链表指的是链表的头节点的前驱指针指向尾部节点。尾部节点的后继指针指向头部节点。

跳表

链表虽然通过增加指针域提升了自由度,但是却导致数据的查询效率恶化。

特别是当链表长度很长的时候,对数据的查询还得从头依次查询,这样的效率会更低。

跳表的产生就是为了解决链表过长的问题,通过增加链表的多级索引来加快原始链表的查询效率。

栈是一种后进先出(想象往瓶子里放石子,先放进去的后掏出来)的结构,这种结果在很多编程语言中都很常见。

队列

队列是一种 先进先出(排队,先到先得) 的数据结构。

散列表

散列表也叫哈希表,是一种通过键值对直接访问数据的机构。

在初中,我们就学过一种能够将一个x值通过一个函数获得对应的一个y值的操作,叫做映射。

散列表的实现原理正是映射的原理,通过设定的一个关键字和一个映射函数,就可以直接获得访问数据的地址,实现O(1)的数据访问效率。

在映射的过程中,事先设定的函数就是一个映射表,也可以称作散列函数或者哈希函数。

确定好散列函数之后,通过某个key值的确会得到一个唯一的value地址。

但是会出现一些特殊情况,即通过不同的key值可能会访问到同一个地址,这个现象称之为冲突

目前比较常用的冲突解决方法是链地址法,一般可以通过数组和链表的结合达到冲突数据缓存的目的。

树是一种层级结构,数据按层级存储并关联在一起。这种结构和链表不同,链表是线性存储的。即一个父节点最多只有一个子节点。而树则是一个节点可以有多个子节点,但一个子节点只能有一个父节点。

树是具备层次关系的,父子关系清晰,家庭血缘关系明朗;这也是树与图之间最主要的区别。

为了适应不同的应用程序和特定的约束,有很多种类型的树。比如二叉树、B-tree、红黑树、平衡二叉树(AVL树)。

别看树好像很高级,其实可看作是链表的高配版。树的实现就是对链表的指针域进行了扩充,增加了多个地址指向子结点。

树可以衍生出许多的结构,若将指针域设置为双指针,那么即可形成最常见的二叉树,即每个结点最多有两个子树的树结构。

二叉树根据结点的排列和数量还可进一步划分为完全二叉树、满二叉树、平衡二叉树、红黑树等。

树还有更多内容,这里入门级就不介绍了,后面再详细介绍吧。

堆是二叉树的一个特例,总是一颗完全二叉树,其特点是某个节点的值总是不大于或不小于其父节点的值。

堆的具体实现一般不通过指针域,而是通过构建一个一维数组与二叉树的父子结点进行对应。

堆常用来实现优先队列,在面试中经常考的问题都是与排序有关,比如堆排序、topK问题等。

由于堆的根节点是序列中最大或者最小值,因而可以在建堆以及重建堆的过程中,筛选出数据序列中的极值,从而达到排序或者挑选topK值的目的。

在实际的应用场景中却经常出现,比方说交通中的线路图。

图结构一般包括顶点和边,顶点通常用圆圈来表示,边就是这些圆圈之间的连线。

边还可以根据顶点之间的关系设置不同的权重,默认权重相同皆为1。

此外根据边的方向性,还可将图分为有向图和无向图。

具体的代码实现中,为了将各个顶点和边的关系存储下来,却不是一件易事。

邻接矩阵

目前常用的图存储方式为邻接矩阵,通过所有顶点的二维矩阵来存储两个顶点之间是否相连,或者存储两顶点间的边权重。

在对矩阵进行存储时,需要完整的一个二维数组。

若图中顶点数过多,会导致二维数组的大小剧增,从而占用大量的内存空间。

图还有其他的存储方法,有点复杂,回头再单独研究吧。

好了,今天带大家温习一下常见的数据机构,有个初步的认识,后续再根据情况带来深入的探究。

本次的分享到此结束,希望对你有所帮助。

如果你对我分享的内容感兴趣,欢迎扫码关注公众号:新质程序猿,并设置星标,推送更实时哟!

本文由 黄彦祥 创作,采用 知识共享署名 3.0 中国大陆许可协议 进行许可。
可自由转载、引用,但需署名作者且注明文章出处。