DS博客作业05--查找
0.PTA得分截图
1.本周学习总结
1.1 总结查找内容
1.1.1查找的性能指标ASL
- 概念:关键字的平均比较次数,也称平均搜索长度ASL(Average Search Length)
n:记录的个数
pi:查找第i个记录的概率 ( 通常认为pi =1/n )
ci:找到第i个记录所需的比较次数
ASL是衡量查找算法性能好坏的重要指标,一个查找算法ASL越大,其时间性能越差,反之,一个查找算法的ASL越小,其时间性能越好。
顺序查找
- 思路:从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k相比较,若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
- ASL:顺序查找方法在查找成功时的平均比较次数约为表长的一半。若k不在表中,则要进行n次比较后才能确定查找失败。
ASL(成功)=(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) //未找到返回0
return 0;
else
return i+1;//找到返回逻辑序号i+1
}
二分查找
- 思路:二分查找也称为折半查找,要求线性表中的节点必须己按关键字值的递增或递减顺序排列。
- 性能分析ASL(判定树)
把当前查找区间的中间位置上的记录作为根;
左子表和右子表中的记录分别作为根的左子树和右子树。
称为描述二分查找的判定树或比较树。
ASL成功=log(n+1)-1
ASL不成功=log(n+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;
}
递归算法:
int BinSearch1(SeqList R,int low,int high,KeyType k)
{ int mid;
if (low<=high) //查找区间存在一个及以上元素
{ mid=(low+high)/2; //求中间位置
if (R[mid].key==k) //查找成功返回其逻辑序号mid+1
return mid+1;
if (R[mid].key>k) //在R[low..mid-1]中递归查找
BinSearch1(R,low,mid-1,k);
else //在R[mid+1..high]中递归查找
BinSearch1(R,mid+1,high,k); }
}
else
return 0;
}
分块查找
- 思路:
(1)将数据表R[0..n-1]均分为b块;
(2)表是“分块有序”;
(3)抽取各块中的最大关键字及其起始位置构成一个索引表IDX[0..b-1],即IDX[i](0≤i≤b-1)中存放着第i块的最大关键字及该块在表R中的起始位置。 - 性能:介于顺序查找和二分查找之间的查找方法
1.1.2动态查找:二叉搜索树。
概念
二叉搜索树又称二叉排序(查找)树:或者是一棵空树;或者是具有如下特性的二叉树,
若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
它的左、右子树也都分别是二叉排序树
二叉排序树中没有相同关键字的节点。
基本操作
1.结构体定义
typedef struct node
{ KeyType key; //关键字项
InfoType data; //其他数据域
struct node *lchild,*rchild; //左右孩子指针
} BSTNode,*BSTree;
2.构建
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; //返回建立的二叉排序树的根指针
}
3.查找
递归:
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
}
4.插入
- 思路
(1)若二叉排序树T为空?
则创建一个key域为k的结点,将它作为根结点;
(2)否则将k和根结点的关键字比较,若两者相等?
则说明树中已有此关键字k,无须插入,直接返回0;
(3)若kkey?
则将k插入根结点的左子树中。
(4)否则将它插入右子树中。 - 代码
BinTree Insert(BinTree BST, ElementType X)
{
if (BST == NULL) //空结点
{
BST = new BSTNode; //生成新结点
BST->Data = X;
BST->Left = BST->Right = NULL;
}
else if (X < BST->Data)
{
BST->Left = Insert(BST->Left, X); //插入左子树
}
else if (X > BST->Data)
{
BST->Right = Insert(BST->Right, X); //插入右子树
}
return BST;
}
5.删除
- 思路:
分为如下情况:
1)被删除的节点是叶子节点:直接删去该节点,其双亲节点中相应指针域的值改为“空”
2)被删除的节点只有左子树或者只有右子树:用其左子树或者右子树代替它其双亲节点的相应指针域的值改为 “指向被删除节点的左子树或右子树”。
3)被删除的节点既有左子树,也有右子树:以其前驱替代之,然后再删除该前驱节点。前驱是左子树中最大的节点。也可以用其后继替代之,然后再删除该后继节点。后继是右子树中最小的节点。 - 代码:
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;
}
}
6.查找最大或最小结点
BinTree FindMin(BinTree BST)
{
if(BST!=NULL)
while (BST->Left != NULL)
{
BST = BST->Left;
}
return BST;
}
BinTree FindMax(BinTree BST)
{
if (BST != NULL)
while (BST->Right != NULL)
{
BST = BST->Right;
}
return BST;
}
1.1.3AVL树的定义及4种调整做法
定义
- 左、右子树是平衡二叉树;
所有结点的左、右子树深度之差(平衡因子)的绝对值≤1
最坏情况下的时间也均为O(log2n) - 结构体
typedef struct node //记录类型
{ KeyType key; //关键字项
int bf; //增加的平衡因子
InfoType data; //其他数据域
struct node *lchild,*rchild;//左右孩子指针
} BSTNode;
四种调整做法
- 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右子树
- LR型调整:若在A的左子树的右子树上插入结点,使A的平衡因子从1增加至2,(以插入的结点C为旋转轴),先C进行逆时针旋转,A再顺时针旋转。
1.C向上旋转到A的位置,A作为C右孩子
2.C原左孩子作为B的右孩子
3.C原右孩子作为A的左孩子
保持二叉排序树有序
- RL型调整:若在A的右子树的左子树上插入结点,使A的平衡因子从-1增加至-2,(以插入的结点C为旋转轴),先进行顺时针旋转,再逆时针旋转。
1.C向上旋转到A的位置,A作为C左孩子
2.C原左孩子作为A的右孩子
3.C原右孩子作为B的左孩子
保持二叉排序树有序
1.1.4B-树和B+树
B-树:
- 一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
每个节点至多m个孩子节点(至多有m-1个关键字)
除根节点外,其他节点至少有[m/2]个孩子节点(即至少有[m/2]-1个关键字)
若根节点不是叶子节点,根节点至少两个孩子节点 - 节点结构:
1.n为关键字个数,n+1孩子指针。
2.结点中按关键字大小顺序排列,ki<ki+1;
3.pi为该节点的孩子指针,满足:- p0 指向节点关键字<k0
- pi指向节点关键字大于等于[ki,ki+1]
- pn指向节点关键字>kn。
- m阶B-树结点特点:
非根结点:孩子个数最小: m/2, 最大:m
非根结点:关键字个数:最小: m/2 -1 , 最大:m-1
根节点至少2个孩子:2--m - B-树特点:
1.B-树是所有结点的平衡因子均等于0的多路查找树。所有外部结点都在同一层上。
2.在计算B-树的高度时,需要计入最底层的外部结点
3.外部结点就是失败结点,指向它的指针为空,不含有任何信息,是虚设的。一棵B树中总有n个关键字,则外部结点个数为n+1。
B-树基本操作
1.结构体
#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;
2.查找结果返回类型
typedef struct {
BTNode *pt; // 指向找到的结点的指针
int i; // 1..m-1,在结点中的关键字序号
int tag; // 标志查找成功(=1)或失败(=0)
} Result; // 在B树的查找结果类型
3.查找
在一棵B-树上顺序查找关键字为k的方法为:
将k与根节点中的key[i]进行比较:
(1)若k=key[i],则查找成功;
(2)若k<key[1]
则沿着指针ptr[0]所指的子树继续查找;
(3)若key[i]<k<key[i+1]
则沿着指针ptr[i]所指的子树继续查找;
(4)若k>key[n]
则沿着指针ptr[n]所指的子树继续查找。
4.插入
在查找不成功之后,需进行插入。关键字插入的位置必定在叶子结点层,有下列几种情况:
该结点的关键字个数n<m-1,不修改指针;
该结点的关键字个数 n=m-1,则需进行“结点分裂”
节点分裂:
1.如果没有双亲结点,新建一个双亲结点,树的高度增加一层。
2.如果有双亲结点,将ki插入到双亲结点中。
插入图示:
5.删除
和插入的考虑相反,
结点中关键字的个数>[m/2]-1,直接删除
结点中关键字的个数=[m/2]-1
要从其左(或右)兄弟结点“借调”关键字
若其左和右兄弟结点均无关键字可借(结点中只有最少量的关键字),则必须进行结点的“合并”。
删除关键字k分两种情况:
(1)在非叶子结点上删除关键字ki
- 从pi子树节点借调最大或最小关键字key代替ki。
- pi子树中删除key
- 若子树节点关键字个数< m/2-1,重复步骤1
- 若删除关键字为叶子结点层,按叶子结点删除操作法
(2)在B树的叶子结点b上删除关键字共有以下3种情况:
- 假如b结点的关键字个数大于Min,说明删去该关键字后该结点仍满足B树的定义,则可直接删去该关键字。
- 假如b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义。若可以从兄弟结点借。
- 兄弟结点最小关键字上移双亲结点
- 双亲结点大于删除关键字的关键字下移删除结点
- b结点的关键字个数等Min,兄弟节点关键字个数也等于Min
- 删除关键字
- 兄弟节点及删除关键字节点、双亲结点中分割二者关键字合并一个新叶子结点
- 若双亲结点关键字个数<=Min,重复2
删除图示:
6.应用:
B-树常被用于对检索时间要求苛刻的场合,例如:
1.B-树索引是数据库中存取和查找文件(称为记录或键值)的一种方法。
2.硬盘中的结点也是B-树结构的。B-树利用多个分支(称为子树)的结点,减少获取记录时所经历的结点数,从而达到节省存取时间的目的。
B+树
1.定义:索引文件组织中,经常使用B-树的变形B+树。 B+树是大型索引文件的标准组织方式。
2.条件:
- 每个分支节点至多有m棵子树。
- 根节点或者没有子树,或者至少有两棵子树
- 除根节点,其他每个分支节点至少有m/2棵子树
- 有n棵子树的节点有n个关键字。
- 所有叶子节点包含全部关键字及指向相应记录的指针
- 叶子节点按关键字大小顺序链接
- 叶子节点是直接指向数据文件中的记录。
- 所有分支节点(可看成是分块索引的索引表)
- 包含子节点最大关键字及指向子节点的指针。
3.B+树的查找
直接从最小关键字开始进行顺序查找所有叶节点链接成的线性链表。
从B+树的根节点出发一直找到叶节点为止。
m阶的B+树和m阶的B-树的差异
- 非根结点关键字个数n不同、n取值范围不同
B+树中:一个节点n个孩子则对应n个关键字,取值范围:m/2≤n≤m,根节点是1≤n≤m;
B-树中:一个节点n个孩子则对应n-1个关键字,取值范围: m/2-1≤n≤m-1 ,根节点 1≤n≤m-1
根节点孩子至少为2. - 叶子结点不一样
B+树所有叶子节点包含了全部关键字
B-树叶子节点包含的关键字与其他节点包含的关键字是不重复的。 - B+树中所有非叶子节点仅起到索引的作用,而在B-树中,每个关键字对应一个记录的存储地址
- 通常在B+树上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,所有叶子节点链接成一个不定长的线性链表。
1.1.5散列查找
哈希表(Hash Table)
- 概念:又称散列表,是除顺序表存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构。
哈希表是一种存储结构,它并非适合任何情况,主要适合记录的关键字与存储地址存在某种函数关系的数据。
哈希函数和哈希地址
1.哈希函数h(key):把关键字为ki的对象存放在相应的哈希地址中
2.哈希表:存储数据记录的长度为m(m≥n)的连续内存单元
哈希冲突
对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)。
在哈希表存储结构的存储中,哈希冲突是很难避免的。
哈希表设计
哈希表设计主要需要解决哈希冲突。实际中哈希冲突是难以避免的,主要与3个因素有关:
1.哈希表长度
- 与装填因子有关。
- 装填因子α=存储的记录个数/哈希表的大小=n/m
- α越小,冲突可能性就越小;α越大(最大可取1),冲突的可能性就越大。控制在0.6~0.9的范围内
2.与所采用的哈希函数有关。
3.与解决冲突方法有关。
哈希函数构造方法
1.直接定址法
- 直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。
- 优点:计算简单,并且不可能有冲突发生
- 缺点:关键字分布不连续将造成内存单元的大量浪费
2.除留余数法
- 除留余数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
3.数字分析法
- 适合于所有关键字值都已知的情况,并需要对关键字中每一位的取值分布情况进行分析。
哈希冲突解决方法
1.开放定址法
- 线性探查法
- 平方探查法
平方探查法是一种较好的处理冲突的方法,可以避免出现堆积现象。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元。
2.拉链法
- 拉链法是把所有的同义词用单链表链接起来的方法。
时间性能ASL
1.哈希表
ASL成功=∑每个关键字比较次数/关键字个数
ASL不成功=∑关键字比较到空结点所需的次数/p
2.哈希链
对于拉链法构造的哈希表:
ASL成功=∑关键字属于单链表的第几个结点/关键字个数
ASL不成功=∑每条单链表结点个数/p
哈希表上的运算
1.哈希表结构体:
#define MaxSize 100
#define NULLKEY -1
#define DELKEY -2
typedef char *InfoType;
typedef struct
{
int key; // 关键字域
InfoType data; //其他数据域
int count; //探查次数
}HashTable[MaxSize];
2.哈希表插入
int InsertHT(HashTable ha,int p,int k,int &n)
{ //伪代码
计算k的哈希地址adr=k%p;
若ha[adr]=k,则已存在k,不需要插入
while(ha[adr]不为空 或ha[adr]不等删除标记 )
{
线性探查k的位置。即adr=adr+1;
计算探查次数count
}
ha[adr]=k;
哈希表长度增1
}
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++;
}
3.哈希表建表
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);
}
4.哈希表查找算法
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;
}
5.哈希表删除算法
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;
} //查找删除元素,找到话,不能真正删除,做个删除标记DELKEY。
哈希链上的运算
1.哈希链结构体
typedef struct HashNode {
int key;
struct HashNode *next;
}HashNode, *HashTable;
HashTable ht[MAX];
2.创建哈希链
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);
}
3.哈希链插入数据
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;
}
4.哈希链查找
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谈谈你对查找的认识及学习体会。
查找是数据结构中十分重要的一部分。本章学了静态查找中的顺序查找和二分查找,二叉搜索树,哈希表和哈希链的种种操作,B树的基本操作也学习了一些,顺序查找和二分查找平时也都有用过,对于二叉搜索树来说,之前学了树有关内容,所以理解起来不管是操作还是代码上都不是很困难。哈希表和哈希链的时间复杂度很小。本章所有查找均需要计算AVL,衡量一个算法的效率。经过本章的学习,也是了解到了很多查找的算法,在后续的编程中也可能应用的到。由于之前因故缺了两周的课,现在正在快马加鞭的补补补。对于散列查找还是不是很熟练,继续努力!
2.pta题目介绍
2.1 题目1(7-2 二叉搜索树的最近公共祖先)
2.1.1 该题的设计思路
- 题面分析
- 输入的第一行给出两个正整数:待查询的结点对数 M(≤ 1 000)和二叉搜索树中结点个数 N(≤ 10 000)。随后一行给出 N 个不同的整数,为二叉搜索树的先序遍历序列。最后 M 行,每行给出一对整数键值 U 和 V。所有键值都在整型int范围内。
- 对每一对给定的 U 和 V,如果找到 A 是它们的最近公共祖先结点的键值,则在一行中输出 LCA of U and V is A.。但如果 U 和 V 中的一个结点是另一个结点的祖先,则在一行中输出 X is an ancestor of Y.,其中 X 是那个祖先结点的键值,Y 是另一个键值。如果 二叉搜索树中找不到以 U 或 V 为键值的结点,则输出 ERROR: U is not found. 或者 ERROR: V is not found.,或者 ERROR: U and V are not found.。
- 输入的第一行给出两个正整数:待查询的结点对数 M(≤ 1 000)和二叉搜索树中结点个数 N(≤ 10 000)。随后一行给出 N 个不同的整数,为二叉搜索树的先序遍历序列。最后 M 行,每行给出一对整数键值 U 和 V。所有键值都在整型int范围内。
- 解法
1.首先需要根据给出的二叉搜索树的先序遍历序列还原二叉搜索树,二叉搜索树的特点是左子树所有节点值比根结点小,右子树比根结点大,先序序列里第一个值是根结点值,从左往右找到比根结点大的值,它的左边就是左子树节点,右边就是右子树节点。
2.然后在二叉搜索树中寻找给定的一对数值
3.根据题目要求的情况写出相应的功能函数,进行判断输出 - 时间复杂度
O(mlogn)
2.1.2该题的伪代码
struct node
{
int data;
node *lchild, *rchild, *father;
};
int a[10001];
map<int, int>mp;
int main()
{
循环输入并标记树节点值(map标记)
建树
for i=1 to n
{
cin >> u >> v;
if (!mp[u] , !mp[v])给出的值未标记,即树上没有该节点值
三种:u不存在或v不存在或都不存在,输出相应结果
else
{
int x = Find_F(root, u, v);//对两个数值均判断是否是对方的祖先
int y = Find_F(root, v, u);
函数返回1,则前者为后者祖先,输出相应结果
else
{
到这里就说明两者有共同祖先,调用Find_POS找到其中一个节点的指针,再调用Find_LCA找到公共节点,输出相应结果
}
}
}
}
node *CreateBT(int l, int r)
{
if (l > r)
return NULL;
初始化节点
for i=l+1 to r//根据二叉搜索树的性质建树,第一个大于等于根节点的包括其后面的肯定在右子树,其他在左子树
{
找到第一个大于根结点的节点
break;
}
root->lchild = CreateBT(l + 1, i - 1);
root不空,标记该节点左孩子的父节点root->lchild->father = root;
root->rchild = CreateBT(i, r);
root不空,标记该节点右孩子的父节点root->rchild->father = root;
返回root
}
int Find(node *root, int x)//在以root为根的子树中是否查找到x
{
if (节点不空)
{
if (节点值相等)
返回1
if (小于节点值)
递归遍历左子树
else
递归遍历右子树
}
没找到返回0
}
int Find_F(node *root, int a, int b)//a是否是b的祖先
{
if (节点不空)
{
if (节点值等于a)
{
if (Find(root, b))//如果在a的子树中找到了b,即说明a是b的祖先
是返回1
不是返回0
}
if (a小于节点值)
递归遍历左子树
else
递归遍历右子树
}
不是返回0
}
int Find_LCA(node *root, int x)//从第一个节点的地址向上找第二个节点的祖先
{
while (父节点不空)
{
root指向父节点
判断root是否为x的祖先
if (是)
返回对应祖先节点值
}
不是返回0
}
node *Find_POS(node *root, int x)//找到一个节点的位置,返回的是一个指针
{
if (节点不空)
{
if (节点值相等)
返回该节点
if (小于节点值)
递归遍历左子树
else
递归遍历右子树
}
找不到返回NULL
}
2.1.3PTA提交列表
1.刚开始不知道如何建树,但其实是有规律的,根据两个条件可以建树,一个是先序序列,一个是二叉搜索树,可以按照上面说的方发建,也可以将先序序列升序排列得到中序序列,根据先序中序建树
2.对于找两个节点的祖先,先找到其中一个节点的指针,对该指针回溯到父节点,对父节点的子树遍历是否可以找到另一个节点,如果可以,该父节点就是公共祖先,若不能,继续回溯,直到找到或到根结点
3.提交列表里的错误,Find函数里没有加结束return 0,导致有些数据没有返回值,部分错误
2.1.4本题设计的知识点
1.复习树的建树方法,新的建树方法,根据二叉搜索树的先序遍历序列建树
2.二叉搜索树的查找,小于根结点左移,大于根节点右移
3.判断一个节点是否为另一个节点的祖先以及寻找两个节点的公共祖先。
4.map容器的应用,用map容器标记树节点
2.2 题目2(7-4 整型关键字的散列映射)
2.2.1 该题的设计思路
-
题面分析
- 输入第一行首先给出两个正整数N(≤1000)和P(≥N的最小素数),分别为待插入的关键字总数、以及散列表的长度。第二行给出N个整型关键字。数字间以空格分隔。
- 在一行内输出每个整型关键字在散列表中的位置。数字间以空格分隔,但行末尾不得有多余空格。
- 输入第一行首先给出两个正整数N(≤1000)和P(≥N的最小素数),分别为待插入的关键字总数、以及散列表的长度。第二行给出N个整型关键字。数字间以空格分隔。
-
解法
建立哈希表,输出相应的位置,若有相同的数据,则不需要再进行插入,位置相同。
例如
-
时间复杂度
O(n)
2.2.2伪代码
typedef struct node
{
KeyType key; //关键字域
} HashTable[MaxSize]; //哈希表类型
出入数据放入x[i]
初始化关键字域key
for i=0 to n
求出位置p=x[i]%m
if (相应key为空)插入数据ha[p].key=x[i]
else
while 相应key不空,且与所给数据不等
p++,进入下个位置
end while
插入数据
输出p
end for
2.2.3 PTA提交列表
1.刚开始投机取巧没有建哈希表,直接用类似于哈希数组的做法,对已经插入的数据进行标记输出,但过不了。
2.起初以为是有重复数据才过不了,后来改动后,感觉vs上没问题,但还是只过两点,没办法换了哈希表,换了之后就过了。
2.2.4本题设计的知识点
1.哈希表的建立,哈希表的建立需要初始化才能建立
2.哈希冲突的线性探测的方法解决:沿着出现冲突的地址依次向下进行探测,直到找到一个空闲地址的方法
3.当有重复数据时,不能再插入,其对应的地址是一致的
题目3(7-5航空公司VIP客户查询)
2.3.1该题的设计思路
- 题面分析
- 输入首先给出两个正整数N(≤10^5)和K(≤500)。其中K是最低里程,即为照顾乘坐短程航班的会员,航空公司还会将航程低于K公里的航班也按K公里累积。随后N行,每行给出一条飞行记录。飞行记录的输入格式为:18位身份证号码(空格)飞行里程。其中身份证号码由17位数字加最后一位校验码组成,校验码的取值范围为0~9和x共11个符号;飞行里程单位为公里,是(0, 15 000]区间内的整数。然后给出一个正整数M(≤10^5),随后给出M行查询人的身份证号码。
- 对每个查询人,给出其当前的里程累积值。如果该人不是会员,则输出No Info。每个查询结果占一行。
- 输入首先给出两个正整数N(≤10^5)和K(≤500)。其中K是最低里程,即为照顾乘坐短程航班的会员,航空公司还会将航程低于K公里的航班也按K公里累积。随后N行,每行给出一条飞行记录。飞行记录的输入格式为:18位身份证号码(空格)飞行里程。其中身份证号码由17位数字加最后一位校验码组成,校验码的取值范围为0~9和x共11个符号;飞行里程单位为公里,是(0, 15 000]区间内的整数。然后给出一个正整数M(≤10^5),随后给出M行查询人的身份证号码。
- 解法
1.首先需要建立起一个有关顾客信息的哈希链。
2.对每个顾客的id进行处理,取余法寻找地址。后若id相同,直接累加里程即可
3.对顾客id进行查询时,找到相应地址,输出即可 - 时间复杂度
O(n²)
2.3.2伪代码
typedef struct node
{
char id[20];
int len;
struct node* next;
} Node;
typedef struct
{
int TableSize;
Node* Table;
}HashTable;
int main()
{
createHashTable(n);初始化哈希链
for int i= 0 to n
{
插入建立哈希链
}
for int i = 0 to n
{
Find寻找给定id
找到输出里程,找不到输出No info
}
}
HashTable* createHashTable(int n)
{
for int i = 0to H->TableSize
{
初始化H->Table[i]
}
返回H节点
}
Node* Find(HashTable* H, char key[])
{
数据处理,找到哈希地址
Node* ptr = H->Table[index].next;找到对应链表
while (ptr && strcmp(ptr->id, key)) 找到id相同的节点
{
ptr = ptr->next;
}
返回ptr;
}
void Insert(HashTable* H, char key[], int l)
{
Node* p = Find(H, key);//找到对应节点
if (p为空,即没找到)
{
申请节点,复制相应信息
头插法插入该节点
}
else
{
里程累加
}
}
2.3.3 PTA提交列表
1.编译错误是由于strcmp函数的头文件写错了,虽然vs上能运行,但pta过不了
2.不知道为什么用c++语法显示的是段错误,改为c语法就过了
2.3.4 本题设计的知识点
1.哈希链的创建和查找。创建需要计算哈希地址,插入相应位置。查找也需要计算哈希地址,在链表中查找相应信息
2.创建哈希链时,注意不能让单链表的长度过长,过长的话耗时较长,有些数据点会超时
3.头插法建链表
4.scanf和printf在效率上要高于cin和cout