DS博客作业05--查找

| 这个作业属于哪个班级 | 数据结构--网络2011/2012 |
| ---- | ---- | ---- |
| 这个作业的地址 | DS博客作业05--查找 |
| 这个作业的目标 | 学习查找的相关结构 |
| 姓名 | 雷正伟 |

0.PTA得分截图

1.本周学习总结

1.1 查找的性能指标

  • ASL成功
  1. 平均需要和给定值k进行比较的关键字次数称为平均查找长度(ASL)
  2. n是查找表中元素个数,
  3. Pi是查找第i个元素的概率(通常假设每个元素的查找概率相等,此时Pi=1/n)
  4. Ci是找到第i个元素所需要的关键字比较次数
  5. 一个查找算法的ASL越大,其时间性能越差,反之,若越小其时间性能越好
  • ASL不成功
  1. 没有找到查找表中的元素,平均需要关键字比较次数
  • 比较次数
  1. 对于一组数据当它以哈希表,链表,二叉搜索树储存时,关键字的搜索就要通过将关键字与其中的元素进行比较才能找到
  2. 哈希表因会有冲突导致关键字存储位置不是哈希函数所对应的位置,此时的比较次数就会增加
  3. 链表因会有冲突,导致关键字不是某一条链表的第一个元素,此时

1.2 静态查找

  • 顺序查找:
    在顺序查找表中,查找方式为从头扫到尾,找到待查找元素即查找成功,若到尾部没有找到,说明查找失败。所以说,Ci(第i个元素的比较次数)在于这个元素在查找表中的位置,如第0号元素就需要比较一次,第一号元素比较2次......第n号元素要比较n+1次。所以Ci=i;即

    可以看出,顺序查找方法查找成功的平均 比较次数约为表长的一半。当待查找元素不在查找表中时,也就是扫描整个表都没有找到,即比较了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;//没找到则返回0 
		else return i+1;//找到则返回序号i+1 
 } 
  • 二分查找:
    折半查找(二分查找),首先待查找表是有序表,这是折半查找的要求。在折半查找中,用二叉树描述查找过程,查找区间中间位置作为根,左子表为左子树,右子表为右子树,,因为这颗树也被成为判定树比较树查找方式为(找k),先与树根结点进行比较,若k小于根,则转向左子树继续比较,若k大于根,则转向右子树,递归进行上述过程,直到查找成功或查找失败。在n个元素的折半查找判定树中,由于关键字序列是用树构建的,所以查找路径实际为树中从根节点到被查结点的一条路径,因为比较次数刚好为该元素在树中的层数。所以Pi为查找k的概率,level(Ki)为k对应内部结点的层次。而在这样的判定树中,会有n+!种查找失败的情况,因为将判定树构建为完全二叉树,又有n+1个外部结点(用Ei(0<=i<=n)表示),查找失败,即为从根结点到某个外部结点也没有找到,比较次数为该内部结点的结点数个数之和,所以,qi表示查找属于Ei中关键字的概率,level(Ui)表示Ei对应外部结点的层次。所以,在一颗有n个结点判定树中,总数,所以判定树高度为的满二叉树,第i层上结点个数为,查找该层上的结点需要进行i次比较,因此,在等概率情况下ASL为

代码

int binarySearch(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;//找到则返回下标 
		else if(R[mid].key > k)high = mid - 1; 
		elss low = mid + 1;
    }
    return 0;//找不到则返回0 
}

1.3 二叉搜索树

1.3.1 如何构建二叉搜索树(操作)

  1. 第一步 给出一组元素
  2. 第二步 把第一个元素作为根节点
  3. 第二步,把第二个元素拿出来与第一个元素做比较,如果比根节点大就放在右边,如果比根节点小就放在左边
  4. 第四步 同样道理比较第三个元素62
  5. 第五步 插入第四个元素94,先与38比较,进入右子树,然后与62比较
  6. 第六步 按照以上的方式依次把后面的元素插入到树中
  • ASL成功:将每层的节点数和层数相乘并求其的和再除以树的节点数
    以上题为例:ASL成功=(11+22+33+42)/8=2.75
  • ASL不成功:将每层的空节点数和层数-1相乘并求其的和再除以树的节点数
    以上题为例:ASL不成功=(12+43)/5=2.8

1.3.2 如何构建二叉搜索树(代码)

  • 二叉搜索树的构建
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的空间
	}
}
  1. 时间复杂度:在O(log2n)到O(n)之间
  2. 减少代码量,同时便于删除和插入顺利找到父亲节点和孩子节点

1.4 AVL树

  • AVL树:AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树(其特点是每个结点的平衡因子的绝对值小于或等于1)
  • LL平衡旋转
  • RR平衡旋转
  • LR平衡旋转*
  • RL平衡旋转*
  1. AVL树的高度和树的总节点数n的关系:h≈log2N(h)+1
  2. 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+树

  • B-树和AVL树区别
    AVL树结点仅能存放一个关键字,树的敢赌较高,而B-树的一个结点可以存放多个关键字,降低了树的高度,可以解决大数据下的查找
    B-树定义:一棵m阶B-树或者是一棵空树,或者满足一下要求的树就是B-树
  1. 每个结点之多m个孩子节点(至多有m-1个关键字);
  2. 除根节点外,其他结点至少有⌈m/2⌉个孩子节点(至少有⌈m/2⌉-1个关键字);
  3. 若根节点不是叶子结点,根节点至少两个孩子
  • B-树定义

B树(英语:B-tree)是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树,概括来说是一个一般化的二叉查找树(binary search tree),可以拥有多于2个子节点。与自平衡二叉查找树不同,B树为系统大块数据的读写操作做了优化。B树减少定位记录时所经历的中间过程,从而加快存取速度。B树这种数据结构可以用来描述外部存储。这种数据结构常被应用在数据库和文件系统的实现上。
一颗m阶的B树定义如下:

  1. 每个结点最多有m-1个关键字。
  2. 根结点最少可以只有1个关键字。
  3. 非根结点至少有Math.ceil(m/2)-1个关键字。
  4. 每个结点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
  5. 所有叶子结点都位于同一层,或者说根结点到每个叶子结点的长度都相同。
  • B-树的插入
  1. 如果该结点的关键字个数没有到达2个,那么直接插入即可;
  2. 如果该结点的关键字个数已经到达了2个,那么根据B树的性质显然无法满足,需要将其进行分裂
    (分裂的规则是该结点分成两半,将中间的关键字进行提升,加入到父亲结点中,但是这又可能存在父亲结点也满员的情况,则不得不向上进行回溯,甚至是要对根结点进行分裂,那么整棵树都加了一层。)
  • B-树的删除
    首先需要明确一点:删除非叶子结点必然会导致不满足B树性质,那么可以这样处理:被删关键字为该结点中第i个关键字key[i],则可从指针son[i]所指的子树中找出最小关键字Y,代替key[i]的位置,然后在叶结点中删去Y。 因此,把在非叶结点删除关键字k的问题就变成了删除叶子结点中的关键字的问题了,那么B树的删除操作就变成了删除叶子结点中的关键字问题了。
    如图,**被删关键字Ki所在结点的关键字数目等于ceil(m/2)-1,则需调整。 **
  • B+树定义

B+树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入,这与二叉树恰好相反。
一个m阶的B树具有如下几个特征:

  1. 根结点至少有两个子女。
  2. 每个中间节点都至少包含ceil(m / 2)个孩子,最多有m个孩子。
  3. 每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m。
  4. 所有的叶子结点都位于同一层。
  5. 每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。

1.6 散列查找。

  • 哈希表(Hash)
    根据设定的哈希函数 H(key)和所选中的处理冲突的方法,将一组关键字映射到一个有限的、地址连续的地址集 (区间) 上,并以关键字在地址集中的“映像”作为相应记录在表中的存储位置,如此构造所得的查找表称之为“哈希表”。
  • 解决哈希冲突
    线性探测法
    从发生冲突位置的下一个位置开始寻找空的散列地址。发生冲突时,线性探测下一个散列地址是:Hi=(H(key)+di)%m,(di=1,2,3...,m-1)。闭散列表长度为m。它实际是按照H(key)+1,H(key)+2,...,m-1,0,1,H(key)-1的顺序探测,一旦探测到空的散列地址,就将关键码记录存入
    例如设一组初始记录关键字集合为(32,29,25,68,43,79),散列表的长度为8,散列函数H(k)=k mod 7
    其中不成功次数就是寻找到空位置时查找的次数

    则,ASL成功=(14+21+3*1)/6=1.5;
    ASL不成功=(1+2+1+6+5+4+3)/7=3.2

2.PTA题目介绍

介绍3题PTA题目

2.1 是否完全二叉搜索树(2分)

是否完全二叉搜索树

2.1.1 伪代码(贴代码,本题0分)

int main()
{
   输入结点数据;
   层次遍历;
   判断是否为完全二叉树;
}
BTree InsertTree(BTree T, int num)//建树
{
  if 树为空
   {
     建新结点;
     输入根结点数据;
     左右子树分别初始化;
   }
  end if
   else if (待插入数据大于根结点值)
     在左子树插入该结点;
  end if
   else if (待插入数据小于根结点值)
     在右子树插入该结点;
  end if
  返回建成后的树;
}

bool IsBST(BTree T)//判断是否完全二叉树
{
    if 树空
        return false;
     引入队列Q;
     将已建成的树的数据入队;
     while  队列不空
     {
        if 左右孩子都存在
          将左右孩子进队;
        end if
        else if  左孩子不存在,右孩子存在
           return false;
        end if
       else 左孩子存在,右孩子不存在,或者左右孩子都不存在
       {  
         if   左孩子存在,右孩子不存在
            将左孩子进队;
         end if
         while  队列不空
         {
              指针指向队头;
                出队;
                if 左孩子不存在或右孩子不存在
                    return false;
                 end if
         }
       }
     }
}
void LeverOrder(BTree T)//层次遍历
{
      引入队列qu;定义指针p;
      if 树为空
         输出NULL;
      end if
      将该树数据入队;
      while  队列不空
     {
         p指向队头;
         输出p指向的值;
         if 左孩子存在
            右左孩子入队;
         end if
         if 左孩子存在
            将右孩子入队;
         end if
         输出空格;//格式要求
     }
}

2.1.2 提交列表

2.1.3 本题知识点

  1. 二叉搜索树的建立:由题面我们可以看到,题面要求我们左子树键值大,右子树键值小,故我们在建立二叉搜索树时,要注意如果待插入结点比当前结点小的话进右子树,比当前结点大进左子树;
  2. 完全二叉树:若一棵二叉树的高度为h,那么1到h-1层的结个数都达到最大,而最后一层结点连续且都集中在最左边。
  3. 完全二叉树的层次遍历特点:完全二叉树在进行层次遍历时,如果遍历到一个结点只有左孩子,或者左右孩子均为空时,那么剩余未遍历的结点,全都是叶子结点。
  4. 二叉树的层次遍历:和之前在树中学习一样,运用队列辅助实现;
  5. 二叉搜索树的插入操作:边查找边插入,算法中的根节点指针要用引用类型,这样才能将改变后的值传回给实参。

2.2 航空公司VIP客户查询(2分)


航空公司VIP客户查询

2.2.1 伪代码(贴代码,本题0分)

定义Hash类型变量H构建哈希链;
定义List类型变量ptr存储查找到的结点;
调用createHash函数构造哈希链H;
for (inti= 0;i <数据个数; i++) do
输入身份证信息和里程数;
if (里程数<最小里程数)
里程数=最小里程数;
end if
调用Insert函数向H插入结点;
end for
for (inti= 0;i <查找次数; i+ +) do
调用findKey函数在H中查找数据,将查找结果赋值给ptr;
if (ptr == NULL) do
查找失败; 
else
查找成功;
end if
end for

2.2.2 提交列表

2.2.3 本题知识点

  1. 运用map容器
  2. 用了scanf和printf不会导致超时

2.3 基于词频的文件相似度(1分)


基于词频的文件相似度

2.3.1 伪代码(贴代码,本题0分)

* 用map<string,bool>的数组来存放文件,每一个数据代表一个文件,每个文件内可以存多个词组,
string用来存单词,bool用来标记存在一个单词,方便后续单词数量的统计。
* same[101][101] 二维数组是相似矩阵,两个下标是所对应的文件的编号,
存的值是两个文件共有的单词数量。
* num[101]用来存放对应下标的文件所含有的词组的个数。
	1.使用 cin.get()一个一个读取字符,直到遇到终止的字符'#'。
	2.使用 toupper()函数将读入的小写字母转化为大写字母。
	3.将输入的连续字母存入s_word数组(要求不超过10个)
	4.中途遇到非字母字符或者超过10个的时候,都直接封尾,并存入map,
	初始化s_word的下标继续读取数据。
数据的处理:
	1.使用了C++中的迭代器,迭代统计每个map文件中的单词数量,并将每两个文件进行
	一一的元素对比,如果数据相同的话,就在相似矩阵的对应位置的值加1.
	2.对角线的元素本质上等于对应文件的单词数,可以对map求size()得到。
根据相似矩阵和长度表来计算相识度并输出:
	用fixed函数和setprecision(1)来控制计算出来的数值的小数位数,并转化成文本形式。

2.3.2 提交列表

2.3.3 本题知识点

  1. 运用map容器下标可以自动排序的特点,对于计算相似度比较方便string s 字符串型变量
  2. 利用getchar()吸收回车
posted @ 2021-06-14 22:16  Lzwx2  阅读(58)  评论(0编辑  收藏  举报