数据结构

线性表

1、数组  array

数组中,所有的数据元素存储在一片连续的区域内。对数组的访问方式一般是通过下标,数组元素的直接访问几乎没有开销,但是在插入和删除需要移动数组,操作比较频繁的场合,不适合使用数组。

2、 栈(stack):先进后出

栈(stack)是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶(top)。它是后进先出(LIFO)的。对栈的基本操作只有 push(进栈)和 pop(出栈)两种,前者相当于插入,后者相当于删除最后的元素。

3、队列(queue):先进先出


队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的
后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为
队尾,进行删除操作的端称为队头。

4、链表(Link  list)

       在线性表的长度不确定的场合,一般使用链表。

  链表结构的每个节点数据都由两个域组成,一个存放实际数据元素的数据域,另一个就是构成链式结构的指针域。有单链表、双向链表、循环链表等。链表在进行循环遍历时效率不高,但是插入和删除时优势明显。理论上链表的长度是不受限制的,实际使用链表时,常受存储器空间的限制,使得链表长度也不能无限增长。

比如,Java 中我们使用的 ArrayList,其实现原理是数组。而LinkedList 的实现原理就是链表了。

 复杂数据结构

      不仅是线性表,并且每个数据元素之间也可能存在关系,相关的插入、删除操作不仅对数据元素进行操作,同时还要维护元素之间的关系。

1、散列表(Hash Table)与映射(map)


  散列表(Hash table,也叫哈希表)。

  二者都是通过关键字(key)直接访问数据元素的值(value)的数据结构,二者的外部接口是一样的


  散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素,因而必须要在数据元素的存储位置和它的关键字(可用 key 表示)之间建立一个确定的对应关系,使每个关键字和散列表中一个唯一的存储位置相对应。因此在查找时,只要根据这个对应关系找到给定关键字在散列表中的位置即可。这种对应关系被称为散列函数(可用 h(key)表示)。

  哈希表的原理是通过一个哈希函数对关键字进行某种运算,得到对应的数据元素在表中的存储位置,然后访问其值,与普通的有序表查找相比,额外的哈希处理会造成数据访问的开销,但是哈希表的查找时间是固定的,不随哈希表中数据元素的增多而变化。普通有序表的查找时间复杂度是O(lg(n)),随着n的增大,查找时间也变长,当数据元素非常多的时候,哈希表的查找速度会比普通有序表快,这是哈希表的优势。 


用的构造散列函数的方法有:
(1)直接定址法: 取关键字或关键字的某个线性函数值为散列地址。
    即:h(key) = key 或 h(key) = a * key + b,其中 a 和 b 为常数。
(2)数字分析法
(3)平方取值法: 取关键字平方后的中间几位为散列地址。
(4)折叠法:将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址。
(5)除留余数法:取关键字被某个不大于散列表表长 m 的数 p 除后所得的余数为散列地址,
    即:h(key) = key MOD p p ≤ m
(6)随机数法:选择一个随机函数,取关键字的随机函数值为它的散列地址,
    即:h(key) = random(key)

2、集合  set

  集合是具有某种特性的事物的整体,构成集合的事物或对象称为集合的元素。

  集合的特性:

  无序性:一个集合中每个元素的地位都是相同的,元素之间不存在有序关系,也没有类似树和图那样的复杂关系。

  互异性:一个集合中每个元素只能出现一次。

  确定性:集合的定义是确定的,根据这个定义可以明确判定一个对象是否属于这个集合。

3、图   graph

 图是一种特殊的数据组织方式,他不仅可以存储数据元素(对象),还可以存储数据元素之间的复杂关系。从直观上看,图由一些顶点和连接这些顶点的边组成,顶点描述数据元素,便描述数据元素之间的关系。图有很多中国分类方式:

根据边是否有方向:有向图 、无向图

根据任意两个顶点之间边的个数:简单图、多重图。

根据任意两个顶点之间边的个数:连通图、非连通图。

根据边的地位平等性:带权图、不带权图。

定义图的方式:

二元组定义:

对于图G,如果V(G) 表示顶点集,E(G) 表示边集,则(V,E)即为图的二元组定义。

如果存在关联函数 I 将E(G) 中的每一条边映射到V(G) 中的两个顶点,即  I(e) = (u,v) ,其中u,v属于V(G) ,且是 e的两个顶点,则将 (V,E,I)称为图的三元定义。 

  图的存储常采用邻接矩阵(二维数组方式)和邻接表(链表或可变长数组)方式,对于有向图,有时候也采用十字链表方式。图的遍历是一个非常重要的操作,一般可采用深度优先搜索和广度优先搜索两种策略。

       深度优先搜索可以理解为树的中序(先根)遍历的推广,深度优先搜索的过程:从图G的某个顶点 v 出发,先访问v,然后选择一个与v相邻且没被访问过的顶点 vi访问,再从 v出发选择一个与  v相邻且没被访问的顶点 vj进行访问,依次遍历。如果当前被访问过的顶点的所有邻接顶点都已被访问,则退回到已被访问的顶点序列中最后一个拥有未被访问的相邻顶点 v ,从 v出发按同样的方法向前遍历,直到图中的所有顶点都被访问过。伪代码如下:

  

图的使用: 地图中两个点的最佳路线,使用图的连通性判断和最短路径搜索算法。

    交通规划,网络设备等。

4、树 tree

  树是一种表达数据之间层次关系的数据结构,树中的每个节点有0个活多个子节点,但是只有一个父节点,父节点未空的节点就是根节点。

树的度:一个节点含有子树的个数称为该节点的度,一棵树中最大节点的度称为整棵树的度。

叶节点:度为0的节点称为叶节点。

根节点:没有父节点的节点就是根节点。

树的高度:从根节点开始,每多一级子节点,树的层次就 +1 ,一棵树的最大层次就是树的高度。

兄弟节点:具有相同父节点的子节点互称为兄弟节点。

根据每个节点的子节点的数量,可以将树分为二叉树和多叉树。

排序二叉树


首先如果普通二叉树每个节点满足:左子树所有节点值小于它的根节点值,且右子树所有节点值大于它的根节点值,则这样的二叉树就是排序二叉树。

插入操作

首先要从根节点开始往下找到自己要插入的位置(即新节点的父节点);具体流程是:新节点与当前节点比较,如果相同则表示已经存在且不能再重复插入;如果小于当前节点,则到左子树中寻找,如果左子树为空则当前节点为要找的父节点,新节点插入到当前节点的左子树即可;如果大于当前节点,则到右子树中寻找,如果右子树为空则当前节点为要找的父节点,新节点插入到当前节点的右子树即可。

删除操作

删除操作主要分为三种情况,即要删除的节点无子节点,要删除的节点只有一个子节点,要删除的节点有两个子节点。
1). 对于要删除的节点无子节点可以直接删除,即让其父节点将该子节点置空即可。
2). 对于要删除的节点只有一个子节点,则替换要删除的节点为其子节点。
3). 对于要删除的节点有两个子节点,则首先找该节点的替换节点(即右子树中最小的节点),接着替换要删除的节点为替换节点,然后删除替换节点。

查询操作

查找操作的主要流程为:先和根节点比较,如果相同就返回,如果小于根节点则到左子树中递归查找,如果大于根节点则到右子树中递归查找。因此在排序二叉树中可以很容易获取最大(最右最深子节点)和最小(最左最深子节点)值。

红黑树

R-B Tree,全称是 Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。

. 红黑树的特性

(1)每个节点或者是黑色,或者是红色。

(2)根节点是黑色。

(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL 或NULL)的叶子节点!]

(4)如果一个节点是红色的,则它的子节点必须是黑色的。

(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

左旋
对 x 进行左旋,意味着,将“x 的右孩子”设为“x 的父亲节点”;即,将 x 变成了一个左节点(x成了为 z 的左孩子)!。 因此,左旋中的“左”,意味着“被旋转的节点将变成一个左节点

LEFT-ROTATE(T, x) 
y ← right[x] // 前提:这里假设 x 的右孩子为 y。下面开始正式操作
right[x] ← left[y] // 将 “y 的左孩子” 设为 “x 的右孩子”,即 将β设为 x 的右孩子
p[left[y]] ← x // 将 “x” 设为 “y 的左孩子的父亲”,即 将β的父亲设为 x
p[y] ← p[x] // 将 “x 的父亲” 设为 “y 的父亲”
if p[x] = nil[T] 
then root[T] ← y // 情况 1:如果 “x 的父亲” 是空节点,则将 y 设为根节点
else if x = left[p[x]] 
 then left[p[x]] ← y // 情况 2:如果 x 是它父节点的左孩子,则将 y 设为“x 的父节点的左孩子”
 else right[p[x]] ← y // 情况 3:(x 是它父节点的右孩子) 将 y 设为“x 的父节点的右孩子”
left[y] ← x // 将 “x” 设为 “y 的左孩子”
p[x] ← y // 将 “x 的父节点” 设为 “y”

右旋
对 x 进行右旋,意味着,将“x 的左孩子”设为“x 的父亲节点”;即,将 x 变成了一个右节点(x成了为 y 的右孩子)! 因此,右旋中的“右”,意味着“被旋转的节点将变成一个右节点”。

RIGHT-ROTATE(T, y) 
x ← left[y] // 前提:这里假设 y 的左孩子为 x。下面开始正式操作
left[y] ← right[x] // 将 “x 的右孩子” 设为 “y 的左孩子”,即 将β设为 y 的左孩子
p[right[x]] ← y // 将 “y” 设为 “x 的右孩子的父亲”,即 将β的父亲设为 y
p[x] ← p[y] // 将 “y 的父亲” 设为 “x 的父亲”
if p[y] = nil[T] 
then root[T] ← x // 情况 1:如果 “y 的父亲” 是空节点,则将 x 设为根节点
else if y = right[p[y]] 
 then right[p[y]] ← x // 情况 2:如果 y 是它父节点的右孩子,则将 x 设为“y 的父节点的左孩子”
 else left[p[y]] ← x // 情况 3:(y 是它父节点的左孩子) 将 x 设为“y 的父节点的左孩子”
right[x] ← y // 将 “y” 设为 “x 的右孩子”
p[y] ← x // 将 “y 的父节点” 设为 “x”

添加

第一步: 将红黑树当作一颗二叉查找树,将节点插入。
第二步:将插入的节点着色为"红色"。
  根据被插入节点的父节点的情况,可以将"当节点 z 被着色为红色节点,并插入二叉树"划分为三种情况来处理。

  ① 情况说明:被插入的节点是根节点。
  处理方法:直接把此节点涂为黑色。

  ② 情况说明:被插入的节点的父节点是黑色。
  处理方法:什么也不需要做。节点被插入后,仍然是红黑树。

  ③ 情况说明:被插入的节点的父节点是红色。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为 3种情况(Case)。

 

第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。

删除

第一步:将红黑树当作一颗二叉查找树,将节点删除。
这和"删除常规二叉查找树中删除节点的方法是一样的"。分 3 种情况:
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就 OK 了。
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给
“该节点的内容”;之后,删除“它的后继节点”。

第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。
因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正
该树,使之重新成为一棵红黑树。

选择重着色 3 种情况。
① 情况说明:x 是“红+黑”节点。
处理方法:直接把 x 设为黑色,结束。此时红黑树性质全部恢复。
② 情况说明:x 是“黑+黑”节点,且 x 是根。
处理方法:什么都不做,结束。此时红黑树性质全部恢复。
③ 情况说明:x 是“黑+黑”节点,且 x 不是根。
处理方法:这种情况又可以划分为 4 种子情况。这 4 种子情况如下表所示:

 

参考:https://www.jianshu.com/p/038585421b73

代码实现:https://www.cnblogs.com/skywang12345/p/3624343.html

 注:关于红黑树,这篇文章更详细:https://www.cnblogs.com/CarpenterLee/p/5503882.html

B-TREE

B-tree 又叫平衡多路查找树。一棵 m 阶的 B-tree (m 叉树)的特性如下(其中 ceil(x)是一个取上限的函数):
1. 树中每个结点至多有 m 个孩子;
2. 除根结点和叶子结点外,其它每个结点至少有有 ceil(m / 2)个孩子;
3. 若根结点不是叶子结点,则至少有 2 个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);
4. 所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部结点或查询失败的结点,实际上这些结点不存在,指向这些结点的指针都为 null);
5. 每个非终端结点中包含有 n 个关键字信息: (n,P0,K1,P1,K2,P2,......,Kn,Pn)。其中:
a) Ki (i=1...n)为关键字,且关键字按顺序排序 K(i-1)< Ki。
b) Pi 为指向子树根的接点,且指针 P(i-1)指向子树种所有结点的关键字均小于 Ki,但都大于 K(i-1)。
c) 关键字的个数 n 必须满足: ceil(m / 2)-1 <= n <= m-1。

一棵 m 阶的 B+tree 和 m 阶的 B-tree 的差异在于:
1.有 n 棵子树的结点中含有 n 个关键字; (B-tree 是 n 棵子树有 n-1 个关键字)
2.所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本
身依关键字的大小自小而大的顺序链接。 (B-tree 的叶子节点并没有包括全部需要查找的信息)
3.所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字。
(B-tree 的非终节点也包含需要查找的有效信息)

参考:https://www.jianshu.com/p/1ed61b4cca12

位图

位图的原理就是用一个 bit 来标识一个数字是否存在,采用一个 bit 来存储一个数据,所以这样可以大大的节省空间。 bitmap 是很常用的数据结构,比如用于 Bloom Filter 中;用于无重复整数的排序等等。bitmap 通常基于数组来实现,数组中每个元素可以看成是一系列二进制数,所有元素组成更大的二进制集合。

参考:https://www.cnblogs.com/polly333/p/4760275.html

注:oracle的位图索引。

原文地址:https://www.cnblogs.com/yrjns/p/10861266.html