对有序表进行查找运算的时候,可以通过缩减问题的规模,大幅度提高查找效率。

顺序表的查找 - 二分查找法_结点

 

 

 首节点 5 的位置为0,尾结点 为 199 的地址为 11;

求和折半后( (11+1)/ 2 )计算出中间位置的地址为 5;

与 位置5 上的元素 43 比较,21 小于 43,因此 21 只能出现在左半段;

缩小查找范围,舍弃右半段;

重复折半查找的过程:

顺序表的查找 - 二分查找法_结点_02

 

 

计算出中间位置为2 (此处2,指的是下标为2的位置),与 位置2 的 元素12 相比较,21>12。因此 21 只能出现在右半段,舍弃左半段。

现在查找段的首结点地址为3,尾结点地址为4,

顺序表的查找 - 二分查找法_递归_03

 

 

求和折半后,计算出中间位置的地址为 3,与 位置3 上的 元素21 比较,恰好等于待查找元素21,查找成功!

核心思想:① 计算中值位置;② 缩小查找区间。

 left 表示起点,right 表示终点,mid 表示中值点,数组 a 存放元素,x 为待查找元素

left 表示起点
right 表示终点
mid 表示中值点
数组 a[] 存放元素
x 待查找元素

三条语句:

① 计算中值点:mid = (left + right) / 2

② 若 x = a[mid],查找成功,返回mid,结束;

  2.1 若 x < a[mid],则往左缩小查找区间,重复上述过程;

  2.2 若 x > a[mid],则往右缩小查找区间,重复上述过程。

代码:

int binary_search(int a[],int x,int left,int right){

  int mid;

  mid = ( left + right )/ 2;// 计算中值点

  if( x == a[mid] )// 若 x = a[mid],查找成功,返回mid

    return mid;

  if(x < a[mid] )

    return binary_search(a,x,left,mid - 1);// 问题的规模缩小了,但是问题的性质没变化,可以采用递归的方式,只是查找区间变为了 left ~ mid - 1 阶段。

  else

    return binary_search(a,x,mid + 1,right);

}

 

 

例题:用二分法查找元素 83

下标 0 1 2 3 4 5 6 7 8 9 10 11
元素 5 8 12 21 29 43 52 64 70 81 87 199
  left         mid           right

① left = 0,right = 11;

② 计算中值点:mid = (0+11)/ 2;

③ 第一次比较结果:x > a[5] (83 > 43),因此去掉左半段,修改 left 的值 left = mid + 1 = 6,变成如下:

下标 6 7 8 9 10 11
元素 52 64 70 81 87 199
  left = mid + 1 = 6         right

 

 

开始第二次比较:

① left = 6,right = 11;

② 计算中值点:mid =(6 + 11)/ 2 = 8;

③ 第二次比较结果:x > a[8](83 > 70),因此去掉左半段,修改 left 值,left = mid + 1 = 9,变成如下:

下标 9 10 11
元素 81 87 199
  left = mid + 1 = 9   right

 

 

开始第三次比较:

① left = 9,right = 11;

② 计算中值点:mid =(9+11)/ 2 = 10;

③ 第三次比较结果:x < a[10](83 < 87),因此去掉右半段,修改 rigth 值,right = mid - 1 = 9,变成如下:

下标 9 10 11
元素 81 87 199
  right = mid - 1 = 9 mid right

 

下标 9
元素 81
  right = mid - 1 = 9;
left = 9;

 

开始第四次比较:

① left = 9,right = 9;

② 计算中值点:mid =(9+9)/ 2 = 9;

③ 第四次比较结果:x > a[9](83 > 81),因此修改 left 值,left = mid + 1 = 10,当 left > right 时,查找段已经不复存在,换言之,查找失败。

 

二分查找算法----代码部分:

① 第一种写法

int binary_search(int a[],int x,int left,int right){

  int mid;

  if(left > right) return -1;// 查找失败,返回 -1

  mid  =(left + right)/ 2;

  if( x == a[mid] )

    return mid;// 查找到,返回

  if( x < a[mid] )

    return binary_search(a,x,left,mid - 1);// 左端查找

  return binary_searc(a,x,mid + 1,right);// 右端查找

}

 

② 第二种写法

int binary_search(int a[],int n,int x){

  int left,right,mid;

  left = 0;right = n-1;// 确定查找段的 起点 和 终点

  while(left <= right){

    mid = (left + right)/  2;

    if(x == a[mid])return mid;// 查找到,返回

    if(x < a[mid])

      right = mid - 1;// 左端查找

    else

      left = mid + 1; // 右端查找

  }

  return -1;

}

 

关于上述两种算法:

第 ① 种:使用递归,代码更简洁清晰,可读性更好。但由于递归需要系统堆栈,所以空间消耗要比非递归

代码大很多。而且,如果递归深度太大,系统可能撑不住。

第 ② 种:速度快,结构简单,但可读性略逊一筹。

二分查找算法的核心思想是 “分而治之” :将一个难以直接解决的大问题,分割成一些规模较小的,性质相同的子问题,以便各个击破,分而治之。

二分查找算法有两个前提:① 顺序存储;② 有序表。

 

二分查找法性能分析:

此处借助 “判定树” 简要分析一下算法的时间复杂度:

① 以有序数组 a[6] 为例,执行二分算法时

② 首先比较的是下标为 2 的元素 24,mid = (5+0)/  2 = 2 // 注意,此处的 2 ,是下标!

③ 如果查找元素为 24,查找成功!

④ 如果比 24 小,那么再计算出来的 mid 值为 0,mid =(第②步里的 mid - 1)/ 2 =(2-1)/  2 = 0

关于此处mid:开始时 mid =(5+0)/  2 = 2 ,由于是 lift 和 right 都是 int 型,因此计算结果如例所示:

9/2 = 4;7/2 = 3;11/2 = 5;

⑤ 如果查找元素比 14 大,那么再计算出的 mid 值就是 1。// 舍弃 mid 与 mid 左侧段落,然后mid + 1 = 1

顺序表的查找 - 二分查找法_二分查找算法_04

 

成功的查找:查找路径终结于结点 i,查找长度 = 结点 i 的层数

判定树中,24 需要一次比较,14和53 需要两次比较,17和43和76 需要三次比较,因此查找这个六个结点的平均查找长度为 =(1 + 2 + 2 + 3 + 3 + 3)/  6 = 14/6,括号里的数字,可以这样理解:

① 假如我们寻找的元素是 24 ,那么开始时,仅寻找一个,就能找到它,因此查找长度为1;

② 假如我们寻找的元素是 14 ,那么开始时,需要途径两个元素(包括被寻找元素本身),因此长度为2;

又因为第二层,有两个元素,也就是 “14” 和 “53”,所以查找长度就是 2 + 2

③ 第三层同理

 

不成功的查找:

顺序表的查找 - 二分查找法_结点_05

 

上图中用方框表示的结点,就是外结点。查找长度 = 外结点 i 之父的层数;

下面 x 对应的是待查找的元素

图中 0 灰色方片,对应 x < a[0]  // 假如 x < 14 ,这个不等式可以表达为 x < a[0]

图中 1 灰色方片,对应 a[0] < x < a[1]  // 假如 14< x < 17,这个不等式可以 表达为 a[0] < x < a[1]

图中 2 灰色方片,对应 a[1] < x < a[2]  // 同上

 

图中 5 灰色方片,对应 a[4] < x < a[5]  // 同上

图中 6 灰色方片,对应 x > a[5]  // 同上

查找不成功的平均查找长度 =(2+3+3+3+3+3+3)/ 7 = 20 / 7 

2: 24 → 14 → 方片0,所以两次 // 这是 x < 14 的情况;

3: 24 → 14 → 17 → 方片1,所以三次 // 这是 14< x < 17 的情况;

3: 24 → 14 → 17 → 方片2,所以三次 // 这是 17< x < 24 的情况;

3: 24 → 53 → 43 → 方片3,所以三次 // 这是 24< x < 43 的情况;

3: 24 → 53 → 43 → 方片4,所以三次 // 这是 43< x < 53 的情况;

3: 24 → 53 → 76 → 方片5,所以三次 // 这是 53< x < 76 的情况;

3: 24 → 53 → 76 → 方片6,所以三次 // 这是 x > 76 的情况。

因为 查找了 7 次,所以 查找总数 / 7 = 查找不成功的平均查找长度

 

顺序表的查找 - 二分查找法_折半_06

 

顺序表的查找 - 二分查找法_二分查找算法_07