24张图,九大数据结构安排得明明白白( 四 )


取随机数法:使用一个随机函数 , 取关键字的随机值作为散列地址 , 这种方式通常用于关键字长度不同的场合 。
除留取余法:取关键字被某个不大于散列表的表长 n 的数 m 除后所得的余数 p 为散列地址 。这种方式也可以在用过其他方法后再使用 。该函数对 m 的选择很重要 , 一般取素数或者直接用 n 。
确定好散列函数之后 , 通过某个key值的确会得到一个唯一的value地址 。但是却会出现一些特殊情况 。即通过不同的key值可能会访问到同一个地址 , 这个现象称之为冲突 。
冲突在发生之后 , 当在对不同的key值进行操作时会使得造成相同地址的数据发生覆盖或者丢失 , 是非常危险的 。所以在设计散列表往往还需要采用冲突解决的办法 。
常用的冲突处理方式有很多 , 常用的包括以下几种:

开放地址法(也叫开放寻址法):实际上就是当需要存储值时 , 对Key哈希之后 , 发现这个地址已经有值了 , 这时该怎么办?不能放在这个地址 , 不然之前的映射会被覆盖 。这时对计算出来的地址进行一个探测再哈希 , 比如往后移动一个地址 , 如果没人占用 , 就用这个地址 。如果超过最大长度 , 则可以对总长度取余 。这里移动的地址是产生冲突时的增列序量 。
再哈希法:在产生冲突之后 , 使用关键字的其他部分继续计算地址 , 如果还是有冲突 , 则继续使用其他部分再计算地址 。这种方式的缺点是时间增加了 。
链地址法:链地址法其实就是对Key通过哈希之后落在同一个地址上的值 , 做一个链表 。其实在很多高级语言的实现当中 , 也是使用这种方式处理冲突的 。
公共溢出区:这种方式是建立一个公共溢出区 , 当地址存在冲突时 , 把新的地址放在公共溢出区里 。
目前比较常用的冲突解决方法是链地址法 , 一般可以通过数组和链表的结合达到冲突数据缓存的目的 。
24张图,九大数据结构安排得明明白白

文章插图
 
左侧数组的每个成员包括一个指针 , 指向一个链表的头 。每发生一个冲突的数据 , 就将该数据作为链表的节点链接到链表尾部 。这样一来 , 就可以保证冲突的数据能够区分并顺利访问 。
考虑到链表过长造成的问题 , 还可以使用红黑树替换链表进行冲突数据的处理操作 , 来提高散列表的查询稳定性 。
9 图图相较于上文的几个结构可能接触的不多 , 但是在实际的应用场景中却经常出现 。比方说交通中的线路图 , 常见的思维导图都可以看作是图的具体表现形式 。
图结构一般包括顶点和边 , 顶点通常用圆圈来表示 , 边就是这些圆圈之间的连线 。边还可以根据顶点之间的关系设置不同的权重 , 默认权重相同皆为1 。此外根据边的方向性 , 还可将图分为有向图和无向图 。
24张图,九大数据结构安排得明明白白

文章插图
 
图结构用抽象的图线来表示十分简单 , 顶点和边之间的关系非常清晰明了 。但是在具体的代码实现中 , 为了将各个顶点和边的关系存储下来 , 却不是一件易事 。
邻接矩阵目前常用的图存储方式为邻接矩阵 , 通过所有顶点的二维矩阵来存储两个顶点之间是否相连 , 或者存储两顶点间的边权重 。
24张图,九大数据结构安排得明明白白

文章插图
 
无向图的邻接矩阵是一个对称矩阵 , 是因为边不具有方向性 , 若能从此顶点能够到达彼顶点 , 那么彼顶点自然也能够达到此顶点 。此外 , 由于顶点本身与本身相连没有意义 , 所以在邻接矩阵中对角线上皆为0 。
24张图,九大数据结构安排得明明白白

文章插图
 
有向图由于边具有方向性 , 因此彼此顶点之间并不能相互达到 , 所以其邻接矩阵的对称性不再 。
用邻接矩阵可以直接从二维关系中获得任意两个顶点的关系 , 可直接判断是否相连 。但是在对矩阵进行存储时 , 却需要完整的一个二维数组 。若图中顶点数过多 , 会导致二维数组的大小剧增 , 从而占用大量的内存空间 。


推荐阅读