顺序查找
基于无序链表
public class SequentialSearchST(Key, Value){
privateNode first;
privateclass Node {
Keykey;
Valuevalue;
Nodenext;
publicNode(Key key,Vaule value,Node node) {
this.key= key;
this.value= value;
this.node= next;
}
}
publicValue get(Key key) {
for(Nodex = first;x != null;x = x.next) {
if(key.equals(x.key))return x.val;
returnnull;
}
}
publicvoid put(Key key,Value value) {
for(Nodex = first;x != null;x = x.next) {
if(key.equals(x.key)){
x.val= val;return;
}
first= new Node(key,val,first);
}
}
}
在含有N对键值的基于无序链表中,在最坏情况下查找和插入操作都需要N次比较。特别地,向一个空表中插入N个不同的键需要~N2次比较(1+2+3…+N)。
二分查找
有序数组:使用一对平行数组,一个存储键,一个存储值。
//迭代
public int rank(Key key) {
intlo = 0,hi = N-1;
while(lo<=hi){
intmid = lo+(hi-lo)/2;
intcmp = key.compareTo(keys[mid]);
if(cmp< 0) hi = mid-1;
elseif(cmp > 0) lo = mid+1;
elsereturn mid;
}
returnlo;
}
//递归
piblic int rank(Key key, int lo, int hi) {
if(lo> hi) return lo;
intmid = lo + (hi-lo)/2;
intcmp = key.compareTo(keys[mid]);
if(cmp< 0) return rank(key,lo,mid-1);
elseif(cmp > 0) return rank(key,mid+1,hi);
elsereturn mid;
如果表中存在该键,rank()返回该键的位置,也就是表中小于它的键的数量。
如果表中不存在该键,rank()还是返回表中小于它的键的数量。
使用的数据结构 | 实现 | 优点 | 缺点 |
链表(顺序查找) | SequentialSearchST | 适用于小型问题 | 对于大型很慢 |
(有序数组)二分查找 | BinarySearchST | 最优的查找效率和空间需求,能够进行有序性的相关操作 | 插入操作很慢 |
二叉查找树 | BST | 实现简单,能够进行有序性相关操作 | 没有性能上界的保证,链接需要额外的空间 |
平衡二叉树 | RedBlackBST | 最优的查找和插入效率,能够进行有序性的相关操作 | 链接需要额外的空间 |
散列表 | SeparateChainHashST LinearProbingHashST | 能够快速查找和插入常见类型的数据 | 需要计算每种数据类型的数据的散列,无法进行有序相关性的操作链接,空结点需要额外的空间 |
二叉树查找
二叉树是每个结点最多有两个子树的有序树。
二叉查找树(BST)是一颗二叉树,其中每个结点都有一个父节点(根节点除外),而且每个结点可以有左右两个结点,父节点的键值大小大于左结点而小于右结点。
基本实现
数据表示:链表。结点(一个键、一个值、一条左链接、一条右链接、一个结点计数器)。
每个公有方法中都对应着一个私有方法,它接收一个额外的链接作为参数指向某个结点。
public class BST(Key extendsComparable<key>, Value) {
privateNode root;
privateclass Node {
privateKey key;
privateValue val;
privateNode left,right;
privateint N;
publicNode(Key key, Value value, int N) {
this.key= key;
this.val= value;
this.N= N;
}
}
//countsize
publicint size() {
returnsize(root);
}
privateint size(Node x) {
if(x== null) return 0;
elsereturn x.size;
}
//get value
publicValue get(Key key) {
returnget(root,key);
}
privateValue get(Node x,Key key) {
//在以x为根结点的子树中查找并返回key所对应的值
//如果找不到则返回null
if(x== null) return null;
intcmp = key.compareTo(x.key);
if(cmp<0)return get(x.left, key);
elseif(cmp>0) return get(x.right,key);
elsereturn x.val;
}
//putvalue
publicvoid put(Key key, Value val) {
//查找key,找到则更新它的值,否则为它创建一个新的结点
root= put(root,key,val);
}
privateNode put(Node x, Key key, Value val) {
if(x== null) return new Node(key,val,1);
intcmp = key.compareTo(x.key);
if(cmp<0)x.left = put(x.left,key,val);
elseif(cmp>0) x.right = put(x.right,key,val);
elsex.vai = val;
x.N= size(x.left)+size(x.right)+1;
returnx;
}
}
递归调用前的代码:沿着树向下走
它会将给定的键和每个结点的键相比较并根据结果向左或者向右移动到下一个结点。
递归调用后的代码:沿着树向上爬
对于get()方法,这对应着一系列的返回return。
对于put()方法,这意味着重置搜索路径上的每个父节点指向子节点的链接,并增加路径上每个结点的计数器的数量。
插入
如果树为空,则返回一个含有该键值对的新结点。
如果被查找的键小于根结点的键,则继续在左子树插入该键,反之在右子树插入该键。
新的结点会链接到树底层的空连接上,树的其他部分不发生变化。
性能分析
分析1
二叉查找树的算法运行时间取决于树的形状,而树的形状取决于键被插入的先后顺序。在最好的情形下,一颗含有N个结点的树是完全平衡的,每条空链接和根节点的距离都是~lgN;在最坏的情形下,搜索路径上可能有N个结点。
二叉查找树和快递排序几乎是双胞胎。树的根节点就是快速排序中的第一个切分元素(左侧的键都比它小,右侧的都比它大),而这对于所有的子树都适用,这个和快速排序中子数组的递归排序完全对应。
分析2
在由N个随机键构造的二叉查找树中,查找命中平均所需的比较次数为约1.39lgN.
证明:一次结束于给定结点的命中查找所需的比较次数为查找路径的深度加1.如果将树中的所有结点的深度加起来,我们就能够得到一棵树的内部路径长度。因为在二叉查找树中的平均比较次数为平均内部长度加1.令CN为由N个随机排序的不同键构造得到的二叉查找树的内部路径长度,则查找命中的平均成本为(1+CN/N),我们有C0=C1=0,且对于N>1,我们可以得到一个归纳关系式:CN=N-1+(C0+CN-1)/N++(C1+CN-2)/N+…+(CN-1+ C0)/N
有序性相关的 方法与删去操作
一棵二叉查找树代表了一组键值的集合,而一个集合可以用多棵不同的二叉查找树来表示。如果将一棵二叉查找树的所有键投影到一条直线上,保证一个结点的左子树中的键值出现它的左边,右在右,那么我们一定可以得到一条有序的键列。
访问键值:1通过键2通过键的相关顺序
最大键和最小键
【最小键】如果根结点的左子树为空,则根结点为最小键;如果左子树非空,那么树中的最小键为左子树的最小键。Min()&&max()
向上取整合向下取整
【小于等于key的最大键】如果给定的键key小于二叉查找树的根结点的键,那么小于等于key的最大键floor一定在根节点的左子树中;如果给定的键key大于二叉查找树中根节点,那么只有当根节点右子树中存在小于等于key的结点时,小于等于key的最大键才会出现在右子树,否则根结点就是小于等于key的最大键值。Floor()&&ceiling()
选择操作
【结点计数器N】假设我们想找到排名为k的键(即树种正好拥有k个小于它的键)。如果左子树中的结点数t大于k,那么我们继续(递归)在左子树中查找排名为k的键;如果t等于k,返回根节点中的键;如果小于k,那么我们继续(递归)在左子树中查找排名为(k-t-1)的键。Select()
排名
如果给定的键和根的键相等,返回左子树中的结点总数t,如果给定的键小于根结点,我们会返回该键在左子树中的排名,如果给定的键大于根结点,我们会返回t+1加上它在右子树中的排名。Rank()
public Key select(int k) {
returnselect(root,k).key;
}
private Node select(Node x, int k) {
//返回排名为K的结点
if(x== null) return null;
intt = size(x.left);
if(t>k)return select(x.left,k);
elseif(t<k) return select(x.right,k-t-1);
elsereturn x;
}
public int rank(Key key) {
returnrank(key,root);
}
private int rank(Key key, Node x) {
//返回x为根节点的子树中小于x.key的键的数量
if(x== null) return 0;
intcmp = key.compareTo(x.key);
if(cmp<0)return rank(key,x.left);
elseif(cmp>0) return 1+size(x.left)+size(key,x.right);
elsereturn size(x.left);
}
平衡查找树
保持平衡性。查找树中所有的空链接(叶结点)到根结点的距离都是相同的。
2-3查找树
2个结点:底下2分支,本身就1个键值
3个结点:底下3分支,本身就2个键值
查找
插入
进行未命中的查找,然后将新结点挂在树的底部。保持平衡性,插入的位置三种情形:
本身是2结点:直接插入变成三结点
本身是3结点:分父结点
父节点是2结点:将本身结点创建为3结点,再向上分解,把父节点变为3结点。
父节点是3结点:将本身结点创建为4结点,如果向下分解则会破坏平衡性,因此必须向上分解,这样需要不断的向上分解直到一个2结点将其替换为不需要继续分解的3结点,或者到达3结点的根。
2-3树的生长是由下向上的。
一颗含有N个结点的2-3树的高度在log3N和lgN之间。全是3结点和2结点。
红黑二叉查找树
基本思想:用标准的二叉查找树(完全有2结点构成)和一些额外的信息(替换3结点)来表示2-3树。红链接将2个2结点连接起来构成一个3结点,黑链接则是2-3树种的普通链接。确切来说,将3结点表示为一条左斜的红色连接相连的两个2结点。
查找
http://blog.jobbole.com/79307/
http://blog.youkuaiyun.com/yang_yulei/article/details/26066409
红黑树既是二叉查找树,也是2-3树。
二叉树中简洁高效的查找方法和2-3中高效的平衡插入算法。
有序性和完美平衡性 不存在连续的两条红链接不存在红色的右链接
性质1.节点是红色或黑色。
性质2.根是黑色。
性质3.所有叶子都是黑色(叶子是NIL节点)。
性质4.每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5.从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
红黑树的插入:插入新节点总是红色的,因为不会破坏性质5,尽可能的维持所有的性质。
插入节点的关键是:
插入新节点总是红色节点
如果插入节点的父节点是黑色, 能维持性质
如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质。
情况1:插入的是根结点。
原树是空树,此情况只会违反性质2。
对策:直接把此结点涂为黑色。
情况2:插入的结点的父结点是黑色。
此不会违反性质2和性质4,红黑树没有被破坏。
对策:什么也不做。
情况3:当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色。
此时父结点的父结点一定存在,否则插入前就已不是红黑树。
与此同时,又分为父结点是祖父结点的左子还是右子,对于对称性,我们只要解开一个方向就可以了。
在此,我们只考虑父结点为祖父左子的情况。
同时,还可以分为当前结点是其父结点的左子还是右子,但是处理方式是一样的。我们将此归为同一类。
对策:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。
红黑树的删除:
删除节点的关键是:
如果删除的是红色节点, 不破坏性质
如果删除的是黑色节点, 那么这个路径上就会少一个黑色节点, 破坏了性质. 故删除算法就是通过重新着色或旋转, 来维持性质。
真正的删除点必定是只有一个孩子或没有孩子的结点。
为叶子节点
为只有一个孩子的结点
为两个孩子的结点
散列表
基于拉链法的散列表
基于线性探测法的散列表
算法 数据结构 | 最坏情况N次插入 | 平均情况N次随机插入 | 关键接口 | 内存使用 字节 | ||
查找 | 插入 | 查找命中 | 插入 | |||
顺序查询 无序链表 | N | N | N/2 | N | Equals() | 48N |
二分查找 有序数组 | lgN | N | LgN | N/2 | Comparable() | 16N |
二叉树查找 | N | N | 1.39lgN | 1.39lgN | Comparable() | 64N |
2-3树查找 红黑树 | 2lgN | 2lgN | 1.001lgN | 1.001lgN | Comparable() | 64N |
拉链法 链表数组 | <lgN | <lgN | N/2M | N/M | Equals() hashCode() | 48N+32M |
线性探测法 并行数组 | clgN | clgN | <1.5 | <2.5 | Equals() hashCode() | 32N~128N |
矩阵中非零元素的个数远远小于矩阵元素的总数,并且非零元素的分布没有规律,则称该矩阵为稀疏矩阵(sparse matrix);与之相区别的是,如果非零元素的分布存在规律(如上三角矩阵、下三角矩阵、对称矩阵),则称该矩阵为特殊矩阵。