哈希表
哈希表存储的是由键(key)和值(value)组成的数据。
把键当成数据的标识符,把值当成数据的内容。
在存储数据的过程中,如果发生冲突,可以利用链表在已有数据的后面插入新数据来解决冲突。这种方法被称为“链地址法”。
我们将每个人的性别作为数据进行存储,键为人名,值为对应的性别。
结构如下:
存储
把Joe存进去
使用哈希函数(Hash)计算Joe的键,也就是字符串“Joe”的哈希值。得到的结果为4928。
将得到的哈希值除以数组的长度5,求得其余数。这样的求余运算叫作“mod运算”。此处mod运算的结果为3。
我们将Joe的数据存进数组的3号箱子中。重复前面的操作,将其他数据也存进数组中。
Sue键的哈希值为7291, mod 5的结果为1,将Sue的数据存进1号箱中。
Dan键的哈希值为1539, mod 5的结果为4,将Dan的数据存进4号箱中。
冲突
Nell键的哈希值为6276, mod 5的结果为1。本应将其存进数组的1号箱中,但此时1号箱中已经存储了Sue的数据。这种存储位置重复了的情况便叫作“冲突”。
可使用链表在已有数据的后面继续存储新的数据
Ally键的哈希值为9143, mod 5的结果为3。本应将其存储在数组的3号箱中,但3号箱中已经有了Joe的数据,所以使用链表,在其后面存储Ally的数据。
Bob键的哈希值为5278, mod 5的结果为3。本应将其存储在数组的3号箱中,但3号箱中已经有了Joe和Ally的数据,所以使用链表,在Ally的后面继续存储Bob的数据。
像这样存储完所有数据,哈希表也就制作完成了。
查询
查询Dan的性别
为了知道Dan存储在哪个箱子里,首先需要算出Dan键的哈希值,然后对其进行mod运算。最后得到的结果为4,于是我们知道了它存储在4号箱中。
查看4号箱可知,其中的数据的键与Dan一致,于是取出对应的值。由此我们便知道了Dan的性别为男(M)。
想要查询Ally的性别时该怎么做呢?为了找到它的存储位置,先要算出Ally键的哈希值,再对其进行mod运算。最终得到的结果为3。
然而3号箱中数据的键是Joe而不是Ally。此时便需要对Joe所在的链表进行线性查找。
于是我们找到了键为Ally的数据。取出其对应的值,便知道了Ally的性别为女(F)。
特点
在哈希表中,我们可以利用哈希函数快速访问到数组中的目标数据。如果发生哈希冲突,就使用链表进行存储。这样一来,不管数据量为多少,我们都能够灵活应对。
如果数组的空间太小,使用哈希表的时候就容易发生冲突,线性查找的使用频率也会更高;反过来,如果数组的空间太大,就会出现很多空箱子,造成内存的浪费。因此,给数组设定合适的空间非常重要。
堆
堆是一种图的树形结构,被用于实现“优先队列”(priority queues)
优先队列是一种数据结构,可以自由添加数据,但取出数据时要从最小值开始按顺序取出。
假设数据量为n,根据堆的形状特点可知树的高度为log2n,那么重构树的时间复杂度便为O(logn)。
堆中的每个结点最多有两个子结点。树的形状取决于数据的个数。另外,结点的排列顺序为从上到下,同一行里则为从左到右。
在堆中存储数据时必须遵守这样一条规则:子结点必定大于父结点。
添加
往堆里添加数字5
寻找新数据的位置。该图中最下面一排空着一个位置,所以将数据加在此处。
如果父结点大于子结点,则不符合上文提到的规则,因此需要交换父子结点的位置。
这里由于父结点的6大于子结点的5,所以交换了这两个数字。重复这样的操作直到数据都符合规则,不再需要交换为止。
现在,父结点的1小于子结点的5,父结点的数字更小,所以不再交换。
往堆中添加数据的操作就完成了
移除
从堆中取出数据时,取出的是最上面的数据。这样,堆中就能始终保持最上面的数据最小。
由于最上面的数据被取出,因此堆的结构也需要重新调整。
按照01中说明的排列顺序,将最后的数据(此处为6)移动到最顶端
如果子结点的数字小于父结点的,就将父结点与其左右两个子结点中较小的一个进行交换。
这里由于父结点的6大于子结点(右)的5大于子结点(左)的3,所以将左边的子结点与父结点进行交换。重复这个操作直到数据都符合规则,不再需要交换为止。
现在,子结点(右)的8大于父结点的6大于子结点(左)的4,需要将左边的子结点与父结点进行交换。
从堆中取出数据的操作便完成了。
二叉查找树
二叉查找树(又叫作二叉搜索树或二叉排序树)是一种数据结构,采用了图的树形结构。数据存储于二叉查找树的各个结点中。
我们可以把二叉查找树当作是二分查找算法思想的树形结构体现。
添加
我们试试添加数字4
从二叉查找树的顶端结点开始寻找添加数字的位置。
由于4<9,所以将其往左移。
由于4>3,所以将其往右移。
由于4<8,所以需要将其往左移,但前面已经没有结点了,所以把4作为新结点添加到左下方。
4的添加操作也完成了
删除
删除结点8
删除的结点只有一个子结点,那么先删掉目标结点
把子结点移到被删除结点的位置上即可
删除结点9
删除的结点有两个子结点,那么先删掉目标结点
在被删除结点的左子树中寻找最大结点
最后将最大结点移到被删除结点的位置上。这样一来,就能在满足二叉查找树性质的前提下删除结点了。如果需要移动的结点(此处为4)还有子结点,就递归执行前面的操作
查找
查找12
特点
比较的次数取决于树的高度。所以如果结点数为n,而且树的形状又较为均衡的话,比较大小和移动的次数最多就是log2n。因此,时间复杂度为O(logn)。但是,如果树的形状朝单侧纵向延伸,树就会变得很高,此时时间复杂度也就变成了O(n)。