数据结构——顺序表查找和有序表查找

1.顺序表查找

       又叫线性查找,是最基本的查找技术:从表中第一个(或最后一个)记录开始,逐个与给定值进行比较,若某个记录的关键字和给定值相等,则查找成功。如果顺序表遍历完毕,关键字和给定值都不相等,则查找失败。

(1)基本算法

int Sequential_Search(int* a, int n, int key) {
	int i;
	for (int i = 1; i <= n; i++) {
		if (a[i] == key)
			return i;//输出位置
	}
	return 0;
}

就是在数组a中查看有没有给定值(注意索引是从1开始的,原因是因为有优化算法)。 

(2)优化算法

int Sequential_Search2(int* a, int n, int key) {
	int i=n;
	a[0] = key;
	while (a[i] != key) {
		i--;
	}
	return i;
}

       给定a[0]=key,称a[0]为哨兵,这时代码从尾部开始查找,如果有key则返回i值,如果没有,那么一定在i==0的时候返回(因为a[0]等于key),这样返回值也是0。这个算法的好处是免去了在查找过程中每一次比较后都要判断查找位置是否越界。

时间复杂度:O(n)

示例:

int main()
{
	int a[MaxSize];
	int n;
	cout << "输入元素个数:";
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		int t;
		cin >> t;
		a[i + 1] = t;
	}
	cout << "输入查找的元素:";
	int key;
	cin >> key;
	int h = Sequential_Search2(a, n, key);
	if (h > 0)
		printf("该元素的位置为:%d\n", h);
	else
		cout << "查找失败!";
}

 

2.有序表查找

当线性表有序时的查找方式。

(1)折半查找

       又称二分查找,它的前提是线性表中的记录必须是关键码有序(通常从小到大排序),线性表必须采取顺序存储。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

int Binary_Search(int* a, int n, int key) {
	int low, high, mid;
	low = 1;
	high = n;
	while (low <= high) {
		mid = (low + high) / 2;//折半
		if (key < a[mid])//查找值比中值小
			high = mid - 1;//最高下标调整到中位下标小一位
		else if (key > a[mid])//查找值比中值大
			low = mid + 1;//最低下标调整到中位下标大一位
		else
			return mid;
	}
	return 0;
}

时间复杂度:O(logn) 

示例:

int main()
{
	int a[MaxSize];
	int n;
	cout << "输入元素个数:";
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		int t;
		cin >> t;
		a[i + 1] = t;
	}
	cout << "输入查找的元素:";
	int key;
	cin >> key;
	int h = Binary_Search(a, n, key);
	if (h > 0)
		printf("该元素的位置为:%d\n", h);
	else
		cout << "查找失败!";
}

(2)插值查找

       是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心在于插值的计算公式:(key-a[low])/(a[high]-a[low])

int Interpolation_Search(int* a, int n, int key) {
	int low, high, mid;
	low = 1;
	high = n;
	while (low <= high) {
		mid = low + (high - low) * (key - a[low]) / (a[high] - a[low]);
		if (key < a[mid])//查找值比中值小
			high = mid - 1;//最高下标调整到中位下标小一位
		else if (key > a[mid])//查找值比中值大
			low = mid + 1;//最低下标调整到中位下标大一位
		else
			return mid;
	}
	return 0;
}

其中核心代码就是:

mid = low + (high - low) * (key - a[low]) / (a[high] - a[low]);//插值

   (high-low)*(key-a[low])/(a[high]-a[low]) 就是mid的改变量,也就是把二分查找中的二分之一改为了(key-a[low])/(a[high]-a[low])

       注意:对于表长较大,而关键字分布比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好得多。反之,如果数组中极端不均匀的数据较多,用插值查找未必合适。

时间复杂度:O(logn) 

示例:

int Interpolation_Search(int* a, int n, int key) {
	int low, high, mid;
	low = 1;
	high = n;
	while (low <= high) {
		mid = low + (high - low) * (key - a[low]) / (a[high] - a[low]);//插值
		if (key < a[mid])//查找值比中值小
			high = mid - 1;//最高下标调整到中位下标小一位
		else if (key > a[mid])//查找值比中值大
			low = mid + 1;//最低下标调整到中位下标大一位
		else
			return mid;
	}
	return 0;
}

(3)斐波那契查找

void Create_Fibonacci(int* f)    //构建斐波那契序列
{
	f[0] = 0;
	f[1] = 1;
	for (int i = 2; i < MaxSize; ++i)
		f[i] = f[i - 2] + f[i - 1];
}
int Fibonacci_search(int* a, int n, int key)
{
	int low = 0, high = n - 1, mid = 0, k = 0;
	int F[MaxSize];
	Create_Fibonacci(F);
	while (n > F[k] - 1) //计算出n在斐波那契中的位置
		++k;
	for (int i = n; i <= F[k] - 1; ++i) //把数组补全,使用a[n-1]
		a[i] = a[n-1];
	while (low <= high) {
		mid = low + F[k - 1] - 1;  //根据斐波那契数列进行分割
		if (key < a[mid]) {
			high = mid - 1;
			k = k - 1;
		}
		else if (key > a[mid]) {
			low = mid + 1;
			k = k - 2;
		}
		else {
			return mid;
		}	
	}
	return -1;
}

参考资料:https://zhuanlan.zhihu.com/p/106883697

       斐波那契查找采用和二分查找/插值查找相似的区间分割策略,都是通过不断的分割区间缩小搜索的范围

          斐波那契数列的一些性质:从第三项开始,每一项都等于前两项之和,通项公式:F[n]=F[n-1]+F[n-2]   该性质可以用于区间分割,将一个长度为F(n)数组看做前后两半,前面一半长度是F(n-1),后面一半的长度是F(n-1)。(资料里图画反了)

具体步骤分析:
 

(1)构建斐波那契数列:

void Create_Fibonacci(int* f)    //构建斐波那契序列
{
	f[0] = 0;
	f[1] = 1;
	for (int i = 2; i < MaxSize; ++i)
		f[i] = f[i - 2] + f[i - 1];
}

也即代码中的F[n]=[0,1,1,2,3,5,8,13,21···],采用递归算法。

(2)计算数组长度对应的斐波那契数列元素个数:

	while (n > F[k] - 1) //计算出n在斐波那契中的位置
		++k;

      数组的长度不对应斐波那契数列中的任何一个元素,这种情况是很常见的。因此,策略是采用“大于数组长度的最近一个斐波那契数值”。注意没有等于,因此使用F[k]-1作为判断,即使F[k]=n也要让k++(因为n>F[k]-1)。

(3)对数组进行填充:

	for (int i = n; i <= F[k] - 1; ++i) //把数组补全,使用a[n-1]
		a[i] = a[n-1];

      确定了斐波那契数值后,就要进行数值填充,填充是从索引为n开始,一直填充到索引为F[k]-1为止。其中填充的数值均采用最后一个元素a[n-1]的数值(一般从小到大排,也就是最大值)。

(4)循环进行区间分割,查找中间值:

	while (low <= high) {
		mid = low + F[k - 1] - 1;  //根据斐波那契数列进行分割

       这一个步骤与前面介绍的二分查找和插值查找相似,都是不断的缩小搜索区间,进而确定目标值的位置。重点就是每次分割中间位置的计算公式: mid = low + F[k-1] - 1  此时数组被分成左右两个区间,左区间有F(n-1)个元素,右区间有F(n-2)个元素。 减一是因为下标从0开始。

(5)判断中间值和目标值的关系,确定更新策略 :

		if (key < a[mid]) {
			high = mid - 1;
			k = k - 1;
		}
		else if (key > a[mid]) {
			low = mid + 1;
			k = k - 2;
		}
		else {
			return mid;
		}	
	}

  中间值和目标值有三种大小关系,分别对应三种处理方式:

  1.相等,则查找成功,返回中间位置即可(问题是是否需要判断越界

  2.中间值小于目标值,这说明目标值位于中间值到右边界之间(即右区间),右区间含有F(n-2)个元素,所以k应该更新为k=k-2,找小了,要在右区间找,因此修改low为mid+1。

  3.中间值大于目标值,这说明目标值位于左边界和中间值之间(即左区间),左区间含有F(n-1)个元素,所以k应该更新为k=k-1,找大了,要在左区间找,因此修改high为mid-1。

注意第一种情况的另一种写法,额外判断了一下是否越界:

		else {//当key==a[mid]时
			if (mid < n)//说明mid在数组内被找到,查找成功
				return mid;//注意返回的是索引
			else//越界
				return -1;
		}

(6)while循环退出:

 说明没有找到元素,返回-1。

 时间复杂度:O(logn) 

 示例:

int main()
{
	int a[MaxSize];
	int n;
	cout << "输入元素个数:";
	cin >> n;
	for (int i = 0; i < n; i++)//序号从0开始
	{
		int t;
		cin >> t;
		a[i] = t;
	}
	cout << "输入查找的元素:";
	int key;
	cin >> key;
	int h = Fibonacci_search(a, n, key);
	if (h != -1) {
		cout << "查找的元素索引为:" << h;
	}
	else {
		cout << "查找失败!";
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值