DS博客作业05--查找
这个作业属于哪个班级 | 数据结构--网络2012 |
---|---|
这个作业的地址 | DS博客作业05--查找 |
这个作业的目标 | 学习查找的相关结构 |
姓名 | 李兴果 |
PTA得分截图
本周学习总结
查找的性能指标
- 关键字的平均比较次数,也称平均搜索长度ASL(Average Search Length)
- 平均查找长度:
查找算法中的基本运算是记录的关键字与给定值所进行的比较。其执行时间通常取决于关键字的比较次数,也称为平均查找长度ASL 。
n | 记录的个数 |
---|---|
pi | 查找第i个记录的概率 |
ci | 找到第i个记录所需的比较次数 |
-
ASL成功:找到其中任一记录平均需要的关键次数比较次数
例:
关键字 5 1 4 8 7 9 6 5 3 找到的比较次数 1 2 3 4 5 6 7 8 9 ASLsuccess=(1+2+3+4+5+6+7+8+9)/9(个数)=5
不成功,比较次数,移动次数、时间复杂度
静态查找
-
仅作查询和检索操作的查找表
线性表查找属于静态查找,是将查找表视为一个线性表,将其顺序或链式存储,再进行查找,因此查找思想较为简单,效率不高。如果查找表中的数据元素有一定的规律(如按关键字有序),可以利用这些信息获得较好的查找效率。
(1)顺序查找
- 即数据存储在顺序表中,然后逐项查找元素
思路:从表的一段开始,顺序扫描线性表,一次将扫描到的关键字和给定值k相比较,若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,任未找到关键字等于k的记录,则查找失败
例:
3 | 10 | 2 | 8 | 6 | 7 | 9 | 15 | 5 | 4 |
---|---|---|---|---|---|---|---|---|---|
k | k | k | k | k | k | k | k | k | k |
成功:如找k=6时,关键字与k比较次数:5
不成功:如找k=20时,关键字与k比较次数:10
-
代码块
-
时间复杂度:O(n)
#define MAXNUM 100 //查找表的容量 typedef int KeyType; typedef struct{ KeyType key; //关键字 }DataType; typedef struct{ DataType data[MAXNUM]; int n; //元素个数 }SeqList; int Seq_Search_1 (SeqList list, KeyType kx) {/*数据存放在list.data[1] 至list.data[n]中,在表list中查找关键字为kx的数据元素*/ //若找到返回该元素在查找表中的位置,否则返回0 int i=1; while(i<=list.n && list.data[i].key!= kx ) i++; //从表头端向后查找 if (i>list.n) return 0; else return i; }
-
查找成功时平均比较次数约为表长的一半
-
查找不成功时的平均查找长度:n
顺序查找的特点
- 顺序查找的优点是算法简单,对表中数据元素的存储方式、是否按关键字有序均无要求
- 缺点是平均查找长度较大,效率低,当n很大时,不宜采用顺序查找
为了提高查找效率,查找表中的数据存放需依据查找概率越高,使其比较次数越少;查找概率越低,比较次数可相对较多的原则来存储数据元素
(2)二分查找
-
思路:在有序表中,取中间元素作为比较对象,若给定值与中间元素的关键字相等,则查找成功;若给定值小于中间元素的关键字,则在中间元素的左半区继续查找;若给定值大于中间元素的关键字,则在中间元素的右半区继续查找。不断重复上述查找过程,直到查找成功,或所查找的区域无数据元素,查找失败。
-
折半查找,要求线性表中的节点必须按关键字值的递增或递减顺序排列
-
代码实现:
-
时间复杂度:O(log2n)
int BinarySearch(SeqList list, KeyType kx)
{
//若找到返回该元素在表中的位置,否则返回0
int mid,low=1, high=list.n; //设置初始区间
while(low<=high)
{ /*当查找区间非空*/
mid=(low+high)/2; //取区间中点
if(kx==list.data[mid].key)
return mid; //查找成功,返回mid
else if (kx<list.data[mid].key)
high=mid-1; // 调整到左半区
else low=mid+1; // 调整到右半区
}
return 0; //查找失败,返回0
}
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
8 | 12 | 23 | 28 | 31 | 45 | 51 | 99 |
low | mid=low+[(high-low)/2] | high |
while (low<=high) {
int mid= low + [(high-low)/2];
if (ST.elem[mid].key==key) return mid; //查找成功
else if (key < ST.elem[mid].key) high=mid-1; //下一次到前半区间查找
else low=mid+1; //下一次到后半区间查找
}
方法二:递归算法
int BinarySearch(SeqList list, int low,int high.KetType k)
{
//若找到返回该元素在表中的位置,否则返回0
int mid,low=1, high=list.n; //设置初始区间
if(low<=high)
{ /*当查找区间非空*/
mid=(low+high)/2; //取区间中点
if(k==list.data[mid].key)
return mid+1; //查找成功,返回逻辑序号
if (k<list.data[mid].key)
BinarySearch(list,low,mid-1. k)//递归查找
else
BinarySearch(list,low,mid+1. k)//递归查找
}
return 0; //查找失败,返回0
}
折半查找的性能分析-判定树
-
把当前查找区间的中间位置上的记录作为根
-
左子表和右子表中记录分别作为根的左子树和右子树
序号 0 1 2 3 4 5 6 7 8 9 10 2 4 7 9 10 14 18 26 32 40 50
查找成功:ASLsucc=(1x1+2x2+4x3+4x4)/11=3
查找不成功:ASLunsucc=(4x3=8x4)/12=3.67
- 平均查找长度
二叉搜索树
- 定义:
二叉排序树又称二叉搜索树。在一棵二叉排序树中,若根节点的左子树不为空,那么左子树上所有的结点都小于根结点;若根节点的右子树不为空,那么右子树上所有结点都小于根节点。且根结点的左右子树叶都是二叉排序树。二叉排序树中所有的关键字都是唯一的。二叉树还有特殊的性质:按中序遍历可以得到一个递增有序序列
二叉排序树满足的条件:
- 左子树为空树或所有的结点值均小于其根结点的值
- 右子树为空树或所有的结点值均大于其根结点的值
- 左右子树也统统都是二叉排序树
如何构建二叉搜索树(操作)
-
结合一组数据介绍构建过程,及二叉搜索树的ASL成功和不成功的计算方法
构造{60,28,55, 10, 75, 95, 40, 35, 70, 86,35}
......
ASLscuss=(1+2x2+3x3+4x2+5x2)/10=3.2
ASLunscuss=(1x2+4x3+2x4+4x5)/11=3.8
如何在二叉搜索树做插入、删除
- 插入:
插入38
左小右大
-
删除:
- 二叉排序树删除的结点只有左子树或右子树
删除只有左孩子或右孩子的节点,只要让双亲节点的指针域指向它的左孩子或右孩子,只需把55指向35
-
二叉排序树删除的节点既有左子树和右子树
1.以前驱代替,再删除该节点,前驱为左子树中最大的节点
2.以后继代替,再删除该节点,后继为右子树中最小的节点
二叉搜索树的特性:左子树<根节点<右子树
- 二叉排序树删除叶子节点
如何构建二叉搜索树(代码)
-
结构体定义:
typedef struct node { KeyType key;//关键字; InfoType data;//其他数据域; struct node*lchild,*rchild;//左右孩子指针; }BSTNode,*BSTree;
-
构建二叉排序树
创建一棵二叉排序树是从一个空树开始的,每插入一个关键字,就调用一次插入算法把它插入到当前已经生成的二叉排序树中
代码:
int InsertBST(BSTree &p,int k) { if(p==NULL)//插入永远都在叶子节点插入 { p=new BSTNode; p->key=k; p->lchild=p->rchild=NULL; return 1; } else if(k==p->key) { return 0; } else if(k<p->key) { return InsertBST(p->lchild,k); } else { return InsertBST(p->rchild,k); } }
二叉排序树的查找
因为二插排序树可看做是一个有序表,所以在二插排序树上进行查找,与二分查找类似
伪代码:
```c
BSTNode* BSTNode * SearchBST(BSTNode* bt,KeyType k)
{
若bt为NULL或者bt->key==k ,返回bt;
若bt->key<k,递归查找左子树;
若bt->key>k,递归查找右子树;
}
```
**代码块:**
BinTree *SearchBST(BinTree *bt, KeyType key)
{
if(bt == NULL || key == bt->key)
return T;
if(key < bt->key) //递归进入左子树查找
return SearchBST(bt->Left, key);
else //递归进入右子树查找
return SearchBST(bt->Right, key);
}
非递归算法:
BinTree *SearchBST(BinTree *bt, KeyType key)
{
while(bt!= NULL)
{
if(key== bt->key)
return bt;
else if(key < bt->key) //递归进入左子树查找
bt=bt->lchild;
else //递归进入右子树查找
bt=bt->rchild;
}
二插排序树的插入
1.若二叉排序树为空,则直接插入
2.若二叉排序树非空,则对于给出的值 key 与根结点的数据 data 进行对比:
若 key 小于 data,则递归进入左子树插入
若 key 大于 data,则递归进入右子树插入
代码:
bool InsertBST(BinTree BST, ElementType X)
{
bool flag = true;
if (BST == NULL) //找到插入位置
{
BST = new TNode;
BST->data = X;
BST->lchild = NULL;
BST->rchild = NULL;
}
else
{
if (X < BST->data) //递归进入左子树插入
{
InsertBST(BST->lchild, X);
}
else if(X > BST->data) //递归进入右子树插入
{
InsertBST(BST->rchild, X);
}
else //若结点已存在,无需插入
{
flag = false;
}
}
return flag;
}
二插排序树的删除
首先基于查找的框架,找到需要删除的结点
二叉搜索树的删除需要考虑可能性:
- 结点的左右子树都为 NULL,直接修改其双亲结点
- 结点仅有左子树或右子树,令子树的第一个结点来替代删除结点即可
- 结点同时拥有左子树和右子树,则需要进行选择,一种做法就是选择右子树的最小结点或左子树的最大结点来取代被删除结点
代码:
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;
}
}
为什么要用递归实现插入、删除
- 简洁
2.在树的前序,中序,后序遍历算法中,递归的实现明显要比循环简单得多
AVL树(平衡二叉树)
-
为了解决二叉搜索树极端的情况,如单支树,那时,查找的ASL,就好顺序查找一样
-
特点:
1.左右子树是平衡二叉树
2.所有节点的左右子树深度(高度)之差的绝对值<=1,在平衡二叉树里,用平衡因子来表示左右子树的高度之差,从而控制树的平衡
-
结合一组数组,介绍AVL树的4种调整做法
-
LL型调整
发现插入2后5变成了失衡点,需要把5的左孩子右旋上去作为5的根节点,5右下旋转作为4的右孩子,若4原先有右孩子,则吧它作为5的左子树
-
RR型调整
若在根节点的右子树的右子树上插入节点,使得根节点的平衡因子改变,需要进行逆时针旋转,8左旋作为根节点,根节点3旋转作为8的左子树,8原先的左子现作为3的右子树
-
RL型调整
在根节点的左子树的右子树上插入节点,使得平衡因子从1到2,以插入的结点为旋转轴,先逆时针旋转,在将根节点顺时针旋转,6旋转到10的位置作为根节点,10作为6的右孩子,若6有左孩子,则将之作为1的右孩子,若6有右孩子,将之作为10的左孩子
-
LR型调整
在根节点的右子树的左子树上插入节点,使得平衡因子从-1到-2,以插入的结点为旋转轴,先顺时针旋转再逆时针旋转,10为旋转点,旋转到8的位置作为根节点,8作为它的左孩子,10原来的左孩子9现作为8的右孩子,若原来10有右孩子,则作为12的左孩子
-
AVL树的高度和树的总节点数n的关系?
-
高度为h的平衡二叉树,节点个数为N(h)=N(h-1)+N(h-2)+1
-
平衡二叉树高度h和n的关系:h=log(n+1)
-
平衡二叉树上最好 最坏进行查找关键字的比较次数不会超过平衡二叉树的深度O(logn)
-
介绍基于AVL树结构实现的STL容器map的特点、用法。
B-树和B+树
-
B-树和AVL树区别,其要解决什么问题?
B-树和AVL树都是内存中,适用小数据量
每个节点放一个关键字,树的高度比较大。B-树一个节点可以放多个关键字,降低树的高度。可放外存,适合大数据量查找
-
B-树定义:
又称多路平衡查找树,B树中所有结点的孩子个数的值成为B树的阶
一个节点有多于两个(不能小于)结点的平衡多叉树- 定义:
- 树中每个结点至多有m 棵子树(孩子节点);
- 若根结点不是叶子结点,则至少有 2 棵子树;
- 除根之外的所有节点至少有 m/2孩子节点;
- 所有的叶子结点都出现在同一层次上,并且不带信息,通常称为失败结点
- 所有的非终端结点最多有 m一1 个关键字
- 每个节点的结构:
1.n为关键字个数,n+1孩子指针
2.节点中按关键字大小顺序排序,K1<Ki+1
3.pi为该孩子的孩子指针
满足:
Pi+1所指指数的结点的关键字均小于Ki中
Pi所指指数的结点的关键字均大于Ki中
- B-树的搜索:
从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果
命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;
- m阶B-树的核心特性:
1.根节点的子树个数[2,m]
关键字数[1,m-1]
其他结点的子树数[m/2,m],关键字[m/2,m-1]
2.任何一个关键字出现且只出现在一个结点中
3.对任一结点,所有子树高度相同
4.其搜索性能等价于在关键字全集内做一次二分查找;
所以B-树的性能总是等价于二分查找(与M值无关),也就没有B树平衡的问题;
由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占
M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并;
- B-树的结构体定义
#define MAXN 10
typedef int KeyTypw;
typedef struct node{
int keynum;//节点当前拥有关键字的个数
KeyType key[MAXM];//存放关键字
struct node *parent;//双亲节点指针
struct node *ptr[MAXM];
//孩子节点指针数组}BTNode;
- B-树的结查找结果返回类型
typedef struct
{
BTNode *pt;//指向找到的结点的指针
int i;//存放关键字序号
int tag;//标志查找成功(=1)或者失败(=0)
}Result;
- B-树查找伪代码
关键字k 根节点中key[i]
若k<key[1]
则沿着指针ptr[0]所指的子树继续查找
若key[i]<key[i+1]
则沿着指针ptr[i]所指的子树继续查找
若k>key[n]
则沿着指针ptr[n]所指的子树继续查找
- 说明:查找到的某个叶子结点,若相应指针为空,落入一个外部结点,表示查找失败
- 结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况
B-树的插入
-
向B-树中插入关键字,可能引起节点的分裂,最终可能导致整个B-树的高度增一
-
插入的节点一定是叶子节点层(终端节点层)
在插入 key 后,若导致原结点关键字数超过上限,则从中间位置(m/2)将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置m/2的结点插入原结的父结点
举例:
关键字序列为:{1,2,6,7,11,4,8,13,10,5,17,9,16,20,3,12,14,18,19,15}
创建5阶B树,最多关键字个数:Max=m-1=4
插入1 2 6 7 11 4 8 13
插入10 5 17 9 16
插入20
插入3 12 14 18 19 15
删除的操作
* 结点中关键字的个数>m/2 -1,直接删除
* 结点中关键字的个数=m/2-1
要从其左(或右)兄弟结点“借调””关键字
若其左和右兄弟结点均无关键字可借(结点中只有最少重的关键字),则必须进行结点的“合并
-
若被删除关键字在非终端节点,则用直接前驱或直接后继来替代被删除的关键字
直接前驱:当前关键字左側指针所指子树中“最右下”的元素
直接后继:当前关键字右側指针所指子树中“最左下”的元来删除77
删除38
兄弟够借。若被删除关键字所在结点删除前的关键字个数低于下限,且与此结点右(或存)兄弟结点的关键字个数还很宽裕,则需更调整该结点、右(或左)兄弟结点及其双亲结点(父子换位法)
说白了:当右兄弟很宽裕时,用当前结点的后继,后继的后继来填补空缺
合并:
删除49后
兄弟不够借,若披删除关键字所在结点制除前的关键字个数低于下限,且此时与该结点相邻的左右兄结点的关键字个数均=[m /2]-1,则将关键字删除后与左(或右)兄弟结点双亲结点中的关键字**进代合并井在合并过程中,双亲结点中的关键字个数会减1。
若其双来结点是根结点具关键字个改减少至0(根结点关键字个数为1时,有2棵子树),则直接将根结点删除,合并后的新结点成为根;
若双亲结点不是根结点,且关健字个数减少到m/2-2,则又要与它自己的兄弟绍点进行调整或合并操作,并重复上述步骤
B+树定义,其要解决问题
B+树是B-树的变体,也是一种多路搜索树:
1.其定义基本与B-树同,除了:
2.非叶子结点的子树指针与关键字个数相同;
3.非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树
(B-树是开区间);
5.为所有叶子结点增加一个链指针;
6.所有关键字都在叶子结点出现;
-
具体差异:
-
有 n 棵子树的结点中含有 n 个关键字;
-
所有的叶子结点中包含了全部关键字的信息,以及指向含这些关键字记录的指针,叶子结点本身依关键字的大小自小而则顶序链接;
-
所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中的最大或最小关键字。
-
B+的特性:
1)每个分支结点最多有 m 子树(孩子结点)
2)非叶根结点至少有两棵子树,其他每个分支结点至少[ m /2]子树
3)结点的子树个数与关键字个数相等
4)所有叶结结点包含全部关键字及指向相应记录的指针,叶绍点中将关键字字技大小顺序排列,并且相邻叶子结点按大小顺序相互链接起来
5)所有分支结虚中仅包含它的各个子结点中关键字的量大值及指向其子结点的指计
4.更适合文件索引系统;支持顺序查找
散列查找。
哈希表是一种线性存储结构,记录关键字与存储地址存在某种函数关系的结构
装填因子=存储的记录个数/哈希表的大小,哈希表长度和装填因子有关,装填因子越小,冲突可能性就越小
哈希表的构造
直接定址法
哈希函数:h(k)=k+c
优势:计算简单,不可能有冲突发生
缺点:关键字分布不连续,将造成内存单元的大量浪费
除留取余数法
用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址,h(k)=k%p(这里的p最好为不大于m的质数,减少冲突的可能性)
例题:
将关键字序列{7,8,30,11,18,9,14}散列存储到散列表中,散列表的存储空间是一个下标从0开始的一维数组,散列函数为:H(key)=(key×3) mod 7,要求装填(载)因子为0.7画出所构造的散列表
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
关键字 | 7,14 | 8 | 11,18 | 30,9 |
优势:计算简单,适用范围广泛
缺点:容易发生哈希冲突
由表格引起冲突改进后:
线性探测法解决冲突
线性探测是从发生冲突的地址d0开始,依次探测d0的下一个地址(当到达哈希表表尾时,下一个探测地址为表首地址0),直到找到一个空闲位置为止,探测序列为:d=(d+i)%m
以上述关键字序列{7,8,30,11,18,9,14}散列存储到散列表中
d=11x3%7=18x3%7=5,此时下标为5的单元格先存储了11,,1则继续往后寻找下一个地址6(不空闲),继续7(空闲)存入,
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
关键字 | 7 | 8 | 11 | 30 | 18 |
......
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
关键字 | 7 | 14 | 8 | 11 | 30 | 18 | 9 |
探测平方法解决冲突
d0=h(k)
di(d0 +(-) i^2)mod m
查找的位置依次为d0, d0+1, d0-1,d0+4,d0-4,,,,
线性探测法计算ASL
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
关键字 | 7 | 14 | 8 | 11 | 30 | 18 | 9 | |||
成功探测次数 | 1 | 2 | 1 | 1 | 1 | 3 | 3 | |||
不成功探测次数 | 3 | 2 | 1 | 2 | 1 | 5 | 4 |
ASL(成功)=(有数字的关键字探测总次数)/有数据的关键字总数
ASL=(1+2+1+1+3+3)/7
ASL(不成功),其实是从每个下标(0开始),查找到没有数据(空位NULL)
ASL=(3+2+1+2+1+5+4+3+2+1)/7(m)不是表长
拉链法解决冲突
把具有相同地址的关键字用一条链连在一起
列:序列{68,27,10,8,32,25},m,p都为7
- 处理冲突简单,无堆积现象,非同义词不会发生冲突
- 结点空间时动态申请的,空间利用效率高
- 在链表中,删除结点操作方便
哈希表代码实现
- 哈希表结构体
#define NULLKEY -1 //定义空关键字值
#define DELKEY -2 //定义被删关键字字值
typedef int KeyType;
typedef struct
{
KeyType key;//关键字域
int count;//探测次数域
}HashTable;//哈希表单元类型
- 哈希表插入
void InsertHT(HashTable ha,int &n,KeyType k,int p) //哈希表插入数据,n表示哈希表数据个数,k插入关键字,p除数
{
n++;
int i;//表示地址
int temp;//哈希函数值
int counts=0;//表示查找次数
temp=k%p;
if(ha[temp].count==0)
{
ha[temp].key=k;
ha[temp].count=1;
}
else{//该哈希地址已被填满
while(ha[temp].count!=0)//线性探测法
{
counts++;//查找次数依次递增
temp=(temp+1)%p;
}
counts++;//找到de位置也算一次查找
ha[temp].key=k;
ha[temp].count=counts;
}
}
- 哈希表查找
int SearchHT(HashTable ha,int p,int k)
{
int i;
temp=k % p;
while(ha[temp].key!=NULLKEY && ha[temp].key!=k)
{
temp=(temp+1) % m;//探查下一个地址
}
if(ha[temp].key==NULLKEY)
return -1;//地址为空,找不到
if(ha[temp].key==k)
return adr; //找到关键字k
else
return -1;
}
- 哈希表删除
int DeleteHT(HashTable ha,int p,int k,int &n)
{
int temp;
temp=SearchHT(ha,p,k);
if(temp!=-1)
{
ha[temp].key=DELKEY;//逻辑删除,该元素贴删除标记
n--;
return 1;
}
else
return 0;
}
- 哈希链结构体
typedef struct _Node
{
int data;
struct Node*next;
}HashNode,*HashList;//链节点的结构体定义
哈希链初始化
void Inithash(HashList hash)
{
int i;
Node *hash=new HashNode[HASHSIZE];
for(i=0; i<HASHSIZE; i++) {
hash[i].next = NULL;//初始化每个槽位
}
}
- 哈希链搜索
HashList SearchHash(HashList &hash,int key ,int adr)
{
HashList p;
int i;
p=hash[adr].next;
while(p)
{
if(p->data==key)
{//有重复关键字出现
break;
}
p=p->next;
}
return p;
}
- 哈希链插入
void InsertHash(HashList &hash,int p)
{
int adr,key;
int i;
HashList p;
for(i=0;i<n;i++)
{
cin<<key;
adr=key%p;
p=Search_Hash(hash,key,adr);
if(p==NULL)
{
p=new HashNode;
p->data=key;
p->next=hash[adr].next;
hash[adr].next=p;
}
}
}
PTA题目介绍
是否完全二叉搜索树
将一系列给定数字顺序插入一个初始为空的二叉搜索树(定义为左子树键值大,右子树键值小),你需要判断最后的树是否一棵完全二叉树,并且给出其层序遍历的结果。
输入格式:
输入第一行给出一个不超过20的正整数N;第二行给出N个互不相同的正整数,其间以空格分隔。
输出格式:
将输入的N个正整数顺序插入一个初始为空的二叉搜索树。在第一行中输出结果树的层序遍历结果,数字间以1个空格分隔,行的首尾不得有多余空格。第二行输出YES,如果该树是完全二叉树;否则输出NO。
输入样例1:
9
38 45 42 24 58 30 67 12 51
输出样例1:
38 45 24 58 42 30 12 67 51
YES
输入样例2:
8
38 24 12 45 58 67 42 51
输出样例2:
38 45 24 58 42 12 67 51
NO
- 注意:这道题左子树是较大的关键字,右子树是较小的关键字
完全二叉树就是生成结点的顺序是严格按照从上到下,从左往右的顺序来构建的二叉树
例如对于题设测试样例 1 所建立的二叉搜索树:
若按照“从上到下,从左到右”的顺序去读这个二叉树,空结点只会集中出现在末尾部分
测试样例 2:
按照“从上到下,从左到右”的顺序去读二叉树,发现有个空结点穿插在了结点之间
遍历完毕之前遇到了空结点,就说明这不是完全二叉树
如何实现“从上到下,从左到右”的顺序遍历
这就是使用层序遍历法,需要用队列来实现
1.建立一棵二叉搜索树,采用树结构
2.判断是否为完全二叉搜索树
3.对树进行层次遍历
伪代码:
建树函数void CreateBST(BinTree& BST, int n)
初始化BST=NULL;
for 0 to n
读取结点值x并调用插入函数插入树中
end for
插入函数void Insert(BinTree& BST, int X)
if 树空
申请树节点并初始化左右孩子,讲节点值存入结构体
end if
else if 插入节点值大于树根节点对应值
调用插入函数,用递归的方法插入左子树中Insert(BST->Left, X);
end else
else if 插入节点值小于树根节点对应值
调用插入函数,用递归的方法插入左子树中Insert(BST->Right, X);
end else
- 判断是否是完全二叉搜索树(在此只需判断是否完全二叉树)
用于判断的函数bool IsCompleteBinaryTree(BinTree& BST, int n)
定义num来计算层次遍历可输出的个数,队列tree存树节点,
if 树空
也算作是完全二叉树,返回true
end if
先把树的根节点放入队列
while 1
取队首node
如果为空则跳出循环
不为空则让左右子树进栈,先左后右
出栈队首并使得输出个数num增1
end while
判断num的值,等于节点个数n则说明在遇到空节点前树节点遍历完成,返回true,反之,则返回false
提交列表
- 调试及提交过程遇到的问题:
1.这道题有个坑,就是,它定义的二叉搜索树,是左子树键值大,右子树键值小,而不是我们平时说的左小右大,这个需要注意,在建树的时候需要特别注意,否则最后的输出结果会有问题。
本题设计的知识点
1.二叉搜索树的建立,主要得先明白它的定义和特点,才好结合着写代码。
2.完全二叉树的判断,当然,还是得从定义出发,结合它的特点,对它进行分析。
3.层次遍历,这是之前学过的内容,主要是利用队列的存储结构,进行临时存储和遍历。
航空公司VIP客户查询
不少航空公司都会提供优惠的会员服务,当某顾客飞行里程累积达到一定数量后,可以使用里程积分直接兑换奖励机票或奖励升舱等服务。现给定某航空公司全体会员的飞行记录,要求实现根据身份证号码快速查询会员里程积分的功能。
输入格式:
输入首先给出两个正整数N(≤105)和K(≤500)。其中K是最低里程,即为照顾乘坐短程航班的会员,航空公司还会将航程低于K公里的航班也按K公里累积。随后N行,每行给出一条飞行记录。飞行记录的输入格式为:18位身份证号码(空格)飞行里程。其中身份证号码由17位数字加最后一位校验码组成,校验码的取值范围为0~9和x共11个符号;飞行里程单位为公里,是(0, 15 000]区间内的整数。然后给出一个正整数M(≤10
5),随后给出M行查询人的身份证号码。
输出格式:
对每个查询人,给出其当前的里程累积值。如果该人不是会员,则输出No Info。每个查询结果占一行。
输入样例:
4 500
330106199010080419 499
110108198403100012 15000
120104195510156021 800
330106199010080419 1
4
120104195510156021
110108198403100012
330106199010080419
33010619901008041x
输出样例:
800
15000
1000
No Info
本题去学习了解了一下学长做法,自己对哈希链的掌握不够好,对此题的思路更是meng
- 实现思路:
客户身份证和里程数固定一一对应,可以使用哈希链存储数据,所以最主要的就是建好哈希链
这题,需要存储的是身份证号,以及对应的飞行记录。主要考察的是,对哈希链的操作
对数据巧妙地处理,因为身份证中包含x,它不是数字,所以处理的时候,可以把它看成10,或11等等,单独处理
伪代码
建哈希链void Create(HashList* h, int n, int k)
for 0 to n
输入id和距离dist
调用函数插入数据Insert(h, id, dist, k);
end for
插入数据void Insert(HashList* h, char id[], int dist, int k)
调用函数计算哈希地址adr = GetAdr(id);
调用函数找节点p = FindNode(h, id, adr);
if p不为空说明找到
根据里程大小dist,加入p点对应的里程中
end if
else
申请节点空间node
把id赋值给node的对应数据:strcpy(node->number, id);
根据里程大小分情况存入node对应的数据
将node插入哈希链h[adr]中
end else
计算哈希地址int GetAdr(char id[])
初始化adr=0;
for 12 to 16取后6位
adr = adr * 10 + id[i] - '0';
end for
最后一位是x则加10,其他则直接加
adr=adr%max;
返回adr
在哈希链中找用户HashList FindNode(HashList* h, char id[], int adr)
取node = h[adr]->next;
while node不为空
if 找到
返回node节点
end if
else
node下移
end else
end while
返回空
本题知识点
1.时间性能上,若用c++的cin来输入字符串类型,会容易超时,当数据量较大时,cin和cout的耗时可能会是scanf,printf的好几倍,因此这里要使用 C 语言的 scanf() 和 printf() 函数来进行输入和输出
2.哈希链的构造,特别注意,建链前需要对哈希链进行初始化和申请空间。
3.哈希链的寻找,和邻接表类似,确定头结点后,遍历头结点后的链表节点,比较对应的数据即可。
4.哈希地址的确定,用的是除留余数法,但也涉及了一点数字分析法,因为身份证号的构成比较特殊且数字较多,需要对其进行一定的分析。
2.3 基于词频的文件相似度(1分)
实现一种简单原始的文件相似度计算,即以两文件的公共词汇占总词汇的比例来定义相似度。为简化问题,这里不考虑中文(因为分词太难了),只考虑长度不小于3、且不超过10的英文单词,长度超过10的只考虑前10个字母。
输入格式:
输入首先给出正整数N(≤100),为文件总数。随后按以下格式给出每个文件的内容:首先给出文件正文,最后在一行中只给出一个字符#,表示文件结束。在N个文件内容结束之后,给出查询总数M(≤104),随后M行,每行给出一对文件编号,其间以空格分隔。这里假设文件按给出的顺序从1到N编号。
输出格式:
针对每一条查询,在一行中输出两文件的相似度,即两文件的公共词汇量占两文件总词汇量的百分比,精确到小数点后1位。注意这里的一个“单词”只包括仅由英文字母组成的、长度不小于3、且不超过10的英文单词,长度超过10的只考虑前10个字母。单词间以任何非英文字母隔开。另外,大小写不同的同一单词被认为是相同的单词,例如“You”和“you”是同一个单词。
输入样例:
3
Aaa Bbb Ccc
Bbb Ccc Ddd
Aaa2 ccc Eee
is at Ddd@Fff
2
1 2
1 3
输出样例:
50.0%
33.3%
设计思路:
每个单词而言,可以使用哈希链来做,不过这里可以用 STL 库的 set 容器来存放,
伪代码:
定义int类型变量files1 files2为待查找的文件编号
定义变量类型为string的set容器files构建文件单词表
用哈希构建files文件单词表
for(i=0;i<比对文件组数;i++)do
输入file1 file2
file1 为单词数比较小的文件编号
for(iterator=set.begin();itertor!=set.end();iterator++)do
if(迭代器指向的单词存于files[files2中]do
end if
end for
end for
计算并输出文件相似度