课程笔记 04 :数据结构(清华) 向量-查找算法

先看无序向量的查找操作:

Rank vector<T>::find(T const & e, Rank lo, Rank hi) //在区间[lo, hi)中寻找值为e且秩为最大者

{

while ((lo < hi--) && (e != _elem[hi])); //逆向寻找秩最大者

return hi; //当hi < lo时意味着查找失败

}

我们看到,对于无序向量,由于元素间的大小关系不确定,所以要想查找符合要求的元素,不论如何都必须将整个向量进行一次遍历,直到找到目标元素为止,这就意味着无序向量的查找操作的复杂度为O(n)。而在处理有序向量时,由于已知元素间的大小关系,所以当我们对向量中的某一个元素做出判断后,就可以确定目标元素是在该元素之前或是之后抑或就是该元素自身,因而不必大费周章的往两端判断,只需取其一端即可。这样一来,整体的算法复杂度就变为了O(logn),相比于无序向量的查找算法,已经有了很大的改进。

现在,我们针对有序向量的特点,采用二分查找策略,其实现如下:

Rank vector<T>::binsearch(T const & e, Rank lo, Rank hi) //在区间[lo, hi)中寻找值为e的元素

{

while (lo < hi)

{

Rank mi = (lo + hi) / 2; //取区间[lo, hi)的中点

if (e < _elem[mi])

hi = mi; //深入前半段[lo, mi)中查找

else if (e > elem[mi])

lo = mi + 1; //深入后半段[mi, hi)中查找

else

return mi; //在秩为mi处命中

}

return -1; //否则,查找失败

}

乍看上去,二分查找算法的时间复杂度为logn阶,已经比较令我们满意了。但是,若我们更为精细的考察其复杂度,也就是在常系数意义下的复杂度,会发现该算法的平均查找长度约为O(1.50 logn)。我们发现常系数值的大小主要是取决于判断次数的多少。由于算法从向量中点mi处向前或向后查找的概率基本相等,虽然锁定向前查找只需一次判断,但锁定向后查找却需两次判断,这就造成了平均的判断次数多达1.50次。那么,自然地我们会想到,可以让向前查找的可能性适当增加,而向后查找的可能性适当减小,这样一来,总的判断次数不就可以有所降低了吗?是的,如果我们适当的改变取点的位置,那么这一想法的确是可行的。实际上,当我们取查找分界点为区间的黄金分割点时,恰好能实现算法的最优化。若区间长度恰好为fib(n),即fibonacci数列的第n项时,则取得分割点正是第fib(n-1)项。这种算法被称为fibonacci查找算法。

可是以上两种算法都有一个重大的问题,那就是如何保证查找到的元素是秩最大者?这是一个很重大的缺陷。尽管对于许多算法而言,只要能找到等值的元素就够了,但是依然有不少算法对于目标元素的次序是敏感。因此我们必须对上述算法做出进一步的改进。结合复杂度分析,我们还可以这样考虑:之所以造成锁定分界点前后区间的判断次数不相等,是由于我们需要判断分界点是否命中目标。假如我们不去判断分界点,而是只用一次判断决定是往前还是往后查找,那么不解决了判断次数不平衡的问题的了吗?幸运的是,采用这一思路,不仅能解决这个问题,更关键的是,只要取对区间的边界我们所查找到的结果恰好就是等值且秩最大者。二分查找的改进版本如下:

Rank vector<T>::binsearch(T const & e, Rank lo, Rank hi) //在区间[lo, hi)中寻找值为e的元素且秩最大者

{

while (1 < hi - lo) //当区间退化为单个元素时即停止

{

Rank mi = (lo + hi) / 2; //取区间[lo, hi)的中点

(e < _elem[mi]) ? hi = mi: lo = mi; //取[lo, mi)或者[mi, hi)

}

return (e == _elem[lo]) ? lo: -1; //成功返回lo或失败

}

若我们要求查找方法必须返回一个确定的秩而非报告失败的情形,那么可做约定:search()接口必须返回不大于e的秩最大者。也就是说当有多个元素命中时,我们必须返回最靠后的元素;失败时,我们应当返回小于e的最大者(包括哨兵_elem[lo - 1])。可得二分查找的适用版如下:

Rank vector<T>::binsearch(T const & e, Rank lo, Rank hi) //在区间[lo, hi)中寻找值不大于e的秩最大者

{

while (hi > lo) //当区间退化为单个元素时即停止

{

Rank mi = (lo + hi) / 2; //取区间[lo, hi)的中点

(e < _elem[mi]) ? hi = mi: lo = (mi + 1); //取[lo, mi)或者(mi, hi)

}

return lo--; //返回值符合语义的要求

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值