算法——二分查找

Posted 诊断协议那些事儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法——二分查找相关的知识,希望对你有一定的参考价值。


文章目录


数据查找算法

我们在使用DTC列表时,会使用升序进行排列,以供查找,常见的查找算法有顺序查找、二分查找、插值查找等,顺序查找也叫线性查找,其基本原理是对于任意一个序列以及一个给定的元素,将给定元素与序列中元素依次比较,直到找出与给定关键字相同的元素,或者将序列中的元素与其都比较完为止,当然此种算法效率偏差。


一、二分查找

二分查找也叫折半查找,是一种效率比较高的查找算法,但是使用它有个前提:必须是顺序存储的线性数据表。首先看两个例子:

输入:nums = [5,13,19,21,37,56,64,75,80,88,92],keyData = 19
输出:2
解释:19出现在nums中,并且下标为2
输入:nums = [5,13,19,21,37,56,64,75,80,88,92],keyData = 23
输出:-1
解释:23在nums中不存在,因此返回-1

二、基本思想

因为整个数组是有序的,假设为升序:
①首先选择数组中间的数据,与需要查找的查找值比较,如果相等,则查找结束,直接返回数组下标。
②如果不相等:
i.如果中间的数字大于查找值,则中间数字向右的所有数字都大于查找值,全部排除。将右值设置为中间位置的前一个!
ii.如果中间的数字小于查找值,则中间数字向左的所有数字都小于查找值,全部排除。将左值设置为中间位置的下一个!
③设置完成后,继续从新区间的中间值开始比较,即继续第一步的内容直至查找完成。
④如果没有查找到,则返回-1.

三、测试代码

使用此设计:数组元素不存在时,选择与其最接近的数组元素下标!如当查找数据小于最小数据时输出数组下标0,当查找数据大于最大数据时,输出最后一个数组下标。

#include<stdio.h>

#define TEMP_ADC_COUNT 11

static const unsigned int TEMP_ADC[TEMP_ADC_COUNT]=

	5,13,19,21,37,56,64,75,80,88,92
;

static int ABS(int data)

	if(data >= 0)
	
		return data;
	
	else
	
		return (-data);
	


/* sortedSeq是数组,size是数组的大小,keyData是需要查找的值 */
static int BinarySearch(const unsigned int *sortedSeq,unsigned int size,unsigned int keyData)

	unsigned int low = 0,mid,high = size - 1;
	if(sortedSeq == NULL || size < 1)
	return -1;
	if(1 == size)
	return 0;
	if(sortedSeq[0] >= keyData)
	return 0;
	if(sortedSeq[high] <= keyData)
	return high;

	while(low <= high)
	
		mid = low + ((high - low)/2); /* 等价于mid = (low + high)/2 ,防止溢出 */
		if(ABS(sortedSeq[mid + 1] - keyData) > ABS(sortedSeq[mid] - keyData))
		
			high = mid - 1;
		
		else
		
			low = mid + 1;
		
	
	if(ABS(sortedSeq[mid + 1] - keyData) < ABS(sortedSeq[mid] - keyData))
	
		return (mid + 1);
	
	else
	
		return mid;
	



int main(void)

	int temp1,temp2,temp3,temp4,temp5;
	temp1 = BinarySearch(TEMP_ADC,TEMP_ADC_COUNT,1);
	temp2 = BinarySearch(TEMP_ADC,TEMP_ADC_COUNT,19);
	temp3 = BinarySearch(TEMP_ADC,TEMP_ADC_COUNT,23);
	temp4 = BinarySearch(TEMP_ADC,TEMP_ADC_COUNT,36);
	temp5 = BinarySearch(TEMP_ADC,TEMP_ADC_COUNT,95);
	printf("temp1 = %d\\ntemp2 = %d\\ntemp3 = %d\\ntemp4 = %d\\ntemp5 = %d\\n",temp1,temp2,temp3,temp4,temp5);

	getchar();

	return 0;

输出结果:


当数据位于两者之间时,取数组元素下标较小的!

temp2_1 = BinarySearch(TEMP_ADC,TEMP_ADC_COUNT,16);/* 13 14 15     17 18 19*/ 
printf("temp2_1 = %d\\n",temp2_1);
输出:temp2_1 = 1 /*13对应下标为1 , 19对应下标为2 */

四、复杂度分析(升序)

我们可以将查找的过程用一棵二叉排序树来表示,二分法也称判定树,根节点即为顺序表的中间元素,根节点的左子树,全是比根节点小的元素,右子树全是比根节点大的元素,每一棵子树同样满足这一性质,如下图所示:

由上图可知,如果想在查找表中查找 21 的位置,只需要进行 3 次比较,依次和 56、19、21 进行比较,而比较的次数恰好是该关键字所在判定树中的层次(关键字 21 在判定树中的第 3 层)。对于具有 n 个结点(查找表中含有 n 个关键字)的判定树,它的层次数至多为: log2(N) + 1

总结

二分查找的缺点很明显,当在1-1000的数组(步进为1)查找2时,会从中间500开始二分,然后循环下去,这样二分次数会比较多,查找的相对路径较深。基于此我们可以优化二分算法——插值查找。
插值查找是根据查找的关键字keyData与查找表中最⼤和最⼩记录的关键字比较后的查找方法, 其核心就是在于插值的计算公式: (keyData - a[low]) / (a[high] - a[low])。举个例子,在1-1000的范围查找2,会判断2和1000的比例k,然后从数组长度*k的位置开始查找。

int InterpolationSearch(const unsigned int *sortedSeq,unsigned int size,unsigned int keyData)
    
    int low, high, mid;
    low = 1;
    high = size;
    
    while (low <= high) 
        
        //插值
        mid = low + (high - low) * (keyData - sortedSeq[low]) / (sortedSeq[high] - sortedSeq[low]);
    
        if (keyData < sortedSeq[mid]) 
            //若keyData比sortedSeq[mid]插值小,则将最高下标调整到插值下标小一位;
            high = mid - 1;
        
        else if (keyData > sortedSeq[mid])
            //若keyData比sortedSeq[mid]插值 大,则将最低下标调整到插值下标大一位;
            low = mid + 1;
        
        else 
            //若相等则说明mid即为查找到的位置;
            return mid;
        
    
    return 0;

以上是关于算法——二分查找的主要内容,如果未能解决你的问题,请参考以下文章

查找算法——二分查找

可查找重复元素的二分查找算法

算法练习(二分查找/排序)

算法练习(二分查找/排序)

学习数据结构笔记(15) --- [二分查找算法(非递归)]

二分查找又叫折半查找,是一种简单又快速的查找算法