查找算法之二分查找(对半查找)

  当有序表采用顺序存储时,可以采用二分查找的方式查找指定关键字的元素。
  二分查找的基本思想是选择表中某一位置 i i i的元素 A i A_i Ai,设该元素的关键字为 K i K_i Ki,将 K i K_i Ki与待查找关键字 k e y key key比较。

关键字 K 0 K_0 K0 K 1 K_1 K1 K 2 K_2 K2 K 3 K_3 K3 K 4 K_4 K4…… K i − 1 K_{i-1} Ki1 K i K_i Ki k i + 1 k_{i+1} ki+1…… K n − 4 K_{n-4} Kn4 K n − 3 K_{n-3} Kn3 K n − 2 K_{n-2} Kn2 K n − 1 K_{n-1} Kn1 K n K_n Kn
元素值 A 0 A_0 A0 A 1 A_1 A1 A 2 A_2 A2 A 3 A_3 A3 A 4 A_4 A4…… A i − 1 A_{i-1} Ai1 A i A_i Ai A i + 1 A_{i+1} Ai+1…… A n − 4 A_{n-4} An4 A n − 3 A_{n-3} An3 A n − 2 A_{n-2} An2 A n − 1 A_{n-1} An1 A n A_n An

  比较的结果当然只有三种可能性:

  • k e y key key < K i K_i Ki:当带查找的关键字存在于有序表中,且该有序表是采用升序方式存储的,那么待查找关键字 k e y key key对应的元素肯定在子表( A 1 A_1 A1, A 2 A_2 A2,……, A i − 1 A_{i-1} Ai1)中。那么只需在子表中继续按这一规则进行查找便可完成查找。
  • k e y key key = K i K_i Ki:若待查找的关键字等于 K i K_i Ki时,则表示查找成功。
  • k e y key key > K i K_i Ki:当待查关键字大于当前关键字时,则表示待查找关键字对应的元素如果存储在表中,则一定在子表( A i + 1 A_{i+1} Ai+1, A i + 2 A_{i+2} Ai+2,……, A n A_n An)中,按这一规则继续查找该子表。
      那么这个 i i i如何确定呢,根据不同的规则来确定这个 i i i,可以得到不同的二分搜索方法,如对半搜索,斐波那契搜索和插值搜索等。

对半查找

  设查找子表中第一个元素的序号定为 l o w low low,最后一个元素的序号定为 h i g h high high,令 i = ( l o w + h i g h ) / 2 i=(low+high)/2 i=(low+high)/2,这种二分查找称为对半查找。对半查找算法将表划分为几乎相等的两个子表。
  从二分查找的思想中可以看出这种查找方式是一种递归查找,根据这种查找算法思想,可以很容易写出对半查找的递归算法:

typedef struct
{
	int key;
	int value;
}s_eletype;

typedef struct
{
	int size;
	s_eletype* element;
}s_list;


int _search(s_list lst, int k, int low, int high)
{
	int mid;
	if (low <= high)
	{
		mid = (low + high) / 2;
		if (k < lst.element[mid].key) return _search(lst, k, low, mid - 1);
		else if (k > lst.element[mid].key) return _search(lst, k, mid + 1, high);
		else return mid;
	}
	return -1;
}

int search(s_list lst, int k, s_eletype* x)
{
	int i = _search(lst, k, 0, lst.size - 1);
	if (i != -1)
	{
		*x = lst.element[i];
		return true;
	}
	else return false;

}

  递归算法效率往往比较低,在递归中过程中,需要不断调用递归函数本身,对栈开销比较大,调用过程中压栈和返回同样也需要花费大量时间。因此递归函数在能够转换成迭代算法时,一般采用迭代的方式进行运算。
  将_search函数以迭代的方式实现:

int _search(s_list lst, int k, int low, int high)
{
	int mid;
	while (low <= high)
	{
		mid = (low + high) / 2;
		if (k < lst.element[mid].key) high = mid - 1;
		else if (k > lst.element[mid].key) low = mid + 1;
		else return mid;
	}
	return -1;
}

  对半查找只适用于顺序存储的有序表,在运行过程中需要不断的计算子表的 m i d mid mid序号,这对于链式存储来说无法实现。

二叉判定树

  如果将有序数据构建成一颗平衡因子只有0-1的的二叉搜索平衡树,那么对半查找过程会变成什么样呢?以下列数据为例:

i0123456789
key21303641525466728397

  如果要查找 k e y key key66的元素,对半查找的过程如下:

序号0123456789
第一次21303641525466728397
第二次21303641525466728397
第三次21303641525466728397
第四次21303641525466728397

  蓝色和绿色关键字表示该次查找的子表范围,绿色表示该次查找时与待查找关键字比较的关键字。
  这是查找成功的情况,那么查找失败的情况呢?例如要查找 k e y key key67的元素,很显然过程跟上面的过程一样,只是在第最后一次查找的时候仍然没有找到匹配的关键字,程序会继续执行一次,如果是迭代算法,那么low = mid+1,此时条件已经不满足while,程序返回-1
  将这些数据建成一颗平衡因子只有0-1的的二叉搜索平衡树,那颗树会长什么样?
在这里插入图片描述
  在这颗二叉搜索树上进行查找 k e y key key值为66的元素过程是怎么样的?
在这里插入图片描述
  这颗二叉搜索树的查找路径不就是对半查找的查找过程吗?那对半查找的性能分析就可以在这颗树的基础上进行了。
  一颗有n个节点的二叉判定树的高度为[lbn]+1(本篇中:[lbn]表示向下取整,{lbn}表示向上取整)。所以对半查找在查找成功的情况下,关键字值之间的比较次数不超过[lbn]+1;对于不成功的查找,需要进行[lbn][lbn]+1次比较。
  为了讨论简单,假定表的长度为 2 k − 1 2^k-1 2k1,即构成的二叉判定树为一颗满二叉树,二叉树的高度为 k k k=lb(n+1)根据二叉树的性质,在每个元素的查找概率相等时,可以求得查找成功是的平均搜索长度为
A S L s    =    1 n ∑ i = 1 k i ∗ 2 i − 1 = 1 n ∑ i = 1 k i ∗ ( 2 i − 2 i − 1 ) = l b ( n + 1 ) + l b ( n + 1 ) n − 1 ⩽ l b ( n + 1 )                                                        ( 当 n 较 大 时 ) \begin{array}{rcl}ASL_s\;&=&\;\frac1n\sum_{i=1}^ki\ast2^{i-1}=\frac1n\sum_{i=1}^ki\ast(2^i-2^{i-1})\\&\\&=&lb(n+1)+\frac{lb(n+1)}n-1\\&\\&\leqslant&lb(n+1)\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;\;(当n\mathrm{较大时})\end{array} ASLs==n1i=1ki2i1=n1i=1ki(2i2i1)lb(n+1)+nlb(n+1)1lb(n+1)(n)
  因此,在有序表中成功查找一个元素,对半查找的平均搜索长度为 O ( l b n ) O(lbn) O(lbn)。但是需要注意的是,这棵树在查找过程中并没有实际建立,这只是为了便于分析而建的分析模型。

本篇完 😉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值