DS博客作业05--查找

0.PTA得分截图

1.本周学习总结

1.1 总结查找内容

1.1.1 查找的概念

查找表:相同类型的数据元素组成的集合,每个元素通常由若干数据项组成

内查找和外查找:若整个查找过程都在内存进行,则称之为内查找;反之,若查找过程中需要访问外存,则称之为外查找

查找方式:若在查找的同时对表做修改操作(如插入和删除),则相应的表称之为动态查找表;否则称之为静态查找表

查找方法的性能指标:查找的运算时间主要花费在关键字比较上,通常把查找过程中执行的关键字平均比较个数(也称为平均查找长度ASL)作为衡量一个查找算法效率优劣的标准

平均查找长度ASL定义为:
n是查找表中记录的个数; pi是查找第i个记录的概率,一般认为每个记录的查找概率相等,即pi=1/n; ci是找到第i个记录所需进行的比较次数

1.1.2 线性表的查找

顺序查找

思路:从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k相比较,若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败

算法:

int SeqSearch(RecType S[],int n,KeyType k)
{     
      int i=0;
      while (i<n && S[i].key!=k) //从表头往后找
	 i++;
      if (i>=n)	//未找到返回0
	 return 0;
      else 
	 return i+1;//找到返回逻辑序号i+1
}

平均查找长度:

成功ASL=(n+1)/2
不成功ASL=n
由不成功ASL可以得出该算法的时间复杂度是O(n)

折半查找

概念:折半查找也称为二分查找,要求线性表中的记录必须己按关键字值有序(递增或递减)排列

思路:

用low、high、mid来表示待查找区域的下界、上界和中间位置指针,设初值low=1,high=n

1.取中间位置mid=(low+high)/2 ps:最后求出来的值要向下取整

2.比较mid和给出的关键字K

①相等:查找成功
②大于:待查记录为区间的前半段,修改上界指针high=mid-1 转1步骤
③小于:待查记录为区间的后半段,修改下界指针low=mid+1 转1步骤

直到越界low>high,则查找失败

算法:

int BinSearch(RecType R[],int n,KeyType k)
{ 
       int low=0,high=n-1,mid;
       while (low<=high)//当前区间存在元素时循环
       {	
         mid=(low+high)/2;
	 if (R[mid].key==k)	//查找成功返回其逻辑序号mid+1
	      return mid+1;
	 if (k<R[mid].key)	//继续在R[low..mid-1]中查找
	      high=mid-1;
	 else
	      low=mid+1;      //继续在R[mid+1..high]中查找
       }
      return 0;
}

算法示例:

查找成功:

查找不成功:

算法分析:

查找时每经过一次比较,范围就缩小一半,这个过程可用二叉树来描述:  

①把当前查找区间的中间位置上的记录作为根
②左子表和右子表中的记录分别作为根的左子树和右子树

这样的二叉树称为判定树或比较树

二分查找的时间复杂度为O(log2n)

分块查找

概念:分块查找又称索引顺序查找,是前面两个查找方法的结合

查找表的构成:

①将查找表分成几块,块间有序,即第i+1块的所有记录关键字均大于(或小于)第i块记录关键字,块内无序
②在查找表的基础上附加一个索引表,索引表是按关键字有序的,索引表中记录的构成是:(关键字,地址)

思路:先确定待查找记录所在块,再在块里顺序查找

算法示例:

1.1.3 树表的查找

二叉排序树

概念:

二叉排序树(简称BST)又称二叉查找(搜索)树,其定义为:二叉排序树或者是空树,或者是满足如下性质(BST性质)的二叉树:

①若它的左子树非空,则左子树上所有结点值(指关键字值)均小于根结点值
②若它的右子树非空,则右子树上所有结点值均大于根结点值
③左、右子树本身又各是一棵二叉排序树

特点:

①二叉排序树的中序序列是一个递增有序序列
②根结点的最左下结点是关键字最小的结点
③根结点的最右下结点是关键字最大的结点

二叉排序树的查找

思路

首先将给定的K值与二叉排序树的根结点的关键字进行比较:

①给定的K值小于BST的根结点的关键字:继续在该结点的左子树上进行查找
②给定的K值大于BST的根结点的关键字:继续在该结点的右子树上进行查找
③若相等:则查找成功

递归算法
BSTree SearchBST(BSTree bt,KeyType k)
{ 
      if (bt==NULL || bt->key==k) 
         return bt;
      if (k<bt->key)
        return SearchBST(bt->lchild,k);   //在左子树中递归查找
      else
        return SearchBST(bt->rchild,k);   //在右子树中递归查找
}

在随机情况下,二叉排序树的平均查找长度ASL和log(h)(树的深度)是相等的

二叉排序树的插入

思路

在BST树中插入一个新结点x时,若BST树为空,则令新结点x为插入后BST树的根结点;
否则,将结点x的关键字与根结点T的关键字进行比较:

①若相等:不需要插入
②若x.keykey: 结点x插入到T的左子树中
③若x.key>T->key: 结点x插入到T的右子树中

任何结点插入到二叉排序树时,都是以叶结点插入的

递归算法
int InsertBST(BSTree &p,KeyType k)	
{ 
     if (p==NULL)  //原树为空, 新插入的记录为根结点
     {     
          p=new BSTNode;
          p->key=k;p->lchild=p->rchild=NULL;
          return 1;
     }
      else if  (k==p->key) //存在相同关键字的结点,返回0
           return 0;
      else if (k<p->key) 
           return InsertBST(p->lchild,k); //插入到左子树中
      else  
           return InsertBST(p->rchild,k);  //插入到右子树中
 }

二叉排序树的生成

算法
BSTree CreatBST(KeyType A[],int n) //返回树根指针
{      
       BSTree bt=NULL;    //初始时bt为空树
       int i=0;
       while (i<n) 
       {    
          InsertBST(bt,A[i]);  //将A[i]插入二叉排序树T中
         i++;
       }
       return bt;  //返回建立的二叉排序树的根指针
} 
平均查找长度

成功ASL=每一行的结点数÷总结点数 即 ASL(success)=(1×1+2×2+3×4+4×6)/13=3.15

不成功ASL=每一行结点空的结点数÷总共空的结点数 即 ASL(unsuccess)=(3×2+12×4)/14=3.86

二叉排序树的删除

过程分析
  • 若p是叶子结点:直接删除p,如图中步骤(b)所示
  • 者p只有一棵子树(左子树或右子树):直接用p的左子树(或右子树)取代p的位置而成为f的一棵子树,即原来p是f的左子树,则p的子树成为f的左子树;原来p是f的右子树,则p的子树成为f的右子树,如图中步骤(c)、(d)所示
  • 若p既有左子树又有右子树:处理方法有以下两种,可以任选其中一种
    • 用p的直接前驱结点代替p即从p的左子树中选择值最大的结点s放在p的位置(用结点s的内容替换结点p内容),然后删除结点s;s是p的左子树中的最右边的结点且没有右子树,如图中步骤(e)所示
    • 用p的直接后继结点代替p即从p的右子树中选择值最小的结点s放在p的位置(用结点s的内容替换结点p内容),然后删除结点s;s是p的右子树中的最左边的结点且没有左子树

算法
int DeleteBST(BSTree &bt,KeyType k)  //在bt删除关键字为k的结点
{ 
        if (bt==NULL) return 0;	//空树删除失败
        else 
        {      
            if (k<bt->key) return DeleteBST(bt->lchild,k); //递归在左子树中删除为k的结点	      
	    else if (k>bt->key) return DeleteBST(bt->rchild,k);//递归在右子树中删除为k的结点	 
            else   //相等               
           {      
               Delete(bt);    //调用Delete(bt)函数删除bt结点
	       return 1;
           }
        }
} 

void Delete(BSTree &p)   	 //从二叉排序树中删除p结点
{     
      BSTree q;
      if (p->rchild==NULL)        	//p结点没有右子树的情况
      {     
            q=p; p=p->lchild;		//用其左孩子结点替换它
            free(q);  
      }
      else if (p->lchild==NULL)    	//p结点没有左子树的情况
      {     
            q=p; p=p->rchild;	//用其右孩子结点替换它
            free(q);  
      }
      else Delete1(p,p->lchild);	
            //p结点既没有左子树又没有右子树的情况
}

void Delete1(BSTree p,  BSTree &r)//当被删p结点有左右子树时的删除过程
{     
      BSTree q;
      if (r->rchild!=NULL)
	   Delete1(p,r->rchild);	//递归找*r的最右下结点
      else  		              //r指向最右下结点
      {        
           p->key=r->key;  p->data=r->data   //值替换
	   q=r; r=r->lchild;           //删除原r结点
	   free(q); 	              //释放原r的空间
      }
}

平衡二叉树

概念:若一棵二叉树中每个结点的左、右子树的高度至多相差1,则称此二叉树为平衡二叉树(AVL)

平衡因子:该结点左子树的高度减去右子树的高度---->若一棵二叉树中所有结点的平衡因子的绝对值小于或等于1,该二叉树称为平衡二叉树

平均查找长度:在平衡二叉排序树上进行查找的平均查找长度和log2n是相等的,平均时间复杂度为O(log2n)

平衡二叉树的插入调整:

平衡二叉树中插入新结点方式与二叉排序树相似,只是插入后可能破坏了平衡二叉树的平衡性,解决方法是调整,共有四种调整方式

LL型调整
  • 调整过程:

    • B结点带左子树α一起上升
    • A结点成为B的右孩子
    • 原来B结点的右子树β作为A的左子树

RR型调整
  • 调整过程:

    • B结点带右子树β一起上升
    • A结点成为B的左孩子
    • 原来B结点的左子树α作为A的右子树

LR型调整
  • 调整过程:

    • C结点穿过A、B结点上升
    • B结点成为C的左孩子,A结点成为C的右孩子
    • 原来C结点的左子树β作为B的右子树;原来C结点的右子树γ作为A的左子树

RL型调整
  • 调整过程:

    • C结点穿过A、B结点上升
    • A结点成为C的左孩子,B结点成为C的右孩子
    • 原来C结点的左子树β作为A的右子树;原来C结点的右子树γ作为B的左子树

B树

概念:B树又称为多路平衡查找树,是一种组织和维护外存文件系统非常有效的数据结构

定义:一棵度为m的B-树称为m阶B-树,其定义是:

一棵m阶B-树,或者是空树,或者是满足以下性质的m叉树:
(1)根结点或者是叶子,或者至少有两棵子树,至多有m棵子树;
(2)除根结点外,所有非终端结点至少有m/2(向上取整)棵子树,至多有m棵子树;
(3)所有叶子结点都在树的同一层上;
(4)每个结点的结构如下,结点中按关键字大小顺序排列:

n p0 k1 p1 k2 p2 kn pn
(5)B树是所有结点的平衡因子均等于0的多路查找树,在计算B树的高度时,需要计入最底层的外部结点;

B树的存储结构

#define MAXM 10		//定义B树的最大的阶数
typedef   int KeyType;       	//KeyType为关键字类型
typedef struct node 
{      int keynum; 	         	 //结点当前拥有的关键字的个数
       KeyType key[MAXM];      	//[1..keynum]存放关键字
       struct node *parent;	   	//双亲结点指针
       struct node *ptr[MAXM]; 	//孩子结点指针数组[0..keynum]
}  BTNode;

B树的查找

将k与根结点中的key[i]进行比较:
①若k=key[i],则查找成功;
②若k<key[1],则沿着指针ptr[0]所指的子树继续查找;
③若key[i]<k<key[i+1],则沿着指针ptr[i]所指的子树继续查找;
④若k>key[n],则沿着指针ptr[n]所指的子树继续查找。
⑤当查找到某个叶结点时,若相应的指针为空,落入一个外部结点,表示查找失败

B树的插入

插入思想

①在B_树的中查找关键字K,若找到,表明关键字已存在,返回;否则,K的查找操作失败于某个叶子结点,转②;
②将K插入到该叶子结点中,插入时,若:
◆叶子结点的关键字数<m-1:直接插入;
◆叶子结点的关键字数=m-1:将结点“分裂”

分裂方法

◇如果没有双亲结点,新建一个双亲结点,树的高度增加一层
◇如果有双亲结点,将ki插入到双亲结点中

具体示例

B树的删除

插入思想

①在B_树的中查找关键字K,若找不到,表明关键字不存在,返回;否则,转②;
②将K删除,删除时,若:
◆在非叶子结点上删除关键字k→在叶子结点上删除关键字k:

◆在B树的叶子结点b上删除关键字共有以下3种情况:

◇假如b结点的关键字个数大于Min,说明删去该关键字后该结点仍满足B树的定义,则可直接删去该关键字

◇假如b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义,若可以从兄弟结点借:

◇假如b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义,若不能从兄弟结点借:

B+树

定义:B+树是B树的一些变形

一棵m阶B+树满足下列要求:  
(1)每个分支结点至多有m棵子树;
(2)根结点或者没有子树,或者至少有两棵子树;
(3)除根结点外,其他每个分支结点至少有m/2(向上取整)棵子树;
(4)有n棵子树的结点恰好有n个关键字;
(5)所有叶子结点包含全部关键字及指向相应记录的指针,而且叶子结点按关键字大小顺序链接。并将所有叶子结点链接起来;
(6)所有分支结点(可看成是索引的索引)中仅包含它的各个子结点(即下级索引的索引块)中最大关键字及指向子结点的指针;

1.1.4 哈希表的查找

哈希表的基本概念

哈希表适合情况:哈希表是一种存储结构,它并非适合任何情况,主要适合记录的关键字与存储地址存在某种函数关系的数据

哈希冲突:对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突

同义词:具有相同函数值的两个不同的关键字,称为该哈希函数的同义词

装填因子:装填因子α=存储的记录个数/哈希表的大小=n/m

α越小,冲突的可能性就越小; α越大(最大可取1),冲突的可能性就越大

哈希函数构造方法

1.直接定址法:取关键字或关键字的某个线性函数作哈希地址,即H(key)=key或H(key )=a×key +b(a,b为常数)

  • 特点:直接定址法所得地址集合与关键字集合大小相等,不会发生冲突,但实际中很少使用

2.除留余数法:取关键字被某个不大于哈希表表长m的数p除后所得余数作哈希地址,即H(key)=key MOD p (p≤m)

  • 特点:是一种简单、常用的哈希函数构造方法;利用这种方法的关键是p的选取,p选的不好,容易产生同义词

    • p最好是质数(素数)因为除留余数法就是把n个记录按关键字映射的0~m-1的哈希空间中,而模p(素数)时出现冲突的可能性更小

3.数字分析法:对关键字进行分析,取关键字的若干位或组合作为哈希地址

  • 特点:适用于关键字位数比哈希地址位数大,且可能出现的关键字事先知道的情况

冲突处理方法

开放定址法:冲突时找一个新的空闲的哈希地址

1.线性探测法
  • 线性探查法的数学递推描述公式为:

    • d0=h(k)
    • di=(di-1+1) mod m (1≤i≤m-1)
  • 线性探测法的特点:

    • 优点:只要散列表未满,总能找到一个不冲突的散列地址;
    • 缺点:每个产生冲突的记录被散列到离冲突最近的空地址上,从而又增加了更多的冲突机会(这种现象称为冲突的“聚集”)
开放定址法哈希表查找k过程:
d=h(k);
while (ha[d]!=空 && ha[d]!=k)
      d=采用某种探查法求出下一地址;
if (ha[d]==空)
      return 失败标记;
else
     return ha[d];
计算平均查找长度:

举例:本例H(k)=k mod 13

下标 0 1 2 3 4 5 6 7 8 9 10 11 12
k 77 54 16 43 31 29 46 60 74 88 90
探查次数 2 1 1 1 1 4 1 1 1 1 1
不成功探查次数 2 1 10 9 8 7 6 5 4 3 2 1 3
  • 成功ASL:探查次数恰好等于查找到该记录所需要的关键字比较次数,分母是给出的数据个数

    • ASL(success)=(2+1+1+1+1+4+1+1+1+1+1)/11=1.364
  • 不成功ASL:分母是mod后的数字

    • ASL(unsuccess)=(2+1+10+9+8+7+6+5+4+3+2+1+3)/13=4.692
2.平方探测法
  • 平方探查法的数学描述公式为:

    • d0=h(k)
    • di=(d0± i2) mod m (1≤i≤m-1)
  • 平方探测法的特点

    • 优点:平方探查法是一种较好的处理冲突的方法,可以避免出现堆积现象
    • 缺点:不能探查到哈希表上的所有单元,但至少能探查到一半单元

拉链法:拉链法是把所有的同义词用单链表链接起来的方法

拉链法哈希表查找k过程:
d=h(k);
p=ha[d];
while (p!=NULL && p->key!=k)
      p=p->next;   //在ha[d]的单链表中查找
if (p==NULL)
      return 失败标记;
else
    return p所指结点;
计算平均查找长度:

举例:本例H(k)=k mod 7

  • 成功ASL:成功找到第1层的结点,均需要1次关键字比较;成功找到第2层的结点,均需要2次关键字比较;分母为数据个数

    • ASL(success)=(15+21)/6=1.17
  • 不成功ASL:有1个结点的单链表,不成功查找需要1次关键字比较;有2个结点的单链表,不成功查找需要2次关键字比较;分母为mod后的数字

    • ASL(不成功)=(14+21)/7=0.86

1.2 谈谈你对查找的认识及学习体会

首先是预习的时候,看课件什么删除插入分裂看的头都大了,就搞不清楚而且课件不是幻灯片就有些字什么的都胡在一起and动图不会动都体现不出来过程,后来都是有看不懂的就去找相关的博客和论坛搞清楚的

对于ASL,其实一开始做哈希表的互动题目搞错了,以为不成功ASL是除以哈希表的长度,然后到了上课一时还是没搞懂,后来是老师几次三番强调说是除以mod后面的数也就是除余的那个数p

上课听老师讲解课件的代码,就觉得现在的代码是要更精简的嘛,从时间复杂度到空间复杂度都在用更好的算法来减少,然后代码看着越来越短,知识点又很多很复杂,说起来这个章节应该算是总结吧,有一种把之前的知识点整合在一起的感觉,要掌握这些代码算法也有一点难度

老师一直强调说虽然有现成的分装函数可以用,但是还是要了解底层代码的具体思想,还是要在运用的时候想清楚各个查找算法的适用环境

2.PTA题目介绍

2.1 (map) QQ帐户的申请与登陆

2.1.1 该题的设计思路

  1. 这题用map容器来解决,思路上比较好理解,不过对map容器不熟练还是需要花一点时间研究一下

具体代码截图:

时间复杂度:O(n)

2.1.2 该题的伪代码

定义int型变量N来记录N行命令;
定义char型变量ch记录是注册or登录;
定义字符串:账户account,密码password;
定义容器map; 

输入 N;

while (n--) 
    if(登录)
	if(账号不存在)输出 ERROR: Not Exist;
	else 
            if(密码正确) 输出 Login: OK;
            else 输出 ERROR: Wrong PW;
	else//注册
            if(账号不存在) 保存密码password,输出 New: OK;
            else 输出 ERROR: Exist;
end while 

2.1.3 PTA提交列表

  1. 一开始是多种错误,有格式错误和答案错误,然后当时不知道怎么下手修改,于是就从最简单的格式错误开始
  2. 格式错误对照了题目之后发现是在冒号后面少了一个空格,太大意了
  3. 对于这个答案错误,一直找不出来,然后就想是不是map有问题,然后去网上又看了一遍map容器的相关操作解释,发现是我理解错了,我一开始if语句里面用的是p.find(account) != p.end(),应该把!=换成==,因为是不成功的时候返回end()函数二不是成功的时候返回,我把两个搞混了

2.1.4 本题设计的知识点

  1. 这题主要是用map容器相关知识点来解决题目的,因为题目中要求提到QQ号已经有十位数了,所以适用字符串来定义变量

  2. map中,find()函数用来定位数据出现的位置,当含有该数据,即查找成功时,返回数据所在未知的迭代器,如果查找失败,则返回end()函数所在的迭代器,因此用是否等于end来判断是否查找成功

  3. 在注册的时候如果账号不存在可以用p[account] = password这个语句来保存密码,然后下一次登录就是输入账号然后查找对应账号位置的密码是否正确

2.2 二叉搜索树的最近公共祖先

2.2.1 该题的设计思路

  1. 首先看到题面说的是给出一个二叉搜索树的先序遍历序列,先按给出的序列画一棵树:

  2. 因为是二叉搜索树嘛,然后把它的结点值从小到大排序排序一下就可以得到中序遍历序列,两个序列就可以建树了

  3. 根据输入的先序和中序遍历可以生成一个father数组,用来保存每个结点其父节点的序号

  4. 在寻找最近公共祖先节点的时候,利用两个栈,将待查的u、v自身结点以及之上的所有祖先节点压入栈内,然后依次弹出两个栈顶的元素,直至找到不相同的两组数据,则上一个栈顶的元素即是最近公共祖先结点

具体代码截图:

时间复杂度:O(M·N)

2.2.2 该题的伪代码

定义全局变量M,N,root,输入的先序遍历queue<int>pro,中序遍历vector<int>ino,整型数组father[10010];
int main()
{
  读入M,N;
  for(i=1 to N)
     读入先序遍历序列tp并保存到pro和ino中;
  将father[0]设为0,并把0存入ino中;
  将ino中的数字整理成递增序列,即中序,并将前序的第一个数赋值给root;
  调用函数Build(1,N,0);
  while(M--)
      读入整数键值u和v并调用函数closet_father(u,v)寻找最近祖先结点;
}

void Build(int left, int right, int f)
{
  if(左值>右值) 说明树不存在,直接返回;
  定义t=当前节点的值,并把当前值pop出去;
  if(左值=右值) 说明树只有一个结点,将f的值赋给father[left];
  else
      for(i=left to right)
          寻找根节点并建树;
}

void closest_father(int u, int v)
{
  int u1=u在中序遍历序列中的下标,v1=v在中序遍历序列中的下标;
  if (u1 v1 都为0 or 其中一个为0) 为0表示找不到所在下标,则输出两者都不存在or其中一个不存在;
  if(输入的为两个根节点) 那么最近公共祖先是自己(根节点),输出;
  if (如果两个数值相同) 直接返回其父结点,输出;
  
  定义两个栈s1,s2;
  将u、v和其所有祖先节点压入栈中;
  定义flag,初值为0,表示u或v其中有一个为另一个祖先结点;
  依次弹出两个栈顶元素比较,找到不相等的两个数据,将flag置为1;
  if(栈s1为空&&flag==0||栈s2为空&&flag==0)输出对应u(v)是v(u)的祖先;
  输出u和v的公共祖先;
}

2.2.3 PTA提交列表

  1. 这题就是错的很多...一开始写的时候输出的都是乱七八糟的东西要不然就是输出不了说栈溢出or空指针,然后那个多种错误我找bug找了很久,发现是函数设计的有问题,我把函数的传参传反了 晕

  2. 修改完这个问题之后就是一堆的答案错误,我又没什么头绪,就想会不会是输入的格式有问题导致的然后就又改了一遍输出(果然不是呢)后来就一直瞎改瞎提交

  3. 后来我就是问同学,然后发现是从建树开始有问题,于是就修改了建树的函数内容,我一开始是直接用的前序遍历搞建树,然后这题是二叉搜索树,跟之前的不一样,不能盲目套用

  4. 剩下的错误点不好调试,我就问同学然后慢慢一点点的修改,找公共祖先那里绕了好一会才绕出来,然后findorder的函数里面i从0开始循环导致return出现了错误

2.2.4 本题设计的知识点

  1. 首先是建树,因为这里是二叉搜索树,如果是单纯的前序排列是建不出来树的,然后我是设置了一个变量来保存中序遍历的结果,然后用中序和先序遍历的数值建树

  2. 运用sort(ino.begin(), ino.end());这个语句对二叉搜索树进行排序,也就是得出了中序遍历的结果,然后建树就比较方便了,不过要记得加好头文件

  3. 要注意输入数据并非是连续的从1到N的数,而是N个int型的整数,因此father中存储的都是将输入数据按非递减排序得到的相对顺序标号

2.3 (哈希链) 航空公司VIP客户查询

2.3.1 该题的设计思路


具体代码截图:

时间复杂度:O(n²)

2.3.2 该题的伪代码

定义结构体node存储用户ID和飞行里程,结构体指针为List;结构体table用来存储哈希链长度和指针,结构体指针为Hashlist;
int main()
{
  读入飞行记录条数n和最少里程k;
  定义Hashlist型变量H并调用函数Init()赋值给H;
  for(i=0 to n-1)
     读入ID和里程tmp,if(tmp<k)将tmp修改为k的值;
     调用函数Insert();
  读入查询人数m和用户ID,调用函数Find查找是否存在,不存在输出"No Info",存在输出里程;
}

LL Hash(char key[], LL size)
{
  定义tmp=0;
  for(i=15 to 18)
      将身份证后四位转化为数字存入tmp中并返回tmp;
}

LL NextPrim(LL x)
{
  for(i = x;;i++)
     for(j = 2 to j²<= i)
	 if (i是j的整数倍) break;
     if (j²> i) return i;
}

Hashlist Init(LL size)
{
  定义Hashlist型变量H并初始化;
  调用函数NextPrim找出链表的长度Tablesize;
  for(i=0 to Tablesize-1)
      初始化链表结点;
  return H;
}

List Find(char key[], Hashlist H)
{
  定义变量t存储hash链表的内容;
  定义p=t->next;
  while(ID相等且p!=NULL) p指向下一个结点;
  return p;
}

void Insert(char key[], LL miles, Hashlist H)
{
  if(输入的ID的里程信息为空)
     将miles赋值给tmp并用头插法插入结点;
  else  里程累加; 
}

2.3.3 PTA提交列表

  1. 一开始是两个运行超时,然后我以为是循环数据太大,改了一下发现还是不行

  2. 然后答案错误是我把Insert函数的插入操作写错了,就导致哈希链出了问题

  3. 段错误是在find函数的if条件判断里面,我把p != NULL漏了,忽略了这个条件导致出了差错

  4. 然后又回到开始的部分正确,我就很气急败坏,然后想起来老师说不能用cin和cout要改成scanf和printf,然后我就试了一下发现还是差一个测试点过不去

  5. 后来又看了一遍代码,想着cin啥的不行的话应该new也不太行,然后就用malloc来申请动态空间,然后可以过辽

2.3.4 本题设计的知识点

  1. 这题是哈希链的应用,不过这题用哈希就有一点不好解决,每个客户的里程怎么保存,当初课上想了很久,然后大概根据老师的讲解写了一个结构体,平常用数组模拟链表的时候都是直接开的一维数组,所以当每个客户的信息多了后就没有结构体来的理解方便了

  2. 要注意哈希函数的选取,尽量让哈希函数取值接近数据最大值,这样能减少很多冲突

  3. 虽然尽可能减少不过哈希还是会出现冲突,需要处理,所以数组模拟链表处理冲突

posted @ 2020-05-24 21:07  陈璧君  阅读(405)  评论(0编辑  收藏  举报