数据结构比较

15 篇文章 0 订阅
3 篇文章 0 订阅

数据结构各有不同,某些特性使其在某些方面性能良好,没有完美的数据结构,只有合适的。
下面是一些数据结构的特性及适用场景,会不断更新。
有错漏望纠正,谢谢。

完全二叉树有如下性质:

In a heap, the parent of the node in position k is in position k/2; and, conversely, the two children of the node in position k are in positions 2k and 2k + 1. We can travel up and down by doing simple arithmetic on array indices(索引): to move up the tree from a[k] we set k to k/2; to move down the tree we set k to 2*k or 2*k+1.

Heap(堆)

最大(小)堆,对于每一个节点,其左右孩子的key值均比其key值小(大),亦称为complete heap-ordered binary tree。

Java 源代码 http://algs4.cs.princeton.edu/24pq/Heap.java.html

最大(最小)堆的添加新节点删除最大(最小)节点的时间复杂度皆为O(logN)查找最大(最小)节点的的时间复杂度为O(1)
所以堆这种数据结构适用于频繁添加、删除最大(最小)值的场景,比如系统调度和打印机之类的优先队列,当总是取耗时最少的任务先执行,就会用堆来实现;比如求前k个最大大,前k个最小数据的时候。

优先队列的实现 http://algs4.cs.princeton.edu/24pq/MaxPQ.java.html

BST 二叉查找树

对于每一个节点,其左孩子key值比其key值小,右孩子key值比其key值大。中序遍历结果就是升序排序的结果。
这种结构在范围查找时速度很快,假设给定min和max值进行范围查找,首先找min的上界,时间复杂度O(lgN),然后中序遍历找找max的下界,好费时间为m,m为匹配个数。故范围查找的时间复杂度是时间复杂度O(lgN+m)

Java源代码 http://algs4.cs.princeton.edu/32bst/BST.java.html

但是普通的BST的查找的速度并不是严格的O(lgN),因为你可能会构造出一个链表形状的BST,这时候效率是很低的(O(lgN))
之后的AVL会解决这个问题。

Balanced Search Tree(AVL,发明者骄傲的以自己的名字来命名)

普通BST不能保证时间复杂度为lgN,而平衡二叉搜索树可以保证。这是一种高度不会超过2lgN的树,对于每一个节点,其左子树和右子树的高度之差不能大于一。

高度的定义:节点X的高度就是其到叶子节点最长的一条路径的边数。如果是从一层开始,还要加上一。
深度的定义:节点X的深度就是从根节点到节点X的路径的边数再加一。
树的高度就是根节点的高度,树的深度就是最深叶子节点的深度。相同深度的节点,高度不一定相同。

要做到这样只需在插入和删除时就一些限制即可。

https://visualgo.net/bst
http://www.cnblogs.com/huangxincheng/archive/2012/07/22/2603956.html

这种数据结构用途跟BST一样,但是更稳定。

Red-black BSTs

红黑树,平衡搜索树的一种。
要介绍红黑树就得先介绍一种特别的树。

http://algs4.cs.princeton.edu/33balanced/

2-3 search trees,它长这个样子:
这里写图片描述
一个2-3 search trees要么是空的,要么是这样:

  • 一个2-node,有一个key值和对应的一个value值以及两条边,左孩子是key值较小的一个2-3 search trees,右孩子是key值较大的一个2-3 search trees。
  • 一个3-node,有两个key值keyA、keyB(keyA<keyB)和两个对应的value值以及三条边,左孩子是key值小于keyA的一个2-3 search trees,中间的孩子是key值大于keyA,但小于keyB的一个 2-3 search tree,右孩子是key值大于keyB的一个2-3 search trees。

所以查找是这样的

这里写图片描述

插入是这样的,分几种情况
Insert into a 2-node.


Insert into a tree consisting of a single 3-node


Insert into a 3-node whose parent is a 2-node


Insert into a 3-node whose parent is a 3-node


Splitting the root

但是要实现直接的2-3 search trees是挺麻烦的,所以用红黑树的代替。
这里写图片描述
对于一个3-node,用红边把3-node的两个节点连起来,用黑边把两个 2-3 tree连起来。
这里写图片描述
对于上面这个图,只要把红边连的两个节点合并,就变成2-3 tree了。
更详细的实现请参考:

http://algs4.cs.princeton.edu/33balanced/

In a red-black BST, the following operations take logarithmic time in the worst case: search, insertion, finding the minimum, finding the maximum, floor(找到小于某个key的key最大的那个节点), ceiling(找到大于某个key的key最小的那个节点), rank(对于某颗树,找到某个key值在这棵树的排名), select(对于某棵树,找到第k小的节点), delete the minimum, delete the maximum, delete, and range count(列出一定内范围的值).


哈希表(Hash Tables)

http://algs4.cs.princeton.edu/34hash/

数组的索引是简单的数字,如果key是小整数,可以用数组实现symbol table。而哈希表是一种简单的扩展方法,对key进行计算,将其转为数组的索引(整数)。
实现哈希表主要有两步:用一个哈希函数算出哈希值,即key对应的数组索引;解决冲突(当两个不同的key的哈希值相同时如何处理)。
最常用的方法是模哈希,就是选择一个数组大小M,用key值%M,得到哈希值。
如果key值是0到1的实数,第一种想到的办法是,乘以M然后四舍五入。虽然这看上去很直观,但这样做的话,其实后面那些小数起不了作用。(举个例子,对于1.23456789,如果M是5,那结果是6,3456789这几个数字是没有用的,再去计算乘法就是浪费时间)一个更好的办法是对key的二进制位表示进行模运算:其实就是进行多次异或,直到值不变,就相当于先把小数转为整数,然后对整数进行模运算了,这样效率更高,而且每个数都起了作用。
对于字符串,模哈希法是把字符串当作一个大整数。

int hash = 0;
for (int i = 0; i < s.length(); i++)
    hash = (R * hash + s.charAt(i)) % M;

其中R是一个较小的素数,Java中用31。

为什么用31呢?
It’s prime, so that when the user mods out by another number, they have no common factors (unless it’s a multiple of 31). 31 is also a Mersenne prime (like 127 or 8191) which is a prime number that is one less than a power of 2. (二进制表示就是全部位都是1)This means that the mod can be done with one shift and one subtract if the machine’s multiply instruction is slow.(这一句没懂啊,shift是移位吗?)

也可以使用复合的key作哈希值,比如:

int hash = (((area * R + exch) % M) * R + ext) % M; 

Java的数据类型中有hashCode()方法作为哈希函数。其实现必须与equals方法保持一致,即if a.equals(b) is true, then a.hashCode() must have the same numerical value as b.hashCode(). If the hashCode() values are the same, the objects may or may not be equal, and we must use equals() to decide which condition holds.
如何把哈希值转为数组索引呢:

private int hash(Key key) {
   return (key.hashCode() & 0x7fffffff) % M;
}

这是把一个32位的整数转为31位的非负整数,然后进行模运算(对负数进行模运算会返回负数结果,造成数组溢界)。

(s.hashCode() % M);
Math.abs(s.hashCode()) % M; 

所以以上两种方法都不可以,因为Math.abs(Integer.MIN_VALUE )仍然为Integer.MIN_VALUE。 因为求绝对值的函数会对传进来的参数进行判断,若是正数直接返回,若是负数,取反后加一返回。(这里就用八位吧)所以0的负数是0,1的负数是(1111 1111),2的负数是(1111 1110),而最小值-128即1000 0000的负数依然是(1000 0000)。这种错误实际运行时很少出现,所以经常测试不出来,要在编码时就避免。

对一个数据类型设计哈希函数时,首先应考虑这三个方面:
1.确定性,相同的key值得到的哈希值应该是一样的
2.计算哈希值应该很快速、高效
3.键值对应该均匀分布

解决冲突的策略:
简单直接的方法是在同一个哈希值对应的索引放一个键值对的链表。
这里写图片描述
另一种做法叫open-addressing hashing methods,发现冲突时放到下一个空位。
故这样查找时有三种情况:
1.key相等,成功
2.该位置无键值对,空
3.该位置的key与搜索的key不等,尝试下一个位置
这里写图片描述

As with separate chaining, the performance of open-addressing methods is dependent on the ratio α = N/M.
For separate chaining α is the average number of items per list and is generally larger than 1.(每个链表的平均项目数)
For open addressing, α is the percentage of table positions that are occupied; it must be less than 1.
We refer to α as the load factor of the hash table.


Trie树

http://blog.csdn.net/hackbuteer1/article/details/7964147

特点:
1.根节点不包含不包含任何字符,非根节点都包含一个字符
2.从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
3.每个节点的所有子节点包含的字符都不相同。

如给出字符串”abc”,”ab”,”bd”,”dda”,根据该字符串序列构建一棵Trie树。则构建的树如下:
这里写图片描述

typedef struct Trie_node  
{  
    int count;                    // 统计单词前缀出现的次数  
    struct Trie_node* next[26];   // 指向各个子树的指针  
    bool exist;                   // 标记该结点处是否构成单词    
}TrieNode , *Trie;  

应用:

http://dongxicheng.org/structure/trietree/

  • 字符串检索
    • 事先将已知的一些字符串(字典)的有关信息保存到trie树里,查找另外一些未知字符串是否出现过或者出现频率。
  • 字符串最长公共前缀
    • Trie树利用多个字符串的公共前缀来节省存储空间,反之,当我们把大量字符串存储到一棵trie树上时,我们可以快速得到某些字符串的公共前缀。
  • 排序
    • Trie树是一棵多叉树,只要先序遍历整棵树,输出相应的字符串便是按字典序排序的结果。
      举例:
      @ 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出。
  • 作为其他数据结构和算法的辅助结构
    • 如后缀树,AC自动机等
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值