Hash查找法在Keil C51中的实现

发布者:会飞的笨鱼最新更新时间:2016-12-14 来源: eefocus关键字:Hash查找法  Keil  C51 手机看文章 扫描二维码
随时随地手机看文章

摘要:散列(hash)是一种重要的存储方法,也是一种常见的查找方法。它是指在记录的存储位置和它的关键字之间建立一个确定的对应关系。本文以射频卡门禁控制器为例,说明用射频卡卡号作为关键字,用Hash查找法确定此卡能否开门,并给出对应的Keil C51程序。

    单片机应用系统中,经常要涉及到数据的存储和查找。以射频卡门禁系统为例,见图1。系统由51系列单片机、射频卡(RF卡)读卡电路、存储单元24C256、继电器等部分组成。其基本原理为:用户刷卡后,RF卡读卡电路读出卡号,通过I/O口线送入单片机。单片机收到卡号后,在存储单元中查找此卡是否允许开门。如允许,则命令继电器动作,打开电磁门锁:如不允许,则返回。
 
图1 射频卡门禁系统
    在内存中查找卡号有多种方法,最简单的有线性查找法,即从存储单元内第一个记录起与关键字比较,一直到记录与关键字匹配。时间复杂程度为O(n),记录数越多,比较过程耗时越长。一般记录数为100~200时较合适,如在系统内存储1000~2 000条记录,则用户在刷卡开门时,因比较的次数较多,等待时间较长,将难以忍受。
    对于记录数较多(1000~2 000)的场合,可以采用对分法查找。此方法的时间复杂程度为O(log2n),2000个左右的记录只需查找10~11次即可完成,效率大大提高。只是此法需要将记录事先排序,随机增加一个记录或养活一个记录将较麻烦。
    二叉排序树的方法可以兼有对分法查找的高效率和随机插入记录、删除记录的便利。但在编程中,由于要使用到指针变量,KeilC51要生成较大的目标代码。
    Hash查找法在实践中被证实是最快的一种查找方法,它的时间复杂度为O(1),即几乎只要比较一次就可确定比较结果。它的基本思想是以空
间换时间,需要数据存储器容量大,好在当前存储器的价格并不贵,采用大容量的存储并不会使系统成本增加多少。
    Hash查找法又称散列查找,是一种重要的存储和查找方法。它是指在记录的存储位置和它的关键字之间建立一个确定的对应关系,使每个关
键字和存储器中的唯一的存储位置相对应。将记录的关键字与记录的存储位置对应起来的关系称为Hash函数。在查找时,如关键字是Key,只需要根据对应关系计算出关键字Key对应的值H(Key),就可得到记录的存储位置,这个位置称为Hash地址。以射频卡门禁系统为例,开门卡的卡
号为关键字,通过文中给定的一种运算即可直接得到对应的记录的存储位置(Hash地址),从中取出是否可以开门的信息。
    使用Hash查找法,会产生对于不同的关键字其Hash函数计算的Hash地址相同的情况,这种情况称为冲突。在Hash查找法中冲突是不可避
免的。关键的问题是如何构造Hash函数,使所有关键字对应的Hash地址均匀地分布在整个地址空间,尽可能少地减少活冲突。同时一旦发生冲
突要有足够的办法解决。本文采用折叠法来构成Hash函数,用线性探测法解决一旦发生的冲突。其细节可见参考文献[1]、[2]。
    当前单片机应用的普遍趋势是采用片内ROM,采用SPI或I2C等串行方式扩展外部设备,所以文中采用的存储器是I2C总线的24C256,共32K字节。其中分配给Hash查找的存储空间是16K,每记录8个字节,共2000条记录,即可存储2000个开门卡卡号。(24C256中剩余的地址留作它用)每条记录分配如下:

0     1    2   3   4    5   6   7
55 16 92 64 02 XX XX

    第0个字节0X55,表示该地址已有记录。
    第1个字节到第4个字节存放后8位卡号,BCD方式。
    第5个字节~第7个字节留作参数,如可控制开门卡在什么时间段内可以开门,也可控制该卡不同的权限。
    记录从0000号单元开始存放。
    卡号存放在数组d[0]~d[9]中,它是一个10位的10进制数,如卡号是0016926402,则d[0]=0,d[1]=0,d[2]=1,d[3]=6,d[4]=9……。折叠法把关键字分割成位数相同的几部分,然后取这几部分叠加其和再取2000的模(舍去进位)作为哈希地址。
  1692
+ 6402
————H(key)=94
  8094
程序中作如下运算
1000 d[2]+100*d[3]+10*d[4]+d[5]+1000*d[6]+100*d[7]+10* [8]+d[9]=1000*(d[2]+d[6])+100*(d[3]+d[7])+10*(d[4]+d[8])+d[5]+d[9]这样的运算体现在hashfunc()函数中,hashfunc()函数的功能为根据10位卡号计算出对应的hash地址。

复制代码

uint hashfunc()   
{   
  uint hashtem;   
  hashtem=1000*(d[2]+d[6])+100*(d[3]+d[7])+10*(d[4]+d[8]+d[5]+d[9];   
  hashtem =hashtem%2000;   
  retun(hashtem);   
}

复制代码

    本文所附C51程序中要用到其他一些函数,限于篇幅,这里不再申明,请参考main()程序中相应的注释。程序是以位变量setflag控制程序分支,setflag=1表明要将读到的卡号存储(函数save())到相应的hash地址中,setflag=0表明要将读到的卡号作为关键字,在内存中进行hash查找,找到相匹配的纪录。KeilC51程序如下:

复制代码

Main()   
{   
  uint hashaddr,IICaddr;   
  uchar status,i,k,cardmen,cardnum,seterr;   
  reterr=0;   
start:   
  Rfread(); //读卡,卡号存d[0]-d[9]   
  Setflag=checkcard(); //检测是否是设置卡,是设置卡返回1,是开门卡返回0。   
  if(setflag==0)   
  {   
    k=0;   
    hashaddr=hashfunc();//对关键字进行Hashing运算,得到Hashing地址。   
    while(k<10)   
    {   
      IICaddr=(hashaddr+k)*8;//从Hashing地址换算到实际内存地址。   
      Status=IICRead(IICaddr);   
      if(status==0x55)   
      {   
        for(i=1;i<5;i++)   
        {   
          cardmen=IICRead(IICaddr+i);//取出内存中卡号。   
          cardnum=d[i*2]*16+d[1+i*2];//与当前的卡号比较,   
          if(cardmen!=cardnum)   
            break;//           }   
        if(i==5)   
        {   
          open(3);//开门3秒   
          goto start;   
        }   
      }   
      k++;//进行10次探测。       }   
  }   
  else  
  {   
    k=0;   
    hashaddr=hashfunc();//对关键字进行Hashing运算,得到Hashing地址。   
    while(k<10)。   
    //进行10次线性探测,10次不成功.不再进行探测,令错误标记errflag=1       {   
      IICaddr=(hashaddr+k)*8;//从Hashing地址换算到实际内存地址   
      status=IICRead(IICaddr);   
      if(status==0x55)   
      {   
        for(i=1;i<5;i++)   
        {   
           cardmem=IICRead(IICaddr+i);   
           cardnum=d[i*2]*16+d[1+i*2];   
           if(eardmem!=cardnum)   
             break;   
           if(i==5)   
             goto start;//内存中已有本卡的纪录,不须再写入。   
           k++;   
         }   
         else  
         {   
           if(k<8)   
             save(IICaddr);//将一条记录存入。   
           else seterr=1;   
           goto start;   
         }   
      }   
   }   
}

复制代码

参考文献:
[1] 严蔚敏,吴伟民.数据结构(C语言版)[M].北京:清华大学出版社.
[2] 李云清.数据结构(C语言版)[M].北京:人民邮电出版社.
[3] 马忠梅. 单片机的C语言应用程序设计[M].北京:北京航空航天大学出版社.



关键字:Hash查找法  Keil  C51 引用地址:Hash查找法在Keil C51中的实现

上一篇:关于KeilC51的指针
下一篇:C51指针小结

推荐阅读最新更新时间:2024-03-16 15:25

单片机入门教程第22课-串行口应用编程实例
1. 串口方式0应用编程 8051单片机串行口方式0为移位寄存器方式,外接一个串入并出的移位寄存器,就可以扩展一个并行口。 例:用8051串行口外接CD4094扩展8位并行输出口,如图所示,8位并行口的各位都接一个发光二极管,要求发光管呈流水灯状态。 串行口方式0的数据传送可采用中断方式,也可采用查询方式,无论哪种方式,都要借助于TI或RI标志。串行发送时,可以靠TI置位(发完一帧数据后)引起中断申请,在中断服务程序中发送下一帧数据,或者通过查询TI的状态,只要TI为0就继续查询,TI为1就结束查询,发送下一帧数据。在串行接收时,则由RI引起中断或对RI查询来确定何时接收下一帧数据。无论采用什么方式,在开始通讯之前,都要先对控制
[单片机]
单片机入门教程第22课-串行口应用编程实例
C51编译器-预处理器
Cx51编译器中的预处理器处理源程序文件中的指令。Cx51支持所有的ANSI C指令。 Directives指令 预处理器指令前面不能有空格,并且必须加前缀 # 如: #pragma #include stdio.h #define DEBUG 1 下面列出预处理器指信令和简单描述 指令 描述 Define 定义一个预处理器宏或常量 elif 如果前面的if, ifdef, ifndef或elif分支都不成立的话,初始化if条件的一个分支 else 如果前面的if, ifdef或 ifndef分支都不成立的话,初始化if条件的一个分支 endif 结束一个if, ifdef, ifndef, elif,
[单片机]
C51单片机重要知识点总结
01 C51基本数据类型总结 我们要记得定义变量时,到底选择哪里一个,有一条重要原则是:在合理情况下,尽可能选择内存小的,单片机的内存资源很珍贵。51单片机只有128个字节。 讲讲全局变量和局部变量, 全局变量:main函数以前定义;局部变量:函数体内部定义; 如果没有被main调用时,不占用内存;能使用局部变量,就不使用全局变量;声明时可以不写变量名。 02 C51数据类型扩充定义 这部分内容是程序最开始前,我们常碰到的内容; sfr :特殊功能寄存器说明 sfr16: sfr的16位数据声明 sbit: 特殊功能位声明 bit : 位变量声明 例如:SFR SCON=0x98 SFR T2=0xCC Sbit OV
[单片机]
keil mdk中如何确保某一段程序不被优化掉
使用mdk编程,假如有一个有用的函数你定义了但是没有显式的调用,mdk在默认方式下,将会把这个函数从整个程序总删除掉,以节省ROM. 比如,你在ROM的0x00002000处定位了一个函数,假设为void test(void),然后使用函数指针来调用它: void (*UserProgram)();        //函数指针 UserProgram = (void (*)()) (0x00002000);  //定位到指定的入口地址0x00002000 (*UserProgram)();               //调用test()函数 这样做的本意是调用test()函数,但编译器并不知情,它仍会按照默
[单片机]
LCD12232串行显示C51程序
这个程序包含三个方面的知识: 1。4*4按键的部份--完成(0~9)数字键,功能键,字母键的输入;按键抬起后才能作用; 2。LCD12232的显示部份;串行显示,只用两根线,显示中英文字母及数字; 3。TTL系列芯片与CMOS系列芯片的知识,及做库;这部份还没完成; 我想第三步完成了,MM还符合一名合格的大学毕业生的; 哈!直接上代码了,下载地址: http://www.51hei.com/f/12232ch.rar #include reg52.h #define uint unsigned int #define uchar unsigned char sbit SID = P0^5 ; sbit SC
[单片机]
AVR/C51和PIC八位单片机性能比较
八位单片机由于内部构造简单,体积小,成本低廉,在一些较简单的控制器中应用很广。即便到了本世纪,在单片机应用中,仍占有相当的份额。由于八位单片机种类繁多,本文仅将常用的几种在性能上作一个简单的比较,供读者在使用时作参考。 1. 51系列   应用最广泛的八位单片机首推Intel的51系列,由于产品硬件结构合理,指令系统规范,加之生产历史 悠久 ,有先入为主的优势。世界有许多著名的芯片公司都购买了51芯片的核心专利技术,并在其基础上进行性能上的扩充,使得芯片得到进一步的完善,形成了一个庞大的体系,直到现在仍在不断翻新,把单片机世界炒得沸沸扬扬。有人推测,51芯片可能最终形成事实上的标准MCU芯片。   51系列优点之一是它从内部的
[单片机]
Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器
配置 PWM 输出 选择芯片 System Core - SYS- Debug: Serial Wire 防止下次无法烧录 System Core - RCC- High Speed Clock (HSE): Crystal/Ceramic Resonator 启用外接高速晶振 Clock Configuration: (配置为最高84MHz)选择外部晶振, 把HSE和PLLCLK连上, 在HCLK上输入84回车, 软件会自动调节各节点倍数 Timers - TIM2 Clock Source: Internel Clock, 使用系统的时钟源 Channelx: PWM Generation CHx PWM输出 Counter
[单片机]
Keil "RECURSIVE CALL TO SEGMENT"彻底解决
我们在做菜单程序或通过函数指针调用函数时,如果被调用的函数中有包含了常量字符串,那么经常会出现这样的的错误提示: RECURSIVE CALL TO SEGMENT 意思是 递归调用段 ,如何解决呢,之前我没有找到很好的方法,这段时间我回过头来看keil的datasheet,找到了解决方法,当然keil手册提供的解决方法是编写一个.lin文件,我觉得麻烦,现提供我的解决方法,实例还是用keil提供的那个实例: #pragma code symbols debug oe void func1(unsigned char *msg ) { ; } void func2( void ) { unsigned char uc; func1
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved