【各种查找算法】 实现及分析

基本查找算法

顺序查找

说明:顺序查找适合于存储结构为顺序存储或链接存储的线性表。
基本思想:顺序查找也称为线形查找,属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。
复杂度分析:
查找成功时的平均查找长度为:ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;当查找不成功时,需要n+1次比较,时间复杂度为O(n);顺序查找的时间复杂度为O(n)。

二分查找

说明:元素必须是有序的,如果是无序的则要先进行排序操作。
基本思想:也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。
复杂度分析:最坏情况关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);
注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,就不建议使用

二叉查找树

基本思想:二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。
二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:
1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2)若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3)任意节点的左、右子树也分别为二叉查找树。
二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。
复杂度分析:它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。

红黑树

基本思想:红黑树的思想就是对2-3查找树进行编码,尤其是对2-3查找树中的3-nodes节点添加额外的信息。红黑树中将节点之间的链接分为两种不同类型,红色链接,他用来链接两个2-nodes节点来表示一个3-nodes节点。黑色链接用来链接普通的2-3节点。特别的,使用红色链接的两个2-nodes来表示一个3-nodes节点,并且向左倾斜,即一个2-node是另一个2-node的左子节点。这种做法的好处是查找的时候不用做任何修改,和普通的二叉查找树相同。
红黑树的定义:红黑树是一种具有红色和黑色链接的平衡查找树,同时满足:
1)红色节点向左倾斜
2)一个节点不可能有两个红色链接
3)整个树完全黑色平衡,即从根节点到所以叶子结点的路径上,黑色链接的个数都相同。
红黑树的性质:整个树完全黑色平衡,即从根节点到所以叶子结点的路径上,黑色链接的个数都相同(2-3树的第2)性质,从根节点到叶子节点的距离都相等)。
复杂度分析:最坏的情况就是,红黑树中除了最左侧路径全部是由3-node节点组成,即红黑相间的路径长度是全黑路径长度的2倍。

其他查找算法

查找算法分类:
静态查找和动态查找;
静态或者动态都是针对查找表而言的。动态表指查找表中有删除和插入操作的表。
无序查找和有序查找。
无序查找:被查找数列有序无序均可;
有序查找:被查找数列必须为有序数列。

插值查找

基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。
对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。
复杂度分析:查找成功或者失败的时间复杂度均为O(log2(log2n))。

斐波那契查找

基本思想:也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。
复杂度分析:最坏情况下,时间复杂度为O(log2n),且其期望复杂度也为O(log2n)。

树表查找

树表查找包含很多算法,如最简单的树表查找算法——二叉树查找算法,红黑树等。
1)2-3查找树
定义:和二叉树不一样,2-3树运行每个节点保存1个或者两个的值。对于普通的2节点(2-node),他保存1个key和左右两个自己点。对应3节点(3-node),保存两个Key,2-3查找树的定义如下:
A.要么为空,要么:
B.对于2节点,该节点保存一个key及对应value,以及两个指向左右节点的节点,左节点也是一个2-3节点,所有的值都比key要小,右节点也是一个2-3节点,所有的值比key要大。
C.对于3节点,该节点保存两个key及对应value,以及三个指向左中右的节点。左节点也是一个2-3节点,所有的值均比两个key中的最小的key还要小;中间节点也是一个2-3节点,中间节点的key值在两个跟节点key值之间;右节点也是一个2-3节点,节点的所有key值比两个key中的最大的key还要大。
2-3查找树的性质:
A.如果中序遍历2-3查找树,就可以得到排好序的序列;
B.在一个完全平衡的2-3查找树中,根节点到每一个为空节点的距离都相同。(这也是平衡树中“平衡”一词的概念,根节点到叶节点的最长距离对应于查找算法的最坏情况,而平衡树中根节点到叶节点的距离都一样,最坏情况也具有对数复杂度。)
复杂度分析:
2-3树的查找效率与树的高度是息息相关的。
在最坏的情况下,也就是所有的节点都是2-node节点,查找效率为lgN
在最好的情况下,所有的节点都是3-node节点,查找效率为log3N约等于0.631lgN
2)B树和B+树(B Tree/B+ Tree)
B树(B-tree)是一种树状数据结构,它能够存储数据、对其进行排序并允许以O(log n)的时间复杂度运行进行查找、顺序读取、插入和删除的数据结构。B树,概括来说是一个节点可以拥有多于2个子节点的二叉查找树。与自平衡二叉查找树不同,B树为系统最优化大块数据的读和写操作。B-tree算法减少定位记录时所经历的中间过程,从而加快存取速度。普遍运用在数据库和文件系统。
B树定义:B树可以看作是对2-3查找树的一种扩展,即他允许每个节点有M-1个子节点。
A.根节点至少有两个子节点
B.每个节点有M-1个key,并且以升序排列
C.位于M-1和M key的子节点的值位于M-1 和M key对应的Value之间
D.其它节点至少有M/2个子节点
B+树定义:
B+树是对B树的一种变形树,它与B树的差异在于:
A.有k个子结点的结点必然有k个关键码;
B.非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中;
C.树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。

分块查找

分块查找又称索引顺序查找,它是顺序查找的一种改进方法。
算法思想:将n个数据元素"按块有序"划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素
算法流程:
先选取各块中的最大关键字构成一个索引表;查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。
(5)哈希查找
算法思想:哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。
算法流程:
1)用给定的哈希函数构造哈希表;
2)根据选择的冲突处理方法解决地址冲突;常见的解决冲突的方法:拉链法和线性探测法。
3)在哈希表的基础上执行哈希查找。
哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。

其他树型数据结构

除了二叉查找树、红黑树还有以下树型数据结构:
二叉树:一棵树,其中每个结点都不能有多于两个子树。二叉树的一个性质是一颗平均二叉树的深度要比及结点个数N小得多,这个性质很重要,尤其对于特殊类型的二叉树即二叉查找树而言,其深度的平均值是O(logN),这将大大降低查找时的时间复杂度。
AVL树:-一种带有平衡条件的二叉查找树,这种平衡条件必须要容易保持,而且它保证树的深度必须是O(logN)。对于AVL树来说,它的平衡必须满足以下特征之一:空树或每个结点的左子树和右子树深度最多差1。
线索二叉树:二叉链表表示的二又树存在大量空指针。N个结点的二叉链表,每个结点都有指向左右孩子的结点指针,所以一共有2N个指针,而N个结点的二叉树一共有N-1条分支,也就是说存在2N-(N-1)=N+1个空指针。指向前驱和后继的指针称为线索,加上线索的二叉链表就称为线索链表,相应的二叉树就称为线索二叉树,对二又树以某种次序遍历使其变为线索二又树的过程就叫做线索化。
哈夫曼树:含有N个带权叶子结点的二叉树中,其中带权路径长度(WPL)最小的二叉树,也称为最优二叉树。将这N个结点分别作为N棵仅含一个结点的二叉树,构成森林F(森林是m(m>= 0)颗互不相交的树的集合);构造一个新结点,并从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且将新结点的权值置为左、右子树上根结点的权值之和;从F中删除刚才选出的两棵树,同时将新得到的树加入F中;重复步骤2)和3),直至F中只剩下一棵树为止。
此外还有节点大小平衡树(SBT)、伸展树、树堆(Treap)等树型数据结构。

代码实现

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
/*顺序查找*/
int _Search(double *a, double x, int length) /*被调用的函数search()*/
{
	assert(&a);
	for (int i = 0; i < length; i++)
	{
		if (x == a[i])//查找到所需的元素,跳出循环
		{
			return i;
			break;
		}
	}
	return -1;
}

/*二分查找*/
void ShellSort(double* a, int length)
{
	assert(a);
	int gap = length;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < length - gap; i++)
		{
			int end = i;
			double tmp = a[end + gap];
			while (end >= 0 && a[end] > tmp)
			{
				a[end + gap] = a[end];
				end -= gap;
			}
			a[end + gap] = tmp;
		}
	}
}
int BinarySearch(double* a, double x, int length)
{
	assert(a);
	ShellSort(a, length);
	int left = 0, right = length - 1;
	int mid;
	while (left <= right)
	{
		mid = left + (right - left) / 2;
		if (x < a[mid])
		{
			right = mid - 1;
		}
		else if (x > a[mid])
		{
			left = mid + 1;
		} 
		else
		{
			return mid;
		}
	}
	return - 1;
}

/*二叉查找树*/
typedef struct BinaryTreeNode{
	double data;
	struct BinaryTreeNode* lchild; 
	struct BinaryTreeNode* rchild; 
}BSTnode, *PBSTnode;


PBSTnode BinaryTreeInsert(double data, PBSTnode *root)
{
	assert(root);
	if (*root == NULL)
	{
		*root = (PBSTnode)malloc(sizeof(BSTnode));
		(*root)->data = data;
		(*root)->lchild = NULL;
		(*root)->rchild = NULL;
		return *root;
	}
	if (data<(*root)->data)
		(*root)->lchild = BinaryTreeInsert(data, &((*root)->lchild));
	else if (data>(*root)->data)
		(*root)->rchild = BinaryTreeInsert(data, &((*root)->rchild));
	return *root;
}

PBSTnode BinaryTreeCreate(double* a, int length, PBSTnode root)
{
	assert(&root && a);
	for (int i = 0; i < length; i++)
	{
		BinaryTreeInsert(a[i], &root);
	}
	return root;
}

PBSTnode BinaryTreeSearch(double* a, double x, int length, PBSTnode root)
{
	assert(&root && a);
	BinaryTreeCreate(a, length, root);
	while (root != NULL)
	{
		if (x > root->data)
			root = root->rchild;
		else if (x < root->data)
			root = root->lchild;
		else if (x == root->data)
			return root;
	}
	return -1;
}

void testTime()
{
	int num;
	int k = 20;
	double time1 = 0, time2 = 0, time3 = 0;
	scanf("%d", &num);
	while (k--)
	{
		double* b = (double*)malloc(num * sizeof(double));
		for (int i = 0; i < num; i++)
		{
			int random_num1 = rand();
			int random_num2 = rand() % 100;
			double random = random_num1 + 0.01 * random_num2;
			b[i] = random;
			/*printf("%.2f ", b[i]);*/
		}
		/*printf("\n");*/

		double* b1 = (double*)malloc(num * sizeof(double));
		double* b2 = (double*)malloc(num * sizeof(double));
		double* b3 = (double*)malloc(num * sizeof(double));


		memcpy(b1, b, sizeof(double)* num);
		memcpy(b2, b, sizeof(double)* num);
		memcpy(b3, b, sizeof(double)* num);

		clock_t start1, end1, start2, end2, start3, end3;
		start1 = clock();
		printf("%d", _Search(b1, 999.99, num));
		end1 = clock();
		time1 += ((double)end1 - (double)start1);
		
		start2 = clock();
		printf("%d", BinarySearch(b2, 999.99, num));
		end2 = clock();
		time2 += ((double)end2 - (double)start2);
		
		PBSTnode root = NULL;
		start3 = clock();
		printf("%d", BinaryTreeSearch(b3, 999.99, num, root));
		end3 = clock();
		time3 += ((double)end3 - (double)start3);
	}
	double ave1 = time1 * 50;
	double ave2 = time2 * 50;
	double ave3 = time3 * 50;
	printf("顺序查找运行时间:%.1lf us\n", ave1);
	printf("二分查找运行时间:%.1lf us\n", ave2);
	printf("二叉树查找运行时间:%.1lf us\n", ave3);

}

int main()
{
	testTime();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44759710/article/details/105909137