目录
1.哈希表
优点:
- 把数据存储和查找所消耗的时间大大减少,几乎可以看成是O(1),代价为消耗较多的内存
- 以空间换时间
- 编程复杂度比较低
基本原理:
哈希表的基本原理是使用一个下标范围比较打的数组A来存储元素,设计一个函数h,对于要存储的线性表的每一个元素node,取一个关键字key,算出一个函数值h(key),把h(key)作为数组下标,用A[h(key)]这个数组来存储node。
构造:
- 直接定址法
- 除余法
- 数字分析法
- 平方取中法
- 折叠法
- 基数转换法
冲突处理:
- 拉链法:要求哈希表每个结点增加一个link字段,用于链接同义词子表。
- 开地址法:当冲突发生时一定要产生一个探查序列,检索沿这个探查序列进行。I(i)=(h(key)+i)%m(0<i<=m-1)
2.树
定义:
一棵树(Tree)是由元素组成的有限集合,其中:
- 每一个元素成为结点(node)
- 有一个特定的结点,称为根结点或树根(root)
- 除根结点外,其余结点被分成m(m>=0,m是与根相连的结点数)个互不相交的有限集合,而每一个子集Ti又都是一颗树,称为原树的子树。
树的存储结构:
- 父亲表示法
- 孩子表示法
- 父亲孩子表示法
- 孩子兄弟表示法
树的遍历:
- 先序
- 后序
- 层序(宽度)
- 叶结点遍历(深度)
二叉树(Binary Tree)
定义:二叉树是一种特殊的树形结构,他的特点是每一个结点最多只有俩棵子树,即二叉树不存在度大于2的结点,而二叉树的子树有左子树,右子树之分,孩子有左孩子,右孩子之分,其次序不能颠倒。所以二叉树是一棵严格的有序树。
特性:
- 二叉树的第i层上至多有(2的k次方)-1个结点(i>=1)
- 对于如何一颗二叉树,如果其叶结点数为n0,度为2的结点的个数为n2,则一定满足n0=n2+1
- 深度为k的二叉树至多有(2的k次方)-1个结点。
一棵n结点的完全二叉树,对于任一编号为i的结点,满足:
- 如果i=1,则结点i为根,如果i》1,则父结点编号为trunc(i/2) trunc为取整数
- 如果2*i>n,则结点i为叶结点;否则左孩子编号为2*i,右孩子为2*i+1
普通数转换成二叉树
- 将树中每个结点除了最左边的一个分支保留外,其余分支都去掉
- 从最左边结点开始画一条直线,把同一层兄弟上的结点都连起来
- 以整棵树的根结点为轴心,将整棵树顺时针大致旋转45度。
遍历:
- 先序
- 中序 L根R
- 后序 LR根
二叉排序树(Binary Sort Tree,BST)
二叉排序数又称为二叉查找树,它的优势在于有序性,而且插入和查找算法的时间复杂度均为O(h),h=log2n,n表示结点数,h表示
树的高度。操作高效便捷。
性质:
- 若它的左子树非空,则左子树上的所有结点的均值都小于它的根结点的值
- 若它的右子树非空,则右子树上所有的结点的均值都大于它的根结点的值
- 它的左右子树也分别为二叉排序数
对有序数列进行动态的插入和查找工作时,就可以采用二叉排序树。
哈夫曼二叉树(Huffman)
带权值的二叉树
构造:(贪心)
- 选权值最小的俩棵二叉树分别作为左右子树构造一颗新的二叉树,并置这棵新的二叉树根结点为左右子树根结点之和
字典树(Trie)
又称查找树,建树,是哈希表的一个变种
应用:
- 存储字典,字符串的快速检索,求最长公共前缀
- 快速统计和排序大量字符串等
优点:最大限度的减少无畏的字符串比较,查询效率比哈希表高
性质:
- 根结点不包含字符,除根结点之外每一个结点都只包含了一个字符
- 从根结点到某一个结点,路径上经过的字符串连接起来为该结点对于的字符串
- 每个结点的所有子结点包含的字符都不相同
3.优先队列(Priority Queue)
是一种抽象数据类型(Abstract Data Type),可以把它看成一种容器,里面有一些元素,这些元素称为队列的结点。
优先队列的结点必须至少包含一种性质:有序性,也就是说任意俩个结点可以比较大小。
优先队列不是按照先进先出,而是按照一定的优先级调用。
优点队列最早出现在操作系统种,实现对任务进程的调度。
应用:统计问题,最值问题,贪心问题,模拟问题
三种基本操作:
- 插入结点
- 取最小结点
- 删除最小结点
一维数组实现:插入为O(1),取值和删除为O(n)
二叉排序数实现:操作都为O(log2N),但二叉树可能退化成线性表
二叉堆
二叉堆是一种常用的优先队列,他支持优先队列的三种基本操作:初始化,插入,取最小结点。
二叉堆形式上是一个数组,本质上是一棵完全二叉树。
性质:
- 对除根以外的每个结点i,heap[parent(i)]>=heap[i] ,即所有的结点的值不得超过其父结点的值,这种堆称为大根堆,反之为小根堆。
操作:
- put:往堆尾加入一个元素(插入结点),并通过从下往上调整,使其继续保持堆的性质。
- get:取最小结点直接读出heap[1],get操作就是取出堆头元素,删除该结点,并通过从上往下的调整,使其保持堆的性质。
可并堆
除了支持优先队列的三种基本操作,还支持一个额外的操作-合并操作
左偏树(Leftist Tree)
定义:是一棵堆有序的二叉树,或者说是一棵优先级树,优先级的根结点存储的元素具有最小优先级,从根到叶子的任意一条路径上,各结点中元素按优先级的非递减序排列。
优先级的结构和二叉堆类似,每个结点存储一个元素,每个结点的键值比它的俩个孩子优,不过它并不是一棵完全二叉树。
左偏树定理:若有n各结点,左偏树距离不超过(log2(n+1))-1
4.并查集(Union Find Set)朋友圈
- 是一种用于处理分离集合的抽象数据类型。
- 当给出俩个元素的一个无序对(a,b)时,需要快速合并a,b所在集合,这期间需要反复查找某元素所在集合,并,查,集三个字就是这么来滴。
- 并查集的作用就是动态的维护和处理集合元素之间的复杂关系。
三种操作:
- 初始化:make-set(x):由于各集合是分离的,所以要求x没有在其它集合中出现过。使用并查集前要执行一次初始化操作,时间复杂度为O(n)
- 查找:find:查找一个元素所在集合,本操作返回一个包含x的集合的代表。
- 合并:union(x,y):将包含x和y的动态集合合并成一个新的集合S,本操作返回集合代表。
5.线段树
根据索引信息(例如元素下标)来对元素进行划分的数据结构。
能够之间维护所需处理的区间,将需要处理的区间不相交的分成若干个小区间,每次维护都可以在这样一些分解后的区间上进行,并且查询的时候,我们也能根据这些被分解了的区间上的信息合并出整个询问区间上的查询结果。
结构:线段树是一棵平衡二叉树
性质:
- 结点数:假设该线段树处理的数列长度为n,即根结点的区间为[1,n+1),那么总结点个数不超过2n个。
- 深度:由于线段树可以看成一棵满二叉树,所以不难得出线段的深度不超过log2(n-1) +1
- 线段分解数量级:线段树把区间上的任意一条长度为L的线段都分成了不超过2log2L条线段。这条性质使得大多数查询能够在O(log2n)的时间内解决
- 线段树存储空间消耗O(n),但是由于深度的性质,使得效率很高
线段树的存储:
- 链表存储:
- 数组模拟链表
- 堆结构存储
- 更紧凑的优化:由于线段树并非等价于完全二叉树或者满二叉树,所以线段最低最底一层的结点可能为空,但这些空结点所占的数量可能达到总结点数一半左右,所以使用堆结构的存储方式某种意义上是对空间的极大浪费。
- 一种改进方法是:改变编号方案,对于结点区间为[l,r)的结点,我们将其存储在(l+r-1),这种方式可以将每个结点不重复且不遗漏的映射到[0,2n-2]中
线段树的构造:
- 线段树的构造过程是自顶向下的,即从根结点开始递归构建。
应用:
- 逆序对问题
- 矩形覆盖问题
- 用线段树优化动态规划
与平衡二叉树的比较:
- 通常情况,线段树效率优于平衡树,但是不难插入和删除区间,这是二叉树的基本操作。
树状数组(Binary Indexed Tree)
描述:定义一个数组a[1...n],并维护一下俩种操作
- 修改,给a[i]加上一个增量delta
- 查询,询问某个前缀的和a[1...index]的和
低位技术Lowbit函数 n&(-n)
树状数组与线段树的比较:
树状数组能够处理的问题一般都可以用线段树来解决,并且它们具有相同的查询和修改的复杂度。但是相比线段数组有三个优点
- 节约空间:树状数组的规模为n,而线段树至少要2n个结点,再算上需要维护的数据域,空间会大十倍左右。
- 易于编码:线段树的复杂度比树状数组要高的多
- 时间效率高
不足:
- 树状数组由于前缀的特点使他可以处理区间和查询,这是因为区间和具有可加性。但是要处理区间最值问题,树状数组就无能为力了
实现:
#include"iostream"
using namespace std;
int NUM = 100;
int tree[101];
void update(int index,int n){
for(int i=index;i<=NUM;i+=(i&(-i))){ //lowbit函数 i&(-i) 求从右到左第一个1,分割数,求的是距离和最左边的1差几位,
cout<<i<<" "<<"\n";
tree[i]+=n;
}
cout<<"\n";
}
int sum(int n){
int ans = 0;
for(int i=n;i>0;i-=(i&(-i))){
ans += tree[i];
}
return ans;
}
int main(){
for(int i=1;i<=100;i++){
update(i,i);
}
cout<<sum(100);
}
伸展树(Splay Tree)
是对二叉排序数的一种改进。对然它不能保证树一直是平衡的,但对于它的一系列操作,平摊时间复杂度都是O(log2n)。
可以认为伸展树是一种平衡的二叉排序树
自调整数据结构的优点:
- 从平摊角度来说,它们忽略常量因子,因此绝对不会差于有明确限制的数据结构,而且由于他们可以根据使用情况进行调整,所以在使用模式不均匀的情况下更加有效
- 由于无需存储平衡信息或者其它限制信息,所以所需的存储空间更小
- 它们查找和更新的算法与操作都很简单,易于实现
缺点:
- 它们需要更多的局部调整,尤其是在查找期间,而那些有明确限制的数据结构仅需要在更新期间内进行调整,查找期间则不需要
- 一系列查找操作中的某一个可能会耗时较长,这在实时处理的应用程序中可能有一个不足之处
核心操作:伸展操作splay
Zig右旋操作
Zag左旋操作
伸展操作是在保持伸展树有序的前提下,通过一系列旋转,将伸展树S中的元素x调整至树的根部。分三种情况:
- 结点x的父结点y是根结点 若在左边就Zig 反之
- 结点x的父结点y不是根结点,设y的父结点为z,且x与y同时使各自父结点的左孩子或者同时是右孩子 Zig-Zig 或Zag Zag
- 结点x的父结点y不是根结点,设y的父结点为z,且x与y中的一个是其父结点的左孩子,而另一个是其父结点的右孩子 Zig-Zag或Zag-Zig
伸展树的基本操作:
- Find:判断元素是否在伸展树S表示的有序集中
- Insert:将元素x插入到伸展树S表示的有序集中
- Join:将俩棵伸展树合并为一棵
- Delete:将元素x从伸展树S所表示的有序集中删除
- Split:以x为界,将伸展树S分离为俩棵伸展树S1,S2,其中S1的所有元素都小于x,S2的所有元素都大于x
Treap
满足堆性质的二叉排序数
- 在保持二叉排序数基本性质不变的同时,为每一个结点设置一个随机的权值,权值满足堆的性质,其结构和效果相当于按随机顺序插入结点而建立的二叉排序树。它的实现简单支持伸展树的大部分操作,而且效率高于伸展树
- Treap与堆有一点不同,堆必须是完全二叉树,而Treap并不一定要求
- Treap记录了一个额外的数据域--优先级
左旋右旋需要注意的问题:
- 针对子结点R或者父结点P都可以
- 旋转的前提是必须要有一父一子俩个结点,也就是说不能把根结点通过旋转上调
- 要注意有些子树可能是不存在的,先要判断该结点不指向空结点再访问
- 如果存储了父指针,子结点指向新的父结点之后,父结点必须立即收编这个子结点,俩者必须配对进行,否则树中的父子关系容易出现差错
平衡树(Balanced Binary Tree)
AVL树(平衡二叉排序树 Balanced Binary Search Tree)
平衡二叉树或者是一棵空树或者满足:
它的左子树和右子树都是平衡二叉树,且左右子树深度之差的绝对值不超过1
平衡因子:某个结点的左子树深度减去右子树深度叫做该结点的“平衡因子”,因此平衡二叉树上所有的结点的平衡因子只可能是-1,0,1
我们可以再二叉树的存储结构中再增加一个数据域来存放每个结点的平衡因子
调整规律:
- LL
- RR
- LR
- RL
红-黑树
- 红黑树也是一种二叉排序树,但在每个结点上增加了一个数据域,表示该结点的颜色,RED或BLACK
- 通过任意一条从根到叶子的路径上个结点着色方式的限制,红黑树确保了没有一条路径会比任何其他路径长俩倍,因而基本上是平衡的
- 树种每个结点包含五个域:Color、Key、Left、Right、P
- 如果某结点的一个子结点或者父结点不存在,则该结点相应的指针域包含值NULL。我们把这些NULL视为二叉排序树的外部结点(叶结点),而把带关键值的普通结点视为树的内部结点
性质:
- 每个结点是红或者是黑的
- 根一定是黑色
- 每个叶结点(NULL)都是黑的
- 如果一个结点是红的,则它的俩个子女一定是黑的
- 从某一个结点到达子孙叶结点的每一条简单路径上包含相同个数的黑结点
这些性质保证了根节点到任意叶子节点的路径长度,最多相差一半(因为路径上的黑色节点相等,差别只是不能相邻的红色节点个数),所以红黑树是一个基本平衡的二叉搜索树,它没有AVL树那么绝对平衡,但是同样的关键字组成的红黑树相比AVL旋转操作要少,而且删除操作也比AVL树效率更高,实际应用效果也比AVL树更出众。
黑高度:
- 从某个结点出发,到达其子树上的任意一个叶结点的任意一条路径上的黑结点个数称为该结点的黑高度
- 一棵含有n个内部节点的红黑树的高度至多为2log2(n+1)
SBT(Size Balanced Tree)子树大小平衡树
是一种自平衡二叉树,通过size域来维持平衡的二叉排序树。可以很方便的实现动态顺序统计中的选择和排名操作。
对于每一个结点t,使用left[t]和right[t]来存储它俩个孩子的指针,并且定义key[t]来表示结点t用来作比较的值。另外增加s[t]来表示以t为根的子树的大小,让他维持这棵树上结点的个数。
定理:一个有n个结点的SBT,它在最坏情况下的高度是满足f[h]<=n的最大h
SBT的优点:
- SBT跑的块
- 调试简单
- 书写简单
- 小巧玲珑
- 用途广泛
块状链表
概念:块状链表看起来是一个链表,链表每个结点存放的一段数组,链表中每个结点的数据拼接起来就是原先的整个线性表的内容。
基本思想:
常见的线性表结构操作有:在某一位置插入一段数,删除若干个数
常用线性结构 | 定位的复杂度 | 插入的复杂度 | 删除的复杂度 |
数组 | O(1) | O(n) | O(n) |
链表 | O(n) | O(1) | O(1) |
可见各有优缺点,且恰好互补。
块状链表域其他数据结构的比较
- 易于支持序列操作
- 较小的时间复杂度系数 O(√n)
块状树
待续
后缀树
处理字符串相关的操作
定义:
- 假设给定一个长度为m的字符串S(下标从1到m),S的后缀树T为一个有m个叶结点的有根树,其叶结点从1到m编号;除了根结点之外,每个内部结点至少有两个孩子:每条边上都标有S的一个非空子串
- 从同一个结点引出的任意两条边上标的字符串都不会以相同字符开始
- 对任意一个叶结点 i,从根结点到i的路径上所有边上标的字符串连接起来,就是S从位置i开始的后缀,也就是说,上述路径恰好拼出了S[i. . m]
后缀数的应用:
- 字符串集合的精确匹配
- 公共子串问题
- 重复子串问题
后缀数组
数链剖分
动态树