【1】查找概论

查找表是由同一类型是数据元素(或记录)构成的集合。

关键字是数据元素中某个数据项的值,又称为键值。

若此关键字可以唯一标识一个记录,则称此关键字为主关键字。

查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。

查找分为两类:静态查找表和动态查找表。

静态查找表:只作查找操作的查找表。主要操作:

(1)查询某个“特定的”数据元素是否在查找表中。

(2)检索某个“特定的”数据元素和各种属性。

动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经已经存在的某个数据元素。 主要操作:

(1)查找时插入数据元素。

(2)查找时删除数据元素。

好吧!两者的区别: 静态查找表只负责查找任务,返回查找结果。

而动态查找表不仅仅负责查找,而且当它发现查找不存在时会在表中插入元素(那也就意味着第二次肯定可以查找成功)

【2】顺序表查找

顺序表查找又称为线性查找,是最基本的查找技术。 它的查找思路是:

逐个遍历记录,用记录的关键字和给定的值比较:

若相等,则查找成功,找到所查记录; 反之,则查找不成功。

顺序表查找算法代码如下:

对于这种查找算法,查找成功最好就是第一个位置找到,时间复杂度为O(1)。

最坏情况是最后一个位置才找到,需要n次比较,时间复杂度为O(n) 显然,n越大,效率越低下。

【3】有序表查找

所谓有序表,是指线性表的数据有序排列。

(1)折半查找

关于这个算法不做赘述,代码如下:

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. // 折半查找算法(二分查找)
  5. int Binary_Search(int* a,int n,int key)
  6. {
  7. int low = , high = n, mid = ; // 初始化
  8. while (low <= high) // 注意理解这里还有等于条件
  9. {
  10. mid = (low + high)/; // 折半
  11. if (key < a[mid])
  12. high = mid -; // 最高小标调整到中位小一位
  13. else if (key > a[mid])
  14. low = mid + ; // 最低下标调整到中位大一位
  15. else
  16. return mid; // 相等说明即是
  17. }
  18. return ;
  19. }
  20.  
  21. void main ()
  22. {
  23. int a[] = {,,,,,,,,,,};
  24. int n = Binary_Search(a,, );
  25. if (n != )
  26. cout << "Yes:" << n << endl;
  27. else
  28. cout << "No:" << endl;
  29. }

折半查找算法的时间复杂度为O(logn)。

(2)插值查找

考虑一个问题:为什么是折半?而不是折四分之一或者更多呢? 好吧,且看分解:

(3)斐波那契查找

斐波那契查找利用了黄金分割原理来实现。 如何利用斐波那契数列作为分割呢?

为了理清这个查找算法,首先需要一个斐波那契数列,如下图所示:

查找算法如下描述:

注意阅读以下详解之前,请先编译并运行第四部分的实例代码,结合代码再理解算法。

首先要明确一点:

如果一个有序表的元素个数为n,并且n正好是某个斐波那契数-1,即n == F[k]-1时,才能用斐波那契查找法。

1. 如果有序表的元素个数n不等于某个斐波那契数-1,即n != F[k]-1,如何处理呢?

 这时必须要将有序表的元素个数扩展到比n大的第一个斐波那契数-1的个数才符合算法的查找条件。

 通俗点讲,也就是为了使用斐波那契查找法,那么要求所查找顺序表的元素个数n必须满足n == F[k]-1这样的条件才可以。

 因为查找表为从小到大的顺序表,所以如果数据元素个数不满足要求,只有在表末用顺序表的最大值补满。

 代码中第9-10行的作用恰是如此。

2. 对于二分查找,分割点是从mid= (low+high)/2开始。

 而对于斐波那契查找,分割是从mid = low + F[k-1] - 1开始的。 为什么如此计算?

 用实例验证,比如本例中: 第一次进入查找循环时,数组元素个数准确说应该是12(包括随后补满的元素)

 而黄金分割点比例为0.618,那么12*0.618=7.416,此值对应12个元素应该为a[8]

 观察程序运行第一次mid=1+F[7-1]-1=8,正是此原理所体现。

 key=59,a[8]=73,显然key<a[8],可知low=1,high=7,k=7-1=6

 注意此条件意思即为7个数据元素,正好满足F[6]-1=7的再次查找客观要求

 而同理,黄金分割点比例为0.618,那么7*0.618=4.326,此值对应7个元素应该为a[5]

 再看第二次进入循环mid=1+F[6-1]-1=5,正是此原理所体现。

 key=59,a[5]=47,显然key>a[5],可知low=6,high=7,k=6-2=4

 注意此条件意思即为2个数据元素,正好满足F[4]-1=2的再次查找客观要求

 而同理黄金分割点比例为0.618,那么2*0.618=1.236,此值对应2个元素中的第二个即为a[7]

 key=59,a[7]=62,显然key<a[7],可知low=6,high=6,k=4-1=3

 同理mid=6+F[3-1]-1=6。此时a[6]=59=key。 即查找成功。

3. 注意紧接着下面一句代码可以改写为:

 return  (mid <= n) ? mid : n;

 当然这样写也没有功能错误,但是细细琢磨还是有逻辑问题:

 mid == n时,返回为n; mid > n时返回也是n。

 那么到底n属于那种情况下的返回值呢?是否有违背if的本质!

 窃以为写成if(mid < n)会合理些。

 另外,许多资料对于这步判断描述如下:

 return  (mid <= high) ? mid : n;

 其实分析至此,我认为这种写法从代码逻辑而言更为合理。

4. 通过上面知道:数组a现在的元素个数为F[k]-1个,即数组长为F[k]-1。

 mid把数组分成了左右两部分,左边的长度为:F[k-1]-1

 那么右边的长度就为(数组长-左边的长度-1): (F[k]-1)-(F[k-1]-1)= F[k]-F[k-1]-1 = F[k-2] - 1

5. 斐波那契查找的核心是:

a: 当key == a[mid]时,查找成功;

b: 当key<a[mid]时,新的查找范围是第low个到第mid-1个,此时范围个数为F[k-1] - 1个,

 即数组左边的长度,所以要在[low, F[k - 1] - 1]范围内查找;

c: 当key>a[mid]时,新的查找范围是第mid+1个到第high个,此时范围个数为F[k-2] - 1个,

 即数组右边的长度,所以要在[F[k - 2] - 1]范围内查找。

关于斐波那契查找, 如果要查找的记录在右侧,则左侧的数据都不用再判断了,不断反复进行下去。

对处于中间的大部分数据,其工作效率要高一些。

所以尽管斐波那契查找的时间复杂度也为O(logn),但就平均性能来说,斐波那契查找要优于折半查找。

可惜如果是最坏的情况,比如这里key=1,那么始终都处于左侧在查找,则查找效率低于折半查找。

还有关键一点:折半查找是进行加法与除法运算的(mid=(low+high)/2)

插值查找则进行更复杂的四则运算(mid = low + (high - low) * ((key - a[low]) / (a[high] - a[low])))

而斐波那契查找只进行最简单的加减法运算(mid = low + F[k-1]-1)

在海量数据的查找过程中,这种细微的差别可能会影响最终的效率。

【4】斐波那契算法代码实现

实例算法代码如下:

  1. #include <iostream>
  2. #include <assert.h>
  3. using namespace std;
  4.  
  5. #define MAXSIZE 11
  6.  
  7. // 斐波那契非递归
  8. void Fibonacci(int *f)
  9. {
  10. f[] = ;
  11. f[] = ;
  12.  
  13. for (int i = ; i < MAXSIZE; ++i)
  14. {
  15. f[i] = f[i-] + f[i-];
  16. }
  17. }
  18. // 斐波那契数列
  19. /*---------------------------------------------------------------------------------
  20. | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
  21. ----------------------------------------------------------------------------------
  22. | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 |
  23. -----------------------------------------------------------------------------------*/
  24. // 斐波那契数列查找
  25. int Fibonacci_Search(int *a, int n, int key)
  26. {
  27. int low = ; // 定义最低下标为记录首位
  28. int high = n; // 定义最高下标为记录末位(一般输入的参数n必须是数组的个数减一)
  29.  
  30. int F[MAXSIZE];
  31. Fibonacci(F); // 确定斐波那契数列
  32.  
  33. int k = , mid = ;
  34. // 查找n在斐波那契数列中的位置,为什么是F[k]-1,而不是F[k]?
  35. while (n > F[k]-)
  36. {
  37. k++;
  38. }
  39. // 将不满的数值补全
  40. for (int i = n; i < F[k]-; ++i)
  41. {
  42. a[i] = a[high];
  43. }
  44. // 查找过程
  45. while (low <= high)
  46. {
  47. mid = low + F[k-] - ; // 为什么是当前分割的下标?
  48. if (key < a[mid]) // 查找记录小于当前分割记录
  49. {
  50. high = mid - ;
  51. k = k - ; // 注意:思考这里为什么减一位?
  52. }
  53. else if (key > a[mid]) // 查找记录大于当前分割记录
  54. {
  55. low = mid + ;
  56. k = k - ; // 注意:思考这里为什么减两位?
  57. }
  58. else
  59. {
  60. return (mid <= high) ? mid : n; // 若相等则说明mid即为查找到的位置; 若mid > n 说明是补全数值,返回n
  61. }
  62. }
  63. return -;
  64. }
  65. void main()
  66. {
  67. int a[MAXSIZE] = {,,,,,,,,,,};
  68. int k = ;
  69. cout << "请输入要查找的数字:" << endl;
  70. cin >> k;
  71. int pos = Fibonacci_Search(a, MAXSIZE-, k);
  72. if (pos != -)
  73. cout << "在数组的第"<< pos+ <<"个位置找到元素:" << k;
  74. else
  75. cout << "未在数组中找到元素:" << k;
  76. }

若结合以上相关分析深入理解代码。

Good  Good  Study, Day   Day  Up.

顺序  选择  循环  总结

查找(顺序表&有序表)的更多相关文章

  1. 查找->静态查找表->折半查找(有序表)

    文字描述 以有序表表示静态查找表时,可用折半查找算法查找指定元素. 折半查找过程是以处于区间中间位置记录的关键字和给定值比较,若相等,则查找成功,若不等,则缩小范围,直至新的区间中间位置记录的关键字等 ...

  2. java数据结构之有序表查找

    这篇文章是关于有序表的查找,主要包括了顺序查找的优化用法.折半查找.插值查找.斐波那契查找: 顺序优化查找:效率极为底下,但是算法简单,适用于小型数据查找: 折半查找:又称为二分查找,它是从查找表的中 ...

  3. 查找->静态查找表->顺序查找(顺序表)

    文字描述 顺序查找的查找过程为:从表中最后一个记录开始,逐个进行记录的关键字和给定值的比较,若某个记录的关键字和给定值比较相等,则查找成功,找到所查记录:反之,若直至第一个记录,其关键字和给定值比较都 ...

  4. Java 二维数组,排序、切换顺序,查表法二进制十进制,这班查找、排序(冒泡、选择)、遍历,获取最大小值(4)

    Java 二维数组,排序.切换顺序,查表法二进制十进制,折半查找.排序(冒泡.选择).遍历,获取最大小值(4)

  5. Java数据结构与算法(1) - ch02有序表(OrderedArray)

    有序表需要掌握的插入方法,删除方法和二分法查找方法. 插入方法: 从前往后找到比要插入的值大的数组项,将该数组项及之后的项均后移一位(从最后一项起依次后移),最后将要插入的值插入当前数组项. 删除方法 ...

  6. JAVA通过继承线性表来实现有序表

    1,对于线性表而言,里面的元素是无序的,可以随意地将新元素增加到线性表中而不需要考虑该元素在线性表中的位置.但是,对于有序表而言,其中的元素是按照某种方式进行排序的,因此在有序表中插入元素时,需要按照 ...

  7. 将两个各有n个元素的有序表归并成一个有序表,其最多的比较次数

    最多的比较次数是当两个有序表的数据刚好是插空顺序的时候,比如:第一个序列是1,3,5,第二个序列是2,4,6,把第二个序列插入到第一个序列中,先把第二个序列中的第一个元素2和第一个序列依次比较,需要比 ...

  8. YTU 2987: 调整表中元素顺序(线性表)

    2987: 调整表中元素顺序(线性表) 时间限制: 1 Sec  内存限制: 2 MB 提交: 1  解决: 1 题目描述 若一个线性表L采用顺序存储结构存储,其中所有元素都为整数.设计一个算法,将所 ...

  9. Hdu5737-Differencia(有序表线段树)

    题意很直观,我就不说了. 解析:这是我以前没有接触过的线段树类型,有序表线段树,每个节点申请了两段空间,主要是为了保存左边儿子会有多少比v小的,右边儿子会有多少比v小 的,所以在建树过程中要归并排序. ...

随机推荐

  1. Oracle deadlock SX/SSX caused by no index on foreign key.

    Example to show the dead lock caused by lack of index on foreign key of child table. Session 1: crea ...

  2. linux sed命令参数及用法详解

    linux sed命令参数及用法详解 http://blog.csdn.net/namecyf/article/details/7336308 1. Sed简介 sed 是一种在线编辑器,它一次处理一 ...

  3. C# Excel写入

    基本思路,就是using Microsoft.Office.Interop.Excel;然后启动excel来处理 创建excel文件,代码如下: if (File.Exists(path)) { re ...

  4. python_如何建立包

    步骤: (1)包的名称为drawing (2)drawing中建立模块color和shape 视图: 备注: (1) E:/python_script/已经加入到系统变量path中 (2) 建立包时, ...

  5. SQL Server字符串函数(超实用)

    1. len():计算字符串长度 2. lower().upper():字符串转换为大.小写 3. ltrim().rtrim():截去字符串左.右侧空格 4. space():返回由重复的空格组成的 ...

  6. ON_NOTIFY_REFLECT : Message Reflection for Windows Controls

    转自: https://msdn.microsoft.com/en-us/library/eeah46xd.aspx TN062: Message Reflection for Windows Con ...

  7. spring 依赖注入 小结

    通过 @Autoiwired注解  和接口  注入实现这个接口的实现类 的  类  也必须是 可注入的(必须归spring容器所管理)

  8. IntelliJ IDEA 常用设置讲解2

    IntelliJ IDEA 有很多人性化的设置我们必须单独拿出来讲解,也因为这些人性化的设置让我们这些 IntelliJ IDEA 死忠粉更加死心塌地使用它和分享它. 常用设置 如上图 Gif 所示, ...

  9. 学习CSS3BUTTON(二)

    今天,继续学习其源代码: button { margin-left: 0; margin-right: 0; *padding: 5px 5px 3px 5px; } /*margin-left:设定 ...

  10. ofbiz进击 第一节。 新建自己的webapp项目

    创建一个webapp的过程更新下来项目(直接从svn上面切下来就好),要先ant clean 下,然后在重新ant下.一: start sheel here :ant create-component ...