一、算法
1.什么是算法
算法是一个有穷规则(或语句、指令)的有序集合。他确定了解决问题的一个运算序列。对于问题的初始输入,通过算法的有限步的运行,产生一个或多个的输出
2.如何评判算法的好坏
一个好的算法通常:
(1)算法对应的程序所耗时间少
(2)算法对应的程序所耗存储空间少
(3)算法的结构性好、易读、易移植和调试等
3.算法的特性
4.时间复杂度(T(n) )
二、查找算法
1.顺序查找
顺序遍历,随着n的变大,其效率会逐渐下降
2.二分查找
前提条件:首先要保证查找的数据 按大小排列 顺序存放
思路:对给定值key,逐步去确定待查记录所在区间,每次将搜索空间减少一半,直到查找成功或失败为止。
设两个指针(或下标)low、high,分别指向当前待查找表的上界(表头)和下界(表尾)
mid=(low+high)/2
mid的值>key :high=mid-1
mid的值<key :low=mid+1
到最后low=high,就没有找到
代码实现:
int bin_search(int *arr,int len,int key)
{
int low,mid,high;
low=1;
high=len-1;
while(low<=high)
{
mid=(low+high)/2;
if(key==arr[mid]) return mid;
else if(key>arr[mid])
low++;
else
high--;
}
return -1;
}
3.分块查找
索引表+源数据表
前提条件:块间有序,块内无序 (第二块的所有值一定得大于第一块得最大值)
思路:
(1)先在索引表中确定在那一块
(2)在遍历这一块进行查找
算法思路:
(1)又索引表确定待查找数据在那个块
(2)在块内顺序查找
程序实现:
具体实现思路:
1.将数据分块存入一个结构体数组中,结构体内容为块的起始位置和块最大值
2.将val在结构体数组里的最大值遍历一遍,找到其对应块
3.确定了相应块,同时就确定了初始位置,末尾位置,便于后期遍历可直接为后一个块的初始位置,即end=末尾位置+1
注意:要判断是否为最后一个块,是最后一个块的话,直接把最后的下标赋值给end
4.遍历start----end,找出val
typedef struct{ //索引表
int post; //块的起始位置
int max; //用来保存块的最大值
}indext_t; //索引
//a原数据表 index_list 索引表 value 被查找的值
int findByBlock(int *a, index_t *index_list,int value)
{
//start和end作为源数据表的下标取搜索
int start; //保存起始下标
int end; //终止下标的后一个位置
//先确定value在哪一块中,遍历索引表中的max
int i;
for(i = 0; i < 4; i++)
{
if(value <= index_list[i].max)//说明value有可能在i块中
{
start = index_list[i].post; //确定start
if(i == 3) //是否在最后一块//假设value在最后一块中,i+1数组越界,所以end的赋值,需要进行条件判断
end = 19;
else
end = index_list[i+1].post; //确定end
break; //注意此处一定要有break
}
}
for(i = start; i < end; i++) //start-end的遍历
{
if(a[i] == value)
return i;
}
return -1;
}
int main(int argc, const char *argv[])
{
int i;
int a[19] = {18, 10, 9, 8, 16, 20, 38, 42, 19, 50, 84, 72, 56, 55, 76, 100, 90, 88, 108};
// 0 4 5 9 10 15
index_t index_list[4] = {{18,0},{50,5},{84,10},{108,15}}; //索引表,结构体数组
for(i = 0; i < 19; i++) //把源数据表中的每一个数据查询一遍,测试程序
{
printf("%d post is %d\n",a[i],findByBlock(a,index_list,a[i]));
}
printf("%d post is %d\n",22,findByBlock(a,index_list,22));
return 0;
}
4.哈希表(hash函数)
散列存储
有一张表,保存了数据中关键字与对应存储位置的关系
在选择key的时候,要选择数据中不重复的关键字作为key
存时按照对应关系存,取时按照对应关系取
这里说的对应关系就是哈希函数
哈希函数的选取
1.直接定址法
要查找99岁老人的人数时,直接读出第99项即可。但该方法比较特殊,关键字很少是连续的,用该方法产生的哈希表会造成空间的大量浪费,适应性不强
程序实现:
int hashFun(int key) //hasn函数,代表了数据中的关键字与存储位置之间的关系
{
int post = key-1; //key-1代表关系 post通过关系得到存储位置
return post;
}
int saveAgeNum(int *hash_list,int key,int num) //存数据(按照对应关系存)
{
int post=hashFun(key); //1.通过key得到数据存储的位置,调用哈希函数
hasn_list[post]=num; //2.将数据存储到哈希表中
}
int getAgeNum(int *hash_list,int key) //取数据(按照对应关系取)
{
int post = hashFun(key); //1.通过key得到数据存储的位置,调用哈希函数
return hash_list[post]; //2.将数据取出
}
int main(int argc, const char *argv[])
{
int i;
int age,num; //年龄和对应年龄的人口数
int hash_list[200] = { 0 };//哈希表,用来保存年龄和对应年龄的人口数,之所以长度为200,暂定人的寿命为200岁
for(i = 0; i < 4; i++)
{
printf("请您输入年龄和年龄对应的人口数:\n");//输入四组数据保存到哈希表中
scanf("%d %d",&age,&num); //将输入的数据保存到哈希表中
saveAgeNum(hash_list,age,num);
}
//进行查找对应年龄的人口数
for(i = 0; i < 6; i++)
{
printf("请您输入要查询的年龄:\n");
scanf("%d",&age);
printf("%d: %d人\n",age,getAgeNum(hash_list,age));
}
return 0;
}
2.保留函数法
// 数据个数为n
n=11
// 哈希表的长度 m = n/a
// n存储数据的个数 a的为装填因子,0.7-0.8之间最为合理
// m = 11 / 0.75 == 15 0-15之间最大的指数为13
//prime 质数 为 不大于表长的质数
int hashFun(int key)
{
int post = key % prime; //key % 13
return post;
}
3.叠加法
程序实现:
//图书馆书码
typedef struct{
int num; //图书条形码
char name[30]; //图书的名字
}book_info_t;
int hashFun(int key) //hash函数
{
int post = (key / 1000000 + key % 1000000 / 1000 + key % 1000) % 1000;
return post;
}
int saveBookInfo(book_info_t *hash_list , book_info_t book) //存数据
{
int post=hashFun(book.number); //1.通过key得到数据存储的位置,调用哈希函数
hasn_list[post]=book; //2.将数据存储到哈希表中
}
book_info_t getBookInfo(book_info_t *hash_list,int key) //取数据
{
int post = hashFun(key); //1.通过key得到数据存储的位置,调用哈希函数
return hash_list[post]; //2.将数据取出
}
int main(int argc, const char *argv[])
{
int i;
int num; //用来保存输入的条形码
book_info_t hash_list[1000] = { 0 }; //结构体数组,因为叠加法后存储位置三位数
book_info_t book; //存入的一个结构体变量
for(i = 0; i < 4; i++) //1.循环输入四组图书信息
{
printf("请您输入图书条形码和图书的名字:\n");
scanf("%d %s",&book.number,book.name);
saveBookInfo(hash_list,book); //将数据存储到哈希表中
}
for(i = 0; i < 6; i++) //2.取数据 ,查询图书信息
{
printf("请您输入要查询的条形码:\n");
scanf("%d",&num);
book = getBookInfo(hash_list,num);
printf("%d---%s\n",book.number,book.name);
}
return 0;
}
4.随机函数法
5.平方取中法
程序实现:
// 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平方后,发现中间的三位重复次数最少
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;
}
注意:不管怎么选取函数,都要尽可能的减少冲突
冲突没法避免要考虑如何解决冲突
解决冲突
冲突是指:表中某地址j∈[0,m-1]中己存放有记录,而另一个记录的H(key)值也为j(两个记录相同冲突了)
处理冲突的方法一般为:在地址j的前面或后面找一个空闲单元存放冲突的记录,或将相冲突的诸记录拉成链表等等。
1.开放地址法(必散列)
思想就是在H(key)的前后找一个空位置存放数据,找不到就一直找,直到找到为止
程序实现:
int hashFun(int key) //哈希函数
{
int post = key % 13; //取余13的原因是因为 选不大于哈希表长的最大质数
return post;
}
int hashSearch(int *hash_list,int key) //哈希查找(确定post的值,找空位)
{
int d = 1; //d 取值 1 2 3 4 5 当冲突发生的时候采用一次线性探查法
int post; //用来保存存储位置
int remnum; //用来保存余数
post = remnum = hashFun(key);
while(d < 15 && hash_list[post] != 0 && hash_list[post] != key)
{
post = (remnum + d) % 15; //采用一次线性探查法
d++;
}
if(d >= 15)
return -1;//代表表已经溢出
return post;
//hash_list[post] == 0//意味着当前post这个位置可以存放数据,初始化哈希表所有位置全为0,代表没有存放数据
//hash_list[post] == key //意味着当前表中的key已经存在
}
void hashSave(int *hash_list, int key) //先通过key获取存储位置,对key存储位置进行判断
{
int post = hashSearch(hash_list,key); //算出post的值
if(post == -1 || hash_list[post] == key)
{
printf("表溢出或key已经存在!!!\n");
return;
}
hash_list[post] = key; //将数据存储到哈希表中
}
int main(int argc, const char *argv[])
{
int i;
int a[11] = {23,34,14,38,46,16,68,15,7,31,26};
int hash_list[15] = { 0 };//哈希表长度为15 因为 数据长度n / 装填因子a 11 / 0.75,装填因子通常采用0.7-0.8之间最为合理
for(i = 0; i < 11; i++) //将数据全部保存到哈希表中
{
hashSave(hash_list,a[i]);
}
for(i = 0; i < 15; i++) //打印哈希表
{
printf("%d ",hash_list[i]);
}
printf("\n");
return 0;
}
2.链地址法(开散列)
typedef struct node_t
{
int key;
struct node_t *next;
}link_node_t,*link_list_t;
int hashFun(int key) //哈希函数
{
int post = key % 13;
return post;
}
link_list_t hashSearch(link_list_t *hash_list,int key)
{
link_list_t h = NULL; //用来保存无头链表的头指针
//1.先通过key调用哈希函数获取位置
int post = hashFun(key); //需要将判断key放入第条链表
h = hash_list[post]; //h指向key对应存储位置的那条链表
while(h != NULL && h->key != key) //相当于遍历无头链表,同时检查key
{
h = h->next;
}
return h;
//h == NULL 说明没找到key可以进行存储
//h != NULL h->key == key 说明key已经存在
}
//存储数据
void hashSave(link_list_t *hash_list,int key)
{
int post = hashFun(key);
link_list_t new = NULL; //用来保存新创建的节点
link_list_t p = hashSearch(hash_list,key);
if(p == NULL)//key不存在,可以进行插入数据
{
new = (link_list_t)malloc(sizeof(link_node_t)); //1.创建一个新的节点用来保存key
if(NULL == new)
{
perror("new malloc failed");
return;
}
//2.将key保存到新节点中
new->key = key;
new->next = NULL;
//3.将新的节点插入到对应存储位置的链表中,将新节点每次插入无头链表头的位置
new->next = hash_list[post];
hash_list[post] = new;
}
else
printf("key已经存在了!!!\n");
}
int main(int argc, const char *argv[])
{
int i;
link_list_t h = NULL;//临时保存每条链表的头
int a[11] = {23,34,14,38,46,16,68,15,7,31,26};
//hash_list是一个结构体指针数组,每一个元素都是结构体指针
link_list_t hash_list[13] = { 0 };//为什么长度是13,因为保留余数法每次%13 得到的位置在0-12之间
for(i = 0; i < 11; i++) //将所有的key保存起来
{
hashSave(hash_list,a[i]);
}
//遍历哈希表 有13条链表
for(i = 0; i < 13; i++)//hash_list中保存的是13条无头链表的头指针
{
printf("%d:",i);
h = hash_list[i];
while(h != NULL)//相当于遍历无头链表
{
printf("%d ",h->key);
h = h->next;
}
printf("\n");
}
return 0;
}
作业
#####作业#####
char a[] = "asdfasdlifadshjklrgeopaewrfpawedfoplhafgd"
统计找到字符串中出现次数最多的字符并统计其出现次数,打印输出
#include <stdio.h>
int hashFun(char key)
{
int post = key-'a';//下标为0的位置存的字符'a'的个数 1位置 'b'的个数
return post;
}
int main(int argc, const char *argv[])
{
int max = -1;
int i = 0;
int post;//保存存储位置
char a[] = "aaaabbbbacdddddeeemmmlzoe";
int hash_list[26] = { 0 };//长度为26,因为总共就26个字母
while(a[i] != '\0')
{
post = hashFun(a[i]);
hash_list[post]++;
i++;
}
//经过上面的遍历后,已经统计出结果
for(i = 0; i < 26; i++)//max保存出现字母最多的次数
max = max < hash_list[i] ? hash_list[i] : max;
for(i = 0; i < 26; i++)
{
if(hash_list[i] == max)//只要与最多次数相同,那么就是最多的字母
{
printf("%c出现的次数是%d\n",i+'a',hash_list[i]);
}
}
return 0;
}