数据结构:数据项目的结构化集合。按照逻辑次序分为线性结构(linear structure)、半线性结构(semi-linear struct)、非线性结构。
序列:线性结构的统称。向量、列表、栈、队列
栈:解决问题的特点,输出以线性序列形式,输入输出规模不确定,难以事先确定存储大小。例如,网页访问的回退,编辑工具的撤退
队列:公平分配资源的情况。例如调度银行,医院服务,土地管理
输入敏感算法(input sensitive):对于规模相同,内部组成不同的输入,算法的渐进运行时间不同
排序算法分类:根据处理数据的规模和存储特点不同,分为内部排序算法和外部排序算法
内部排序算法:数据规模不大,内存即可处理
外部排序算法:数据规模大,必须借助外部存储器。排序的过程中,内存只能容乃一小部分数据。
就地算法(in-place argorithm):仅需O(1)的辅助空间的算法
递归:函数和过程调用的一种特殊形式,即函数和过程进行自我调用。
- 递归基base case of recursion:递归的有穷性的平凡情况
- 线性递归linear recursion:更深一层只有一个递归实例
- 多递归基:
CBA算法(comparison-based algorithm):基于比较式算法,算法的所有可能执行过程都可表示和概括为一颗比较树。
二路归并:将两个有序序列合并为一个有序序列。二路归并属于迭代算法,每次迭代,比较两个待归并序列的首元素,取出较小者追加到输出序列的尾部。
帧:调用栈(call stack)的基本单位
栈混洗(stack permutation):三个栈A/B/S,B/S初始为空。A=<a1,a2,a3,...an],n个元素
只允许的操作:
弹出A的顶元素压入S中,S.push(A.pop);
弹出S的顶元素压入B中,B.push(S.pop);
n次操作后,A/S为空,B即为输入序列的一个栈混洗.
性质一、任一前缀的push不少于pop操作,则该序列对应于某一个栈混洗。
逆波兰表达式(reverse Polish notation,PRN):PRN又叫后缀表达式(postfix),语法规则是操作符(Operator)紧邻与对应的最后一个操作数(Operand)之后。常见的表达式成为中缀表达式(infix)。
中缀表达式转PRN规则:
遇到操作数直接追加到prn;
运算符再栈中弹出并执行时,追加
鸽巢原理: 如果把n+1个物体放入n个盒子,至少有一个盒子包含两个或更多的物体。
证明 :反证法 n个盒子每个盒子至多一个物品,总数至多为n,与有n+1个物体矛盾。
树:半线性结构。附加某种约束,可以再树中的元素间确定某种线性次序。任何有根有序的多叉树都可以等价地转化并实现为二叉树。从图论的角度看,树等价于连通无环图。由一组顶点(vertex)和连接其间的若干条边(edge)组成。
有根树(rooted tree):指定根节点的树。根节点(root)是程序实现中指定的特定的顶点,也叫节点(node)。
连通性:每一个节点与根之间都有一条路径相连。
无环性:根通往每个节点的路径必然唯一
深度(depth):沿每个节点v到根r的唯一通路所经过的边的数目,记作depth(v),约定depth(r)=0
祖先(ancestor):任何一个节点v通往树根沿途所经过的每个节点都是其祖先。v是他们的后代(descendant)。祖先与后代都包括本身,v本身以外的祖先/后代成为真祖先(proper ancestor)、真后代(proper descendant)
父亲(parent):节点u是节点v的祖先,且恰好比v高出一层,则u是v的父亲。v是u的孩子(child)
度数(degree):v的孩子总数,称作其度,记作deg(v)
叶节点(leaf):没有孩子的节点
内部节点(internal node):包含跟在内的其余节点
子树(subtree):v所有后代及其之间的连边称作子树,记作subtree(v)
高度(height):树T中所有的节点深度的最大值称为该树的高度,记作height(T)。约定单个节点的树高度为0,空树为-1。 height(T)=height(r)
节点的高度:任一节点v所对应的子树subtree(v)的高度成为该节点的高度。
二叉树(binary tree):每个节点的度数均不超过2。
有序二叉树(ordered binary tree):同一父节点的孩子都可以左右相互区分
真二叉树(proper binary tree):不含一度节点
K叉树(k-ary tree):每个节点的孩子均不超过k个的有根树。再多叉树中,根节点以外的任一节点有且仅有一个父节点。多叉树表示方式:父节点法、孩子节点法、父节点+孩子节点法、二叉树法
有序树(ordered tree):多叉树增加一项约束条件---同一节点的所有孩子之间必须具有某一线性次序。表示方法:长子+兄弟法
二进制编码:信息转化为二进制形式叫编码(encoding),经过信道到达目标后由二进制编码恢复原始信息的过程叫解码(decoding)
字符集(alphabet):某一个特定的有限字符集合S。字符是原始信息的基本组成单位
前缀无歧义编码(prefix-free code,简称RFC):各个字符的编码串互不为前缀,不会导致歧义
根通路串(root path string):从根节点到每个节点的唯一通路,为各节点v赋予一个互异的二进制串,记作rps(v),|rps(v)|=depth(v),即是v的深度
RFC编码的策略:将字符集中的字符分别映射到二叉树的节点,例如字符x的二进制编码串为rps(v(x))。只要所有字符都对应于叶节点,歧义现象自然消失。
在线算法:RFC解码可以在接收的过程中实时进行,不必等到所有比特位到达之后开始,此类算法叫在线算法
遍历序列:遍历后的节点按照某一线性次序排列。先序遍历VLR、中序遍历LVR、后续遍历LRV
完全二叉树(complete binary tree):二叉树的层次遍历中,前次迭代都有左孩子入队,后Tn/2T - 1 次迭代中都有右孩子入队。拓扑特征:
- 叶节点只能出现在最底层的两层,且最底层叶节点均处于次底层的左侧
- 高度为h的完全二叉树,规模介于
与
之间
- 规模为n的完全二叉树的高度
- 叶节点不少于内部节点,但至多多出一个
满二叉树(full binary tree):所有叶节点都处于最底层(非底层节点均为内部节点)的完全二叉树。是每层都饱和的完全二叉树特例。拓扑特征:
- 高度为h的满二叉树节点个数为
- 叶节点比内部节点多一个
词条(entry):查找或者搜索,从一组数据对象中找出符合条件特定条件者,其中的数据对象统一表示为entry形式。
寻关键码访问(call-by-key):查找的过程与结果,仅仅取决于目标对象的关键码
二叉搜索树(binary search tree):
- 任一节点x的左子树中,所有节点(若存在)均不大于x
- 任一节点x的右子树中,所有节点(若存在)均不小于x
- 结论:任何一棵二叉树是二叉搜索树,当且仅当其中序遍历序列单调非降
- 二叉搜索树的查找算法:从树根出发,逐步缩小查找范围,知道找到目标或者空树。为二分查找的推广
- 渐进复杂度
或者
,后者重复搜索树多,高估算法。最坏可能
。ADT复杂度都正比于树高
理想平衡树:对于n个节点的二叉树,高度正好为
适度平衡:渐进意义下适当放松标准后的平衡性
平衡二叉搜索树(balanced binary search tree,BBST):树高渐进意义不超过
等价二叉搜索树:二叉搜索树的中序遍历序列相同。
等价变换的基本特性:等价二叉搜索树中,各节点的垂直高度可能不同,水平高度一致。“上下可变,左右不乱”。
等价变换的局部性:
- 经过单次动态修改后,至多只有
处局部不在满足限制条件
- 可以在
时间内,使这
处局部(以至全树)重新满足限制条件
使刚刚失去平衡的二叉搜索树可以迅速转换为一颗等价地平衡二叉搜索树。平衡二叉搜索树之间的转换也叫等价变换
平衡因子(balace factor):左右树高的高度差。空树取-1,叶节点取0
AVL树(AVL Tree):平衡因子受限的二叉搜索树,其中各节点平衡因子的绝对值均不超过1
AVL树得名于它的发明者G. M. Adelson-Velsky和E. M. Landis,他们在1962年的论文《An algorithm for the organization of information》中发表了它,以他们名字首字母命名。
局限性:由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树.当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树.
应用:Windows NT内核中广泛存在.
二叉搜索树的数据局部性:
- 刚刚被访问过的节点,极有可能再不久之后再次被访问
- 将被访问的下一节点,极有可能就处于不久之前被访问节点的附近
伸展树:基于以上特性,只需要将刚刚被访问过的节点,即使转移至树根,即可加速后续的操作。
简单伸展树:每访问一个节点后,反复以它的父节点为轴,经适当旋转将其提升一层,直至树根。
双层伸展:逐层伸展改为双层伸展。每次从当前节点向上追溯两层,根据父亲、祖父相对位置进行旋转。
应用:不适应喝点,医院等可靠性稳定性要求高的场合。
I/O操作:两个相邻存储级别之间的数据传输
多路搜索树(mutlti-way search tree):以k层为间隔重组,可将二叉搜索树转化为路搜索树。以两层为间隔的四路搜索树,以三层为间隔的八路搜索树。根据外存的访问特性,计算关键码的规模。
m阶B-树(B-tree)又叫m(m>=2)路平衡搜索树,由于各节点的分支数介于与m之间,又叫m阶B-树为(
,m)树。
应用:B树,B+树:一般用于数据库中做索引,因为它们分支多层数少,因为磁盘IO是非常耗时的,而像大量数据存储在磁盘中所以我们要有效的减少磁盘IO次数避免磁盘频繁的查找。
B+树是B树的变种树,有n棵子树的节点中含有n个关键字,每个关键字不保存数据,只用来索引,数据都保存在叶子节点。是为文件系统而生的。B+树相对B树磁盘读写代价更低:因为B+树非叶子结点只存储键值,单个节点占空间小,索引块能够存储更多的节点,从磁盘读索引时所需的索引块更少,所以索引查找时I/O次数较B-Tree索引少,效率更高。而且B+Tree在叶子节点存放的记录以链表的形式链接,范围查找或遍历效率更高。Mysql InnoDB用的就是B+Tree索引。
红黑树:任一节点左右子树的高度相差不超过两倍。
- 树根总是黑色
- 外部节点总是黑色
- 内部节点(除去树根与外部节点)若为红色,其孩子必为黑色
- 从任一外部节点到根节点的路径上,黑节点数目相等
- 推论:红节点都为内部节点,且其父亲、左右孩子必然存在;红节点之父必未黑;任一通路都不包含相邻红节点
黑深度(black depth):根节点黑深度为0。除根节点,从根节点通往任一节点的沿途,黑节点的总数叫该节点的黑深度。即所有外部节点的黑深度相同。
黑高度(black height):外部节点黑高度为0。除外部节点,从任一节点通往任一后代外部节点的沿途,黑节点的总数叫该节点的黑高度。即所有外部节点的黑深度相同。
红黑树:在AVL平衡二叉树基础上进一步放宽适度平衡,通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,确保没有一条路径会比其他路径长2倍,因而是近似平衡的。所以相对于严格要求平衡的AVL树来说,它的旋转保持平衡次数较少。用于搜索时,插入删除次数多的情况下我们就用红黑树来取代AVL。
应用:linux进程调度CFS(Completely Fair Scheduler);C++的STL; epoll在内核中的实现,用红黑树管理事件块; nginx中,用红黑树管理timer等; Java的TreeMap实现
一维范围查询(range query):给定知悉先L上的点集P={,...,
},对于任一区间R=[
,
],P中的哪些点落在R中。
- 遍历点集P,逐个花费O(1)时间判断各点是否落在R内,总体O(n)。输入点集规模大到需要外存存储,遍历需要大量I/O操作,此方法不适合
预处理:输入点集P通常会在相当长的时间内保持相对固定---这种的数据以及处理方式叫批处理(batch)或离线(offline)方式。对于同一输入点集,往往需要大量的随机定义的区间R,反复进行查询----这种的数据以及处理方式叫在线方式(online)。
输出敏感的算法(output sensitive):将点集P提前处理或者组织成某种结构,就可能进一步提高各次查询的效率。方法一,在时间内排序P,总体查询时间
。
最低公共祖先(lowest common ancestor,LCA):两个叶节点共同祖先的最低者。
- 点集问题处理成平衡二叉搜索树,从LCA,重走路径,忽略过程中的左、右转;对于每次左右转,遍历对应的右子树、左子树,将节点合并即是查找的结果
kd-树(K-dimension tree):是一种高维索引树形数据结构,对数据点在k维空间中划分的一种数据结构,主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。是一颗递归定义的二叉搜索树。
词典(dictionary):逻辑上的词典,是由一组数据构成的集合,其中各元素都是由关键码与数据项合成的词条(entry)
符号表(symbol table):映射(map,关键码互异)与字典都支持动态更新,二者统称符号表
跳转表:是一种高效的字典结构。
- 内部由沿横向分层、沿纵向分层相互耦合的多个列表{S0,S1,S2...Sh},h称为跳转表的高度
- 每一个水平列表称作一层(level),S0称为底层(bottom),Sh称为顶层(top)
- 塔(tower):层次不同的节点沿着纵向构成塔
- 高层列表总是底层列表的子集。S0包含所有,Sh只包含头尾哨兵节点,不包含实质词条
- 跳转表内部节点沿水平、垂直方向都可以定义前驱后继,这种连接方式叫四联表(quadlist)
散列表(hashtable):是散列方法的底层基础,逻辑上由一系列可存放词条(或者引用)的单元组成,这些单元也成为桶(bucket)或者桶单元。各桶单元按照逻辑次序再物理上连续排序。
- 直接使用数组,此事的散列表也叫桶数组(bucket array)
- 若桶的容量为R,合法的秩区间(0,R],又叫地址空间(address space)
散列函数(hash function):从关键码空间到桶数组地址空间的函数,hash():key->hash(key)。hash(key)称为key的散列地址(hashing address)
完美散列(perfect hashing):时间空间性能方面均达到最优的散列
除余法(division method):将散列表长度M取作素数,并将关键码key映射至key关于M整除的余数。
- hash(key) = key mode M
- 能保证词条的均匀分布,但残留某种连续性,从关键码空间到散列地址空间映射的角度看。
MAD法(multiply-add-divide method):
- hash(key) = (a * key + b) mode M
- 除余法是特殊情况,a=1,b=0
数字分析法(selecting digits):从关键码key特定进制的展开中抽取除特定的若干位,构成一个整数地址。
平方取中法(mid-quare):从关键码key的平方的十进制或者二进制展开中取居中的若干位,构成整数地址
折叠法(folding):从关键码key的平方的十进制或者二进制展开位分割成等宽的若干段(可以往返折叠,即反序),取其和作为散列地址
位异或法(xor):从关键码key的平方的十进制或者二进制展开位分割成等宽的若干段,经异或运算得到散列地址
随机数法:rand(key) mod M
冲突的普遍性:开辟物理地址连续的桶数组ht[],借助散列函数hash(),将词条关键码key映射为桶地址hash(key),从而快速地确定待操作词条的物理地址。
- 多槽位(multiple slots)
- 独立链(separate chaining)
- 公共溢出区(overflow area)
- 重散列(rehashing)
封闭定址(closing addressing)或 开散列(open hashing):
开放定址(open addressing):散列地址对所有词条开放,即发生冲突,哈希表未满,寻找下一个空散列地址
闭散列(closed hashing):所有可用的散列地址只能在散列表范围内。
- 线性试探(linear probing):Hi = (H(key) + di) MOD m, i=1,2,…, k(k<=m-1),其中H(key)为散列函数,m为散列表长,di为增量序列。di可有下列三种取法:di=1,2,3,…, m-1,称为线性探测再散列;di=1^2, -(1^2), 2^2, -(2^2), 3^2, …, ±(k^2),(k<=m/2),称二为次探测再散列;di=伪随机数序列,称为伪随机探测再散列。
- 再散列(double hashing):Hi = (Hash(key) + Hash2(key)) MOD m
- 平方试探(quadratic probing):一定程度化解聚集现象。只要散列表长度M为素数,且装填因子小于等于50%,则平方试探迟早必将终止于某个空桶
散列码:利用某一散列码转换函数,将关键码key统一转换为一个整数,然后再利用散列函数将散列码映射为散列地址
Trie树:
又名单词查找树,一种树形结构,常用来操作字符串。它是不同字符串的相同前缀只保存一份。
相对直接保存字符串肯定是节省空间的,但是它保存大量字符串时会很耗费内存(是内存)。
类似的有:前缀树(prefix tree),后缀树(suffix tree),radix tree(patricia tree, compactprefix tree),crit-bit tree(解决耗费内存问题),以及前面说的double array trie。
前缀树:字符串快速检索,字符串排序,最长公共前缀,自动匹配前缀显示后缀。
后缀树:查找字符串s1在s2中,字符串s1在s2中出现的次数,字符串s1,s2最长公共部分,最长回文串。
trie 树的一个典型应用是前缀匹配,比如下面这个很常见的场景,在我们输入时,搜索引擎会给予提示。