DS博客作业05--查找

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

0.PTA得分截图

1.本周学习总结

1.1 查找的性能指标

在查找运算中时间主要花费在关键字的比较上,把平均需要和定值k进行比较的关键字次数称为平均查找长度(ASL),定义如下图所示

ASL是衡量查找算法性能好坏的重要指标。一个查找算法的ASL越大,其时间性能越差;反之,一个查找算法的ASL越小,其时间性能越好。

1.2 静态查找

1.2.1 顺序查找

从表的一端开始,逐个扫描,将扫描到的关键字和给定的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;//找到后返回逻辑序号
}

查找成功:
从表的一端开始查找,按顺序扫描,每个数据的查找次数依次递增1,且每个位置上数据出现的概率都相等,所以第i个数据的查找次数ci为i

查找不成功:
若要查找一个不存在于顺序表中的数据时,一定要遍历完整个顺序表之后才能确定改数据不存在于顺序表中
ASL不成功=n

1.2.2 折半查找

设n个元素的数组a已经有序,用low和high两个变量来表示查找的区间,即在a[low]~a[high]中去查找用户输入的值x,和区间中间位置的元素a[mid] (mid=(low+high)/2)作比较,如果相等则找到,算法结束;如果是大于则修改区间(数组为升序数列:low=mid+1;数组为降序数列:high=mid-1);如果是小于也修改区间(数组为升序的改为:high=mid-1,;数组为降序数列:low=mid+1);一直到x=a[mid] (找到该值在数组中的位置)或者是low>high(在数组中找不到x的值)时算法终止;

int BinSearch(RecTypr R[], int n, KeyType k)
{
	int low = 0, high = n - 1, mid;
	while (low <= high)
	{
		mid = (low + high) / 2;
		if (k == R[mid].key)
			return mid + 1;
		if (k < R[mid].key)
			high = mid - 1;
		else
			low = mid + 1;
	}
	return 0;
}


假设概率都相等的情况下:ASL=1/11(11+2×2+4×3+4*4 )=33/11=3
查找成功时比较次数:为该结点在判定树上的层次数,不超过树的深度 d =  log2 n  + 1
查找不成功的过程就是走了一条从根结点到外部结点的路径d或d-1。
查找过程:每次将待查记录所在区间缩小一半,比顺序查找效率高,时间复杂度O(log2 n)

1.3 二叉排序树(二叉搜索树)

二叉排序树又称二叉搜索树。在一棵二叉排序树中,若根节点的左子树不为空,那么左子树上所有的结点都小于根结点;若根节点的右子树不为空,那么右子树上所有结点都小于根节点。且根结点的左右子树叶都是二叉排序树。二叉排序树中所有的关键字都是唯一的。二叉树还有特殊的性质:按中序遍历可以得到一个递增有序序列。
结构体定义:

typedef struct node
{
   KeyType key;//关键字;
   InfoType data;//其他数据域;
   struct node*lchild,*rchild;//左右孩子指针;
}BSTNode,*BSTree;

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

插入
在二叉排序树中插入一个关键字为k的结点要保证插入后仍然满足BST性质,其插入过程为:
若二叉排序树bt为空,则创建一个key域作为k的结点,将它作为根结点;
若不为空,则与根节点的关键字作比较
若相等,则说明树中已经存在该关键字,无需插入,直接返回;
若比根节点的值小,就进入根节点的左子树中,否则进入右子树;
该结点插入后一定为叶子结点

删除
情况1:p节点为叶子结点,直接删去该结点,双亲节点相应指针域修改为NULL;
情况2:p结点只有左/右子树,有其左子树或者右子树代替他,双亲节点相应的指针域修改;
情况3:p结点既有左子树也有右子树。根据二叉排序树的特点,可以从其左子树中选择关键字最大的结点r(最右结点),用结点r的值代替结点p的值(结点值替换),保存r结点的左孩子,再删去r结点。也可以从右子树中选择关键字最小的结点l(最左结点),用结点l的值代替结点p的值(结点替换),保存l结点的右孩子,再删去结点l。



查找
递归查找,查找思路和普通二叉树不同,因为二叉搜索树的左子树中所有值都比根节点小,右子树中所有值都比根节点大,根据这一特点,我们可以将待查找数据和当前的根节点进行比较,如果待查找数据比较小,就有选择的进入左子树进行查找,不用像之前普通二叉树的查找一样,不仅要进左子树还要进右子树中查找。

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

插入

bool InsertBST(BSTNode*& bt, KeyType k)
{
	if (bt == NULL)
	{
		bt = new BSTNode;
		bt->key = k;
		bt->lchid = 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 deletek(BSTNode*& bt, KeyType k)
{
	if (bt != NULL)
	{
		if (k == bt->key)
		{
			delete(bt);
			return true;
		}
		else if (k < bt->key)
			delete(bt->lchild, k);
		else
			delete(bt->rchild, k);
	}
}

查找

BSTNode* SearchNode(BSTree bt, KeyType k)
{
	if (bt == NULL || bt->key == k)
		return bt;
	else if (bt->key < k)
		return SearchNode(bt->rchild, k);
	else
		return SearchNode(bt->child, k);
}

1.4 AVL树

二叉排序树中查找的操作执行时间与树形有关,在最坏情况下执行的时间为O(n)(单边树时)。为了提高查找效率,在既能保持BST性质,又能保证树的高度在任何情况下为log2,这样在查找过程中,即使是最坏情况下,执行的时间也还是O(log2),这样的二叉树称为平衡二叉树。
一棵平衡二叉树中,每个结点的左,右子树高度最多相差1。在算法中,我们通过平衡因子来具体实现平衡二叉树的定义。一个结点的平衡因子是该结点左子树的高度减去右子树的高度(或者右子树的高度减去左子树的高度)。如果一棵二叉树中的所有结点平衡因子的绝对值都不超过1,那么这颗二叉树就是平衡二叉树
平衡二叉树中插入结点的过程
若向平衡二叉树中插入一个新结点(总是作为叶子结点插入)后破坏了平衡性,首先从该新插入的结向根结点方向查找第一个失衡的结点,然后进行调整。
调整的操作可以归纳为下列四种情况:

  • 1.LL型

  • 2.RR型

  • 3.LR型

  • 4.RL型

向AVL树中插入元素可能会导致树失去平衡。但是,我们只需要调整插入点至根节点的路径上不满足平衡状态的各节点中深度最大的那个即可。假设该最深节点为X。导致X失去平衡可能有四种情况:
(1)插入点位于X的左子结点的左子树-左左(LL)。
(2)插入点位于X的左子结点的右子树-左右(LR)。
(3)插入点位于X的右子结点的左子树-右左(RL)。
(4)插入点位于X的右子结点的左子树-右右(RR)。

1.5 B-树和B+树

B-树

B-树一个节点可以放多个关键字,降低树的高度。可放外存,适合大数据量查找,一棵m阶B-树符合以下特性:
1.每个结点至多m个孩子结点,至多有m-1个关键字,除根节点外,其他节点至少有m/2个孩子结点,至少有m/2-1个关键字;
2.若根节点不是叶子结点,根节点至少两个孩子结点;
3.叶子结点一定都在最后一层,叶子结点下面还有一层是虚设的外部结点————就是表示查找失败的结点,不含任何信息,在计算B-树高度时要计入最底层的外部结点;
结构体定义:

#define MAXM 10
typedef node* BNode;
typedef struct node
{
	int keynum;
	int key[MAXM];
	BNode parent;
	BNode ptr[MAXM];
}BTNode;
int m;
int MAX;
int MIN;

B-树的查找

在一棵B-树上顺序查找关键字,将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[i],则沿着指针ptr[i]所指的子树进程查找;
5.查找某个结点,若相应指针为空,落入一个外部结点,表示查找失败;类似判断树和二叉排序树的查找。

B-树的插入

往B-树中插入一个结点,插入位置必定在叶子结点层,但是因为B-树中对关键字个数有限制,所以,插入情况分为以下两种:
1.关键字个数n<m-1,不用调整
2.关键字个数n=m-1,进行分裂;如果分裂完之后,中间关键字进入的结点中关键字个数达到n=m-1,就继续进行分裂

B-树的删除

非叶子结点删除
1.从Pi子树节点借调最大或者最小关键字key代替删除结点;
2.pi子树中删除key;
3.若子树节点关键字个数 < m/2-1,重复步骤1;
4.若删除关键字为叶子结点层,按叶子结点删除操作。
叶子结点删除
情况1:结点b关键字个数 > m/2-1, 直接删除;

情况2:结点b关键字个数 = m/2-1, 兄弟结点关键字个数大于 m/2-1,于是我们可以向兄弟结点借一个关键字;

  • 步骤1:兄弟结点最小关键字上移至双亲;
  • 步骤2:双亲节点大于删除关键字的关键字下移至删除结点。

情况3:结点b关键字个数 = m/2-1,兄弟结点关键字个数 = m/2-1,兄弟结点没有关键字可以借

  • 步骤1:删除关键字
  • 步骤2:兄弟结点及删除结点,双亲节点中分割二者的关键字合并成一个新的叶子结点
  • 步骤3:若双亲节点的关键字个数 < m/2-1 ,则进行步骤2

B+树

一棵m阶b+树满足条件:
1.每个分支节点至多有m棵子树;
2.根结点或者没有子树,或者至少两棵子树;
3.除根结点,其他每个分支结点至少有m/2棵子树;
4.有n棵子树的结点有n个关键字;
5.所有叶子结点包含全部关键字及指向相应记录的指针;

  • 叶子结点按关键字大小顺序链接;
  • 叶子结点时直接指向数据文件的记录;
    6.所有分支结点(可以看成是分块索引的索引表),包含子节点最大关键字及指向子节点的指针;
    B+树的查找
    因为所有的叶子结点链接起来成为一个线性链表,可直接总最小关键字开始进行顺序查找所有叶子节点;

B-树和B+树的区别

这都是由于B+树和B-具有这不同的存储结构所造成的区别,以一个m阶树为例。
1.关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B-树虽然也有m个子结点,但是其只拥有m-1个关键字。
2.存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B-树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。
3.分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。
4.查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。

1.6 散列查找

哈希表又称为散列表,是除顺序表存储结构,链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构,其基本思路是,设要存储的元素个数为n,设置一个长度为m的连续内存单元,以每个元素的关键字ki(i取值为0~n-1)为自变量,通过一个称为哈希函数的函数h(ki),把ki映射为内存单元的地址(或下标),并把该元素存储在这个内存单元中,这个地址也称为哈希地址。这样构造的线性存储结构就是哈希表。
在建哈希表示可能存在两个关键字虽然不相同,但是其哈希地址却一样的情况,这种现象叫做哈希冲突。通常把具有不同关键字却有相同哈希地址的元素称为同义词。

哈希表构造

直接定址法
直接定址法是以关键字k本身或关键字加上某个数值常量C作为哈希链地址的方法,哈希函数为:h(k)=k+c
优势:计算简单,不可能有冲突发生;
缺点:关键字分布不连续,将造成内存单元的大量浪费。
除留取余数法
用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址,h(k)=k%p(这里的p最好为不大于m的质数,减少冲突的可能性)
优势:计算简单,适用范围广泛;
缺点:容易发生哈希冲突。如果在上面的例子中插入14:h(14)=14%7=0 于7发生了哈希冲突。

哈希冲突的解决

开放定址法
在出现哈希冲突时,再哈希表中找到一个新的空闲位置存放元素,例如:要存放关键字k,d=h(k),而地址d已经被其他元素占用了,那么就在d地址附近寻找空闲位置进行填入。开放定址法分为线性探测和平方探测等
线性探测
线性探测是从发生冲突的地址d0开始,依次探测d0的下一个地址(当到达哈希表表尾时,下一个探测地址为表首地址0),直到找到一个空闲的位置单元为止,探测序列为:d=(d+i)%m,所以,我们在设置哈希表的长度时一定要大等于元素个数,保证每个数据都能找到一个空闲单元插入

优势:解决冲突简单;
缺点:容易产生堆积问题。
拉链法
拉链法是把所有的同义词用单链表链接起来的方法,所有哈希地址为i元素对应的结点构成一个单链表,哈希表地址空间为0~m-1,地址为i的单元是一个指向对应单链表的头结点。这种方法中,哈希表的每个单元中存放的不再是元素本身,而是相应同一词单链表的首结点指针。

优点:

  • 处理冲突简单,无堆积现象,非同义词不会发生冲突;
  • 结点空间时动态申请的,空间利用效率高;
  • 在链表中,删除结点操作方便。

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容器且对应类型都是long long的名称为vip
声明一个迭代器ster
定义整型变量IDNum存省份证号
定义字符型变量tail存身份证最后一位
定义distance存里程数
输入n,k//n代表n组数,k代表最低里程
while n-- do
    输入IDNum,tail,distance
    若tail为x则将IDNum置为负数
    iter为IDNum在map中的位置
    if 此IDNum不在vip中 do
        若distance小于最低里程k,则置为k
        将IDNum和distance的对应存入vip中
    else do    //IDNum存在
        若distance小于最低里程k,则置为k
        在该用户拥有的里程数基础上加上distance
    end if
end while
输入m
while m-- do//查找身份证所包含的里程数
    输入IDNum和tail
    若tail为x则将IDNum置为负数
    找IDNum在vip中的位置
    找到输出里程数,找不到输出No Info
end while

2.2.2 提交列表

2.2.3 本题知识点

2.3 基于词频的文件相似度

2.3.1 伪代码

int main()
{
   定义一个文件列表list保存所有文件;
   初始化文件列表;
   for i=0 to n
      提取每个文件的内容;
   end for
   for i=0 to m
      比较两个文件的单词数量大小,单词数量小的作为模板,计算两文件的重复率;
      输出结果;
   end for
}

void InserFile(File& F,HashNode* word)//根据单词的首字母插入文件的相对应位置
{
   计算该单词应该插入的位置:ard = word->key[0]-'A';
   遍历单链F.content[ard]上的单词
      若有重复内容 return;
   若遍历完毕没有找到重复内容
      头插法插入,并使该文件的单词数量+1;
}

void GetFile(FileTable& list, int number)//获取每个文件内容,且将每个小写字母都转换为大写
{
    while(1)
       若str为'#' ,说明已经到文件内容末尾,break;
       while(未遍历到字符串str末尾)
           开辟新空间,用于保存新单词word;
           while(为字母)
              if 单词长度小于10,可以继续加入字母
                 若为小写字母:word->key += (str[i++]-'a'+'A');
                 若为大写字母:word->key += str[i++];
              else 
                 直接舍弃,i++;
           end while
           若单词长度小于3,则为不合法单词,舍弃:delete word;
           i++;//跳过非字母字符;
       end while
    end while
}
double File_Similarity(File*file1,File*file2)//计算两文件的重复率
{
   定义count保存模板file1的单词个数
   定义same记录两文件重复的单词数量,all保存两文件总的单词个数;
   
   for i=0 to 26 //遍历模板的哈希表
      p=file1->countent[i].next;//提取其中一个单链表遍历
      while(p不为空)
          count--;//表示模板中的有一个单词已经遍历过
          遍历另一个文件中相对应的位置总寻找是否存在和p相同的单词
              若存在 same++;
          p=p->next;//遍历单链中下一个单词;
      end while
      if  count==0 
         表示模板文件中所有单词都遍历完 ,提前退出对模板的遍历,break;
      end if
   end for
}

2.3.2 提交列表

2.3.3 本题知识点

  • 拉链法构造哈希表
  • 头插法插入链表
posted @ 2021-06-14 22:59  箱推人  阅读(72)  评论(0编辑  收藏  举报