一、查找问题的介绍
查找问题就是在给定的集合(或者是多重集,它允许多个元素具有相同的值)中找寻一个给定的值,我们称之为查找键。有许多查找算法可供选择,其中既包括直截了当的顺序搜索,也包括效率极高但应用受限的折半查找,还有那些将原集合用另一种形式表示以方便查找的算法。最后一类算法对于现实应用具有特别重要的价值,因为它们对于大型数据库的信息存取来说是不可或缺的。
对于查找来说,没有一种算法在任何情况下都是最优的。有些算法速度比其他算法快,但需要较多的存储空间;有些算法速度非常快,但仅适用于有序的数组,诸如此类。和排序算法不同,查找算法没有稳定性问题,但会发生其他问题。具体来说,如果应用里的数据相对于查找次数频繁变化,查找问题就必须结合另外两种操作一起考虑:在数据集合中添加和删除元素的操作。在这种情况下,必须自习选择数据结构和算法,以便在各种操作的需求之间达到一个平衡。而且,对于用于高效查找的特大型数据集合来说,如何组织其结构是一项不同寻常的挑战,而这对实际应用具有非常重要的意义。
针对查找问题采用了三种方法:线性表直接查找,二叉排序树查找,平衡二叉排序树查找三种方法。
1)其中线性表(Linear List,LL)直接查找,通过将数据集合存储在数组中,每次从数组首地址开始一直搜索到数组末尾,直到查找到待查元素和到达数组末尾为止。并针对该方法给出了一个优化的算法,对数组中的元素增加一个频度域,将查找频度最高的元素放在数组最前面,依次按频度对数组中元素进行排序。
2)二叉排序树(Binary Sort Tree,BST)查找,BST又称二叉搜索树,其定义为:二叉排序树或者是一颗空树,或者是具有如下性质的二叉树:①若左子树不空,则左子树上所有节点的值均小于它的根节点的值;②若右子树不空,则右子树上所有节点的值均大于它的根节点的值;③左、右子树也分别是二叉排序树;④没有键值相等的节点。从BST的定义中可知,在BST中查找一个元素的过程,即从根节点开始,每次根据待查元素与根节点的比较,决定查找成功或者依左或右子树查找,直到找到待查元素或者都到叶子节点为止。
3)平衡二叉查找树(Adelson-Velskii and Landis,AVL树),一棵AVL树是一棵二叉查找树,其中每个节点的平衡因子定义为该节点左子树和右子树的高度差,这个平衡因子要么是0,要么为+1或者-1(一棵空树的高度定义为-1)。因此AVL树是一种特殊的二叉查找树,其左右分支深度比较平衡,这样就缩短了树的高度,从而降低了查找的次数。在AVL树种查找元素的过程类似在二叉排序树中查找元素。
二、对查找问题的理论分析
设集合中的数据个数为n,则在采用线性表直接存储时,每次查找一个元素,在最好的情况下,时间复杂度是o(1),最坏的情况下时间复杂度是o(n),因此平均查找长度是
在对线性表直接查找进行优化的算法中,增加了频度域,数组中数据按照数据查找的频度从高到低排列,在这种算法上,最好的情况下,时间复杂度是o(1),最坏的情况下时间复杂度是o(n)。数据的平均查找长度依赖于所查找的序列。但可以确定的是,其查找长度低于优化前的查找长度
在分析二叉排序树的理论查找性能时,我们首先要清楚二叉排序树的形态,因为当二叉排序树的形态不同,其树的深度就会不同,因而查找的性能也会不同。在极端的情况,二叉排序树退化为一个单分支的树,则其查找性能跟线性表直接查找相同。在这里我们可以分析,对一棵随机生成的二叉排序树,其平均查找性能优于线性表直接查找。为了确定二叉排序树查找性能的上界和下界,我们首先分析一棵完全平衡的二叉排序树,即既是一棵平衡二叉树,又是一棵完全二叉树。假设每一个节点的查找频率相同,则完全平衡二叉查找树的平均查找长度为
因此随机生成的一棵二叉查找树的平均长度为
上述三种方法从时间复杂度上来说,查找一个数据的时间复杂度都是o(n),空间复杂度均需要o(n)大小的辅助存储空间。
三、实验仿真结果
实验在VC6.0平台上,采用的C语言编写程序,将实验中测试的数据存储在record.txt文本中,再将文本数据导入到matlab中进行可视化比较。
图3.1和图3.2是测试不同查找算法的运行所耗费的时间的实时输出截图。其中图3.1是样本数量较少情况下的运行输出图,图3.2是样本数量较大时候的运行输出图。每一次输出会连续输出四组数据,每一组数据包含两个数值,第一个数值表示的是查找所耗费总的时间,第二个数值是查找中总的查找长度。
并将运行中的数据记录在文件中,图3.3和图3.4均是在测试样本数量不同时的各算法查找性能的评价值。每一行为一个样本的记录,包含九个数字,第一个数值是测试样本的数量,后面的八个数字,两个一组,共四组,第一个数字是查找所耗费的时间,第二个数字是查找总的长度,四组数字分别对应,直接连表查找,带频度的连表查找,二叉查找树和AVL树查找。在图3.4中,有的部分数字是负数,那是因为查找的长度值超过了C语言中int类型的最大数值表示范围,产生了溢出。
在图3.5中,是四种算法的查找耗费时间的对比图,通过将上面记录的实验数据导入到Matlab中,生成的对比图。从图中我们可以直观看到四种算法中,线性表直接查找所耗费的时间最长,带频度的线性变性能次之,相对比,二叉查找树和AVL树的查找性能要好得多,耗费的时间也大大的低于线性表。随着样本数量的增加,线性表查找的时间耗费整体趋势呈正比例增长,而二叉排序树和二叉查找树的时间变化比较缓慢。
图3.6和图3.7分别是对图3.5的局部进行放大所成的图。从这两幅图中,可以直观的看到两种线性表和两种二叉树数据结构算法的性能对比。
在下面,我们将呈现各算法总的查找长度的性能对比图。在图3.8中,我们可以看到,相对于直接线性表和带频度的线性表来说,二叉查找树和AVl树的总的查找长度要低得多。而图中在接近900000样本时,所出现的查找长度变为负数,是因为存储查找长度的int类型变量发生了正向溢出。
图3.9分别为对图3.8的局部放大,更清晰的呈现其性能的对比。从图3.8中可以直观的看到,带频度的线性表能够在线性表直接查找的基础上改进查找性能,降低总的查找长度。从图3.9中可以看到,AVL树的查找性能在平均的情况向,其查找所耗费的总的查找长度,大约为二叉查找树的3/4。