DS博客作业05--查找

0.PTA得分截图

1.本周学习总结(0-4分)

1.1 总结查找内容

1.1.1 查找性能指标ASL:

  • ASL即平均查找长度,在査找的过程中,一次査找的长度是指需要比较的关键字次数,而平均査找长度则是所有査找过程中进行关键字的比较次数的平均值。
  • 其数学定义为:

    式中,n 是査找表的长度,Pi 是査找第 i 个数据元素的概率,一般认为每个数据元素的査找概率相等,即 Pi=1/n,Ci 是找到第 i 个数据元素所需进行的比较次数。平均査找长度是衡量査找算法效率的最主要的指标。

1.1.2 静态查找几种算法

  • 顺序查找:
    • 顺序查找,又称为线性査找,主要用于在线性表中进行査找。
    • 顺序査找通常分为对一般的无序线性表的顺序査找和对按关键字有序的顺序表的顺序査找。
    • 一般线性表的顺序查找的ASL:
      • 成功:
      • 不成功:
    • 有序表的顺序查找的ASL:
      • 不成功:
  • 二分查找:
    • 二分査找,又称为折半查找,它仅适用于有序的顺序表。
    • 基本思路是:
      • 首先将给定值 key 与表中中间位置元素的关键字比较,
      • 若相等,则査找成功,返回该元素的存储位置;
      • 若不等,则所需査找的元素只能在中间元素以外的前半部分或后半部分中
    • ASL成功=log(n+1)-1
    • ASL不成功=log(n+1)

1.1.3 动态查找:二叉搜索树

  • 定义:二叉搜索树,也称有序二叉树,排序二叉树,是指一棵空树或者具有下列性质的二叉树:
      1. 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
      1. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
      1. 任意节点的左、右子树也分别为二叉查找树。
      1. 没有键值相等的节点。
  • 二叉搜索树的构建:
    • 结构体定义:
typedef struct node 
{     KeyType key;            	  //关键字项
     InfoType data;          	  //其他数据域
      struct node *lchild,*rchild; 	  //左右孩子指针
}  BSTNode,*BSTree;
  • 生成:
BSTNode *CreatBST(KeyType A[],int n) //返回树根指针
{      BSTNode *bt=NULL;    //初始时bt为空树
       int i=0;
       while (i<n) 
       {    
         InsertBST(bt,A[i]);  //将A[i]插入二叉排序树T中
       i++;
       }
       return bt;       	    //返回建立的二叉排序树的根指针
} 
  • 插入:
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);    //插入到右子树
 }
  • 删除:
int DeleteBST(BSTree &bt,KeyType 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);                                    //删除*bt节点
	   return 1;
     }
   }
} 
void Delete(BSTreee &p)              //从二叉排序树中删除*p节点
{  
   BSTNode *q;

   if (p->rchild==NULL)              //*p节点没有右子树的情况
   {   
      q=p; p=p->lchild;delete q; 
   }
   else if (p->lchild==NULL)         //*p节点没有左子树
   {    
      q=p; p=p->rchild;delete q; 
   }
   else Delete1(p,p->lchild);	     //*p节点既有左子树又有右子树的情况
}
void Delete1(BSTNode *p,BSTNode *&r)    //被删节点:p,p的左子树节点:r           
{  
   BSTNode *q;

   if (r->rchild!=NULL)
      Delete1(p,r->rchild);             //递归找最右下节点
   else  	                        //找到了最右下节点*r
   {                                    //将*r的关键字值赋给*p
      p->key=r->key;
      q=r; r=r->lchild;delete q;     	   
   }
}
  • 查找:
    递归法:
BSTNode *SearchBST(BSTNode *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);    //右子树中查找
 }

迭代法:

BSTNode *SearchBST1(BSTNode *bt,KeyType k)
{                         //迭代版
   while (bt!=NULL)
   {
      if (k==bt->key)
          return bt;
      else if (k<bt->key)
          bt=bt->lchild;  //在左子树中迭代查找
      else
          bt=bt->rchild;  //在左子树中迭代查找
   }
   return NULL;           //没有找到返回NULL
}

1.1.4 AVL树的定义及4种调整做法

  • 定义:
    • 左、右子树是平衡二叉树;
    • 所有结点的左、右子树深度之差的绝对值(平衡因子)≤ 1
    • 平衡因子:该结点左子树与右子树的高度差
  • 结构体定义:
typedef struct node     //记录类型
{ KeyType key;    	//关键字项
   int bf;			//增加的平衡因子
   InfoType data;     	//其他数据域
 struct node *lchild,*rchild;//左右孩子指针
} BSTNode;
  • 如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转,包括:

    • LL平衡旋转
    • RR平衡旋转
    • LR平衡旋转
    • RL平衡旋转
  • 左左(LL)型调整:

    • 若在A的左子树的左子树上插入结点,使A失衡,平衡因子2,需要进行一次顺时针旋转。
      1.A的左孩子B右上旋转作为A的根节点
      2.A节点右下旋转称为B的右孩子
      3.B原右子树称为A左子树
  • 右右(RR)型调整:

    • 若在A的右子树的右子树上插入结点,使A的平衡因子从-1增加至-2,需要进行一次逆时针旋转
      1.A的右孩子B左上旋转作为A的根节点
      2.A节点左下旋转称为B的左孩子
      3.B原左子树称为A右子树
  • 右左(RL)型调整:

    • 若在A的右子树的左子树上插入结点,使A的平衡因子从-1增加至-2,(以插入的结点C为旋转轴),先进行顺时针旋转,再逆时针旋转。
      1.C向上旋转到A的位置,A作为C左孩子
      2.C原左孩子作为A的右孩子
      3.C原右孩子作为B的左孩子
  • 左右(LR)型调整:

    • 若在A的左子树的右子树上插入结点,使A的平衡因子从1增加至2,(以插入的结点C为旋转轴),先C进行逆时针旋转,A再顺时针旋转。
      1.C向上旋转到A的位置,A作为C右孩子
      2.C原左孩子作为B的右孩子
      3.C原右孩子作为A的左孩子
  • 总结:

    • 多个失衡点,从最下面失衡点开始调整
    • 了解是哪种失衡,谁是失衡点
    • LL和RR是选择失衡点的L或R孩子旋转;LR和RL选择失衡点的相应的LR或RL孙子旋转。
    • 每次调整后,检查是否二叉排序树。还有失衡点否。

1.1.5 B-树

  • 定义:
    一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
    每个节点至多m个孩子节点(至多有 m-1 个关键字)
    除根节点外,其他节点至少有 m/2 个孩子节点(即至少有m/2-1 个关键字);
    若根节点不是叶子节点,根节点至少两个孩子节点
  • 用途:
    使用B-树结构可以显著减少定位记录时所经历的中间过程,从而加快存取速度。这个数据结构一般用于数据库的索引,综合效率较高。
  • B-树的存储结构有关定义:
#define MAXM 10		  //定义B-树的最大的阶数
typedef int KeyType;      //KeyType为关键字类型
typedef struct node       //B-树节点类型定义
{  
   int keynum; 	          //节点当前拥有的关键字的个数
   KeyType key[MAXM];     //[1..keynum]存放关键字,[0]不用
   struct node *parent;	  //双亲节点指针
   struct node *ptr[MAXM];//孩子节点指针数组[0..keynum]
} BTNode;
  • 查找返回结果类型定义:
typedef struct {
  BTNode  *pt;     // 指向找到的结点的指针
  int  i;       // 1..m-1,在结点中的关键字序号
  int  tag;   // 标志查找成功(=1)或失败(=0)
} Result;             // 在B树的查找结果类型
  • B-树的查找:
    在一棵B-树上顺序查找关键字为k的方法为:
    将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-树的插入:
    在查找不成功之后,需进行插入。关键字插入的位置必定在叶子结点层,有下列几种情况:
    • 该结点的关键字个数 n<m-1 ,不修改指针;
    • 该结点的关键字个数 n=m-1 ,则需进行“结点分裂”
      结点分裂
    • 1.如果没有双亲结点,新建一个双亲结点,树的高度增加一层。
    • 2.如果有双亲结点,将ki插入到双亲结点中。
  • B-树的删除:
    删除关键字k分两种情况:
    • 在叶子结点层上删除关键字k。
    • 在非叶子结点层上删除关键字k。
      注意:非根、非叶子结点的关键字最少个数Min=m/2-1
      B树非叶子节点删除
    • 1.从pi子树节点借调最大或最小关键字key代替ki。
    • 2.pi子树中删除key
    • 3.若子树节点关键字个数< m/2-1,重复步骤1
      B树叶子节点删除
      假如b结点的关键字个数大于Min,说明删去该关键字后该结点仍满足B树的定义,则可直接删去该关键字。
      假如b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义。若可以从兄弟结点借。
    • 1.兄弟结点最小关键字上移双亲结点
    • 2.双亲结点大于删除关键字的关键字下移删除结点
      结点的关键字个数等Min,兄弟节点关键字个数也等于Min
    • 1.删除关键字
    • 2.兄弟节点及删除关键字节点、双亲结点中分割二者关键字合并一个新叶子结点
    • 3.若双亲结点关键字个数<=Min,重复2

1.1.6 B+树

  • 应用:索引文件组织中,经常使用B-树的变形B+树。B+树是大型索引文件的标准组织方式。
  • 定义:
    • 1.每个分支节点至多有m棵子树。
    • 2.根节点或者没有子树,或者至少有两棵子树
    • 3.除根节点,其他每个分支节点至少有m/2棵子树
    • 4.有n棵子树的节点有n个关键字。 
    • 5.所有叶子节点包含全部关键字及指向相应记录的指针
      • 叶子节点按关键字大小顺序链接
      • 叶子节点是直接指向数据文件中的记录。
    • 6.所有分支节点(可看成是分块索引的索引表)
      • 包含子节点最大关键字及指向子节点的指针。
  • B+树的查找
    • 直接从最小关键字开始进行顺序查找所有叶节点链接成的线性链表。
    • 从B+树的根节点出发一直找到叶节点为止。

1.1.7 散列查找:

哈希表:

  • 定义:
    哈希表又称散列表,是除顺序表存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构。
  • 哈希函数h(key):
    把关键字为ki的对象存放在相应的哈希地址中
  • 哈希表:
    存储数据记录的长度为m(m≥n)的连续内存单元
  • 哈希冲突:
    对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)。
  • 哈希表设计:
    哈希表设计主要需要解决哈希冲突。实际中哈希冲突是难以避免的,主要与3个因素有关:
    • 哈希表长度
      • 与装填因子有关。
      • 装填因子α=存储的记录个数/哈希表的大小=n/m
      • α越小,冲突可能性就越小;α越大(最大可取1),冲突的可能性就越大。控制在0.6~0.9的范围内
    • 与所采用的哈希函数有关。
    • 与解决冲突方法有关。
  • 哈希函数构造方法:
    1.直接定址法:
    • 取关键字或关键字的某个线性函数值为哈希地址:
        即:H(key) = key 或 H(key) = a*key+b
    • 优点:简单,均匀,不会产生冲突;
    • 缺点:需要实现直到关键字的分布情况,适合查找表比较小且连续的情况。
      2.数字分析法:
    • 数字分析法用于处理关键字是位数比较多的数字,通过抽取关键字的一部分进行操作,计算哈希存储位置的方法。
    • 例如:关键字是手机号时,众所周知,我们的11位手机号中,前三位是接入号,一般对应不同运营商的子品牌;中间四位是HLR识别号,表示用户号的归属地;最后四位才是真正的用户号,所以我们可以选择后四位成为哈希地址,对其在进行相应操作来减少冲突。
    • 数字分析法适合处理关键字位数比较大的情况,事先知道关键字的分布且关键字的若干位分布均匀。
      3.除留余数法:
    • 此方法为最常用的构造哈希函数方法。对于哈希表长为m的哈希函数公式为:
         f(key) = key mod p (p <= m)
       - 此方法不仅可以对关键字直接取模,也可以在折叠、平方取中之后再取模。
       - 所以,本方法的关键在于选择合适的p,若是p选择的不好,就可能产生 同义词;根据前人经验,若散列表的表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。
  • 哈希冲突的解决:
    1.开放地址法:
    • 开放地址法就是指:一旦发生了冲突就去寻找下一个空的哈希地址,只要哈希表足够大,空的散列地址总能找到,并将记录存入。
    • 公式:Hi=(H(*key) + Di) mod m (i = 1,2,3,….,k k<=m-1)
    • 其中:H(key)为哈希函数;m为哈希表表长;Di为增量序列,有以下3中取法:
      • 1.Di = 1,2,3,…,m-1, 称为线性探测再散列;
      • 2.Di = 1²,-1²,2²,-2²,。。。,±k²,(k<= m/2)称为二次探测再散列
      • 3.Di = 伪随机数序列,称为伪随机数探测再散列。
    • 例如:在长度为12的哈希表中插入关键字为38的记录:

      从上述线性探测再散列的过程中可以看出一个现象:当表中i、i+1位置上有记录时,下一个哈希地址为i、i+1、i+2的记录都将填入i+3的位置,这种本不是同义词却要争夺同一个地址的现象叫“堆积“。即在处理同义词的冲突过程中又添加了非同义词的冲突;但是,用线探测再散列处理冲突可以保证:只要哈希表未填满,总能找到一个不发生冲突的地方。

2.平方探查法:

  • 平方探查法的数学描述公式为:
  • 平方探查法是一种较好的处理冲突的方法,可以避免出现堆积现象。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元。
  • 查找哈希表和哈希链的时间性能ASL
    • 例子:
    • ASL(succ)=(1+2+1+1+1+3+3/7=12/7
    • ASL(unsucc)=(3+2+1+2+1+5+4/7=18/7
    • 总结:
      • 查找成功ASL:

      • 不成功的ASL:

  • 代码实现:
    • 结构体定义:
#define MaxSize 100
#define NULLKEY -1
#define DELKEY -2
typedef char *InfoType; 
typedef struct
{
	int key;            // 关键字域 
	InfoType data;      //其他数据域 
	int count;          //探查次数 
}HashTable[MaxSize]; 
  • 建立哈希表:
void CreateHT(HashTable ha, KeyType x[], int n, int m, int p) 
{                                              //代码实现
	int i;

	for (i = 0;i < m;i++)                  //初始化
	{
		ha[i].key = NULLKEY;
		ha[i].count = 0;
		ha[i].data = NULL;
	}
	for (i = 0;i < n;i++)                  //插入关键字
		InsertHT(ha, n, x[i], p);
}
  • 哈希表的插入:
int InsertHT(HashTable ha,int p,int k,int &n)
{
   int adr,i;
   adr=k % p;
   if(adr==NULLKEY || adr==DELKEY)    //地址为空,可插入数据
   { 
      ha[adr].key=k;
      ha[adr].count=1;
   } 
   else
   { 
      i=1;
      while(ha[adr].key!=NULLKEY && ha[adr].key!=DELKEY)
      {    
          adr=(adr+1) % m; 
          i++;
      }                                //查找插入位置
      ha[adr].key=k;ha[adr].count=i;   //找到插入位置 
   }
   n++;
}
  • 哈希表的查找:
int SearchHT(HashTable ha,int p,int k)
{
  int i=0,adr;
  adr=k % p;
  while(ha[adr].key!=NULLKEY && ha[adr].key!=k)
  adr=(adr+1) % m;//探查下一个地址 
  if(ha[adr].key==NULLKEY) return -1;//地址为空,找不到 
  if(ha[adr].key==k) return adr;   //找到关键字k  
  else return -1; 
}
  • 哈希表的删除:
int DeleteHT(HashTable ha,int p,int k,int &n)
{   
   int adr;
   adr=SearchHT(ha,p,k);
   if(adr!=-1)
   {
      ha[adr].key=DELKEY;      //逻辑删除,该元素贴删除标记
      n--;
      return 1; 
   }
   else return 0;
} 

哈希链:

  • 哈希链结构体:
#define HashSize 20000
#define ID_LAST 17
#define MAXSIZE 20
typedef struct HashNode
{
	string ID;
	int flight_distance;
	struct HashNode* next;
}HashNode, * HashChain;
  • 创建哈希链以及插入数据
void CreateHashChain(HashChain HC[], int n, int k)
{
	int i;

	for (i = 0;i < HashSize;i++)
	{
		HC[i] = new HashNode;
		HC[i]->next = NULL;
	}
	for (i = 0;i < n;i++) Insert(HC, k);
}
void Insert(HashChain HC[], int k)
{
	string ID;
	int distance;
	int index;
	HashChain node;
	HashChain p;

	ID.resize(MAXSIZE);
	scanf("%s%d", &ID[0], &distance);
	if (distance < k) distance = k;
	index = GetID(ID) % HashSize;
	p = HC[index]->next;
	while (p != NULL)
	{
		if (p->ID == ID) break;
		else p = p->next;
	}
	if (p == NULL)
	{
		node = new HashNode;
		node->ID = ID;
		node->flight_distance = distance;
		node->next = HC[index]->next;
		HC[index]->next = node;
	}
	else p->flight_distance += distance;
}
  • 哈希链的查找:
void Search(HashChain HC[], string ID)
{
	int index;
	HashChain p;

	index = GetID(ID) % HashSize;
	p = HC[index]->next;
	while (p != NULL)
	{
		if (p->ID == ID)
		{
			printf("%d", p->flight_distance);
			return;
		}
		p = p->next;
	}
	printf("No Info");
}

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

  • 查找就是在一堆数据元素中找到我们所需的数据,它是一个平台上最基础的算法,很大部分决定了该平台的运行效率,因此使用高效的查找算法尤为重要
  • 本章学习由简入深地从静态查找学到了动态查找,开拓了新的知识面
  • 在各种情景下对于链、顺序结构中的查找有了明确的方法
  • 在应对小数据和大数据时都有适用的查找方法去应用

2.PTA题目介绍(0--6分)

2.1 7-1 是否完全二叉搜索树 (30分)(2分)

2.1.1 该题的设计思路

  • 题目分析:
    • 输入样例:

    • 存储方式:

      • 输入第一行给出一个不超过20的正整数N。用整型变量int存储。
      • 第二行给出N个互不相同的正整数,其间以空格分隔。用整型数组int[]存储。
  • 解题思路:
    • 题中要求根据输入N个正整数生成二叉搜索树,输出二叉搜索树的层次遍历序列并判断该树是否为完全二叉搜索树。
    • 先将输入的N个正整数,依次插入二叉搜索树中生成一颗二叉搜索树。(需注意本题中左子树键值大,右子树键值小)
    • 对树的结点进行层次遍历,当遍历到空结点即停止,这时候遍历过的结点都为连续的,如果此时记录的遍历结点数=树的所有结点树,即为完全二叉树。
  • 时间复杂度O(n)

2.1.2 该题的伪代码

#include头文件
树结点结构体定义
int main()
{
    定义所需的变量
    输入n
    for循环建树
    if LevelOrder函数返回值为true then 输出'YES'
    else 输出'NO'
}
bool LevelOrder(BSTree bst)
{
    if bst为空 then 返回false
    定义变量,队列和树
    sign = flag = true;
    next = false;
    将bst入队
    while 队列不为空
    {
        将队首赋值给node并弹出队首
        if flag为true then
            输出node->key并将flag赋值为false
        else
            输出空格和node->key
        if node的左孩子不为空 then 将左孩子入队
        if node的右孩子不为空 then 将右孩子入队
        if next为false then
        {
            if node的左孩子为空并且右孩子不为空 then sign赋值为false
            else if node的右孩子为空 then next赋值为true
        }
        else 
            if node的左孩子不为空并且右孩子不为空 then sign赋值为false
    }
    输出回车并返回sign
}
void InitBSTree(BSTree& bst, int key)
{
    为bst申请地址
    bst->key=key
    bst的左右孩子赋值为空
}
void InsertBSTree(BSTree& bst, int key)
{
    if bst为空 then
    {
        为bst申请地址
        bst->key=key
        bst的左右孩子赋值为空
    }
    else if key小于bst->key then InsertBSTree(bst->rchid, key);
    else if key大于bst->key then InsertBSTree(bst->lchid, key);
}

具体代码:


2.1.3 PTA提交列表

  • 在bst为空结点时,原先没有判断队列是否为空,导致出现错误。因为用qu.front()函数的前提是队列不能为空,所以要增加!qu.empty()这个条件。
  • 在得出不是完全二叉树的结论后不能立刻return false,因为此时层序遍历可能还没有全部遍历完成
  • 将空结点的左右孩子入队,导致错误。要先判断目前结点是否是空结点,再根据判断进行不同的操作。

2.1.4 本题涉及的知识点

  • 二叉搜索树的创建,二叉搜索树的创建是通过不断调用插入函数实现的
  • 层序遍历在树中的应用
  • 在使用取队头的函数时,要先确保队列不为空
  • 在二叉树的层序遍历中结合计数法判断是否完全二叉树的方法

2.2 7-4 整型关键字的散列映射 (25分)

2.2.1 该题的设计思路

  • 题目分析:
    • 输入样例:

    • 存储方式:

      • 由于两个正整数N(≤1000)和P(≥N的最小素数),分别为待插入的关键字总数、以及散列表的长度以及输入第二行均为整型数据,因而均可以用int存储。
  • 解题思路:
    • 建立哈希表,在插入关键字的过程中返回对应的逻辑位置并输出
  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)

2.2.2 该题的伪代码

int main()
{
    定义所需变量
    获取输入的数据
    建立哈希表
    return 0
}
int InsertHT(HashTable* ha, KeyType k, int p)
{
    将计算所得的哈希地址赋值给adr
    if h[adr].key == NULLKEY 或者 h[adr].key == DELKEY then 插入数据
    else
    {
	while h[adr].key 不为 NULLKEY 并且 h[adr].key 不为 DELKEY
		if 发现重复关键字 then 返回所在位置adr
		线性探测插入位置
	插入数据
    }
    返回插入位置adr
}
void CreateHT(HashTable*& ha, int n, int p)
{
    创建并初始化哈希表
    for i从0到p 将哈希表h初始化
    for i从0到n
    {
	将数据插入InsertHT(h, key, p)到哈希表中
	输出插入数据的逻辑位置
    }
}

具体代码:


2.2.3 PTA提交列表

  • 没有考虑到插入关键字重复时的插入处理,导致重复插入后,发现重复的关键字就return
  • 发现重复的关键字,return -1,题意没有理解清楚,后修正为return原来的插入位置

2.2.4 本题设计的知识点

  • 哈希表的结构体定义、创建方法和初始化操作,包括关键字的初始化
  • 练习哈希表的建立,每次插入一个关键字,出现哈希冲突就使用线性探测的方法解决冲突

2.3 7-5(哈希链) 航空公司VIP客户查询 (25分)

2.3.1 该题的设计思路

  • 题目分析:
    • 输入样例:

    • 存储方式:

      • 由于正整数N(≤10⁵)和K(≤500)和正整数M(≤10⁵)均在整型范围内,因而可用int来存储
      • 对于身份证可用stl库中的string类来存储,而飞行里程也可以使用int型来存储数据
  • 解题思路:
    • 首先是要建立起顾客信息的哈希链
      • 根据客户的ID,取后五位转化为数字,然后计算哈希地址
      • 遍历对应的链,如果已存在相同的ID,则直接累加里程即可,不存在则添加
    • 查询信息时同取后五位计算哈希地址查找即可。
  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)

2.3.2 该题的伪代码

int main()
{
    定义整型变量n,k,m,字符串ID和哈希链HC
    动态分配哈希数组HC
    输入n和k的值
    调用函数创建哈希链
    输入m的值
    循环查找m个ID并返回其在哈希链中的里程数
    返回0
}     
void Search(HashChain HC[], string ID)
{
    计算ID在哈希链中的地址
    while pi不为空
        if pi->ID等于ID
            输出对应的里程数并退出函数
        pi=pi->next
    输出'No Info'
}
void CreateHashChain(HashChain HC[], int n, int k)
{
    初始化哈希链的各结点
    for(i = 0;i < n;i++) 
        调用Insert函数插入飞行记录
}
void Insert(HashChain HC[], int k)
{
    输入飞行记录,包括用户ID和里程数
    根据VIP规定修改累加前的里程数
    while pi不为空
        if pi->ID等于ID then 退出循环
        else pi=pi->next
    if p为空 then
        申请节点并复制
	头插法插入关键字
    else
        在对应地址链中进行原有结点的累加操作
}
int GetID(string ID)
{
    for i从13到ID_LAST 
        计算并返回ID的后六位数对应的整型数
}

具体代码:



2.3.3 PTA提交列表

  • 发现直接取ID的最后一位作为哈希地址,导致每条单链表过长后,取后五位的对哈希表长度进行取余,余数作为哈希地址,扩大哈希表长度,以减少每条单链表的长度
  • 在适用C++中的cin和cout时耗时过长导致运行超时,后改为scanf和pritf进行输入输出

2.3.4 本题设计的知识点

  • 练习哈希链的创建和查找,创建就是计算哈希地址,然后插入对应的单链表中,查找也得先计算哈希地址,然后在单链表中查找对应的信息
  • 在长字符中取得适当的关键字作为哈希地址的计算值
  • 使用cin和cout比起使用scanf和pritf来输入输出,要更加耗时,在时间限制严格的题目中,应该使用scanf和pritf来输入输出
  • 创建哈希链的过程中,应该注意不要让单链表的长度过长,否则查找和插入都将耗时过长
posted @ 2020-05-24 19:05  叶璃O  阅读(280)  评论(0编辑  收藏  举报