浅谈常用数据结构

计算机科学家尼克劳斯.沃思提出“数据结构+算法=程序”,不同的数据结构所采用的处理方式不同,导致计算的复杂程度也会杂然不同。而算法往往会依赖某些数据结构这说明数据结构对于算法的重要性。了解并掌握常用的数据结构及算法往往是一个合格的开发人员必备的技能之一,接下来笔者将在该章中梳理出开发中或多或少会遇到的数据结构及算法的大致概括。

常用数据结构

  数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。也就是说数据结构规定了所持由的数据应该以什么样的形式存在又或是已经以什么样的行为对数据进行处理。

数组(Array)

  把具有相同数据类型的若干变量按有序的形式组织起来,以便于程序处理,这些数据元素的集合就是数组即将若干相同类型的数据按照连续的物理空间位置编排组织在一起的集合称之为数组。
  因为各个元素的物理位置固定通过下标进行数据检索速度快,但是缺点也是显而易见,由于数组长度是由已知长度进行固化,无法对其进行扩容,除非重新创建数组。

顺序表(Sequential List)

  顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构。 从顺序表的定义来看,它和数组确实有很大的联系。首先,它是以数组的形式来保存的,其次,它和数组一样都是存储的元素物理地址相邻,具有很大的相似性。顺序表能够对元素的加入,移除顺序确定数据逻辑上的顺序,而数组只能够根据下标值确定数据的落地位置。
  在JDK中提供的ArrayList即是对顺序表的具体实现,其具有以下的特点。

  • 要想插入或移除一个元素往往需要移动大量的数据即增删效率低下。
  • 根据下标进行索引由于物理位置上固定可以直接取出即查询效率高。
  • 由于底层是不可变的数组做数据存储,当数据个数超出数组长度需要进行扩容即创建出新的数组然后做数据复制,扩容效率低下。
  • 如果表比较大时,有时比较难分配到足够的连续存储空间,往往会导致内存分配失败。
    顺序表图解

链表(Linked List)

  链表结构是一种动态存储分配的结构形式,可以根据需要动态申请所需的内存单位。典型的链表结构每个结点都应该包含 1.数据信息即保存该节点的实际数据 2.地址信息即保存下一个节点的地址信息。链表就是由若干个节点进行组合,并且每个节点通过自身所记录的下个节点信息获取下个节点的引用,第一个节点指向第二个节点而第二节点指向第三节点直到最后一个节点。链表的好处在于不在需要为节点初始化固定长度的物理内存,在需要的时候为最后一个节点更新地址信息使其指向新增节点。对于顺序表而言每个节点还需要额外保存一个引用变量即下个节点的地址信息。链表结构还可以细分为多类比如单链表、双向链表、单向环链表、多向环链表。
  在JDK中提供的LinkedList即使对链表的具体实现,其具有以下的特点。

  • 不必再为数据集初始化固定长度的内存空间,可做到按需申请内存空间,即来多少元素就申请多少内存空间。
  • 对于链表的访问由于不像顺序表那样有固定的顺序连续地址,即需要从表头出发查找直到找到需要的节点位置,即查询效率低下。
  • 对于数据的数据的插入即移除只需要更新节点的位置信息即增删效率高。
            链表图解

栈结构(Stack)

  栈是一种线性结构但是其具有特殊的运算规则即只能够在栈的一端进行数据操作,该端称之为栈顶而另一端则称之为栈底。从数据运算的角度来看,栈结构是按照“后进先出”(LIFO)的原则处理节点数据的。在栈结构中,只有栈顶才能够被访问,栈的基本操作分为入栈(将数据保存到栈顶的操作)与出栈(将栈顶的数据弹出的操作)。从数据的存储角度栈又可分为顺序栈结构、链表栈结构。栈通用用于终端或者子程序调用过程。将重要的变量压入栈后进入子程序可以做到对外部数据的保护,等到子程序执行完毕弹出栈还原父程序流程及变量,可见在计算机程序设计中栈起到了不可或缺的角色。
          栈结构图解

队列结构(Queue)

  栈是一种线性结构但是其具有特殊的运算规则即允许在两端进行操作,但是两端的操作行为不同。在表的一端只能进行删除操作,该端称之为队头,而另一端只允许进行插入操作,该端称之为队尾。从数据运算的角度来看,队列结构是按照“前进后出”(FIFO)的原则处理节点数据的。一般队列结构的基本操作只有两个即入队(将元素添加到队尾)与出队(将队头元素取出,使下个元素成为队头)。从数据的存储角度来看队列又可分为顺序队列结构和链表队列结构。
          队列结构图解

树结构(Tree)

  数结构是一种非线性层次关系的数据结构,树是由N个节点组成一个集合,该集合中只有一个根节点,根节点下包含着多个子集合。现实生活中也是到处有树结构的踪影,例如,一个国家的行政机构,一个家族的家谱,又或者是电脑里的文件夹目录结构。树的基本特征如下

  • 在一个树结构中,有且仅有一个节点没有前驱节点,这个节点就是树的根节点。
  • 除根节点外,其他节点有且仅有一个前驱节点。
  • 每个节点可以有任意个后驱节点。

  在树结构中可以根据节点中最大持有子节点个数划分出多叉树及二叉树。
  在树结构中,节点可以拥有不止两个子节点的能力的树可以称为多叉树,多叉树代表一般有字典树、B树、B+树等。
  在树结构中,二叉树是最简单的一种形式。它是n个结点的集合,每个结点最多只能有两个子结点。二叉树的子树仍然是二叉树。二叉树的一个结点上对应的两个子树分别称为左子树和右子树。由于子树有左右之分,因此二叉树是有序树。更重要的是任意数都可以通过(将节点的孩子放在左子树,将节点的兄弟放在右子树)原则转换为对应的二叉树。
  对于二叉树可以进一步细分出几种特殊的类型,满二叉树和完全二叉树及平衡二叉树。

  • 满二叉树(二叉树中除最下一层的叶结点外,每层的结点都有两个子节点)
  • 完全二叉树(二叉树中除二叉树最后一层外,其他各层的结点数都达到最大个数,且最后一层叶结点按照从左向右的顺序连续存在,只缺最后一层右侧若干结点)
  • 平衡二叉树( 平衡二叉树又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等)

** 以下配图分别是 多叉树、满二叉树、完全二叉树、平衡二叉树!**
多叉树满二叉树完全二叉树
平衡树
  满二叉树一定是完全二叉树,而完全二叉树不一定是满二叉树,因为其没有达到完全满分支的结构。对于平衡二叉树要特别注意的是,不要求非叶节点都有两个子结点,仅要求两个子树的高度差的绝对值不超过1,或者为空树。
  按照数据的存储方式,树结构可以分为顺序存储树及链表存储树。但在实际中更多的是用链表来表示二叉树的。

图结构(Graph)

  图结构是一种非线性的数据结构,在现实生活中经常有图结构实际体现,比如,人际关系网络,交通枢纽网络等。图结构中各个节点的关系错综复杂因此需要对存储与遍历操作具有更高的要求。以下介绍图的几个基本概念。

  • 无向图(如果在图结构中,所有的边都是不具有方向性质的边,那么该图结构称之为无向图)
  • 有向图(如果在图结构中,边是具有方向性质的边,那么该图结构称之为有向图)
  • 顶点的度(该结点用于连接其他节点的边的个数,也称之为该顶点的度)
  • 邻接顶点(由同一条边两端对应的节点该两个节点称之为邻接顶点)
  • 无向完全图(在一个无向图中,每两个节点之间都存在一条边,那么该图结构称之为无向完全图)
  • 有向完全图(在一个有向图中,每两个节点之间都存在方向相反的两条边,那么该图结构称之为有向完全图)
  • 子图(类似子集合)
  • 路径(两个节点所需要经过的边称之为路径)
    • 简单路径(如果一条路径上的节点不重复出现,那么该路径称之为简单路径)
    • 环(如果路径中第一个节点与最后一个节点重复则称为环有时也叫做回路)
    • 简单回路(如果路径中除了第一个节点与最后一个节点重复,其余各个节点都不重复那么称为简单回路)
  • 权(边带有权重值则成为该边的权)

无向图有向图
  在Google Guava中提供了Graph模块,内置了对图进行节点及边的操作,如果在实际工作中遇到类似图结构的数据处理问题,不妨引入guava进行图计算。

堆结构

  堆的特性:父节点的值一定不大于或不小于子节点的值(堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权)。堆被认为在计算机算法中起到重要作用。堆并不一定是完全二叉树,平时使用完全二叉树的原因是易于存储,并且便于索引。堆的实现有二叉堆、二项堆、斐波那契堆、左偏树等。
  二叉堆可以看做一棵树的数组对象,具有以下的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或最小堆最大堆与最小的

散列表(Hash)

  散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。哈希表的原理非常简单,通过一个固定的算法将Key转为一个数字,然后将该数字对数组长度取余,将取余结果作为数组下标,然后将Value存储在该数字为下标的数组里。考虑到对于多个Key可能计算出的Hash值可能会一致而导致重复性,需要解决Hash冲突的问题。解决Hash冲突的主要措施有开放定址法、链地址法、再哈希、建立公共溢出区这里不做过多的介绍。
  存储位置=Hash(key)
  在JDK中HashMap,HashTable就是参照散列表原理进行实现的,通过Hash算法计算出key位于数组中对应位置,并且在数组每个位置采用链表(在JDK1.8后对于链表过长会转换至红黑树降低链表高度提高检索能力)即拉链发来解决哈希冲突的问题。当数组中元素达到阈值将会到数组进行重新创建数组以便扩容。
哈希表

参考《JAVA常用算法手册》

原文地址:https://www.cnblogs.com/cjunn/p/12143544.html