数据结构与算法 _ 查找

一、查找的概念

  • 查找的概念

             查找(或检索)是在给定信息集上寻找特定信息元素的过程。

               待查找的数据单位(或数据元素)称为记录。记录由若干数据项(或属性)组成,如学生记录。

               若某个数据项的值能标识(或识别)一个或一组记录,则称为关键字。

                                            

  • 其中,“学号”、“性别”、“姓名”、“年龄”等都是记录的数据项。若某个数据项的值能标识(或识别)一个或一组记录,称其为关键字(Key)。若一个key能够唯一标识一个记录,称此key为主key。如“学号”的值给定就唯一的对应一个学生,不可能多个学生的学号相同。如“年龄”值为20时,可能有若干个同学的年龄为20,故“年龄”可作次key。下面主要讨论对主Key的查找。
  • 查找的方法:

           查找的方法很多,有顺序查找、折半查找、分块查找、树表查找以及Hash表查找等等。查找算法的优劣将影响到计算机的使用效率,应根据应用场合选择相应的查找算法。

二、查找算法

1、顺序查找法

  • 记录可以是无序的,也可以是有序的。
  • 弊端:查找的效率非常的低。
#include "seqsearch.h"


int Seqsearch(int *a,int num)
{
	int i;

	for(i = 9;i>=0;i--)
	{
		if(a[i] == num)
		{		
			return i;
		}
	}
	return -1;
}
#include "seqsearch.h"
#include <stdio.h>


int  main(int argc, const char *argv[])
{
	int a[N] = {1,2,3,4,5,6,7,8,9,0};
	int i,key;
	char ch;
	while(1)
	{
		printf("Please input key :  ");
		scanf("%d",&key);

		i = Seqsearch(a,key);
		if(i == -1 )
		{
			printf("search  faile !!! ");
			return -1;
		}else 
		{
			printf("search %d of %d ",a[i],i);
		}
		while(getchar() != '\n');//清空输入缓存
		printf("continue?(y/Y)");
		scanf("%c",&ch);	
		if(ch == 'y' || ch == 'Y')
		{
			continue;
		}else break;
	}
	return 0;
}

2、二分法查找法

  • 使用该 查找方法 的要求: 记录必须是有序的。

                       

(1)第一次找到mid后,判断要寻找的目标在那个半段上,然后以low为头,以 mid-1为尾,再次二分整个记录,寻找下一个mid

   (2)  一直将记录折半下去,一直到i>=j,则说明范围缩小到了最小,比较该数值是不是需要查找的数值,是则返回数值,不是则返回-1;

3、分块查找法

         分块查找又索引查找,它主要用于“分块有序”表的查找。所谓“分块有序”是指将线性表L(一维数组)分成m个子表(要求每个子表的长度相等),且第i+1个子表中的每一个项目均大于第i个子表中的所有项目。“分块有序”表应该包括线性表L本身和分块的索引表A。因此,分块查找的关键在于建立索引表A。
(1)建立索引表A(二维数组)
索引表包括两部分:关键字项(子表中的最大值)和指针项(子表的第一项在线性表L中位置)
索引表按关键字有序的。
例如:线性表L(有序)为:1 2 3 4 5 6 7 8 9 10 11 12
分成m=3个子表:{1 2 3 4} {5 6 7 8} {9 10 11 12}
索引表A:二维数组:第一列为每个子表的最大值 ,第二列为每个子表的起始地址
即: 4 0
8 4
12 8
(2)利用索引表A,确定待查项X所在的子表(块)。
(3)在所确定的子表中可以用“折半查找”法搜索待查项X;若找到则输出X;否则输出未找到信息。

例如:

         输出顺序表(8,14,6,9,10,22,34,18,19,31,40,38,54,66,46,71,78,68,80,85,100,94,88,96,87)中采用分块查找的方法查找(每块的块长为5,共有5块)关键字46的过程。

#include <stdio.h>
#define MAXL 100                    //定义表中最多记录个数
#define MAXI 20                     //定义索引表的最大长度
typedef int KeyType;
typedef char InfoType[10];
typedef struct
{
    KeyType key;                    //KeyType为关键字的数据类型
    InfoType data;                  //其他数据
} NodeType;
typedef NodeType SeqList[MAXL];     //顺序表类型
typedef struct
{
    KeyType key;                    //KeyType为关键字的类型
    int link;                       //指向分块的起始下标
} IdxType;

typedef IdxType IDX[MAXI];          //索引表类型

int IdxSearch(IDX I,int m,SeqList R,int n,KeyType k)    //分块查找算法
{
    int low=0,high=m-1,mid,i,count1=0,count2=0;
    int b=n/m;              //b为每块的记录个数
    printf("二分查找\n");
    while (low<=high)       //在索引表中进行二分查找,找到的位置存放在low中
    {
        mid=(low+high)/2;
        printf("  第%d次比较:在[%d,%d]中比R[%d]:%d\n",count1+1,low,high,mid,R[mid].key);
        if (I[mid].key>=k)
            high=mid-1;
        else
            low=mid+1;
        count1++;           //累计在索引表中的比较次数
    }
    if (low<m)              //在索引表中查找成功后,再在线性表中进行顺序查找
    {
        printf("比较%d次,在第%d块中查找元素%d\n",count1,low,k);
        i=I[low].link;
        printf("顺序查找:\n    ");
        while (i<=I[low].link+b-1 && R[i].key!=k)
        {
            i++;count2++;
            printf("%d ",R[i].key);
        }           //count2累计在顺序表对应块中的比较次数
        printf("\n");
        printf("比较%d次,在顺序表中查找元素%d\n",count2,k);
        if (i<=I[low].link+b-1)
            return i;
        else
            return -1;
    }
    return -1;
}
int  main()
{
    SeqList R;
    KeyType k=46;
    IDX I;
    int a[]={8,14,6,9,10,22,34,18,19,31,40,38,54,66,46,71,78,68,80,85,100,94,88,96,87},i;
    for (i=0;i<25;i++)              //建立顺序表
        R[i].key=a[i];
    I[0].key=14;I[0].link=0;
    I[1].key=34;I[1].link=4;
    I[2].key=66;I[2].link=10;
    I[3].key=85;I[3].link=15;
    I[4].key=100;I[4].link=20;
    if ((i=IdxSearch(I,5,R,25,k))!=-1)
        printf("元素%d的位置是%d\n",k,i);
    else
        printf("元素%d不在表中\n",k);
    printf("\n");
}

4、树表查找法

  • 定义

         树型存储结构和树型逻辑结构是完全对应的,都是表示一个树形图,只是用存储结构中的链指针代替逻辑结构中的抽象指针罢了,因此,往往把树型存储结构(即树表)和树型逻辑结构(树)统称为树结构或树。在本节中,将分别讨论在树表上进行查找和修改操作的方法 [1]  。

  • 基本思想

        ⑴当二叉排序树不空时,首先将给定值k与根结点的关键字进行比较,若相等则查找成功;

        ⑵若给定值k小于根结点的关键字,则下一次与左子树的根结点的关键字进行比较,若给定值k大于根结点的关键字,则与右子树的根接到的关键字进行比较。如此递归的进行下去直到某一次比较相等,查找成功。如果一直比较到树叶都不等,则查找失败。

5、Hash查找法

  • 在存储每一个记录的同时,key值与位置记录的关系表       -------   哈希表
  • 建立记录 表时,建立记录的关键字与记录的位置之间的关联。------  如何去建立?建立一个怎样的表?哈希表是什么样子的?
  • 映射 的概念 ---  H(key) ------ >  key  Hash函数。

    (1)Hash表的含义

              Ⅰ、 Hash表,又称散列表、杂凑表。在前面讨论的顺序、折半、分块查找和树表的查找中,其ASL的量级在O(n)~O(log2n)之间。不论ASL在哪个量级,都与记录长度n有关。随着n的扩大,算法的效率会越来越低。ASL与n有关是因为记录在存储器中的存放是随机的。或者说记录的key与记录的存放地址无关,因而查找只能建立在key的“比较”基础上。

              Ⅱ、 理想的查找方法是:对给定的k,不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级O(C).这就要求在建表的时候,确定记录的key与其存储地址之间的关系f,即使key与记录的存放地址H相对应:

                                                      

              或者说,记录按key存放。

             Ⅲ、之后,当腰查找key=k的记录时,通过关系f就可得到相应记录的地址而或取记录,从而免去了key的比较过程。这个关系f就是所谓的Hash函数(或则称为 散列函数,杂凑函数),记为H(key)。它实际是一个地址映像函数,其自变量为记录的key,函数值为记录的存储地址(或者称为Hash地址)。

             Ⅳ、另外,不同的key可能得到同一个Hash地址,即当key1≠key2时,可能有H(key1)==H(key2),此时称key1与key2为同义词。这种现象称为“冲突” 或者“碰撞”,因为一个数据单位只可存放一条记录。

             Ⅵ、一般,选取Hash函数只能做到使冲突尽可能少,却不能完全避免。这就要求在出现冲突之后,寻求适当的方法来解决冲突记录的存放问题。   

                                       

                

    (2)关于Hash表的讨论关键是两个问题, 一是 选取Hash函数的方法    二是  确定解决冲突的方法

              选取(或构造)Hash函数的方法很多,原则是尽可能将记录均匀分布,以减少冲突现象的发生。以下介绍几种常见的构造方法。

                                   直接地址法  ,数字分析法,平方取中法,叠加法,保留除数法,随机函数法

  • 直接地址法

        此方法是取key的某个线性函数为Hash函数,即令:  H(key)=a*key+b  (其中a、b为常数,此时称H(key)为直接Hash函数或自身函数)。

          某地区1~100岁的人口统计表:

                                             

                           给定的存储空间为b+1 ~ b+100 号单元(b为起始地址),每个单元可以存放一条记录Ri(1≤i≤100)。取“岁数”为key,令:H(key) = key+b;  则按此函数构造的Hash表如下图所示:

                                           

  • 数字分析法

           例如:设记录数等于80,记录的key为6位10进制数,即key = (k1 k2 k3 k4 k5 k6)10 ki(1≤i≤6)=0|1|2|.......|9,地址空间为00~99,则可令:   H(key )= ki*kj;

                                                                

  • 平方取中法

              当取key中某些位为Hash地址而不能使记录均匀分布时,根据数学原理,取(key)²中的某些位可能比较的理想,所以平方取中法中,令: 

的存储空间而定。

              例如: 设 Hash表地址空间为000~999,对一组随机性不好的Key,按平方取中法选取的H(key)如表所列:

                                                            

  • 叠加法

            

           例如:

           

  • 保留除数法

              

               例如:

                   

  • 随机函数法

                        

    总结:以上介绍了选取Hash函数的6中方法,从中可以看出,选取Hash函数要考虑的因素为:

                           (1)key的长度、类型以及分布的情况。

                           (2)给定的Hash表表长。.

                           (3)记录的查找效率。

                通常是几种方法结合使用,目的是使记录更好的均匀分布,减少冲突的发生。

  (3) 处理冲突的方法

           选取随机度好的Hash函数可使冲突减少,一般来讲不能完全避免冲突。因此,如何处理冲突是Hash表不可缺少的另一方面。

           在处理冲突的过程中,可能发生一连串的冲突现象,即可能得到一个地址序列H1、H2........Hn  

H1是冲突时选取的下一个地址,而H1中可能已有记录,又设法得到下一个地址H2........ 直到某个Hn不发生冲突为止。这种现象叫“聚积”,它严重影响了Hash表的查找效率。

           冲突现象发生有时并不完全是由于Hash函数的随机性不好引起的,聚积的发生也会加重冲突。还有一个因素是表的 装填因子α,α=n/m,其中m为表长,n为表中记录个数。一般α在0.7~0.8之间,使表保持一定的空闲余量,以减少冲突和聚积现象。

  • 开发地址发

          发生冲突时,在H(key)的前后找一个空闲单元来存放冲突的记录,即在H(key)的基础上 获取下已地址。

                                                                            Hi = (H(key)+di)%m

 其中m为表长,%运算是保证Hi落在[0 , m-1]区间;di 为地址增量。

     di 的取法有多种:

                     

例子:使用保留除数法,选取Hash函数。

            

           

  • 链地址法

           发生冲突时,将各冲突记录链在一起,即同义词的记录存于同一链表。

                    

                     

                    

注:冲突时,将同义词存放于同一个链表时,将先入的记录挂载在后入记录的next指针域中,即后入的记录指向先入的的记录。(先入的记录的地址存放于 后入的记录中

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值