DS博客作业05--查找

| 这个作业属于哪个班级 |


| ---- | ---- | ---- |
| 这个作业的地址 |
| 这个作业的目标 | 学习查找的相关结构 |
| 姓名 | 吴慧敏 |

0.PTA得分截图

查找题目集总得分,请截图,截图中必须有自己名字。题目至少完成总题数的2/3,否则本次作业最高分5分。没有全部做完扣1分。

1.本周学习总结(0-5分)

1.1 查找的性能指标

①ASL(Average Search Length),即平均查找长度,在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。
它的定义是这样的:

(其中n为查找表中元素个数,Pi为查找第i个元素的概率,通常假设每个元素查找概率相同,Pi=1/n,Ci是找到第i个元素的比较次数。)

查找表T:含有n个记录。
ASL成功:成功情况下(概率相等)的平均查找长度ASL成功 是指找到T中任一记录平均需要的关键字比较次数。
例如

ASL不成功:ASL不成功表示没有找到查找表中的元素,平均需要关键字比较次数(假设共有m种查找失败情况,qi为第i中情况的概率)。
显然,ASL是衡量查找算法性能好坏的重要指标。一个查找算法的ASL越大,其时间性能越差;反之,一个查找算法的ASL越小,其时间性能越好。
②比较次数: 查找过程的主要操作是关键字的比较来进行的。
③移动次数:若在查找过程中可以将查找表中不存在的数据元素插入,或者从查找表中删除某个数据元素,动态查找表在查找过程中查找表可能会发生变化且需要发生移动来插入或删除数据。
④时间复杂度及空间复杂度:

1.2静态查找

1.顺序查找:

  • 基本思想:顺序查找是一种最简单的查找方法。它的基本思想是:从表的一端开始,顺序扫描线性表,依次将扫描到的结点关键字和给定值K相比较,若当前扫描到的结点关键字与K相等,则查找成功;若扫描结束后,仍未找到关银字等于K的结点,则查找失败。所以说,Ci(第i个元素的比较次数)在于这个元素在查找表中的位置,如第0号元素就需要比较一次,第一号元素比较2次......第n号元素要比较n+1次。所以Ci=i;所以 :
  • ASL成功:

可以看出,顺序查找方法查找成功的平均 比较次数约为表长的一半。
当待查找元素不在查找表中时,也就是扫描整个表都没有找到,即比较了n次,查找失败,所以:

  • ASL不成功:
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 
 } 

时间复杂度为:O(n)
顺序查找的优缺点:
缺点:查找效率较低,特别是当待查找集合中元素较多时,不推荐使用顺序查找。
优点:算法简单而且使用面广。

2.二分查找:

  • 基本思想:首先待查找表是有序表,这是折半查找的要求。要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。在折半查找中,用二叉树描述查找过程,查找区间中间位置作为根,左子表为左子树,右子表为右子树,,因为这颗树也被成为判定树(decision tree)或比较树(Comparison tree)。查找方式为(找k),先与树根结点进行比较,若k小于根,则转向左子树继续比较,若k大于根,则转向右子树,递归进行上述过程,直到查找成功或查找失败。在n个元素的折半查找判定树中,由于关键字序列是用树构建的,所以查找路径实际为树中从根节点到被查结点的一条路径,因为比较次数刚好为该元素在树中的层数。所以
  • ASL成功:

查找失败,即为从根结点到某个外部结点也没有找到,比较次数为该内部结点的结点数个数之和,所以:

  • 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 
}

//递归法
int  BinarySearch(SeqList R,int low,int hight,KeyType k)
 {
 	int mid;
 	if(low<=high){//查找的区间存在一个以上的元素 
 		mid=(low+high)/2;//找中间位置 
 		if(R[mid].key==k)return mid+1;//查找成功,返回序号mid+1 
 		
 		if(R[mid].key>k)BinarySearch(R,low,miid-1,k);//在[low…mid-1]区间递归寻找 
 		else BinarySearch(R,mid+1,high,k);	//在[mid+1…high]区间递归寻找 	
	 }
	 else return 0;
 }

时间复杂度为:O(logn)
二分查找的优缺点:
优点:折半查找的时间复杂度为O(logn),远远好于顺序查找的O(n)。
缺点:虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费O(nlgn)的时间。
eg.
例:给11个数据元素有序表(2,3,10,15,20,25,28,29,30,35,40)采用折半查找。则ASL成功和不成功分别是多少?

查找成功时总会找到途中某个内部结点,所以成功时的平均查找长度为

即25查找一次,成功,10,30要查找2次,成功,2,15,28,35要查找3次,成功,3,20,29,40要查找4次,成功。不成功的平均查找长度为

因为内部结点都能查找成功,而查找不成功的就是那些空的外部结点,所以到查询到2的左孩子,15的左孩子,28的左孩子,35的左孩子,3的左右孩子,20的左右孩子,29的左右孩子,40的左右孩子时,都是查找不成功的时候。

1.3二叉搜索树

二叉搜索树具有以下性质
若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
它的左右子树也分别为二叉排序树。

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

构建二叉排序树的方法:
对于插入的数据x
1.若当前树为空,则x为其根结点。
2.若x小于当前结点,则插入其左子树。若x大于当前结点,则插入其右子树。
若对二叉排序树进行中序遍历,那么其遍历结果必然是一个递增序列,这也是二叉排序树名字的由来。
eg.
step1:给出一组元素 38 26 62 94 35 50 28 55
step2:把第一个元素作为根节点

step3:把第二个元素拿出来与第一个元素做比较,如果比根节点大就放在右边,如果比根节点小就放在左边。
uploading-image-161853.png
step4:同样道理比较第三个元素62

step5:插入第四个元素94,先与38比较,进入右子树,然后与62比较

step6:按照以上的方式依次把后面的元素插入到树中

  • 二叉搜索树的ASL成功:∑(本层高度本层元素个数)/节点总数
    如上图所示,ASL成功=(1
    1+22+33+42)/8
    二叉搜索树的ASL不成功:∑(本层高度本层补上的叶子个数)/补上的叶子总数

    如图所示,ASL不成功=(2
    1+34+44)/9。

如何在二叉搜索树做插入❓
新插入的结点一定是一个新添加的叶子结点,如下图

如何在二叉搜索树做删除❓
分为如下情况:
1)被删除节点是叶子节点:直接删除该节点,其双亲节点中相应指针域的值改为“空”。

2)被删除节点只有左子树或者只有右子树:用其左子树或者右子树代替它,其双亲节点的相应指针域的值改为“指向被删除节点的左子树或右子树”。

3)被删除的节点既有左子树,也有右子树:
◽以其前驱代替之,然后再删除该前驱节点。前驱是左子树中最大的节点。
◽也可以用其后继代替之,然后再删除该后继节点。后继节点是右子树中最小的节点。

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

1.如何构建、插入、删除及代码。

  • 构建:构建过程就是遍历数组,调用插入函数
    具体代码:
BSTNode *CreatBST(KeyType A[],int n)//返回树根指针
{
       BSTNode *bt=NULL;//初始时bt为空树
       int i=0;
       while(i<n)
       {
             InsertBST(bt,A[i]);
             i++;
       }
       return bt;    //返回建立的二叉排序树的根指针
}

InsertBST函数是递归函数,插入结束后都是返回根节点。

  • 插入:
    思路:
    1、如果我们当前要插入元素的树是一颗空树,那么我们直接将其插入即可。
    2、如果不是空树,那么我们就要考虑元素插入以后的树海是不是一棵二叉搜索树。因此我们插入的方法就是:从根节点开始,与根节点的值相比较,如果比根节点的值小,那么就往根节点的左子树插入;如果比根节点的值大,就往根节点的右子树插入,如果相等,我们这里约定该二叉搜索树中不能有重复的元素所以一旦相等就直接插入失败返回。

在二叉排序树中插入一个关键字为k的新节点,要保证插入后仍满足BST性质。
1)若二叉排序树T为空,则创建一个key域为k的结点;
2)否则将k和根结点的关键字比较,若两者相等,则说明树中已有此关键字k,无需插入,直接返回0;
3)若kKey,则将k插入根结点的左子树中。
4)否则将它插入右子树中。
插入过程:边查找表插入

int InsertBST(BSTree &p,KryType 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);//插入右子树
}
  • 删除:
int DeleteBST(BSTree &bt,KeyType k)
{
	if(bt==NULL)return 0;//空树 
	else{
		if(k<bt->key)return DeleteBST(bt->lchild,k);//在左侧递归寻找删除结点 
		else if(k>bt->key)return DeleteBST(bt->rchild,k);//在左侧递归寻找删除结点
		else{//找到结点进行删除 
			Delete(bt);
			return 1;
		}
	}
 }
//从二叉树排序树中删除结点p 
 void Delete(BSTree &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);//既有左子树又有右子树 
  }
//既有左子树又有右子树的删除 
void Delete1(BSTNode *p,BSTNode *&r)
{
	BSTNode *q;
	if(r->rchild!=NULL)Delete1(p,r->rchild);//递归找最右下节点 
	else{
		p->key=r->key;
		q=r;
		r=r->lchild;
		delete q;
	}
}

时间复杂度:最好:O(logn),最差:O(n)
1.查找要删除的关键字k
2.删除关键字k

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(BSTree &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的左子树节点:
{
      BSTNode *q;
      if(r->rchild!=NULL)
           Delete1(p,r->rchild);//递归找最右下节点
      else//找到了最右下节点*r
      {
           p->key=r->key;//将*r的关键字值赋给*p
           q=r;
           r=r->lchild;
           delete q;
      }
}
/*递归查找才能把节点父子关系保留!!!*/

2.分析代码的时间复杂度

Q:为什么要用递归实现插入、删除?递归优势体现在代码哪里?
A:删除时,用递归查找才能把节点父子关系保留,便于删除和插入顺利找到父亲和孩子;
代码简短,便于书写,树的递归更能掌握。

1.4 AVL树

平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。由于二叉搜索树存在数据分配不平衡的现象,会导致查找时的时间复杂度提高,所以诞生了AVL解决此类问题。
AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树
平衡因子:某结点的左子树与右子树的高度(深度)差即为该结点的平衡因子(BF,Balance Factor)。平衡二叉树上所有结点的平衡因子只可能是 -1,0 或 1。如果某一结点的平衡因子绝对值大于1则说明此树不是平衡二叉树。为了方便计算每一结点的平衡因子我们可以为每个节点赋予height这一属性,表示此节点的高度。

Q1:AVL树解决什么问题,其特点是什么?

  • AVL树用于解决的问题:
  • 特点:AVL树本质上还是一棵二叉搜索树,它的特点是:
    1.本身首先是一棵二叉搜索树。
    2.带有平衡条件:AVL树中任何节点的两个子树的高度最大差别为1。
    也就是说,AVL树,本质上是带了平衡功能的二叉查找树(二叉排序树,二叉搜索树)。

结合一组数组,介绍AVL树的4种调整做法。(AVL树的左右高度差大于一时,会进行以下四种调整)

①LL:LeftLeft,也称“左左”。插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。

②RR:RightRight,也称“右右”。插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

③LR:LeftRight,也称“左右”。插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。

④RL:RightLeft,也称“右左”。插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

说明:AVL树不平衡的情况:
AVL树大部分操作都和BST树相同, 只有在插入删除结点时, 有可能造成AVL树失去平衡, 而且只有那些在被插入/删除结点到根节点的路径上的结点有可能出现失衡, 因为只有那些结点的子树结构发生了变化。当插入新结点导致不平衡时, 我们需要找到距离新节点最近的不平衡结点为轴来转动AVL树来达到平衡
eg.输入关键字序列{16,3,7,11,9,26,18,14,15},构造一颗AVL树。 有调整要写成调整类型及结果

AVL树的高度h和树的总节点数n的关系?

h≈log2N(h)+1

介绍基于AVL树结构实现的STL容器map的特点、用法。

map对象是模板类,需要关键字和存储对象两个模板参数:
std:map<int,string> personnel;
这样就定义了一个用int作为索引,并拥有相关联的指向string的指针。为了使用方便,可以对模板类进行一下类型定义,
typedef map<int,CString> mapc
mapc enumMap;

🔸map的基本操作函数:

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的函数
  • 用途:
    map的内部是红黑树实现,其具有自动排序的功能
    通过键值排序,以递增的方式排序
    若键值为整形,以数值递增排序
    若键值为string,以字符串升序排序

1.5 B-树和B+树

B-树和AVL树区别,其要解决什么问题?

AVL树:树都是内存中,适用小数据量。每个节点放在一个关键字,树的高度比较大。
B-树又称为多路平衡查找树,一个节点可以存放多个关键字,降低树的高度。可外存放,适合大数据量查找。

B-树定义。

一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
(1)每个结点至多有m个子结点;
(2)除根结点和叶结点外,其它每个结点至少有ceil(m/2)个子结点;
(3)根结点至少有两个子结点;(唯一例外的是根结点就是叶子结点)
(4)所有的叶结点在同一层;
(5)有k个子结点的非根结点恰好包含k-1个关键码,关键码按照递增次序进行排列。
注:ceil代表向上取整

结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况:

插入:

以一个3阶的B树为例:
(1)如果该结点的关键字个数没有到达2个,那么直接插入即可;
(2)如果该结点的关键字个数已经到达了2个,那么根据B树的性质显然无法满足,需要将其进行分裂
分裂的规则:该结点分成两半,将中间的关键字进行提升,加入到父亲结点中,但是这又可能存在父亲结点也满员的情况,则不得不向上进行回溯,甚至是要对根结点进行分裂,那么整棵树都加了一层。
eg1.5阶的B树:

eg2.

m阶B+树定义

①每个分支至多有m棵子树
②根节点没有子树或者至少有两颗
③除了根节点其他节点至少有m/2向上取整个子树
④有n棵子树的节点有n个关键字
⑤叶子节点包含全部关键字,以及只想相应记录的指针,而叶子节点按关键字大小顺序链接( 每个叶子节点可以看成是一个基本索引块,它的指针指向数据文件中的记录)
⑥所有分支节点(可以看成是索引的索引)中仅仅包含他的各个子节点中最大关键字以及指向子结点的指针

B+树主要解决问题

在索引文件组织中,经常使用B+树,B+树是大型索引文件的标准组织方式。

1.6 散列查找。

  • 哈希表的基本概念
    哈希表(Hash Table)又称散列表,是除顺序表存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构。
    储存地址=h(key);
    注意:哈希表是一种储存结构,它并非适合任何情况,主要适合记录的关键字和存储地址存在某种函数关系的数据。
  • 哈希表的几个概念
    ①哈希函数和哈希地址:
    哈希函数h(key):把关键字为ki的对象存放在相应的哈希地址中;
    哈希表:存储数据记录的长度为m(m>=n)的连续存储单元(n为对象个数)。
    ②哈希冲突:对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)
    (注:在哈希表存储结构的存储中,哈希冲突是很难避免的!!!)
  • 哈希表结构体
#dafine MaxSize 100
#define NULLKEY -1
#define DELKEY -2
typedef char *InfoType;
typedef struct{
        int key;//关键字域
        InfoType data;//其他数据域
        int count;//探测次数
}HashTable[MaxSize];
  • 哈希表插入及建表伪代码:
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++;
}
  • 哈希表查找算法
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;
}
  • 哈希表删除算法(查找删除元素,找到的话,不能真正删除,做个删除标记DELKEY。)
int DeleteHY(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;
}

1.哈希表的设计主要涉及哪几个内容?

哈希表设计主要需要解决哈希冲突。实际中哈希冲突是很难避免的,主要与3个因素有关。
哈希表长度
①与装填因子有关;
②装填因子α=存储的记录个数/哈希表的大小=n/m;
③α越小,冲突可能性就越小;α越大(最大可取1),冲突的可能性就越大。控制在0.6~0.9的范围内。
与所采用的哈希函数有关。
与解决冲突方法有关。

2.哈希函数的构造方法

1.直接定址法:

⬜直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。
⬜直接定址法的哈希函数h(k)为:
h(k)=k+c
❗优点:计算简单,并且不可能有冲突发生;
⭕缺点:关键字分布不连续将造成内存单元的大量浪费。

2.除余留数法

⬜除余留数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
⬜哈希函数h(k)为:
h(k)=k mod p(mod为求余运算,p<=m)
p最好是指数(素数)。
🔺综上所述,p的最佳取值为小于等于m的素数的最大值。

3.数字分析法:

关键字取最后两位作为哈希地址。
主要是把大范围的关键字转化为小范围的。
例如:2378565,我们就将最后两位65作为哈希地址。
适合于所有关键字都已知的情况,并需要对关键字中的每一位的取值分布情况进行分析。

5.哈希冲突的解决方法

1.开放定址法:

开放定址法:冲突时找一个新的空闲的哈希地址

  • 开放定址法哈希表查找k过程:
d=h(k);
while(ha[d]!=空&&ha[d]!=k)
      d=采用某种探查法求出下一地址;
if(ha[d]==空)
      return 失败标记;
else
      return ha[d];

怎么找空闲单元❓❓

  • 线性探查法(容易出现堆积)
    线性探查法的数学描述公式为:
    非同义词冲突:哈希函数值不同的两个记录争夺同一个后继哈希地址➡堆积(或聚集)现象
    eg.
    Q:
    将关键字序列(7、8、30、11、18、9、14)散列存储到散列表中。
    散列表的存储空间是一个下标从0开始的一维数组,散列函数为:
    H(key) = (keyx3) MOD 7,处理冲突采用线性探测再散列法,要求装填(载)因子为0.7。
    (1) 请画出所构造的散列表。
    (2) 分别计算等概率情况下查找成功和查找不成功的平均查找长度。

Ans:
首先,根据题意,可以确定哈希表的长度为 L = 7/0.7 = 10;因此此题需要构建的哈希表是下标为0~9的一维数组。根据散列函数可以得到如下散列函数值表。

(表1)
采用线性探测法处理冲突,所构造的散列表为:

(表2)
下面对散列表的构造方式加以说明,注意表1中的关键字7和14,30和9, 11和18,这三组关键子的H(Key)值相同,这在构建散列表时就会产生冲突,因为他们的地址相同,所以要通过一定的冲突处理方法来解决这个问题。依题,采用线性探测再散列法处理冲突。下面详细介绍如何构建散列表:

   第一个key 7,它的地址是0,因此放到散列表的数组下表为0的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

   第二个key 8,它的地址是3,因此放到散列表的数组下表为3的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

   第三个key 30,它的地址是6,因此放到散列表的数组下表为6的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

   第四个key 11,它的地址是5,因此放到散列表的数组下表为5的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

   第五个key 18,它的地址是5,因此放到散列表的数组下表为5的位置,但这个位置上已经有关键字11,遇到了冲突,此时我们根据线性探测再散列法来处理这个冲突,探测下一个位置6, 6这个位置上已经存在关键字30则继续增加步长1,因此现在的新地址应为7,位置7上没有关键字,放入即可,到此冲突已经解决;

   第六个key 9,它的地址是6,因此放到散列表的数组下表为6的位置,但这个位置上已经有关键字30,遇到了冲突,探测下一个位置7, 7这个位置上已经存在关键字18则继续增加步长1,因此现在的新地址应为8,位置8上没有关键字,放入即可;   

   第七个key 14,它的地址是0,因此放到散列表的数组下表为0的位置,但这个位置上已经有关键字7,遇到了冲突,探测下一个位置1, 位置1上没有关键字,放入即可;   

   到这一步所有关键字均已填入,散列表已经构造完成,如第表2所示。
  • ⅰ ASL成功的计算:
    这一问可以根据第一问的构造过程求解:
    key7一次就填入了表中,因此查找次数为1,同理8, 30, 11查找次数均为1; key18 进行了3次放入操作,探测位置分别是5,6,7 ,因此查找次数为3;key9也是3次;key14 进行了两次探测,因此查找次数为2。次数表如下表所示

    (表3)
    所以ASL成功= (1+1+1+1+3+3+2)/ 7 = 12/7;

  • ⅱ ASL不成功的计算
    接下来讨论不成功的情况, 看表2,计算查找不成功的次数就直接找关键字到第一个地址上关键字为空的距离即可, 但根据哈希函数地址为MOD7,因此初始只可能在06的位置。等概率情况下,查找06位置查找失败的查找次数为:
    地址0,到第一个关键字为空的地址2的距离为3,因此查找不成功的次数为3;
    地址1,到第一个关键为空的地址2的距离为2,因此查找不成功的次数为2;
    地址2,到第一个关键为空的地址2的距离为1,因此查找不成功的次数为1;
    地址3,到第一个关键为空的地址4的距离为2,因此查找不成功的次数为2;
    地址4,到第一个关键为空的地址4的距离为1,因此查找不成功的次数为1;
    地址5,到第一个关键为空的地址9的距离为5,因此查找不成功的次数为5;
    地址6,到第一个关键为空的地址9的距离为4,因此查找不成功的次数为4;
    因此查找不成功的次数表如下表所示

    (表4)
    所以ASL不成功= (3+2+1+2+1+5+4)/ 7 = 18/7。

2.拉链法

把所有的同义词用单链表连接起来。
eg.{25,10,8,27,32,68}(链表采用头插法)

ASL成功:1∗5+2∗16=1.17
ASL不成功:0+1+0+1+2+1+1+07=0.86

2.PTA题目介绍(0--5分)

2.1 题目1:是否完全二叉搜索树(2分)

思路:易知,完全二叉树的特点是只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现,而不完全二叉树的空结点位置提前了。所以应该从该空结点的位置入手,对结点个数进行计数,最后再将计数的结果和元素总数进行比较,相等证明为完全二叉树。

2.1.1 伪代码

/*建立平衡二叉树*/
BSTree InsertBSTree(BSTTree bst, int num)
{
	if (结点为空)
	{
		新建结并且初始化;
	}
	else if( 结点数值> bst->data)
	{
		递归进入右循环;
	}
        else if(结点数值< bst->data)
	{
		递归进入左循环;
	}
}
/*判断是否为完全二叉树(即判断是否所有结点都已经遍历)*/
bool LevelBSTree(BSTree bst)
{
   定义一个存储树结点的队列q
   if (树空)
   {
      也是完全二叉树
      返回true
   }
   把树的根节点放入队列
   while (队列不空)
       p=队首元素;
       弹出队首元素;
       输出p保存的队首元素
       if(有左孩子)
            让左子树进栈
       if(有右孩子)
            让右子树进栈
}
/*判断是否为完全二叉树(即判断是否所有结点都已经遍历)*/
 int IsBSTee(BSTree bst)
{
	输入正整数个数n
	int flag = 0
	定义一个存储树结点的队列q
	定义count = 0来统计节点个数
	if (树为空)
		输出NULL;
	进队列
	while (队列不空)    //直至空队列,结束循环
	{
		if (遇到空结点)      //队列读取到空结点
			flag = 1;      //修改 flag 表示接下来不再统计结点数
		else (队列头结点非空)      
		{
			if (flag == 0)
				count++;//统计结点数
			左结点入队列
			右结点入队列
		}
		队列头出队列
	}
	返回记录的节点个数   
}

2.1.2 提交列表

2.1.3 本题知识点

①完全二叉树:
完全二叉树是特殊形态的满二叉树,在满二叉树的基础上,它的叶子结点可以不全满,但只允许最后一层有空缺结点且空缺在右边,即叶子结点只能在层次最大的两层上出现。
②层次遍历:
借助队列来储存二叉树每一层的元素并依次输出。
③借助flag来计算节点个数。
④建立二叉排序树的知识点。

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

2.2.1 伪代码

#include<iostream>
#include<string>
#include<map>
#include<cstdio>

map<string, int>M;
int mian()
{
     for (i=0;i<m;i++)
     {
         输入身份证,已行驶里程数据
         if (里程小于最小里程k)
              里程=k;
         if (该身份证已由会员记录)
              增加里程数,用原来的里程数再加上刚输入的里程数;
         else (未被登记为会员)
              里程数为刚输入的里程数
     }
     for (i=0;i<m;i++)
     {
           输入身份证
           if(身份证存在)
                输出该身份证号;
           else(身份证不存在)
                输出No Info;
     }
}

2.2.2 提交列表

2.2.3 本题知识点

①map库的使用
②cin cout与scanf printf消耗时间不同
③运用了count函数

2.3 题目3:基于词频的文件相似度(1分)

本题设计一个倒排索引表结构实现(参考课件)。单词作为关键字。本题可结合多个stl容器编程实现,如map容器做关键字保存。每个单词对应的文档列表可以结合vector容器、list容器实现。

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

int main()
{
       getchar();//吸收回车
       for( i=1 ; i<=N i++)
       {
           while(不为#) 
           {
           top=0;
           for( j=0;j<s.length() ;j++)
           {
                if( 内容不是字母)
                {
                      if(单词小于3 )
                      {
                            不记录
                            continue;
                       }
                     if( 单词长度大于10 )
                            只记录前10
                     else 
                     {
                            加结束符 
                     }   
                     单词数+1;
                     top归零    
                }            
                else(是字母)
                {
                   把所有大写改小写  
                }
          }
                /*需要对组后一个单词判断*/
                if(top<3)
                {
		      不记录
	              continue;
                }
                if (top > 10)
                      给t[10]的位置加结束符 ;
		else 
                      给t[top]的位置加结束符
	        单词数+1;
		top = 0;
     }
     /*计算相似度*/
     for( i=0; i<M ;i++)
     {
          输入两个单词 x y;
          定义两个迭代器it1,it2,类似指针;
          用for 循环队两个文件进行遍历
          {
               if (it1字母小了)
                     向后移动
               else if( it2字母小了)
                     向后移动
               else 相同时
                     同时移动   
          }
     }
}

2.3.2 提交列表

2.3.3 本题知识点

①map函数的使用
②getchar()吸收回车
③定义了两个迭代器,类似指针
map<string,int>::iterator it1,it2;
④把大写字母转换为小写字母
t[top++] = (s[j] >= 'a' ? s[j] : s[j] + 32);

posted @ 2021-06-14 16:52  蓝胖子WHM  阅读(59)  评论(0编辑  收藏  举报