基础数据结构和算法概念

本文涉及更多的是概念,代码部分请参考以前写过的 2 篇博客前端

排序算法 基于Javascript
基本数据结构和查找算法web

本文主要是基础的数据结构和算法概念,可能部分地方会涉及更高级的算法和算法,具体内容之后会单独写的。此外一些性质还会不断补充,也但愿能够获得您的指点,谢谢。算法

数据结构

程序 = 数据结构 + 算法数组

数据结构基本概念

  1. 数据的逻辑结构:反映数据元素之间的关系的数据元素集合的表示。数据的逻辑结构包括集合、线形结构、树形结构和图形结构四种。
  2. 数据的存储结构:数据的逻辑结构在计算机存储空间种的存放形式称为数据的存储结构。经常使用的存储结构有顺序、连接、索引等存储结构。

在数据结构中,没有前件的结点称为根结点,没有后件的结点成为终端结点markdown

数据结构的基本操做数据结构

插入和删除是对数据结构的两种基本操做。此外还有查找、分类、合并、分解、复制和修改等。数据结构和算法

线性结构和非线性结构svg

根据数据结构中各数据元素之间先后件关系的复杂程度,通常将数据结构分为两大类型:线性结构和非线性结构。函数

  • 线性结构:有且只有一个根结点;每一个结点最多有一个前件,最多只有一个后件。
  • 非线性结构: 若是一个数据结构不是线性结构,称之为非线性结构。

本文涉及一下内容:atom

  • 四种线性结构的存储结构:顺序表、链表、索引、散列
  • 两种常见的线性逻辑结构:队列、栈
  • 非线性逻辑结构:循环队列、双向队列、双向循环队列、树、图

存储结构

顺序表

顺序表是线性表的顺序存储结构,指的是用一组地址连续的存储单元依次存储线性表的数据元素。
顺序表具有以下两个基本特征:

  1. 顺序表中的全部元素所占的存储空间是连续的;
  2. 顺序表中各数据元素在存储空间中是按逻辑顺序依次存放的。

假设顺序表的每一个元素需占用 K 个存储单元,并以所占的第一个单元的存储地址做为数据元素的存储位置。则顺序表中第 i + 1 个数据元素的存储位置 L O C ( a i + 1 ) 和第 i 个数据元素的存储位置 L O C ( a i ) 之间知足下列关系为:

L O C ( a i + 1 ) = L O C ( a i ) + K

L O C ( a i ) = L O C ( a 1 ) + ( i 1 ) K

其中, L O C ( a 1 ) 是顺序表的第一个数据元素 a 1 的存储位置,一般称作顺序表的起始位置或基地址。顺序存储结构也称随机存取结构。

顺序表常见操做(括号中为算法平均时间复杂度,没有写明的具体复杂度依赖不一样算法和运算规则):

插入( O ( n ) )、删除( O ( n ) )、查找、排序、分解、合并、复制( O ( n ) )、逆转( O ( n ) )

链表

链表指线性表的链式存储结构。一组任意的存储单元存储线性表的数据元素,所以,为了表示每一个数据元素 a i 与其直接后继数据元素 a i + 1 之间的逻辑关系,对数据元素 a i 来讲,除了存储其自己的信息(数据域)以外,还需存储一个变量指示其直接后继的信息(指针域)。这两部分信息组成数据元素 a i 的存储映象,称为结点。 N 个结点链结成一个链表。该链表就是传统的单向链表。

有时,咱们在单链表的第一个结点以前附设一个结点,称之为头结点,它指向表中第一个结点。头结点的数据域可 以不存储任何信息,也可存储如线性表的长度等类的附加信息,头结点的指针域存储指向第一个结点的指针。在单链表中,取得第 I 个数据元素必须从头指针出发寻找,所以,链表是非随机存取的存储结构。

以上提到的链表指针域只包括一个指针,指向下一个数据的地址,若是咱们将链表最后一个结点指针域的指针指向链表的头结点地址,就构成了一个环状的存储结构,咱们称做循环链表。

固然咱们能够给每一个结点的指针域再添加一个指针,使其指向前一个数据结点的地址,这样就构成了双向链表,而将头结点的前一个结点指向尾结点,同时将尾结点的下一个结点指向头结点就构成了双向循环链表。

若是链表的尾结点的指针域指向了该链表以前的任意一个结点,咱们称该链表为有环链表。环形链表就是其中一个特例

顺序表常见操做(括号中为算法平均时间复杂度,没有写明的具体复杂度依赖不一样算法和运算规则):

插入( O ( n ) )、删除( O ( n ) )、查找、排序、分解、合并、复制( O ( n ) )、逆转( O ( n ) )

索引

索引存储除创建存储结点信息外,还创建附加的索引表来标识结点的地址。索引表由若干索引项组成。

对于索引的理解最好的例子就是《新华字典》,它创建的2套索引表(拼音、部首)。字典的正文就是从“啊”到“作”的每一个字的解释,有上千页,就是是数据。而前面的拼音/部首就是索引表,索引表告诉你某个读音/部首在第几页,这就比如是指向数据地址的指针。而索引表能够有一级的也能够是多级的,好比字典中的部首索引就是两级的。

索引存储结构是用结点的索引号来肯定结点存储地址,其优势是检索速度快,缺点是增长了附加的索引表,会占用较多的存储空间。

散列

散列存储,又称哈希(hash)存储,是一种力图将数据元素的存储位置(预留连续存储区域)与关键码之间创建肯定对应关系的查找技术。散列法存储的基本思想是由结点的关键码值决定结点的存储地址。散列技术除了能够用于存储外,还能够用于查找。

散列以数据中每一个元素的关键字 K 为自变量,经过散列函数 H ( k ) 计算出函数值,以该函数值做为一块连续存储空间的的单元地址,将该元素存储到函数值对应的单元中。因为该函数值惟一,因此查找时间复杂度为 O ( 1 )

线性逻辑结构

线性表

线性表知足如下特征:

  1. 有且只有一个根结点 a 1 ,它无前件;
  2. 有且只有一个终端结点 a n ,它无后件;
  3. 除根结点与终端结点外,其余全部结点有且只有一个前件,也有且只有一个后件。线性表中结点的个数 n 称 为线性表的长度。当 n = 0 时称为空表。

栈实际上也是一个线性表,只不过是一种特殊的线性表。栈是只能在表的一端进行插入和删除运算的线性表,一般称插入、删除这一端为栈顶(TOP),另外一端为栈底(BOTTOM)。当表中没有元素时称为栈空。 栈顶元素老是后被插入(入栈)的元素,从而也是最早被移除(出栈)的元素;栈底元素老是最早被插入的元素,从而也是最后才能被移除的元素。因此栈是个 后进先出(LIFO) 的数据结构

栈的基本运算有三种:入栈、出栈与读栈顶,时间复杂度都是 O ( 1 )

队列

队列是只容许在一端删除,在另外一端插入的顺序表,容许删除的一端叫作队头,用对头指针 f r o n t 指向对头元素的下一个元素,容许插入的一端叫作队尾,用队尾指针 r e a r 指向队列中的队尾元素,所以,从排头指针 f r o n t 指向的下一个位置直到队尾指针 r e a r 指向的位置之间全部的元素均为队列中的元素。

队列的修改是 先进先出(FIFO) 。往队尾插入一个元素称为入队运算。从对头删除一个元素称为退队运算。

队列主要有两种基本运算:入队运算和退队运算,复杂度都是 O ( 1 )

循环队列

在实际应用中,队列的顺序存储结构通常采用循环队列的形式。所谓循环队列,就是将队列存储空间的最后一个 位置绕到第一个位置,造成逻辑上的环状空间。在实际使用循环队列时,为了能区分队满仍是队列空,一般须要增长一个标志 S

循环队列主要有两种基本运算:入队运算和退队运算,复杂度都是 O ( 1 )

  • 入队运算
    指在循环队列的队尾加入一个新元素,首先 r e a r = r e a r + 1 , 当 r e a r = m + 1 时,置 r e a r = 1 ,而后将新元素插入到队尾指针 指向的位置。当 S = 1 , r e a r = f r o n t ,说明队列已满,不能进行入队运算,称为“上溢”。
  • 退队运算
    指在循环队列的排头位置退出一个元素并赋给指定的变量。首先 f r o n t = f r o n t + 1 , 并当 f r o n t = m + 1 时,置 f r o n t = 1 , 而后 将排头指针指向的元素赋给指定的变量。当循环队列为空 S = 0 ,不能进行退队运算,这种状况成为“下溢”。

非线性逻辑结构

树是一种简单的非线性结构。树型结构具备如下特色:

  1. 每一个结点只有一个前件,称为父结点,没有前件的结点只有一个,称为树的根结点。
  2. 每个结点能够有多个后件结点,称为该结点的子结点。没有后件的结点称为叶子结点
  3. 一个结点所拥有的后件个数称为结点的度
  4. 树的最大层次称为树的深度。

二叉树

二叉树是一种树型结构,一般采用链式存储结构,知足如下特性:

  1. 它的特色是每一个结点至多只有二棵子树(即二叉树中不存在度大于 2 的结点);
  2. 二叉树的子树有左右之分,其次序不能任意颠倒。

二叉树的基本性质

  • 在二叉树的第 i 层上至多有 2 i 1 个结点
  • 深度为 k 的二叉树至多有 2 k 1 个结点( k 1 )
  • 在任意一个二叉树中,度为 0 的结点老是比度为 2 的结点多一个
  • 具备 N 个结点的二叉树,其深度至少为 l o g 2 N + 1
  • 霍夫曼树的带权路径长度 l e n = 2 n + 1 ; n 为因此叶子权重和。

二叉树的遍历

就是听从某种次序,访问二叉树中的全部结点,使得每一个结点仅被访问一次。分为如下几种:

  1. 前序遍历(DLR): 首先访问根结点,而后遍历左子树,最后遍历右子树。
  2. 中序遍历(LDR): 首先遍历左子树,而后根结点,最后右子树
  3. 后序遍历(LRD): 首先遍历左子树,而后遍历右子树,最后访问根结点。

此外图的遍历也能够用在树上,包括:

  1. 广度优先遍历(层序遍历): 从根结点开开始逐层向下,从左到右遍历。
  2. 深度优先遍历: 从根结点出发沿左子树遍历到叶子结点再逐层向上向遍历右子树。

除此以外还有不少有特色的特殊二叉树:

  • 满二叉树:除最后一层之外,每一层上的全部结点都有两个子结点。
    1. 在满二叉树的第 K 层上有 2 K 1 个结点,且深度为 M 的满二叉树有 2 M 1 个结点
    2. 彻底二叉树:除最后一层之外,每一层上的结点数均达到最大值;在最后一层上只缺乏右边的若干结点。
    3. 具备 N 个结点的彻底二叉树的深度为 l o g 2 N + 1
    4. 彻底二叉树总结点数为 N ,则叶子结点数为 N / 2

最多见的彻底二叉树就是 了。堆知足如下条件

  • 堆中某个结点的值老是不大于或不小于其父结点的值
  • 堆老是一棵彻底二叉树

将根结点最大的堆叫作 最大堆大根堆 ,根结点最小的堆叫作 最小堆小根堆

堆具备如下基本操做:

  • 插入: 向堆中插入一个新元素
  • 获取: 获取当前堆顶元素的值
  • 删除: 删除堆顶元素
  • 包含: 判断堆中是否存在某个元素

哈希表

经常使用的哈希函数

  1. 直接寻址法: H ( k ) 是一个线性函数,若是该位置已经有值就向下寻找到第一个空的地方做为散列地址
  2. 平方取中法: 取 k 平方之后值的中间几位做为散列地址
  3. 数字分析法: 对于比较规律的 k 值,找出其差别较大的部分做为散列地址
  4. 折叠法: 将 k 分红不少部分,而后作模二和做为散列地址
  5. 留余数法: H ( k ) = k ,其中 p 为素数, m 为表的长度
  6. 随机数法: 取键字的随机值做为散列地址,关键字长度时使用

实现映射的函数是哈希函数,简单的 hash 可能会发生碰撞(不一样输入获得相同输出),为了防止碰撞,考虑如下方法:

  • 链地址法(拉链法): 当发生碰撞时,将发生碰撞的数据元素链接到同一个单链表中,而新元素插入到链表的前端
  • 线性探针法: 线性探针法地址增量 d D 1 = { 1 , 2 , 3 , . . . , m 1 } , i 为探测次数, m 为表的长度,该方法遇到冲突地址会依次探测下一个地址( d = d i + 1 )直到有空的地址,若找不到空地址,则溢出。

平均查找长度

  • 线性探针法平均长度推算(其中 m 为表中数据长度, n 为表长度):


    A S L s = i = 1 m d i m ,查找成功

    A S L u = i = 1 n d i n ,查找不成功


注:线性探针法查找成功时 d i 为每次放入元素时的地址增量,不成功时 d i 为在表长度内依次查找每一个元素到下一个空地址的地址增量(索引在表长度内循环)
  • 链地址法平均长度推算(其中 k 为最长链长度,其中 m 为表中数据长度, n 为表长度):


    A S L s = i = 1 k ( × ) m ,查找成功

    A S L u = i = 1 n n ,查找不成功


哈希表相关特性
  • 线性探针法容易“汇集”,影响查找效率,而链地址法不会
  • 链地址法适应表长不肯定状况
  • ( α ) =

图有两种定义:

  • 二元组的定义:图 G 是一个有序二元组 ( V , E ) ,其中 V 称为顶集(Vertices Set), E 称为边集(Edges set), E V 不相交。它们亦可写成 V ( G ) E ( G ) E 的元素都是二元组,用 ( x , y ) 表示,其中 x , y V
  • 三元组的定义: 图 G 是指一个三元组 ( V , E , I ) ,其中 V 称为顶集, E 称为边集, E V 不相交; I 称为关联函数, I E 中的每个元素映射到 V × V 。若是 e 被映射到 ( u , v ) ,那么称边 e 链接顶点 u , v ,而 u , v 则称做 e 的端点, u , v 此时关于 e 相邻。同时,若两条边 i , j 有一个公共顶点 u ,则称 i , j 关于 u 相邻。

图的分类

图有不一样的分类规则,具体以下:

分类1
- 有向图: 若是图中顶点之间关系不只仅是连通与不连通,并且区分两边的顶点的出入(存在出边和入边),则为有向图。
- 无向图: 若是图中顶点之间关系仅仅是连通与不连通,而不区分两边顶点的出入(不存在出边和入边),则为无向图。
单图

分类2
- 有环图: 单向遍历回能够到已遍历的点,好比有环链表
- 无环图: 单向遍历不能回到已遍历的点,好比树

分类3
- 带权图: 图的具备边带有关于该边信息的权值,好比地图中两点间距离
- 无权图: 图的每一个边都不具备有关于该边信息的权值,其仅表示是否连通

其余
- 单图: 一个图若是任意两顶点之间只有一条边且边集中不含环,则称为单图

图的表示采用邻接矩阵和相似树的形式(顶点指针域是个指针数组)的形式,其具备如下特色:

  • 无向图的邻接矩阵是对称矩阵
  • 带权图的矩阵中元素为全职,无权图中用0/1分别表示不连通/连通
  • 主对角线有不为零元素,该图必定有环

图的遍历

  • 广度优先遍历: 广度优先遍历是连通图的一种遍历策略。由于它的思想是从一个顶点 V 0 开始,辐射状地优先遍历其周围较广的区域。
  • 深度优先遍历:

图的相关性质:

  • N 个顶点的连通图中边的条数至少为 N 1
  • N 个顶点的强连通图的边数至少有 N
  • 广度优先遍历用来找无权图最短路径(有权图其实也行,多点东西呗)

简单数据结构的增删改查

操做 添加 删除 查找 使用条件
数组 O ( n ) O ( n ) O ( n ) 数定下标
链表 O ( 1 ) O ( n ) O ( n ) 两端修改
变长数组 O ( 1 ) O ( n ) O ( n ) 数不定下标
O ( 1 ) O ( 1 ) - LIFO
队列 O ( 1 ) O ( 1 ) - FIFO
哈希表 O ( 1 ) O ( 1 ) O ( 1 ) key操做,无序
树字典 O ( l o g 2 n ) O ( l o g 2 n ) O ( l o g 2 n ) key操做,有序
哈希集合 O ( 1 ) O ( 1 ) O ( 1 ) 惟一值,无序
树集合 O ( l o g 2 n ) O ( l o g 2 n ) O ( l o g 2 n ) 惟一值,有序

算法

算法基本概念

  1. 算法的基本特征:可行性,肯定性,有穷性
  2. 算法的基本要素:算法中对数据的运算和操做、算法的控制结构。
  3. 算法设计的基本方法:穷举法、动态规划、贪心法、回溯法、递推法、递归法、分治法、散列法,分支限界法。
  4. 算法设计的要求:正确性、可读性、健壮性、效率与低存储量需求
  5. 算法的基本结构:顺序、循环、选择

算法复杂度

  1. 算法的时间复杂度:指执行算法所须要的计算工做量(不表明算法实际须要时间)
  2. 算法的空间复杂度:执行这个算法所须要的额外内存空间(表明算法实际须要的空间)

复杂度表示方法: 使用大写 O 表示: O ( n ) 表示时间复杂度时指 n 个数据处理完成使用 n 个单位的时间;表示空间复杂度时指 n 个数据处理完成使用了 n 个单位的辅助空间。

字符串算法

字符串算法除了增删改查之外,还有不少匹配算法,好比最耳熟能详的 KMP 算法(不属于基础部分),这里整理一些相关算法的性质:

  • 一个长为 n 的字符串有 n ( n + 1 ) / 2 + 1 个子串
  • n的字符串,其中的字符各不相同,则S中的互异的非平凡子串有 1 2 n 2 + 1 2 n 1

排序算法

排序算法实际上能够分为内排序和外排序:

  • 内排序:在排序过程当中,全部元素调到内存中进行的排序,称为内排序。内排序是排序的基础。内排序效率用比较次数来衡量。按所用策略不一样,内排序又可分为插入排序、选择排序、堆排序、归并排序、冒泡排序、快速排序、希尔排序及基数排序等等。
  • 外排序:在数据量大的状况下,只能分块排序,但块与块间不能保证有序。外排序用读/写外存的次数来衡量其效率。

排序算法时间复杂度

排序算法分为如下几类:

  1. 插入类排序:插入排序、希尔排序
  2. 交换类排序:冒泡排序、快速排序
  3. 选择类排序:选择排序、堆排序
  4. 归并排序
  5. 基数排序
算法 时间复杂度(最好) 时间复杂度(最好) 时间复杂度(最坏) 空间复杂度 稳定性
插入排序 O ( n 2 ) O ( n ) O ( n 2 ) O ( 1 ) 稳定
希尔排序 O ( n 1.3 ) O ( n ) O ( n 2 ) O ( 1 ) 不稳定
选择排序 O ( n 2 ) O ( n 2 ) O ( n 2 ) O ( 1 ) 不稳定
堆排序 O ( n l o g 2 n ) O ( n l o g 2 n ) O ( n l o g 2 n ) O ( 1 ) 不稳定
冒泡排序 O ( n 2 ) O ( n ) O ( n 2 ) O ( 1 ) 稳定
快速排序 O ( n l o g 2 n ) O ( n l o g 2 n ) O ( n 2 ) O ( n l o g 2 n ) 不稳定
归并排序 O ( n l o g 2 n ) O ( n l o g 2 n ) O ( n l o g 2 n ) O ( n ) 稳定
基数排序 O ( d ( r + n ) ) O ( d ( n + r d ) ) O ( d ( r + n ) ) O ( n + r d ) 稳定

注:
1. 基数排序的复杂度中, r 表明关键字基数, d 表明长度, n 表明关键字个数
2. 排序算法的稳定性指在原序列中, r i = r j ,且 r i r j 以前,而在排序后的序列中, r i 仍在 r j 以前,则称这种排序算法是稳定的;不然称为不稳定的。

查找算法

查找算法时间复杂度

算法 查找(最坏) 插入(最坏) 删除(最坏) 查找(最好) 插入(最好) 删除(最好) 是否要求有序
顺序结构 N N N N 2 N N 2 No
二分算法 logN N N logN N 2 N 2 Yes
二叉查找树(BST) N N N 1.39logN 1.39logN N Yes
2-3树 clogN clogN clogN clogN clogN clogN Yes
红黑树 2logN 2logN 2logN logN logN logN Yes
哈希散列查找 logN logN logN 3~5 3~5 3~5 No
哈希探针查找 logN logN logN 3~5 3~5 3~5 No

平均查找长度(ASL) = 查找表中第 i 个元素几率( P i ) × 找到第 i 个元素时已经比较的次数( C i )

相关文章
相关标签/搜索