DS博客作业05--查找
0.PTA得分截图
1.本周学习总结(0-4分)
1.1 总结查找内容
查找
查找,顾名思义就是在一堆数据中找到想要的数据,具体体现就是我们平时使用的搜索引擎,而无论什么搜索引擎,查找的数据都是很庞大的,所以一味的暴力查找不是办法,所以才出现了查找方法的不同
静态查找:仅仅只执行“查找”,“检索”两种指令的查找表,静态的查找不会改变数据内容,仅仅只是在浏览数据‘
动态查找:有时候,对于一些数据进行处理时,我们不仅仅是对它进行查找,同时找不到的情况下,我们还需要将我们想要的数据插入其中,或者找到数据,但是数据不需要,我们需要进行删除;动态的查找不仅仅只是浏览,它还会修改数据的内容
查找的性能指标ASL
之前有说,我们进行查找时不能暴力查找,所以有了不同的查找方法,但是不同的方法性能是不同的,为了对比这些方法,查找里存在一个指标ASL,也就是平均搜索长度;ASl包含了对查找个数,找到概率以及需要找的次数的计算,以此来得出一个查找算法的指标,即
ASL=\(\sum_{i=1}^{n}p_{i}c_{i}\)
n:查找个数
\(p_{i}\):概率
\(c_{i}\):查找次数
对于查找,存在成功与不成功两种,所以ASL也有成功与不成功两种,而不同算法,二者计算方法不同
顺序查找
顺序查找其实我们并不陌生,顺序查找的表是线性表,而查找方法就是依次扫描比对,看是否能成功找到
给定一组数据:3 10 2 8 6 7 9 15 5 4
现在请查找11,未找到,在数据中比较了10次
现在请查找6,找到,在数据中比较了5次
一般顺序查找查找成功时ASL=$\frac{n+1}{2}$,也就是我们一般会查找表一半的长度
而查找不成功,非常简单,遍历完整个表都没有就是不成功的情况,即ASL=n
顺序查找代码,其实就是遍历线性表,这也是我们熟悉的
int SeqSearch(Seqlist R,int n,KeyType k)
{
int i=0;
while(i<n&&R[i].key!=k)
i++;
if(i>=n)
return 0;
else
return i+1;
}
二分查找
之前我们就接触过二分查找,根据将查找区间折半来不断缩小查找范围,以此减少比较次数,这就是二分查找,只是,若想实现二分查找,我们的线性表中数据,必须按递增或递减排序,这里我们就会发现,按递增或者递减,那最适合的存储结构就是顺序存储,通过下标不断改变查找范围,所以根据这些,二分查找的代码就清晰明了了
int BinSearch(SeqList R,int n, KeyType k)
{
int low=0,high=n-1,mid;
while(low<=high)
{
mid=(low+high)/2;
if(R[mid].key==k)
return mid+1;
if(k<R[mid].key)
high=mid-1;
else
low=mid+1;
return 0;
}
}
二分查找的ASL
我们可以构建判定树来计算二分查找的ASL,以当前查找区间的中间位置为个根节点,左右两侧数据作为左右子树
一组数据:2 4 7 9 10 14 18 26 32 40 50
构建判定树,根节点为14,根据不断折半,不断选出下一个子树的结点
查找成功:即在树中找到一个接点,则平均查找长度为
ASL=\(\frac{1*1+2*2+4*3+4*4}{11}\)=3
查找不成功,则出现在空白区域,故需要算到空白区域的可能性
ASL=\(\frac{4*3+8*4}{12}\)=3.67
我们可以看出折半查找的判定树在n较大时,判定树可以近似看做接点树为n=\(2^{h}\)-1,高度h=\(log_{2}\left ( n+1 \right )\)
所以得出二分查找的ASL为
成功
\(ASL_{bn}= \sum_{i=1}^{h}p_{i}c_{i}= \frac{1}{n}\sum_{i=1}^{h}2^{i-1}*i=\frac{n+1}{n}*log_{2}\left ( n+1 \right )-1\approx log_{2}\left ( n+1 \right )-1\)
失败
\(ASL_{sq}= \sum_{i=1}^{h}p_{i}c_{i}= \frac{1}{n}\sum_{i=1}^{h}i=\frac{1}{n}*\frac{n(n+1)}{2}=\frac{n+1}{2}\)
由此我们可以看出二分法在查找上比起顺序查找效率更高
二叉搜索树
对于查找其实树是最普遍的,而在查找树中,二叉搜索树是最基础的
二叉搜索树:是一棵空树,或者:左子树上结点均小于根节点,右子树结点均大于根节点,同时左右子树也都是二叉搜索树
如图就是一棵二叉搜索树
构建二叉搜索树
构建二叉搜索树时我们需要先判断根节点是谁,根据根节点将数插入左右子树即
if(num>根节点)入右子树
if(num<根节点)入左子树
一般二叉搜索树的构建与插入是在一起的,因为代码都是基于不断插入实现
代码展示
BinTree CreateBST(ElementType A[],int n)
{
BinTree bt=NULL;
int i=0;
while(i<n)
{
Insert(bt,A[i]);
i++;
}
return bt;
}
BinTree Insert(BinTree BST, ElementType X)
{
if (BST == NULL)
{
BST = (BinTree)malloc(sizeof(struct TNode));
BST->Data = X;
BST->Left = BST->Right = NULL;
}
else if (X < BST->Data)
BST->Left= Insert(BST->Left, X);
else
BST->Right=Insert(BST->Right, X);
return BST;
}
二叉搜索树的节点删除
删除其实也需要进行判断,因为我们不知道删除结点是不是存在孩子结点,如果存在贸然删除,整棵树都会出现问题,所以删除需要进行三个判定
- 1.删除的是叶子结点
- 2.删除的结点只有左子树或者只有右子树
- 3.删除的结点既有左子树也有右子树
基于三种情况我们的删除也分不同方法 - 1.如果是叶子结点,直接删除,不造成任何影响
- 2.如果存在右子树或者左子树,则先进行替代,即用它的左子树或右子树代替它,用代码解释就是修改指向关系
- 3.如果既有左子树又有右子树,这时我们需要用它的前驱或者后继去替代它,前驱值左子树中最大结点,后继指右子树中最小结点,这样才能保证二叉搜索树的定义
根据三种情况,我们删除的代码为
BinTree Delete(BinTree BST, ElementType X)
{
Position temp;
if (!BST)
{
printf("Not Found\n");
}
else
{
if(X < BST->Data)
{
BST->Left = Delete(BST->Left,X);
}
else if(X > BST->Data)
{
BST->Right = Delete(BST->Right,X);
}
else
{
if(BST->Left && BST->Right)
{
temp = FindMin(BST->Right);
BST->Data = temp->Data;
BST->Right = Delete(BST->Right,temp->Data);
}
else
{
temp = BST;
if(!BST->Left)
{
BST = BST->Right;
}
else if(!BST->Right)
{
BST = BST->Left;
}
free(temp);
}
}
}
return BST;
}
这段代码包含三种情况判断以及不同情况的处理
AVL树的定义及4种调整做法。
前面我们提到了二叉搜索树,而我们二叉搜索树有一个问题就是,如果存在空白太多,查找效率就会降低,而为了提高查找效率,我们需要使树平衡,所以就有了AVL树,也就是平衡二叉树
AVL树的左右子树都是AVL树,且它的所有结点左右子树深度之差的绝对值不大于1,即它的左右子树之间趋于平衡,不会出现一方特别多,一方特别少的情况
最简单的AVL树如图
图中我们可以发现左右子树之差绝对不大于1
AVL树节点类型
typedef struct node
{
KeyType key;//关键字项
int bf;
InfoType data;
struct node* lchild,*rchild;
}BSTNode;
一棵AVL树不可能固定不变,当我们需要进行插入数据时AVL树可能失衡,所以为了保证插入后树还是一棵AVL树,我们需要在插入后进行调整,即平衡旋转
-
平衡旋转类型
根据我们插入位置的不同,需要调整的情况也不同 -
1.LL型平衡旋转
如图,当我们插入d后A处开始失衡,所以我们需要对A的孩子结点进行调整,将失衡处的B调整为根节点,A变为B的右孩子,B的右子树变成A的左子树,即
-
2.RR型平衡旋转
其实RR型调整与LL型很像,如图
其实RR型调整就是LL型调整的相反情况,所以调整方法也只是将之反过来 -
3.LR型平衡旋转
由图我们就能看出来,相比RR型与LL型,LR型要复杂许多,图中是在A右子树的左子树上插入了结点,所以A失衡,所以我们需要去调整A的孙子结点也就是C结点作为根节点,A变成C的左孩子,同时C原来的左孩子是A的右孩子,原来的右孩子是B的左孩子,所以LR型调整主要就是去找失衡结点的孙子结点,对它的孙子结点进行调整
-
4.RL型平衡旋转
与LL型与RR型的关系一样,RL型调整也是LR型调整的相反
如图,我们也是先去找失衡结点A的孙子结点C,调整为根节点后A,B继承C的孩子结点,以达成平衡 -
总结:多个失衡结点存在时,我们要从最下面开始调整;LL,RR调整的是失衡结点的孩子结点,RL,LR调整的是失衡结点的孙子结点
平衡二叉树的查找
平衡二叉树结点存在着一些关系:N(1)=1,N(2)=2,N(h)=N(h-1)+N(h-2)+1
我们知道,平衡二叉树的第二层一定是两个结点,这样才会有左右子树,而根据二叉树广度以及平衡概念,就可以得出结点关系
还有n个结点的平衡二叉树的平均查找长度为\(O\left ( log_{2}n \right )\)
我们对平衡二叉树进行查找时,不管是最好还是最坏,查找次数绝对不会超过它的深度
平衡二叉树的删除
我们找到需要删除的结点后进行删除会使得它的祖先结点失衡,所以删除后我们需要进行调整,且调整后如果子树高度降低就需要继续调整,一直调整到根节点为止
B-树和B+树定义。主要介绍B-树的插入、删除的操作
B-树B+树:一个节点可以存放多个关键字,以降低树的高度。可放外存,适合大数据量查找。
B-树又称为多路平衡查找树
m阶B-树特点
非根节点:孩子个数
最小:m/2 最大:m
非根节点:关键字个数
最小:(m/2)-1 最大:m-1
B-树结点类型定义
#define MAXM 10
typedef int KeyType;
typedef struct node
{
int keynum;
KeyType key[MAXM];
struct node *parent;
struct node *ptr[MAXM];
}BTNode;
B-树的插入
我们知道B-树也是一棵二叉搜索树,所以查找时我们与根节点进行比较后若小于,则进入左子树,大于则进入右子树
在查找不成功后我们需要进行插入,而关键字插入必定在叶子结点层
1.结点关键字个数n<m-1,不修改指针
2.结点关键字个数n=m-1,则需要进行结点分裂
分裂过程:取中间位置的关键字,如果该结点存在双亲结点,则该中间结点进入双亲结点,不然就建一个双亲结点,树增高一层
B-树的删除
- 结点中关键字个数>(m/2)-1,直接删除
- 结点中关键字个数=(m-2)-1
我们需要进行借调,从兄弟结点中借调关键字
如果无关键字可借调,则必须进行结点合并
散列查找。哈希表和哈希链2种构造方法、相关AVL计算。
我们之前学习了很多种查找方法,但是哪怕是效率最高的方法时间复杂度都是\(O\left ( log_{2}n \right )\),当然这已经是很不错的复杂度了,但是如果我们查找的是大数据,我们需要的就是更快的,效率更好的方法
哈希表我们都不陌生,,我们需要的查找办法就是需要用到哈希表及哈希链的散列查找
哈希表:又称散列表,是除顺序结构存储结构,链接表存储结构和索引表存储结构外的有一种存储线性表的存储结构,它并非适合任何情况,主要适合于记录的关键字与存储地址存在某种函数关系的数据
我们在构建哈希表中,需要用到哈希函数与哈希地址
哈希函数:把关键字为k的对象存放在相应的哈希地址中
哈希地址:存储数据记录的长度为m的连续内存单元
如图
哈希冲突:两个不相同的数据,在进行哈希函数运算后发现哈希地址相同,于是存放如同一个哈希地址中,这就是哈希冲突
哈希冲突在哈希表存储结构中是很难避免的
哈希表设计
我们在设计及哈希表时最主要的就是要想办法解决哈希冲突,而哈希冲突产生的原因有
1.哈希表长度
哈希表中存在一个装填因子
装填因子=存储的记录个数/哈希表的大小=n/m
装填因子越小,冲突可能性越小,反之越大,我们在构建哈希表时要确保装填因子在0.6~0.9之间
2.哈希函数
3.解决冲突方法
哈希函数构造
-
1.直接定址法
直接定址法是以关键字k本身或加上某个常量c作为哈希地址的办法
h(k)=k+c
优点:计算简单,不可能存在哈希冲突
缺点:关键字分布不连续导致内存单元大量浪费 -
2.除留余数法
用关键字k除以某个不大于哈希表长度m的数p得到的余数作为哈希地址
h(k)=k mod p
p最好是素数,以降低冲突的可能 -
3.数字分析法
对关键字进行分析,再取最适合的数据作为哈希地址
适用于所有关键字都已知的情况
哈希冲突解决方法
- 开放定址法
在发现所得哈希地址已经存在数据,即将造成冲突时,为关键字找一个新的空闲的哈希地址 - 1.线性探查法
数学公式
\(d_{0}=h(k)\)
\(d_{i}=(d_{i-1}+1)\) mod m
线性探查法其实就是在关键字所得余数发生冲突时,将关键字加1继续求余直到所得余数的哈希地址空闲 - 2.平方探查法
数学公式
\(d_{0}=h(k)\)
\(d_{i}=(d_{0}\pm i^{2})\)mod m
查找位置依次为:\(d_{0}\),\(d_{0}\)+1,\(d_{0}\)-1,\(d_{0}\)+4,\(d_{0}\)-4
平方探查法是一种较好的处理冲突的方法,可以避免出现堆积现象。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元
哈希表查找ASL
- 1.查找成功
根据所有使用哈希地址的探查次数之和,除去已经使用的哈希地址数量
ASL=\(\frac{探查次数和}{哈希地址数}\) - 2.查找不成功
当我们查找不成功时,我们查找的关键字必然是处于空白区域的,故不成功的ASL就是所有空白区域数量加上其他区域到下一个空白区域的距离之和除去哈希表长
ASL=\(\frac{空白区域个数与非空白区域到下一个空白区域距离}{哈希表长}\)
构造哈希表
哈希表结构体
#define MaxSize 100
#define NULLKEY -1
#define DELKEY -2
typedef char *InfoType;
typedef struct{
int key;
InfoType data;
int count;
}HashTable[MaxSize];
哈希表建立
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 adr, i;
adr = k % p;
if (ha[adr].key == NULLKEY || ha[adr].key == DELKEY)
{
ha[adr].key = k;
ha[adr].count = 1;
}
else
{
i = 1;
while (ha[adr].key != NULLKEY)
{
adr = (adr + 1) % p;
i++;
}
ha[adr].key = k;
ha[adr].count = i;
}
}
哈希表删除
在执行删除操作时,我们要面临两个可能,找到,或者找不到,找到我们就直接删除,找不到就要做个标记,所以删除中就需要进行哈希表查找
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;
}
除了上述的方法,我们还有一种解决哈希冲突的办法,并且不会改变冲突的地址,那就是哈希链,也就是拉链法
拉链法:把所有的同义词用单链表链接起来的方法
如
按哈希链建立就是这样
根据哈希链,查找的ASL为
查找成功的情况下,我们就无需遍历空链,而剩下的中第一结点的数量乘1加第二结点的数量乘2,第n结点乘n,所有数之和除去不空链总数,就是我们成功的ASL
ASL= \(\frac{1*第一结点数+2*第二结点数+...n*第n结点}{不空的链数}\)
而不成功的ASL就是不空链的结点数之和除以所有链的数目
ASL= \(\frac{不空链结点数}{链数}\)
哈希链构建
哈希链结构体
typedef struct HashNode
{
int key;
struct HashNode *next;
}HashNode,*HashTable;
建哈希链
void CreateHash(HashTable ht[],int n)
{
int i;
int data;
for (i = 0; i < n; i++)
{
cin >>data
Insert(ht,data);
}
}
void Insert(HashList* ht, int data)
{
int adr;
adr = data%p;
node = new HashNode;
node->data=data;
node->next = ht[adr]->next;
ht[adr]->next = node;
1.2.谈谈你对查找的认识及学习体会。
查找其实是我们生活中最常用的东西,百度,不同的浏览器都需要查找,而这些查找工具的查找数据都特别庞大,所以我们才需要不断优化查找方法。
对于查找的所有方法,顺序查找其实是最复杂的,因为它就是一种暴力查找,一条条不断去对比,这样在数据量特别大得情况下,我们重复操作下来需要时间就特别长,这样才会有了其他查找方法的出现,当然查找还要搭配排序,比如二叉搜索树这样的存在就是在内部排好后我们进行查找才方便。
其实我们所学的查找只要涉及树,都是以二叉搜索树为基础,其他的所有查找树都以它为基础,所以我们必须知道如何去建一棵二叉搜索树
因为查找的普遍性,所以这章的内容非常重要
2.PTA题目介绍(0--6分)
2.1 是否完全二叉搜索树
2.1.1 该题的设计思路,代码展示
本题首先需要知道的就是什么是完全二叉搜索树,对完全二叉树按照从上到下、从左到右的层次遍历,应该满足一下两条要求:
1.某节点没有左孩子,则一定无右孩子
2.若出现只有左孩子的结点,则其所有后继一定无孩子
根据这个定义所以我们第一步一定是去建一棵二叉搜索树,之后开始遍历树判断是否出现只有左子树的结点,出现后开始判断后面是否存在非叶子结点
该题时间复杂度:O(n)
2.1.2 该题的伪代码
BinTree Insert(BinTree BST, ElementType X)
{
if (树为空)
{
申请结点空间,建立根节点
}
else if (关键字 < 当前结点值)
向右子树进行插入
else
向左子树进行插入
return BST;
}
void LevelTraversal(BinTree BST)
{
定义队列存储结点值
while (!tree.empty())
{
定义两个指针指向结点值,其中一个指向最后一个值
当两个指针值相等该层遍历完毕
}
cout << endl;
}
int ISCompletetree(BinTree BST)
{
定义判断出现只有左子树结点的结点的指标flag
定义一个队列方便进行层次遍历
if (BST)
{
不断取当前对=队首,同时入队当前结点的左右孩子
根据flag值进行判断
if(出现只有右子树的结点)
不是完全二叉搜索树
if(出现只有左子树的结点)
flag置为1,以显示出现了这种结点
if(flag==1)
if(后续结点出现非叶子结点)
不是完全二叉搜索树
}
}
2.1.3 PTA提交列表
部分正确:前两个部分正确是因为我弄错了完全二叉搜索树的定义,以为和完全二叉树一样,所以只通过了一个测试点
部分正确:后两个部分正确是因为我以为出现只存在左子树的结点后后续只要不要出现类似结点就好,同时将叶子结点判断写错了
2.1.4 本题设计的知识点
1.完全二叉搜索树:按照从上到下、从左到右的层次遍历,应该满足一下两条要求:某节点没有左孩子,则一定无右孩子;若出现只有左孩子的结点,则其所有后继一定无孩子
2.层次遍历:根据队列先进先出的特性进行遍历后输出,就是层次遍历的实现
2.2 二叉搜索树最近的公关祖先
2.2.1 该题的设计思路及代码展示
这题一开始其实就是先建树,建完再去找结点,以题目数据为例
如图
第一组数据为2 5,由图可看出最近的公共祖先为3
之后我们取5 7,公共祖先为6
由此可看出,公共祖先一定比其中一个数大,比另一个数小,当其中一个数是另一个数的祖先时可以保证,公共祖先绝对不会同时大于它们,也不会·同时小于它们,所以得出判断条件
2.2.2 该题的伪代码
int main()
{
for (i = 0 to m)
{
cin >> x >> y;
flag=判断x是否存在于树
temp=判断y是否存在于树
if (两个数都不存在)
{
printf("ERROR: %d and %d are not found.\n", x, y);
continue;
}
else if (第二个数存在)
{
printf("ERROR: %d is not found.\n", x);
continue;
}
else if (第一个数存在)
{
printf("ERROR: %d is not found.\n", y);
continue;
}
else(两个数都存在)
{
int lca = 寻找公共祖先
if(公共祖先时两个数其中之一)
输出
else
{
printf("LCA of %d and %d is %d.\n", x, y, lca);
continue;
}
}
}
return 0;
}
int FindNum(BinTree BST, int i)
{
while (树不空)
{
判断关键字与当前结点关系,小于就找左子树,大于找右子树
}
return NULL;
}
int FindLCA(BinTree BST, int x, int y)
{
while (1)
{
if (当前结点同时大于两个数,则需要去往当前结点的左子树)
BST = BST->Left;
else if (当前结点同时小于两个数,则需要去往当前结点的右子树)
BST = BST->Right;
else(找到公共祖先)
break;
}
return BST->Data;
}
2.2.3 PTA提交列表
部分正确:一开始我没发现我直接将树建错了,因为没有输出过树的结点所以没发现
部分正确:之后的部分正确全部是最大N时运行超时,但是我也没想明白哪里运行超时了,以为是在找公共祖先时超时了可是也不对,每个部分循环的地方都在vs试着改了改发现两个寻找函数没必要改,之后这里是问了其他人的,改了建树函数
2.2.4 本题设计的知识点
1.二叉搜索树的遍历:需要判断结点是否存在于二叉搜索树中,通过比对关键字与结点大小去找左右子树
2.找最近公共祖先:其实这里一开始是想着加一个parent表示,但是发现太麻烦,所以经过多个数据对比可以得出最近公共祖先的特点,绝对不会同时大于或同时小于两个数
2.3 (哈希链) 航空公司VIP客户查询
2.3.1 该题的设计思路
这道题其实是提示了需要哈希链来做,其实也必须哈希链来做,卡时间太严重了,通过将每个会员信息建立起来,然后才能进行会员信息对比,同时对于里程计算时会员小于规定也按规定计算
2.3.2 该题的伪代码
定义哈希链结构体
typedef struct TNode {
char ID[20];
int miter;
struct TNode* next;
}HashNode,*HashList;
定义用户信息结构体
typedef struct
{
char id[20];
int miles;
}USER;
int main()
{
建哈希链CreateHash(ht, n,k);
for (i = 0 to m)
{
获取当前用户得哈希地址= getHash(user1.id);
寻找在哈希表中的位置= FindNum(ht, user1.id, adr);
if (找到)
输出行驶里程
else(找不到)
输出"No Info\n"
}
}
void CreateHash(HashList *ht, int n,int k)
{
for (i = 0ton)
{
获取身份证号及行驶里程
进行哈希链结点的插入
}
}
void Insert(HashList* ht, char id[],int miles,int k)
{
int adr;
计算当前身份证号发的哈希地址 = getHash(id);
寻找在链中位置 = FindNum(ht,id, adr);
if (找到)
{
if (行驶里程小于规定值)
{
以规定值记录加入原里程
}
else(大于规定)
直接加入原里程
}
else(找不到)
{
建立会员信息
重复上面操作
}
}
int getHash(char id[])
{
int adr = 0;
int i;
for (i 取身份证号13到18位,这是最适合存储的位置)
{
if (校验码== 'x')
{
哈希地址 = adr * 10 + 10;
}
else
adr = adr * 10 + id[i] - '0';
}
adr = adr % MAX;
return adr;
}
HashList FindNum(HashList* ht, char id[], int adr)
{
定义结点访问哈希链的值
while (node)
{
if (会员存在)
return node;
else(继续遍历)
node = node->next;
}
return NULL;
}
###2.3.3 PTA提交列表
![](https://img2020.cnblogs.com/blog/1778794/202005/1778794-20200531064754998-884041073.png)
因为这道题写得很混乱所以提交列表的错误统一说明
1.一开始建哈希链就出现问题,对于链的初始化不到位,导致后面寻找的函数出现越界
2.最大值取的太小了,导致程序直接进行不下去
3.我这题错得最多的就是运行超时,寻找函数里面因为对于取的是头结点,超时了,尝试传结构体,太慢,超时了;最大值我取10003时程序还是运行超时,后来改了10007就过了
###2.3.4 本题设计的知识点
1.哈希链建立:本题最核心就是去建哈希链,建好后其他问题就特别容易解决,建哈希链时一定需要注意初始化问题。
2.哈希链遍历:本题还有就是要找会员,而哈希链遍历其实和链表差不多
3.本题最难的应该是对程序时间的要求,稍微不注意就会超时,所以对于每个变量取值都要特别注意,我尝试将寻找函数结点改成头结点后程序立马超时,还有就是我传用户得结构体时也立马超时,所以本题最大的知识点我觉得应该就是省时