查找(包括顺序查找和折半查找)

顺序查找

顺序查找又叫线性查找,主要用于线性表中的查找。顺序查找通常情况下分为对一般无序线性表的查找和对按关键字有序的顺序表的查找。下面进行比较讨论;

1.一般线性表的查找

作为一个直观的查找方法,其基本思想就是从线性表的一端开始,逐个检查关键字是否满足给定的条件,若满足给定的条件,则查找成功,返回在该线性表中的位置,若已经查找到线性表的另一端还是没有找到符合给定的条件,则返回查找失败的信息;

算法如下:

	typedef struct{
		ElemType *elem;				//元素存储空间基址,建表时按照实际分配长度,0号单元留空
		int TabkeLen;				//表的长度
	}SSTable;
	
	int Search_Seq(SSTable ST, ElemType key){
		ST.elem[0] = key;			//作为哨兵
		for(i = ST.TableLen; ST.elem[i] != key; --i);  //从后往前找
		return i;				//若表中不存在关键字为key的元素,将查找到 i 为 0 时退出for循环  
	}

【注】在上面的算法中,将ST.elem[0] = key作为哨兵,目的是Search_Seq内的循环不必判断数组是否越界,因为当 i = 0时,循环自动就跳出了;

效率分析

对于有 n 个元素的表,给定值 key 与表中第 i 个元素相等,即定位到第 i 个元素,需要进行 i+1 次关键字的比较,即 C i C_i = n - i + 1。
当查找成功时候,平均查找长度为: A L S i ALS_i = i = 1 N P i ( n i + 1 ) \sum_{i=1}^N{P_i(n-i+1)} ;当每个元素的查找概率相等时候,即 P i P_i = 1 n 1\over n 时,有 A L S i ALS_i = i = 1 N P i ( n i + 1 ) \sum_{i=1}^N{P_i(n-i+1)} = ( 1 + n ) 2 (1+n)\over 2 ;
当查找不成功时候,与表中关键字的比较次数显然时 n + 1 次,从而顺序查找不成功的平均查找长度为 A L S i ALS_i =n + 1;
所以可以看出,顺序查找的缺点就是当 n 比较大的时候,平均查找长度比较大,效率低;优点就是对数据的存储没有要求,对表中的有序性也没有要求,。

2.有序表的顺序查找

若在查找之前就已经知道表的关键字有序的,则查找失败时候可以不用再比较到表的另一端就能返回查找失败的信息,从而降低了顺序查找时查找失败的平均查找长度;
有序表的顺序查找的基本思想:假设表L是按照关键字从小到大排列的,查找的顺序是从前往后,待查找元素的关键字为 key,当查找到第 i 个元素时,发现第 i 个元素对应的关键字小于key,但第 i + 1对应的关键字则大于key,这时候就可以返回查找失败的信息,因为第 i 个元素之后的关键字都是大于key的,所以表中也就不存在关键字为key 的元素了。
下面根据一个例子来分析上面的基本思想(判定树来描述):树中的圆形点表示有序顺序表中存在的元素,书中的矩形结点称为失败的结点(它描述的时那些不在表中的数据值的集合);若查找到失败结点,就说明查找失败了;

已知:查找关键字25,查找序列(10,25,30,40,50,60),分析如下:
在这里插入图片描述
效率分析:

在有序表的顺序查找中,查找成功的平均查找长度和一般线性表的顺序查找是一样的;查找失败的时,查找指针一定走到了某个失败结点,这些失败结点时我们虚构的空结点,实际上时不存在的,所以到达失败结点时候所查找的长度等于它上面的一个圆形结点所在层数,查找不成功的平均查找长度在各元素查找概率相等的情况下为: A L S i ALS_i = i = 1 N q i ( l j 1 ) \sum_{i=1}^N{q_i(l_j-1)} = 1 + 2 + . . . + n + n n + 1 1+2+...+n+n\over n+1 = n 2 n\over 2 + n n + 1 n\over n+1 ;其中 q i q_i 是第 j 个失败结点的概率,在查找相等的概率下它等于 1 n 1\over n l j l_j 是第 j 个失败结点所在的层数。

【注】有序表的顺序查找和折半查找的思想是不一样的,且有序表的顺序查找的线性表可以是链式存储结构。

折半查找

折半查找又称二分查找,它仅仅适用于有序的顺序表
折半查找的基本思想:首先将给定的数值key于表中中间位置的元素比较,若相等,则查找成功,返回该元素的存储位置;若不等,则所需查找的元素只能在中间元素以外的前半部分和后半部分(例如在查找表升序排列时,若给定的key大于中间元素,则所查找的元素在后半部分),然后在缩小的范围内继续同样的查找,如此反复,知道找到或者确定表中没有所查找的元素为止;

算法如下:

int Binary_Search(SeqList L, ElemType key){
	int low = 0, high = L.TableLen - 1, mid;
	while(low <= high){
		mid = (high + low)/2;			//取中间位置
		if(L.elem[mid] == key)
			return mid;					//查找成功返回所在位置
		else if(L.elem[mid] > key)
			high = mid -1;				//前半部分继续查找
		else if(L.elem[mid] < key)
			low = mid + 1				//后半部分继续查找
		} 
	return -1;							//查找失败
}

举例如下:

例如11个元素的有序表序列为:(7,10,13,16,19,29,32,33,37,41,43)关键字单调递增。
设指针 low 和 high 分别指向关键字序列的上界和下界,即 low=1,high=11。指针 mid 指向序列的中间位置,即 mid=[(low+high)/2]=6([]向下取整)。在这里 low 指向关键字7,high 指向关键字43,mid 指向关键字29。
    (7,10,13,16,19,29,32,33,37,41,43)
     ↑         ↑          ↑
     low       mid         high
 (1)首先将mid所指向的元素与key进行比较,因为 key=11,小于29,这就是说明待查找的关键字一定位于 mid 和 low 之间。因此下面的查找工作只需在[low,mid-1]中进行。令指针 high 指向 mid-1,high = mid -1,重新求 mid 为3;
    (7,10,13,16,19,29,32,33,37,41,43)
     ↑    ↑    ↑
     low  mid  high
 (2)再将mid所指向的元素与 key 进行比较,因为key=11,小于13,说明待查找的关键字一定位于mid和low之间。所以下面的查找工作仍然只需在[low,mid-1]中进行。令指针 high 指向 mid-1,high = mid -1,重新求 mid 为1;
 (7,10,13,16,19,29,32,33,37,41,43)
  ↑ ↑
 mid high
 low
(3)接下来仍然将mid所指元素与 key 进行比较,因为 key=11,大于7,说明查找的元素不存在,则必在范围[mid+1,high]中,令 low = high +1 = 2,mid = (2+2)/2 = 2;
 (7,10,13,16,19,29,32,33,37,41,43)
  low ↑↑ high
    ↑ mid
(4)此时子表中只含有一个元素,且10不等于11,故表中不存在待查元素;

折半查找的过程可以用下图所示的二叉树来描述,称为判定树。树中每个圆点表示一个记录,结点中的值为该记录的关键字值,树中最下面的页结点都是方形的,表示查找不成功的情况。
在这里插入图片描述
从图中我们可以看出,判定树为一颗平衡二叉树;且用折半查找法查找到给定值的比较次数最多不会超过树的高度

效率分析

在等概率的查找时候,查找的平均长度为: A L S i ALS_i = 1 n 1\over n i = 1 N l j \sum_{i=1}^N{l_j} = 1 n 1\over n (1×1+2×2+…+h× 2 h 1 2^{h-1} )= n + 1 n n+1\over n log 2 ( n + 1 ) \log_2{(n+1)} -1 \approx log 2 ( n + 1 ) \log_2{(n+1)} -1,其中,h为树的高度,并且元素个数为n时树的高度为h=[ log 2 ( n + 1 ) \log_2{(n+1)} ](向上取整)。所以折半查找的时间复杂度为O( log 2 n \log_2{n} ),平均情况下比顺序查找效率高

【注】该查找方法仅适用于顺序存储结构,不适用于链式存储结构,且要求元素按关键字有序排列。

原创文章 44 获赞 47 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_44480968/article/details/105549745