裴波那挈查找有序数据c语言,【算法】先生,您点的查找套餐到了(二分、插入和斐波那契查找)...

参考资料

《算法(java)》                           — — Robert Sedgewick, Kevin Wayne

《数据结构》                                  — — 严蔚敏

Interpolation Search[插值查找]     — —  维基百科

Fibonacci Search[斐波那契查找]   — —  GeeksforGeeks

根据输入的一个关键字(Key),  在一个有序数组内查找与该关键字值相等的元素, 若找到则将该元素的下标返回, 若未查找到则返回 -1。 这是一种很常见的操作。

今天的文章主要介绍在有序数组中的三种查找方法:

二分查找

插值查找

裴波纳契查找

在数据量很小的情况下, 我们可能会选择用顺序查找的方式处理。

顺序查找

顺序查找, 就是逐个遍历数组中的每一个元素,逐个比较它们和关键字是否相等,当查找到相等元素时, 遍历停止。

例如在下面这个数组中查找值为8的元素的下标,  查找成功需要进行9次比较。

872ecdfc956fd88b78744d59bb4e9433.png

当数组的规模逐渐扩大时候, 因为比较次数太多,顺序查找耗时太长。 所以我们想, 有什么方法能减少比较的次数呢?

二分查找

在介绍二分查找前, 先让我来插一段故事。。。

这个名扬京华的食府, 在一天的多数时辰里,都是坐无虚席。

就在这时,一位慕名前来的食客。在等候许久后,终于等来了他点的菜品。

这位客人,叫做有序数组。

“先生您好, 这是您点的查找套餐”, 店小二终于赶了过来。 摆上了第一道菜。

“二分菜系么”,客人轻语着, 瞧见那盛菜的雪色素瓷上, 清晰的镌刻着 1/2 这个数字。

客人笑了,“仅凭一道家常菜,便能艳压京华众食府, 贵店的厨子果然名不虚传呢”

(未完待续)

咳咳,回到正文——二分查找

二分查找的思想

设置一个循环,不断将数组的中间值(mid)和被查找的值比较,如果被查找的值等于a[mid],就返回mid; 否则,就将查找范围缩小一半。如果被查找的值小于a[mid], 就继续在左半边查找;如果被查找的值大于a[mid],  就继续在右半边查找。 直到查找到该值或者查找范围为空时, 查找结束。

279c9ef133467b9bf36ccebff665f09e.png

基于数组的有序性, 每次都将当前的数组分为两半, 通过关键字和中间元素的比较, 立即排除掉其中不可能存在和键值相等的元素的那一半。

这样,每次减少的一半元素的比较, 前后叠加起来, 就是二分查找相对于顺序查找提高的性能。

二分查找代码

/**

* @Author: HuWan Peng

* @Date Created in 22:55 2017/12/7*/public class BinarySearch {/**

* @description: 二分查找

* @param a:   待查找数组

* @param key: 查找的关键字*/public staticint search (int [] a, intkey) {int low = 0;int high = a.length-1;intmid;while(low<=high){

mid= (low + high)/2;

if(key

high= mid - 1;

}else if(key>a[mid]) {

low= mid + 1;

}else{returnmid;

}

}return -1;

}

}

通过mid = (low + high) / 2,并比较Key和a[mid]大小, 根据比较的结果, 用high = mid - 1和low = mid + 1 “丢掉”一半的元素(从mid = (low + high) / 2 可以看出被排除的一半元素不会纳入到下一次的比较中了)

整个过程中,左游标 low 和右游标 high 互相靠近, 最后的结果可能有两个:

1. 在low和high交叉前(low>high) 查找成功,查找结束

2.  数组中没有和关键字等值的元素, 最后low和high交叉(low>high), 跳出while循环, 返回 -1。

二分查找轨迹

下图所示的是我们在数组中查找关键字为8的元素的过程, 一开始的时候左右游标分别指向数组的首末位置

f44cb30bff57acf1c3d5a9709b6a428a.png

根据左右游标的下标取中间位置下标 mid = (0 + 9)/2 = 4,  而a[mid] = a[4] = 4,  Key = 8, 因为Key>a[mid],所以Key必定出现在mid的右半边, 左半边的元素可排除(a[0]~a[4]), 将左半边待比较的元素“丢弃”。 “丢弃”的方法是将左游标 low 移动到当前 mid 右边相邻的位置,这样左半边的元素就不会出现在下次比较中了。

1273942dbb537381f15b0f7d941d2045.png

根据左右游标的下标取中间位置下标 mid = (5 + 9)/2 = 7,  而a[mid] = a[7] = 7,  Key = 8, 因为Key>a[mid],所以Key还是在数组的右半边,  mid左半边的元素可排除(a[5]~a[7]), 丢弃左半边待比较的元素——将左游标low移动到 8 的位置。

703e413e0e31b7f84d1614f1189dc7fa.png

根据左右游标的下标取中间位置下标 mid = (8 + 9)/ 2 = 8, 而a[mid] = a[8] = 8, Key = 8, 因为Key == a[mid], 查找成功。

ffde8d81b0f0afb826cbb4955754aa0b.png

使用了二分查找后,  我们仅仅只比较了3次(比起比较了8次的顺序查找)

插值查找

看到这里你可能有点累了,再来段故事(接上文)

客人并没有像其他食客那样,面对珍馐美味而饕餮一番。

而是浅尝一口后, 用手巾擦擦嘴, 对店小二道:

“《二分》这道菜味道固然不错,软滑香糯,鲜美多汁”

“可这并不是你们大厨最富盛名的菜色吧!”,客人朗声说道, 平静的脸上似有似无地飘着几分愠怒的神色。

店小二一惊, 深知这位客人不是好伺候的主。“请您稍等!”,小二说完后便禀报大厨。

一刻钟后, 第二道菜上来了。

“插值菜系么?” 这位口味刁钻的客人竟面露喜色,又重新拿起了调羹。

(未完待续)

回到正题:

插值查找的思路

上面的二分查找,每次是从数组的中间位置查找的, 让我们把思维发散一下:查找的位置一定要从中间开始查找吗?

打个比方:我们在一本英文字典里面查找apple这个单词的时候, 你肯定不会从字典中间开始查找, 而是从字典开头部分开始翻,因为觉得这样的找法才是比较快的。

这给我提供了一个思路: 如果能在查找前较准确地预测关键字在数组中的位置的话,这样设计出的查找方法能比二分查找提高更多的性能!基于这种思想,我们设计了插值查找的算法。

插值查找和二分查找非常相似, 只要对原代码做少许变动就可以了。

二分查找中关键的一行代码,是mid = (low + high) / 2, 转变一下就是mid = low + (high - low)/2,   (high-low)后面乘的这1/2就是二分查找每次查找的位置。要实现插值查找, 只要把这里的1/2替换成我们所预测的关键字的位置占数组总长度的比例就可以了。

这个比例,可以用公式(key - a[low])/ (a[high] - a[low])来计算, 合起来插值查找对mid的计算公式是:

mid = low + (high - low)*(key - a[low])/ (a[high] - a[low])

在元素数值均匀分布的有序数组里面, 用这种方法查找是很快的。特别的,对绝对均匀分布的数组(相邻元素差值相同), 插值查找用一次比较就能查找成功:

40a50cd8e3a8852a2cdffdc561fb8534.png

当然了, 前提是数组中元素数值是均匀分布的, 如果是对 1,2,40,99,1000这种分布很不均匀的数组, 插值查找的计算会起到反效果, 就不如二分查找了。

插值查找代码

代码如下:

/**

* @Author: HuWan Peng

* @Date Created in 23:09 2017/12/7*/public class InterpolationSearch {/**

* @description: 插值查找*/public staticint search (int [] a, intkey) {int low = 0;int high = a.length-1;intmid;while(a[low]!=a[high]&&key>=a[low]&&key<=a[high]){ //这个判断条件很重要! 不能缺少

mid = low + (high - low)*(key - a[low])/(a[high] - a[low]);

if(key

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值