数据结构(顺序查找、二分查找、分块查找、哈希表及开发地址法)笔记-day10

目录

前言

一、顺序查找

1.1原理思想

1.2代码举例

1.3时间复杂度

二、二分查找

2.1原理思想

2.2代码举例

三、分块查找

3.1原理思想

3.2代码举例

四、哈希表

4.1直接地址法

4.2叠加法

4.3数字分析法

4.4平方取中法

4.5保留余数法

4.6开发地址法解决哈希冲突

4.6.1线性探查法

4.62链地址法

总结



前言

今天主要学习不同的查找方法及其原理思想,例如:顺序查找、二分查找、分块查找以及重要的哈希表及其不同的存取关系、如何解决哈希表的冲突问题(开发地址法:线性探查法、链地址法);还有时间负责度的计算。


提示:以下是本篇文章正文内容,下面案例可供参考

一、顺序查找

1.1原理思想

顺序查找:利用数组的下标去遍历数组,与目标进行逐个比较查找

顺序查找 //循环遍历,逐个比较	
####练习####//查找x,出现在数组的位置的下标
int findByOrder(int* p, int n, int x)

(1)main函数中定义一个数组 int a[8] = {12,34,2,11,32,123,1,2};
(2)定义一个函数,查找指定数据的位置下标
(3)如果找到了,返回它的位置下标,数组下标即可,未找到返回-1
(4)main函数中测试,main函数中输入一个数,查找这个数的位置下标
顺序查找缺点:当数据较多时int a[10000],查找慢
#include<stdio.h>
int findbyoder(int *p,int x,int n)
{
	int i;	
	
	for(i = 0;i < n;i++)
	{
		if(p[i] == x)
		{
			return i;
		}
	}
	
	return -1;
}
int main(int argc, const char *argv[])
{
	int a[8]={11,34,2,11,123,32,46};
	int x;
	scanf("%d",&x);
	int ret=findbyoder(a,x,8);
	if(ret==-1)
	{
		printf("没找到\n");
	}
	printf("下标是%d\n",ret);
	return 0;
}

1.2代码举例

//遍历二维数组所有元素,查找数组内是否有数字n
#include <stdio.h>

#include<stdio.h>

int flag;

int isFind(int (*p)[4],int n)
{
	int i,j;
	for(i = 0; i < 3; i++)// i == 0 1 2 代表行
	{
		for(j = 0; j < 4; j++) // j == 0 1 2 3 代表列
		{
			if(n==p[i][j])
				return flag=1;
		}
	}
	return 0;

}
int main()
{
	int n;
	int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
	printf("Please input find number:\n");
	scanf("%d",&n);
	isFind(a,n);
	if(flag)
		printf("%d isFind!!\n",n);
	else
		printf("%d is no have!!\n",n);
	return 0;
}

1.3时间复杂度

如何评价一个算法的好坏??
(1)消耗时间时间的多少 //时间少 
(2)占用内存的大小 //内存小 
(3)可读性 可维护性 可移植性

时间复杂度(程序运行的速度):

算法的可执行语句重复执行的频度和语句频度: 算法中可执行语句重复执行的次数 
通常时间复杂度用一个问题规模函数来表达	T(n) = O(f(n))	
	
T time 时间  O 时间度量级	f(n)函数关系表达式	n代表研究问题的规模 
int a[10]进行冒泡排序 算法 10 
/例子/
	int i,j;
	for(i = 0; i < n; i++) 
	{
		for(j = 0; j <= i; j++)
		{
			printf("hello world!!"); 
		}
	}
	i = 0  //内循环 循环 1
	i = 1  // 2
	i = 2  // 3
	i = 3  // 4
	...
	...
	i = n-1 /// n 循环次数 = 1 + 2 + 3 + 4 + ...... n 
	f(n) = (1+n)*n/2
	
	//对上面的关系式进行处理
	f(n) = 1/2 * n^2 + 1/2 * n
	
	计算大O的方法 
	(1)根据问题规模n写出表达式 f(n)
	(2)如果有常数项,将其置为1 
	(3)只保留最高项,其它项舍去 //最高项指的是n的最高次幂
	f(n) = 1/2 * n^2 + 1/2 * n  //只保留n的最高次幂 f(n) =  1/2 * n^2
	(4)如果最高项系数不为1,将其置为1 // 系数,指的是前面的1/2	
		f(n) =  1/2 * n^2
		f(n) = 1*n^2
		f(n) = n^2
		T(n) = O(n^2)
/

二、二分查找

2.1原理思想

二分法查找(又叫分半查找、拆半查找、二分搜索技术)

前提条件:数组必须是一个有序的序列 可以是递增的 也可以是递减的
思想:要查找的数据x,每次与中间的位置的元素进行比较大小,来判断
	 被查找的数据x,可能在 中间的左半部分 还是 右半部分
二分法的时间复杂度 T(n) = O(log(n))

2.2代码举例

#include <stdio.h>

//int x是被查找的数据
int findByHalf(int* p, int n, int x)
{
	int low = 0;//low数组起始下标
	int high = n-1;//high数组的终止下标
	int middle;//用来保存数组中间位置的下标
	while(low <= high)//注意此处low == high继续二分查找
	{
		//1.得到中间位置的下标
		middle = (low + high) / 2;
		//2.用查找的数据x与中间位置的元素做比较,判断x在p[middle]左边还是右边
		if(x == p[middle])
			return middle;//找到了
		else if(x > p[middle])//说明在右边,移动low,缩小下一轮的查找范围
			low = middle + 1;
		else if(x < p[middle])//说明在左边,移动high,缩小下一轮的查找范围
			high = middle - 1;
	}
	//如果上面的while循环结束后,都没有执行return middle,说明没有找到
	return -1;
}

int main(int argc, const char *argv[])
{
	int a[7] = {11,22,34,56,78,89,100};
	//用循环将数组中的每个元素都查找一遍
	int i;
	for(i = 0; i < 7; i++)
	{
		printf("%d ----> %d\n",a[i],findByHalf(a,7,a[i]));
	}
	return 0;
}	

其本质还是利用了数组的下标,因为数组的有序排列,可与数组中间的数比较大小,排除一半的元素,同时根据比较结果移动low、high,不断排除一半。

三、分块查找

3.1原理思想

分块查找:把整体的数据,分成几块;先确定你在哪一个块中 ;然后,再顺序查找这一块
	索引存储:	索引表  +  源数据表
例子:
	电话本	
	姓氏 	  姓名和电话号码
	前提条件:	块间有序,块内无序
思路:先确定在哪一个块中,再去这个块中进行 顺序查找
//
索引表//索引表 是一个结构体数组
typedef  struct 
{
	int max; //每一块的最大值
	int post;//每一块在数组中的起始下标
}index_t; //索引

块间有序,块内无序

//每一块有几个元素,不是规定不变的
//源数据表    0   1  2  3  4   5   6   7   8   9   10  11  12 13   14  15   16  17  18  
int a[19] = {18, 10, 9, 8, 16, 20, 38, 42, 19, 50, 84, 72, 56, 55, 76, 100, 90, 88, 108};

块间有序,块内无序(每一块由几个数组成,不做限制)
18, 10, 9, 8, 16   //第0块 18最大值是18

start = 5
end = 9

20, 38, 42, 19, 50 //第1块 50  第1块中的数,随便拿出一个,都比第0块的任意一个大

84, 72, 56, 55, 76 //第2块 84  第2块中的数,随便拿出一个,都比第1块的任意一个大

100, 90, 88, 108   //第3块 108  第3块中的数,随便拿出一个,都比第2块的任意一个大
//索引表
typedef  struct 
{
	int max; //每一块的最大值
	int post;//每一块在数组中的起始下标
}index_t; //索引
				 0      1      2		3
index_t b[4] = {{18,0},{50,5},{84,10},{108,15}};

休息10分钟 

3.2代码举例

索引表代码举例 

#include <stdio.h>

typedef struct
{
	int max;//用来保存每一块的最大值
	int post;//每一块的起始下标
}index_t;

//分块查找 元素个数n暂时不传了
int findByBlock(int* a, index_t* b,int x)
{
	int start,end;//用来表达x可能所在块的起始和终止下标
	//1.先确定查找的数据x,可能在哪一块中
	//先遍历索引表,判断x可能在哪一块中
	int i;
	for(i = 0; i < 4; i++)
	{
		if(x <= b[i].max)//比b[i].max要小,就可能在这一块中,不可能在后面的块中(块间有序)
		{
			break;
		}
	}
	if(i == 4)//说明没有执行过break,那么证明了x 比所有块的最大值都要大,肯定不存在
	{
		return -1;//没找到
	}
	//2.确定x可能在哪一块后,再确定这一块的起始和终止下标,进行顺序查找
	start = b[i].post;
	if(i == 3)//说明在最后一块中,那么终止下标,就是数组最后一个元素的下标
		end = 18;
	else
		end = b[i+1].post-1;//后一块的起始下标-1 == 前一块的终止下标

	//有了这块的起始和终止下标,我就可以在这块中,循环遍历顺序查找x
	for(i = start; i <= end; i++)
	{
		if(x == a[i])
			return i;
	}
	return -1;//没有找到
}


int main(int argc, const char *argv[])
{
	//源数据表
	int a[19] = {18, 10, 9, 8, 16, 20, 38, 42, 19, 50, 84, 72, 56, 55, 76, 100, 90, 88, 108};
	//索引表,里面记录了,每一块的最大值,和每一块的起始下标
	index_t b[4] = {{18,0},{50,5},{84,10},{108,15}};
	int i;
	//用一个for循环将数组中的所有元素都查找一遍
	for(i = 0; i < 19; i++)
	{
		printf("%d -----> %d\n",a[i],findByBlock(a,b,a[i]));
	}
	printf("33 is %d\n",findByBlock(a,b,33));
	return 0;
}

*********************************************************************************

四、哈希表

哈希表(hash 哈希函数)	散列存储
	
	有一张表,保存了数据中关键字与对应的存储位置的关系
	在选择key(关键字)的时候,要选择数据中不重复的关键字作为key
	(因为你需要通过key来得到存储的位置下标,如果key重复了,那么得到的存储位置也冲突)
		
	存时按照对应关系存,取时按照对应关系取

	查找方法: 查找思想,一定要提高你的查找速度 
	
	超市购物 :
	寄存箱 	寄存箱(算法)
	
	存 ---- 09  ----> 包 
	//看了一眼小票
	取 ---- 09  ----> 包
后面的所有哈希表的方法都是对key关系的具体表达与阐述,例如:直接地址法、叠加法等等

4.1直接地址法

key关系:年龄数-1 就是哈希表中的存储位置 0 --     139 
	hash_list[0] = 100; //1岁 100人 
	hash_list[29] = 1000;//30岁 1000人
	hash_list[139] = 10;//140岁 10个人
保存数据 年龄 + 年龄对应的人口数  
	年龄 人口数
	10   100
	20   300
	30   1000
输入年龄和人口数,按照对应关系保存到哈希表中,输入要查询的年龄,打印输出该年龄的人口数

目的:手里一堆数据,全国各个 年龄 --- 人口数
1 ----- 150  年龄
100 ---- 400 人口数
把数据存储起来,存的时候按照对应的关系存,取的时候按照对应的关系取

存储数据的关键 字age 和 存储位置(下标)的关系是什么?? 

//1岁 ----> hash_list[0] = 300;
//2岁 ----> hash_list[1] = 1000;
//...
//...
//150岁 ----> hash_list[149] = 10;
哈希函数:记录存储数据的时候,关键字key 与 存储位置对应 的关系
#include <stdio.h>

int hashFun(int key)//拿年龄当做key
{
	//存储位置,是通过关键字key得到的
	int post = key - 1 ;//年纪-1就是元素在数组中的存储位置
	return post;
}

//存数据函数
void saveAgeNum(int* p,int age, int num)
{
	//存的时候按照对应关系存	先调用哈希函数,得到存储位置
	int post = hashFun(age);
	//得到存储位置下标后,存上人口数
	p[post] = num;
}
//取数据函数
int getAgeNum(int* p, int age)
{
	//取的时候按照对应的关系取
	//先调用哈希函数,得到存储位置
	int post = hashFun(age);
	//有了存储位置,将人口数返回
	return p[post];
}
int main(int argc, const char *argv[])
{
	//定义一个哈希表,为什么哈希表的长度是150?,因为最大年龄是150岁
	int hash_list[150] = { 0 };
	int i,age,num;
	//输入5组年龄对应的人口数,存储起来
	for(i = 0; i < 5; i++)
	{
		printf("请您输入年龄和对应的人口数:\n");
		scanf("%d%d",&age,&num);
		saveAgeNum(hash_list,age,num);
	}
	//输入年龄查找人口数
	printf("请您输入要查找的年龄:\n");
	scanf("%d",&age);
	printf("%d全国的人口数是%d\n",age,getAgeNum(hash_list,age));
	return 0;
}

4.2叠加法

	图书馆馆的图书条形码
	typedef struct 
	{
		int number;//条形码
		char name[20];//图书名字 
		//作者 
		//出版日期
		
	}book_t;
	//两个条形码 会得到同一个存储位置,出现了冲突
	int a[] = {321432543,432321543,654657345,234456300,213342,123453333};
	//int post = key - 1 这个关系式来得到存储位置 不合适

	//叠加法:只保留了 三位数
	321432543
	
	321  key / 1000000
	432  key % 1000000 / 1000
	543  key % 1000
	//依然是通过关键字key ---> 存储的位置,也就是哈希表中数组的下标 
	int hashFun(int number)
	{
		int post = (key/1000000 + key%1000000/1000 + key%1000) % 1000 
		return post;
	}//	0-999 
	int post  = hashFun(条形码);//得到存储位置
	???? hash_list[1000]
//叠加法举例///
#include <stdio.h>

typedef struct 
{
	int number;//条形码
	char name[20];//图书名字
}book_t;

//哈希函数,记录关键字key 与 存储位置的关系
int hashFun(int key)//key == number 条形码
{
	int post = (key/1000000 + key%1000000/1000 + key%1000) % 1000;
	return post;
}


//存储图书信息b到哈希表p中
void saveBookInfo(book_t* p, book_t b)
{
	//存的时候按照对应关系存
	//1.先调用哈希函数,得到存储位置
	int post = hashFun(b.number);
	//2.将图书信息存储到哈希表中
	p[post] = b;
}

//根据条形码得到图书的信息
book_t getBookInfo(book_t* p, int number)
{
	//取的时候按照对应的关系取
	//1.先调用哈希函数,得到存储的位置
	int post = hashFun(number);
	//2.将图书信息返回值 
	return p[post];
}

int main(int argc, const char *argv[])
{
	int number;//用来保存条形码
	book_t temp;//临时保存返回的图书
	book_t b[4] = {{123323567,"三国演义"},{223422443,"水浒传"},{234478964,"西游记"},{345522123,"红楼梦"}};
	//哈希表,哈希表长度是1000,因为我们通过哈希函数得到的存储位置是0-999
	book_t hash_list[1000] = { 0 };

	//用for循环,将b数组中的4本图书,存储到哈希表中
	int i;
	for(i = 0; i < 4; i++)
	{
		saveBookInfo(hash_list, b[i]);
	}
	//根据条形码,查找图书
	printf("请扫码,查询图书:\n");
	scanf("%d",&number);
	temp = getBookInfo(hash_list, number);
	printf("%d -----> %s\n",temp.number,temp.name);

	return 0;
}	
///

4.3数字分析法

///数字分析法///
231586,242346,233796,239886,245786,234296
k1 k2 k3 k4 k5 k6 
2  3  1  5  8  6
2  4  2  3  4  6
2  3  3  7  9  6
2  3  9  8  8  6
2  4  5  7  8  6
2  3  4  2  9  6
通过数字分析,只有中间两位数重复的次数最少	

#include <stdio.h>
//哈希函数
int hashFun(int key)
{
	int post = key % 10000 / 100; //得到中间的两位,作为post
	return post;
}

//将数据存储
void saveNum(int *hash_list,int key)
{
	int post = hashFun(key);
	hash_list[post] = key;
}
//将数据取出
int getNum(int *hash_list,int key)
{
	int post = hashFun(key);
	return hash_list[post];
}

int main(int argc, const char *argv[])
{
	int i;
	int a[] = {231586,242346,233796,239886,245786,234296};
	//创建哈希表
	int hash_list[100];//100因为只取中间两位所以post为两位数
	//将数据存入哈希表
	for(i = 0; i < sizeof(a)/sizeof(a[0]); i++)
		saveNum(hash_list,a[i]);
	//查找数据
	for(i = 0; i < sizeof(a)/sizeof(a[0]); i++)
		printf("post:%d --- %d\n",hashFun(a[i]),getNum(hash_list,a[i]));
	return 0;
}

4.4平方取中法

//平方取中法//

	当取key中的某些值,不能是记录均匀分布时,根据数学原理,对key进行key的2次幂(取平方)
	取key平方中的某些位可能会比较理想 
	
	key    key的平方   H(key)
	0100   00 100 00    100  
	0110   00 121 00    121
	1010   10 201 00    201
	1001   10 020 01    020
	0111   00 123 21    123
//对key平方后,发现中间的三位重复次数最少

#include <stdio.h>

//哈希函数
int hashFun(int key)
{
	int post = key*key % 100000 / 100;
	return post;
}
//存储数据
void saveNum(int *hash_list, int key)
{
	int post = hashFun(key);
	hash_list[post] = key;
}
//取数据
int getNum(int *hash_list,int key)
{
	int post = hashFun(key);
	return hash_list[post];
}

int main(int argc, const char *argv[])
{
	int i;
	int a[] = {100,110,1010,1001,111}; //key
	int hash_list[1000] = { 0 };//哈希表
	for(i = 0; i < sizeof(a)/sizeof(a[0]); i++)
		saveNum(hash_list,a[i]);
	for(i = 0; i < sizeof(a)/sizeof(a[0]); i++)
		printf("post:%3d --- %d\n",hashFun(a[i]),getNum(hash_list,a[i]));
	return 0;
}

4.5保留余数法

//保留余数法
	
	int a[11] = {12,23,4,24,2,4,23,1,24,456,23}
	int hash_list[15];//哈希表长度是15,最大的质数是13
	哈希表的长度 m = n/a 
	
	n存储数据的个数 a的为装填因子,0.7-0.8之间最为合理
	m = 11 / 0.75 == 15  0-15之间最大的质数为13
	
	//哈希表的长度 = 数据个数 / a 
	//保留余数法 key % (1-哈希表长度之间最大的质数)
	int hashFun(int key) //将余数作为存储位置
	{
		int post = key % prime; //key % 13
		return post;
	}

4.6开发地址法解决哈希冲突

    当冲突发生时,通过查找数组的一个空位,将数据填入,而不再用哈希函数得到数组下标

Hash表的查找特点是:怎么构造的表就怎么查找,即造表与查找过程统一。
    算法思路:对给定k,根据造表时选取的H(key)求H(k)。若H(k)单元=^,则查找失败,否则k与该单元存放的key比较,若相等,则查找成功;若不等,则根据设定的处理冲突方法,找下一地址Hi,直到查找到或等于空为止。

4.6.1线性探查法

4.62链地址法

 

#####练习#####
	char a[] = "asdfasdlifadshjklrgeopaewrfpawedfoplhaqerqwrweqweqwtrwrtewrterfgd"
	统计找到字符串中出现次数最多的字母并统计其出现次数,打印输出
	哈希表 
	'a' --  b[0] = 20;//代表字符 'a'出现了20次
	'b' ==  b[1] = 30;//代表字符 'b'出现了30次
	...
	...
	int b[26];//哈希表 因为是26个字母 
#####代码#####
#include <stdio.h>

//需要你用到哈希表
//关键字key(数组a中的每一个字符)与存储位置之间的关系是什么
//int post = a[i] - 'a'
//'b' - 'a' = 1
//'c' - 'a' = 2

int main(int argc, const char *argv[])
{
	int max;
	int post;//用来保存每个字母的存储位置
	char a[] = "sasdkdgfaasdflgfsadfasdfdsafsdafasdfaszvgbcbcxadsflasdflkadf";
	//统计26个字母出现的次数,才能找到最多的字母
	//哈希表的长度是26,因为统计26个字母
	//哈希表的元素类型是int,因为元素的值,代表的是每个字母出现的次数
	int hash_list[26] = { 0 };//默认所有字母出现的次数全为0
	//hash_list[0] = 15  ---- 'a'
	//hash_list[1] = 11  ---- 'b'
	//......
	//hash_list[25] = 10 ---- 'z' 
	int i = 0;//遍历字符串
	while(a[i] != '\0')
	{
		post = a[i] - 'a';//得到存储位置
		hash_list[post]++;//等价于统计个数时候的count++
		i++;
	}
	//上面的循环结束后,统计次数完毕;找最大值
	max = hash_list[0];
	for(i = 1; i < 26; i++)
	{
		max = max > hash_list[i] ? max : hash_list[i];
	}
	//找到最大值后,将哈希表数组中的所有元素与最大值作比较,相等就是出现次数最多的
	for(i = 0; i < 26; i++)
	{
		if(max == hash_list[i])
		{
			printf("%c的次数最多,是%d次!!\n",i+'a',hash_list[i]);
		}
	}

	return 0;
}

总结

这里对文章进行总结:主要总结了不同的查找方法及原理的思想,例如:顺序查找、二分查找、分块查找以及重要的哈希表及其不同的存取关系、如何解决哈希表的冲突问题(开发地址法:线性探查法、链地址法);还有时间负责度的计算。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值