DS博客作业05--查找
0.PTA得分截图
1.本周学习总结(0-4分)
1.1 总结查找内容
查找的性能指标ASL。分析静态查找几种算法包括:顺序查找、二分查找的成功ASL和不成功ASL。
查找的性能指标ASL
关键字的平均比较次数,也称平均搜索长度ASL(Average Search Length)
ASL=P1C1+P2C2+P3C3+----+PiCi
n表示记录的个数
pi记为找到某个记录的概率
Ci记为找到某个记录的次数
顺序查找、二分查找的成功ASL和不成功ASL
动态查找:二叉搜索树。如何构建、插入、删除。会操作及代码。
二叉搜索树概念:
为空树或者有如下特点:(二叉搜索树中,没有相同值的节点)
若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
它的左、右子树也都分别是二叉排序树。
中序遍历序列为升序序列
构建、插入、删除图解
二叉搜索树的各种操作代码:
二叉搜索树的构建,插入操作:
BinTree Insert(BinTree BST, ElementType X)
{
if (BST == NULL)//到了叶子结点,执行插入节点操作,返回该节点
{
BinTree temp;
temp = (BinTree)malloc(sizeof(struct TNode));
temp->Data = X;
temp->Left = temp->Right = NULL;
return temp;
}
if (BST->Data == X)//如果二叉搜索树中已经有了该节点,则不执行任何操作
{
return BST;
}
else if (BST->Data < X)//如果插入值大于树中该节点的值,则去它的右子树中查找
{
BST->Right = Insert(BST->Right, X);
}
else if (BST->Data > X)
{
BST->Left = Insert(BST->Left, X);//如果插入值小于树中该节点的值,则去它的左子树中查找
}
return BST;
}
二叉搜索树的删除操作:
BinTree Delete(BinTree BST, ElementType X)
{
if (BST == NULL)//如果树节点为空,表示找不到该节点,无法删除
{
printf("Not Found\n");
return NULL;
}
if (BST->Data < X)//如果删除值大于树中该节点的值,则去它的右子树中查找
{
BST->Right = Delete(BST->Right, X);
}
else if (BST->Data > X)//如果删除值小于树中该节点的值,则去它的左子树中查找
{
BST->Left = Delete(BST->Left, X);
}
else if (BST->Data == X)//如果找到删除值的节点,进而判断属于哪种删除
{
if (BST->Right == NULL && BST->Left != NULL)//只有左节点,用左节点代替删除节点
{
BinTree temp;
temp = (BinTree)malloc(sizeof(struct TNode));
temp = BST->Left;
free(BST);
return temp;
}
else if (BST->Right != NULL && BST->Left == NULL)//只有右节点,用右节点代替删除节点
{
BinTree temp;
temp = (BinTree)malloc(sizeof(struct TNode));
temp = BST->Right;
free(BST);
return temp;
}
else if (BST->Right == NULL && BST->Left == NULL)//叶子结点,直接删除
{
free(BST);
return NULL;
}
else if (BST->Right != NULL && BST->Left != NULL)//左右节点都有
{
BinTree p;
p = (BinTree)malloc(sizeof(struct TNode));
p = FindMin(BST->Right);//找到右子树中最小的节点
BST->Data = p->Data;//删除节点值代替最小节点的值
BST->Right = Delete(BST->Right, BST->Data);//删除最小节点
return BST;
}
}
return BST;
}
二叉搜索树查找某个值:
Position Find(BinTree BST, ElementType X)
{
if (BST == NULL)//树节点为空,表示没找到
{
return NULL;
}
if (BST->Data > X)//到左子树找
{
return Find(BST->Left, X);
}
else if (BST->Data < X)//到右子树找
{
return Find(BST->Right, X);
}
else if (BST->Data == X)//找到节点
{
return BST;
}
}
二叉搜索树找最大最小值:
Position FindMin(BinTree BST)//while一直找他的左子树,直到为空
{
if (BST == NULL)
{
return NULL;
}
while (BST->Left != NULL)
{
BST = BST->Left;
}
return BST;
}
Position FindMax(BinTree BST)//while一直找他的右子树,直到为空
{
if (BST == NULL)
{
return NULL;
}
while (BST->Right != NULL)
{
BST = BST->Right;
}
return BST;
}
AVL树的定义及4种调整做法
AVL树(平衡二叉树)的定义:
树中每个结点的左、右子树深度之差(称为平衡因子BF)只能取:-1、0 或 1;
两边的高度差小于等于1
是一颗特殊的二叉搜索树
在插入节点时,会造成整棵树失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转
下面为平衡旋转的几种情况图解:
具体事例如下:
B-树和B+树定义。主要介绍B-树的插入、删除的操作
B-树和B+树定义
二叉搜索树和平衡树都是只适合小数据量,大数据量的时候树的高度较大,查找时间较长
而B树每个节点可以放多个关键字,有效地降低了树的高度,并且查找效率更高
B-树:
空树或者具有以下特点:
每个节点至多m个孩子节点(至多有m-1个关键字)
除根节点外,其他节点至少有m/2(取上界)个孩子节点(即至少有m/2(取上界)-1个关键字)
若根节点不是叶子节点,根节点至少两个孩子节点
B+树:
每个分支节点至多有m棵子树
根节点或者没有子树,或者至少有两棵子树
除根节点,其他每个分支节点至少有m/2棵子树
有n棵子树的节点有n个关键字
所有叶子节点包含全部关键字及指向相应记录的指针
叶子节点按关键字大小顺序链接
叶子节点是直接指向数据文件中的记录。
所有分支节点(可看成是分块索引的索引表)
包含子节点最大关键字及指向子节点的指针
B-树的插入、删除的操作图解:
散列查找。哈希表和哈希链2种构造方法、相关AVL计算。
哈希表概念:
是除顺序表存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构
哈希表是一种存储结构,它并非适合任何情况,主要适合记录的关键字与存储地址存在某种函数关系的数据
哈希函数h(key):把关键字为ki的对象转化成存放在相应的哈希地址中
哈希表:存储数据记录的长度为m(m≥n)的连续内存单元
哈希冲突:对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)
装填因子α=存储的记录个数/哈希表的大小=n/m(α越小,冲突可能性就越小; α越大(最大可取1),冲突的可能性就越大)
哈希表构造图解:
哈希表具体代码:
插入哈希表代码:
void InsertHT(HashTable ha, int& n, KeyType k, int p)
{
int adr = k % p;//计算哈希地址
int count = 1;//查找次数
while (ha[adr].key != NULLKEY)//该节点不为空
{
if (ha[adr].key == k)//表示已经有了该关键字,不需要插入
{
return;
}
adr = (adr + 1) % p;
count++;
}
ha[adr].key = k;//插入到哈希表中
ha[adr].count = count;
}
查找哈希表中的元素:
int SearchHT(HashTable ha, int p, KeyType k)
{
int adr = k % p;//计算哈希地址
uns_count++;
while (ha[adr].key != NULLKEY)
{
if (ha[adr].key == k)//表示已经找到
{
return adr;
}
uns_count++;
adr = (adr + 1) % p;
}
return -1;
}
删除哈希表中的元素:
int DeleteHT(HashTable ha, int p, int k)
{
int adr = SearchHT(ha, p, k);//找到该元素所在的哈希地址
if (adr != -1) {
ha[adr].key = DELKEY;//逻辑删除,该元素贴删除标记
return 1;
}
else return 0;
}
哈希链构造图解:
哈希链具体代码:
初始化哈希链:
void CreatHash(HashList Ha[MaxSize])//初始化哈希链
{
for (int i = 0; i < MaxSize; i++)
{
Ha[i] = new HashNode;
Ha[i]->next = NULL;
}
}
插入数据到哈希链中
void InsertHash(HashList Ha[MaxSize], int str, int distance)//插入数据到哈希链中
{
int adr;//保存哈希地址
adr = str % 7;//计算哈希地址
HashList temp;
temp = new HashNode;
temp->Identity = str;
temp->Distance = distance;
temp->next = Ha[adr]->next;//头插法
Ha[adr]->next = temp;
}
查询数据:
void Inquire(HashList Ha[MaxSize], int str)//查询
{
int adr;//保存哈希地址
adr = str % 7;//计算哈希地址
HashList p;
p = Ha[adr]->next;
while (p != NULL)
{
if (p->Identity==str)//表示找到这个人
{
printf("%d\n", p->Distance);
return;
}
p = p->next;
}
//while循环结束,表示没有找到
}
1.2.谈谈你对查找的认识及学习体会。
以前上学期也学过一点点静态查找的内容,学了这章以后,知道明白了如何提高查找的效率
记得前不久在知乎看到一位面试官问为什么他们的搜索引擎可以那么快就搜索到目标,学了这章节后才了解到倒排索引这种查找方法,真的是特别厉害了。虽然还不是特别明白,但是基本了解了一点,后面相信会更加理解掌握的
比起前面几章节,本章相对于更加简单,大约只学习了查找的算法,而真正的结构还是以前学习的顺序表,链表,树的基本操作
2.PTA题目介绍(0--6分)
2.1 题目1(2分)
7-5(哈希链) 航空公司VIP客户查询 (25分)
不少航空公司都会提供优惠的会员服务,当某顾客飞行里程累积达到一定数量后,可以使用里程积分直接兑换奖励机票或奖励升舱等服务。现给定某航空公司全体会员的飞行记录,要求实现根据身份证号码快速查询会员里程积分的功能。
该题的设计思路
题面分析。分析题面数据如何表达
每个人的身份证号用一个string字符串保存,取其中的5位来做哈希地址,使其均匀分布在整个哈希链中,飞行公里数用int型来保存
解法:
由于本题的数据量太大,采用线性探查法会大大增加运行时间,导致超时,只有运用哈希链来做,才可以更好的解决时间问题。
在我的解法中,我开辟了100000个链头,使数据更均匀的分布在整条链中。采用了身份证号的12到17位为哈希地址,最后一位可能出现x的情况,所以并没有取最后一位。
时间复杂度:
时间复杂度:O(n)
只需要计算哈希地址,遍历哈希地址的这条链即可
2.1.2 该题的伪代码
void CreatHash(HashList Ha[MaxSize])//初始化哈希链
{
for遍历整个链
将Ha[i]的next置为NULL
}
void InsertHash(HashList Ha[MaxSize], string str, int distance)//插入数据到哈希链中
{
adr=计算出13位到17位的数字为哈希地址
while遍历adr为哈希地址的这条链
if找到该人的飞行记录
将里程数加上distance,结束
没有找到飞行记录则用头插法插入到链中
}
void Inquire(HashList Ha[MaxSize], string inquire)//查询是否该人
{
adr=计算出13位到17位的数字为哈希地址
while遍历adr为哈希地址的这条链
if找到该人的飞行记录
将里程数加上distance,结束
结束循环,没有找到该人,输出No Info
}
具体代码:
#include<iostream>
using namespace std;
#include<string>
typedef struct HashNode
{
string Identity;//身份证号
int Distance;//飞行距离
struct HashNode* next;
}HashNode, * HashList;
#define MaxSize 100000
void CreatHash(HashList Ha[MaxSize]);//初始化哈希链
void InsertHash(HashList Ha[MaxSize], string str, int distance);//插入数据到哈希链中
void Inquire(HashList Ha[MaxSize], string inquire);//查询是否该人
int main()
{
int N, K;//N表示飞行记录,K表示最低飞行距离
HashList Ha[MaxSize];
CreatHash(Ha);//初始化哈希链
string str;
int distance;
scanf("%d %d", &N, &K);
str.resize(18);
for (int i = 0; i < N; i++)
{
scanf("%s %d", &str[0], &distance);
if (distance < K)
distance = K;
InsertHash(Ha, str, distance);
}
int M;//要查询的人数
string inquire;
scanf("%d", &M);
inquire.resize(18);
for (int i = 0; i < M; i++)
{
scanf("%s", &inquire[0]);//输入查询人的身份证号码
Inquire(Ha, inquire);
}
}
void CreatHash(HashList Ha[MaxSize])//初始化哈希链
{
for (int i = 0; i < MaxSize; i++)
{
Ha[i] = new HashNode;
Ha[i]->next = NULL;
}
}
void InsertHash(HashList Ha[MaxSize], string str, int distance)//插入数据到哈希链中
{
int adr = 0;//保存哈希地址
int n = 10000;
for (int i = 12; i < 17; i++)//第13到17位为哈希地址
{
adr = adr + (str[i] - '0') * n;
n = n / 10;
}
HashList p;
p = Ha[adr]->next;
while (p != NULL)//遍历这条链,看它是否飞行过
{
if (p->Identity == str)//表示已经有该人的飞行记录
{
p->Distance = p->Distance + distance;
return;
}
p = p->next;
}
HashList temp;
temp = new HashNode;
temp->Identity = str;
temp->Distance = distance;
temp->next = Ha[adr]->next;//头插法
Ha[adr]->next = temp;
}
void Inquire(HashList Ha[MaxSize], string inquire)//查询是否该人
{
int adr = 0;//保存哈希地址
int n = 10000;
for (int i = 12; i < 17; i++)//第13到17位为哈希地址
{
adr = adr + (inquire[i] - '0') * n;
n = n / 10;
}
HashList p;
p = Ha[adr]->next;
while (p != NULL)
{
if (p->Identity == inquire)//表示找到这个人
{
printf("%d\n", p->Distance);
return;
}
p = p->next;
}
printf("No Info\n");//while循环结束,表示没有找到
}
2.1.3 PTA提交列表
提交列表说明:
第一次是用cin,cout来进行输入输出的,解决就是;然后听老师的改为printf,scanf,莫名其妙就过了一个,但还有一个过不了
第二次做是只分配了10000个链表头,导致100000个数据在链表里分配过于集中,导致查找的效率太低,解决办法:于是便给他分配了1000000个链表头,大约就能均匀分布在链表里了
2.1.4 本题设计的知识点
1.哈希链的建立,插入,删除
2.string用scanf进行输入
3.复习了链表的操作
2.2 题目2(2分)
7-1 是否完全二叉搜索树 (30分)
将一系列给定数字顺序插入一个初始为空的二叉搜索树(定义为左子树键值大,右子树键值小),你需要判断最后的树是否一棵完全二叉树,并且给出其层序遍历的结果。
2.2.1 该题的设计思路
题面分析。分析题面数据如何表达:
题面需要建出二叉搜索树,并且判断是否为完全二叉树。只是需要通过每个数据进行插入建立二叉搜索树,然后通过层次遍历判断是否完全二叉树即可
解法:
建立二叉搜索树很好建立,主要就是判断是否完全二叉树:
时间复杂度:
时间复杂度:O(n)
只需要层次遍历整棵树
2.2.2 该题的伪代码
BinTree Insert(BinTree BST, int x)//插入操作
{
if 树根节点为NULL
则在该节点进行插入,返回该节点
if 树根节点数据=x
表示已有该数据,不需要插入
else if树根节点数据大于x
递归进树的右子树
else if树根节点数据小于x
递归进树的左子树
}
void Judge(BinTree BST)//判断是否完全二叉树
{
push将树根节点入栈
while栈不为空
访问栈顶temp
if temp只有右子树,不符合,返回
if temp有两个孩子
进栈左子树,进栈右子树
if temp只有左子树
while循环遍历剩下的节点
如果全部为叶子,则返回yes,否则返回no
}
具体代码:
#include<iostream>
using namespace std;
#include<queue>
typedef struct BNode
{
int data;
struct BNode* left;
struct BNode* right;
}BNode, * BinTree;
BinTree Insert(BinTree BST, int x);//插入操作
void Level(BinTree BST);//层次遍历
void Judge(BinTree BST);//判断是否完全二叉树
int main()
{
int N;
cin >> N;
int x;
BinTree BST;
BST = NULL;
for (int i = 0; i < N; i++)
{
cin >> x;
BST = Insert(BST, x);
}
Level(BST);//层次遍历
cout << endl;
Judge(BST);//判断是否完全二叉树
return 0;
}
BinTree Insert(BinTree BST, int X)
{
if (BST == NULL)
{
BinTree temp;
temp = new BNode;
temp->data = X;
temp->left = temp->right = NULL;
return temp;
}
if (BST->data == X)
{
return BST;
}
else if (BST->data > X)
{
BST->right = Insert(BST->right, X);
}
else if (BST->data < X)
{
BST->left = Insert(BST->left, X);
}
return BST;
}
void Level(BinTree BST)//层次遍历
{
int flag = 1;//控制输出格式
queue<BinTree> Q;
BinTree temp;
temp = new BNode;
Q.push(BST);
while (!Q.empty())
{
temp = Q.front();
if (temp->left != NULL)
{
Q.push(temp->left);
}
if (temp->right != NULL)
{
Q.push(temp->right);
}
if (flag == 1)
{
cout << temp->data;
flag = 0;
}
else
cout << " " << temp->data;
Q.pop();
}
}
void Judge(BinTree BST)//判断是否完全二叉树
{
queue<BinTree> Q;
BinTree temp;
temp = new BNode;
Q.push(BST);
while (!Q.empty())
{
temp = Q.front();
if (temp->right != NULL && temp->left == NULL)
{
cout << "NO";
return;
}
if (temp->right != NULL && temp->left != NULL)
{
Q.push(temp->left);
Q.push(temp->right);
}
if (temp->right == NULL && temp->left == NULL || temp->right == NULL && temp->left != NULL)
{
Q.pop();
while (!Q.empty())
{
temp = Q.front();
if (temp->right == NULL && temp->left == NULL)
{
}
else
{
cout << "NO";
return;
}
Q.pop();
}
cout << "YES";
return;
}
Q.pop();
}
}
2.2.3 PTA提交列表
提交列表说明:
在写代码时第一次时,插入时忘记在最后返回根节点,导致了只回去了一个节点,造成的错误
2.2.4 本题设计的知识点
1.二叉搜索树的建立,插入等操作
2.复习了完全二叉树的知识概念,以及判断是否完全二叉树的操作代码
3.复习了层次遍历的操作以及STL中链栈的操作
4.递归熟悉应用
2.3 题目3(2分)
7-3(map) QQ帐户的申请与登陆 (25分)
实现QQ新帐户申请和老帐户登陆的简化版功能。最大挑战是:据说现在的QQ号码已经有10位数了。
2.3.1 该题的设计思路
由于该题是qq号的申请和登录,账号密码都是10位数左右,所以选择字符串来保存账号密码。由于具有一对一的关系,故选择STL中的map容器来做
账号密码的申请登录里的判断重复,正确等操作都在map的操作中完成
时间复杂度应该也不会小,map容器本质也是一颗红黑树,每次失衡都需要调整,所以应该时间也是不低的
2.3.2 该题的伪代码
for 输入N个数据
if 需要注册
if mp.count查找是否有账号
注册失败
else 插入map容器中
else 需要登录
if mp.count查找这个账号
没有则直接返回
if 检查密码
匹配成功则登录
else匹配失败
具体代码:
#include<iostream>
using namespace std;
#include<string>
#include<map>
int compare(string key, string ID);//字符串比较
int main()
{
map<string, string> mp;
int N;
cin >> N;
char flag;//表示登录还是注册
string ID;//账号
string code;//密码
for (int i = 0; i < N; i++)
{
cin >> flag >> ID >> code;
if (flag == 'N')//注册
{
if (mp.count(ID) == true)//表示已有账号
cout << "ERROR: Exist" << endl;
else//插入账号密码
{
mp.insert(map<string, string>::value_type(ID, code));
cout << "New: OK" << endl;
}
}
else if (flag == 'L')//登录
{
if (mp.count(ID) == false)//表示没有这个账号
{
cout << "ERROR: Not Exist" << endl;
continue;
}
if (compare(mp[ID], code) == 0)//密码匹配成功
{
cout << "Login: OK" << endl;
continue;
}
else//密码匹配失败
{
cout << "ERROR: Wrong PW" << endl;
continue;
}
}
}
}
int compare(string key, string ID)//字符串比较
{
int i;
for (i = 0; key[i] != '\0' && ID[i] != '\0'; i++)
{
if (key[i] != ID[i])
return 1;
}
if (key[i] == '\0' && ID[i] == '\0')
return 0;
else
return 1;
}
2.3.3 PTA提交列表
提交列表说明;
由于在检验密码的时候,用了重头开始遍历map容器的做法,导致时间多了许多。解决后来发现直接用map[]便可以应用它对应的键值了,时间便大大下降了
2..4 本题设计的知识点
1.map容器的各种检验,引用键值等使用操作
2.string字符串的比对操作