DS博客作业05--查找
| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业05--查找 |
| 这个作业的目标 | 学习树结构设计及运算操作|
| 姓名 | 黄抒鸿 |
0.PTA得分截图

1.本周学习总结(0-5分)
1.1 查找的性能指标
在查找运算中时间主要花费在关键字的比较上,把平均需要和定值k进行比较的关键字次数称为平均查找长度(ASL),其定义如下图所示:

其中,n是查找表中元素的个数。Pi是查找第i个元素的概率,通常假设每个元素的查找概率相等,此时Pi=1/n(1<=i<=n),Ci是找到第i个元素所需的关键字比较次数。
ASL分为查找成功情况下的ASL成功和查找不成功情况下的ASL不成功。
·ASL成功表示成功查找到查找表中的元素,平均需要关键字比较次数
·ASL不成功表示没有找到查找表中的元素,平均需要关键字比较次数
ASL是衡量查找算法性能好坏的重要指标。一个查找算法的ASL越大,其时间性能越差;反之,一个查找算法的ASL越小,其时间性能越好。
1.2 静态查找
分析静态查找几种算法包括:顺序查找、二分查找的成功ASL和不成功ASL。
1.2.1 顺序查找
基本思路:从表的一端向另一端逐个将元素的关键字和给定k值比较,若相等,则查找成功,给出该元素在查找表中的位置;若整个查找表扫描结束后仍未找到关键字等于k的元素,则查找失败。
代码:
int SeqSearch(RecTypr 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;//找到后返回逻辑序号
}
从查找过程可以看到,Ci(查找的第i个元素的所需要的关键字比较次数)取决于该个元素在查找表中的位置。如第1个元素R[0]仅需要比较一次;而第n个元素R[n-1]需要比较n次,即Ci=i;因此:

可以看出,顺序查找方法查找成功的平均 比较次数约为表长的一半。当k值不在查找表中时,则必须进行n次比较之后才确定查找失败,所以:

1.2.2 折半查找
折半查找,也称二分查找,在某些情况下相比于顺序查找,使用折半查找算法的效率更高。但是该算法的使用的前提是静态查找表中的数据必须是有序的。利用有序表构建二叉树,中间数为根,左小右大,即为判定树。
代码:
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;
}
二分查找ASL成功:总比较次数/内部结点个数
二分查找ASL不成功:总比较次数/外部结点个数
二分查找的时间复杂度为O(log2n)
关键字比较次数为log2(n+1)
1.3 二叉搜索树
二叉排序树又称为二叉搜索树,它是一种特殊结构的二叉树,其定义为:二叉树排序树或者是一棵空树,或者是具有如下性质的二叉树:
(1)若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
(3)它的左右子树也分别为二叉排序树
1.3.1 如何构建二叉搜索树(操作)
假设原二叉排序树为空树,在对动态查找表 {3,5,7,2,1} 做查找以及插入操作时,可以构建出一个含有表中所有关键字的二叉排序树,过程如图所示:

ASL成功和不成功的计算方法。
ASL成功=每一层节点数层次数的总和/总结点数
ASL不成功=其他不存在于树中的每一层节点数(层次数-1)/总结点数
在二叉搜索树中删除数据
1.仅有一颗子树的情况其实也非常简单,只需要将其唯一的子树接到其原父亲结点处,即原本指向被删除结点的指针改为指向被删除结点的子树。
2.有两颗子树:这种情况就有些特殊了,如果要保证删除后二叉搜索树的相对位置不变(即中序遍历顺序不变),那有两种方法:
(1)使待删除结点的左子树代替被删除的结点,将被删除结点的右子树放置于被删除结点的左子树的最右边。
(2)用待删除结点的直接前驱或直接后继(指的是遍历顺序的)代替被删除的结点,然后再删除用以替代的原节点。
1.3.2 如何构建二叉搜索树(代码)
1.如何构建、插入、删除及代码。
二叉搜索树的构建
BSTNode* CreateBST(KeyType a[], int n)//创建二叉排序树
//返回BST树根结点指针
{
BSTNode* bt = NULL;//初始时bt为空树
int i = 0;
while (i < n)
{
InsertBST(bt, a[i]);//将关键字a[门插入二叉排序树bt中
i++;
}
return bt;//返回建立的二叉排序树的根指针
}
二叉搜索树的插入
bool InsertBST(BSTNode*& bt, KeyType k)
//在二叉排序树bt中插入一个关键字为k的结点,若插入成功返回真,否则返回假
{
if (bt == NULL)//原树为空,新插人的结点为根结点
{
bt = (BSTNode*)malloc(sizeof(BSTNode));
bt->key = k; bt->lchild - bt->rchild - NULL;
return true;
}
else if (k == bt->key)
return false;//树中存在相同关键字的结点,返回假
else if (k < bt->key)
return InsertBST(bt → > lchild, k);//插入到左子树中
else
return InsertBST(bt->rchild, k);//插入到右子树中
}
二叉搜索树的删除
bool DeleteBST(BSTNode& bt, KeyType k)//在bt中删除关键字为k的结点
{
if (bt == NULL)
return false//空树删除失败,返回假
else
{
if (k < bt->key)
return DeleteBST(bt->lchild, k);//递归在左子树中删除为k的结点
else if (k > bt->key)
return DeleteBST(bt->rchild, k);//递归在右子树中删除为k的结点
else//找到了要删除的结点bt
{
Delete(bt);//调用Delete(bt)函数删除结点bt
return true;//删除成功,返回真
}
}
}
void Delete(BSTNode*& p)//从二叉排序树中删除结点p
{
BSTNode* q;
if (p->rchild == NULL) //结点p没有右子树(含为叶子结点)的情况
{
q = p;
p = p->lchild;//用结点p的左孩子替代它
free(q);
}
else if (p->lchild = NULL) //结点口没有左子树的情况
{
q = р;
p = p->rchild;//用结点p的右孩子替代它
free(q);
}
else Delete(p, p->lchild);//结点p既有左子树又有右子树的情况
}
void Deletel(BSTNode* p, BSTNode*& r) //被删结点p有左、右子树,r指向其左孩子
{
BSTNode* q;
if (r->rchild != NULL)
Deletel(p, r->rchild);//递归找结点r的最右下结点
else//找到了最右下结点r(它没有右子树)
{
p->key = r->key;//将结点r的值存放到结点p中(结点值替代)
P->data = r->data;
q = r;//删除结点r
r = r->lchild;//即用结点r的左孩子替代它
free(q);//释放结点a的空间
}
}
2.分析代码的时间复杂度
在O(log2n)到O(n)之间
3.为什么要用递归实现插入、删除?递归优势体现在代码哪里?
减少代码量,同时便于删除和插入顺利找到父亲节点和孩子节点
1.4 AVL树
AVL树解决什么问题,其特点是什么?
AVL树又称平衡二叉搜索树,它能保证二叉树高度相对平衡,尽量降低二叉树的高度,提高搜索效率。
特点:
(1)树中的每个左子树和右子树都是AVL树
(2)树中每个结点的左、右子树深度之差(称为平衡因子BF)只能取:-1,0或1.
(3)AVL树的平均查找长度和logn是同数量级的
介绍AVL树的4种调整做法。
若向平衡二叉树中插入一个新结点(总是作为叶子结点插入)后破坏了平衡性,首先从该新插入的结向根结点方向查找第一个失衡的结点,然后进行调整。
调整的操作可以归纳为下列四种情况:
向AVL树中插入元素可能会导致树失去平衡。但是,我们只需要调整插入点至根节点的路径上不满足平衡状态的各节点中深度最大的那个即可。假设该最深节点为X。导致X失去平衡可能有四种情况:
(1)插入点位于X的左子结点的左子树-左左(LL)。
(2)插入点位于X的右子结点的左子树-右右(RR)。
(3)插入点位于X的右子结点的左子树-右左(RL)。
(4)插入点位于X的左子结点的右子树-左右(LR)。




AVL树的高度和树的总节点数n的关系?
AVL树,它的特点是在二叉搜索树的基础上,要求每个节点的左子树和右子树的高度差至多为1。这个要求使AVL的高度h = log2 n.(n为树的结点个数)
介绍基于AVL树结构实现的STL容器map的特点、用法。
Map是STL的一个关联容器,翻译为映射,数组也是一种映射。如:int a[10] 是int 到 int的映射,而a[5]=25,是把5映射到25。数组总是将int类型映射到其他类型。这带来一个问题,有时候希望把string映射成一个int ,数组就不方便了,这时就可以使用map。map可以将任何基本类型(包括STL容器)映射到任何基本类型(包括STL容器)。
| 用法 | 作用 |
|---|---|
| begin() | 返回指向map头部的迭代器 |
| end() | 返回指向map末尾的迭代器 |
| rbegin() | 返回一个指向map尾部的逆向迭代器 |
| rend() | 返回一个指向map头部的逆向迭代器 |
| lower_bound() | 返回键值>=给定元素的第一个位置 |
| upper_bound() | 返回键值>给定元素的第一个位置 |
| empty() | 如果map为空则返回true |
| max_size() | 返回可以容纳的最大元素个数 |
| size() | 返回map中元素的个数 |
| clear() | 删除所有元素 |
| count() | 返回指定元素出现的次数 |
| equal_range() | 返回特殊条目的迭代器对 |
| erase() | 删除一个元素 |
| swap() | 交换两个map |
| find() | 查找一个元素 |
| get_allocator() | 返回map的配置器 |
| insert() | 插入元素 |
| key_comp() | 返回比较元素key的函数 |
| value_comp() | 返回比较元素value的函数 |
1.5 B-树和B+树
1.5.1 B-树和AVL树区别,其要解决什么问题?
AVL树每个结点只能有一个关键字,而B-树的一个结点可以存放多个关键字,降低了树的高度,可以解决大数据下的查找。同时,B-树可以有多个子树,而AVL树只能最多有两个子树。
1.5.2 B-树定义。
一颗m阶的B树定义如下:
1.每个结点最多有m-1个关键字。
2.根结点最少可以只有1个关键字。
3.非根结点至少有Math.ceil(m/2)-1个关键字。
4.每个结点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
5.所有叶子结点都位于同一层,或者说根结点到每个叶子结点的长度都相同。
1.2.3 B+树定义,其要解决问题
m阶B+树定义:
1.每个分支至多有m棵子树
2.根节点没有子树或者至少有两颗
3.除了根节点其他节点至少有m/2向上取整个子
4.有n棵子树的节点有n个关键字
5.叶子节点包含全部关键字,以及只想相应记录的指针,而叶子节点按关键字大小顺序链接( 每个叶子节点可以看成是一个基本索引块,它的指针指向数据文件中的记录)
6.所有分支节点(可以看成是索引的索引)中仅仅包含他的各个子节点中最大关键字以及指向子结点的指针。
解决问题:
B+树的磁盘读写代价更低。B+树的内部结点并没有指向关键字具体信息的指针,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素。B+树的查询效率更加稳定。
1.6 散列查找。
使用散列查找分为两步:
1.用散列函数将被查找的键转换为数组的一个索引。理想情况下,不同的键值都会转换成为不同的索引值,但是一般情况下我们会遇到多个键对应相同的索引值,这也就是所谓的散列冲突。
2.处理碰撞冲突的情况,常用得方法有两种:拉链法(链地址法)和线性探测法。
1.6.1 哈希表的设计主要涉及哪几个内容?
哈希表的构造
1.直接定址法
哈希函数:h(k)=k+c
优势:计算简单,不可能有冲突发生
缺点:关键字分布不连续,将造成内存单元的大量浪费
2.除留取余数法
用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址,h(k)=k%p(这里的p最好为不大于m的质数,减少冲突的可能性)
哈希冲突解决
1.开放定址法
在出现哈希冲突时,再哈希表中找到一个新的空闲位置存放元素,例如:要存放关键字k,d=h(k),而地址d已经被其他元素占用了,那么就在d地址附近寻找空闲位置进行填入。开放定址法分为线性探测和平方探测等
2.线性探测
线性探测是从发生冲突的地址d0开始,依次探测d0的下一个地址(当到达哈希表表尾时,下一个探测地址为表首地址0),直到找到一个空闲的位置单元为止,探测序列为:d=(d+i)%m,所以,我们在设置哈希表的长度时一定要大等于元素个数,保证每个数据都能找到一个空闲单元插入
哈希表ASL:

哈希链ASL:


2.PTA题目介绍
2.1 是否完全二叉搜索树
2.1.1 伪代码
void InserBST(BinTree*& BST,KeyType k)//往二叉树中插入结点k
{
if(BST==NULL) 建立新结点;
else if(k<BST->key) 递归进入右子树;
else 递归进入左子树
}
/*层次遍历二叉树并判断是否为完全二叉树*/
bool Level_DispBST(BinTree* BST)
{
定义变量end表示是否已经找到第一个叶子结点或者第一个只有左孩子结点;
(如果是,则接下来未遍历的结点应该都是叶子结点)
定义flag保存判断结果;
若根结点BST不为空,入队,并输出;
while(队不为空)
出队一个结点p;
若结点p的左,右孩子不为空,依次输出,并入队;
若在剩余节点都应该是叶子结点的情况下p为非叶子节点
不是完全二叉树,flag=flase;
若结点p只有右孩子,没有左孩子
不是完全二叉树,flag=flase;
若结点p为叶子结点或者为第一个只有左孩子的结点
修改end,表示接下来应该都是叶子结点;
end while
return flag;
}
2.1.2 提交列表

2.1.3 本题知识点
完全二叉树的结构,二叉树的层次遍历,二叉搜索树的插入操作
2.2 航空公司VIP客户查询
2.2.1 伪代码
map<string, int>M;
for i=0 to n-1 i++
输入身份证,已行驶里程数据
if 里程小于最小里程k
then 让里程=k
end if
if 该身份证已由会员记录
then 原来的里程数再加上刚才输入的(M[id1]+=len)
end if
else 未被登记为会员
则现在输入身份证及里程信息(M[id1]=len)
end for
for i=0 to m-1 i++
输入身份证
若信息库(M)里由=有该身份证信息就输出里程信息
若信息库M中未找到该身份证信息
则输出“NO INFO
2.2.2 提交列表

2.2.3 本题知识点
哈希链的创建,初始化,查找和插入
浙公网安备 33010602011771号