目录
数据结构大家应该都接触过,对于一个成熟的程序员而言,熟悉和掌握数据结构和算法也是基本功之一。数据结构本身其实不过是数据按照一定的关系进行存储的集合,特殊的结构在不同的应用场景会带来不一样的处理效率。
常用的数据结构可根据数据访问的特点分为线性结构和非线性结构。线性结构包括常见的链表、栈、队列等,非线性结构包括树、图等。数据结构种类繁多,本文将通过图解的方式对常用的数据结构进行理论上的介绍和讲解,以便大家掌握常用的数据结构的基本知识。
1-数组
数组可以说是最基本最常见的数据结构。数组一般用来存储相同类型的数据,使用数组名和下角标经i选哪个数组的访问和更新。数组中的元素是按照先后顺序进行排列的,当然了,在内存中也是如此。数组中两个相邻的元素的内存地址之差就是数组数据类型的大小。
2-链表
链表和数组相比,除了数据域,还多增加了指针域,作用是构建链式结构来存储数据。链表中每一个节点都包括这个节点的数据文件和指向下一个节点地址的指针。由于是通过指针查找和访问数据,这就让链表的自由度很高。
在对节点进行增加和删除时,只需要对上一个节点的指针地址进行修改,不需要修改其他节点。但是,这也是把双刃剑,指针带来高自由度,也会牺牲数据查询的效率和多余空间的使用。
最常见的是有头有尾的单链表,还有双向链表或者循环链表。
链表和数组对比
链表和数组在实际使用的过程中需要根据自身的优劣势进行选择。链表和数组的异同点也是面试中高频的考察点之一。
这里对单链表和数组的区别进行了对比和总结。
数组 | 链表 | |
内存长度 | 连续的内存空间 | 非连续的内存空间 |
数据长度 | 长度固定,一般不可动态扩展 | 长度可动态变化 |
增删效率 | 低,需要移动被修改元素之后的所有元素 | 高,只需要修改指针指向 |
查询效率 | 高,可通过数组名和下标直接访问,时间复杂度为O(1) | 低,只能通过遍历节点依次查询,时间复杂度O(n) |
数据访问方式 | 随机访问 | 顺序访问 |
3-跳表
从上面的对比中可以看出,链表虽然通过增加指针域提升了自由度,但是却导致数据的查询效率恶化。特别是当链表长度很长的时候,对数据的查询还得是从头依次查询,这样的效率会很低。跳表产生就是为了解决链表过长的问题,通过增加链表的多级索引来加快原始链表的查询效率。这样的方式可以让查询的时间复杂度从O(n)提升到O(logn)。
跳表通过增加多级索引的方式能够实现高效的动态插入和删除,其效率和红黑树、平衡二叉树不相上下。目前redis和levelDB都有用到跳表。
从上图中可以看出,索引级的指针域除了指向下一个索引位置的指针,还有一个down指针指向低一级的链表位置,这样才能实现条约查询的目的。
4-栈
栈是一种比较简单的数据结构,常用一句话描述其特性,后进先出。栈本身是一个线性表,但是在这个表只有一个口子允许数据的进出。这种模式可以参考腔肠动物...即进食和排泄都用一个口...
跑题了...
栈的常用操作包括入栈push和出栈pop,对应数据的压入和压出。还有访问栈顶数据、判断栈是否为空和判断栈的大小等。由于栈先进后出的特性,常可以作为数据操作的临时容器,对数据的顺序进行调控,与其他数据结构相结合可获得许多灵活的处理。
5-队列
队列是栈的兄弟结构,与栈的后进先出相对应,队列是一种先进先出的数据结构。顾名思义,队列的数据存储是如同排队一般,先存入的数据先被压出。常与栈一同配合,可发挥最大的实力。
6-树
树作为一种树状的数据结构,其数据节点之间的关系也如大树一样,将有限个节点根据不同层次关系进行排列,从而形成数据与数据之间的父子关系。常见的数的表示形式更接近“倒挂的树”,因为它将根朝上,叶朝下。
树的数据存储在结点中,每个结点有零个或者多个子结点。没有父结点的结点在最顶端,成为根节点;没有非根结点有且只有一个父节点;每个非根节点又可以分为多个不相交的子树。
这意味着树是具备层次关系的,父子关系清晰,家庭血缘关系明朗;这也是树与图之间最主要的区别。
别看树好像很高级,其实可看作是链表的高配版。树的实现就是对链表的指针域进行了扩充,增加了多个地址指向子结点。同时将“链表”竖起来,从而凸显了结点之间的层次关系,更便于分析和理解。
树可以衍生出许多的结构,若将指针域设置为双指针,那么即可形成最常见的二叉树,即每个结点最多有两个子树的树结构。二叉树根据结点的排列和数量还可进一度划分为完全二叉树、满二叉树、平衡二叉树、红黑树等。
完全二叉树:除了最后一层结点,其它层的结点数都达到了最大值;同时最后一层的结点都是按照从左到右依次排布。
满二叉树:除了最后一层,其它层的结点都有两个子结点。
平衡二叉树:
平衡二叉树又被称为AVL树,它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。