目录
前言
今天主要学习不同的查找方法及其原理思想,例如:顺序查找、二分查找、分块查找以及重要的哈希表及其不同的存取关系、如何解决哈希表的冲突问题(开发地址法:线性探查法、链地址法);还有时间负责度的计算。
提示:以下是本篇文章正文内容,下面案例可供参考
一、顺序查找
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;
}
总结
这里对文章进行总结:主要总结了不同的查找方法及原理的思想,例如:顺序查找、二分查找、分块查找以及重要的哈希表及其不同的存取关系、如何解决哈希表的冲突问题(开发地址法:线性探查法、链地址法);还有时间负责度的计算。