代码改变世界

DS博客作业05--查找

2021-06-14 20:19  米奈希尔。  阅读(63)  评论(1编辑  收藏  举报

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

0.PTA得分截图

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

1.啊啊

1.1 查找的性能指标

1.啊啊

ASL成功、不成功,比较次数,移动次数、时间复杂度

  • ASL是衡量查找算法性能好坏的重要指标。一个查找算法的ASL越大,其时间性能越差;反之,一个查找算法的ASL越小,其时间性能越好。
    • ASL成功:表示成功查找到查找表中的元素,平均需要关键字比较次数
    • ASL不成功:表示没有找到查找表中的元素,平均需要关键字比较次数
    • 计较次数:就是查找一个元素在表中的位置,直到确认成功或失败结果这一过程的比较元素个数
    • 移动次数:就是处理冲突时放置一个元素直到位置唯一的移动次数

1.2 静态查找

分析静态查找几种算法包括:顺序查找、二分查找的成功ASL和不成功ASL。

1.21顺序查找

即从左往右顺着查找关键字,成功返回元素序号+1,失败返回0.

int SeqSearch(SeqList R,int n,KeyType k)
{  
   int i=0;

   while (i<n && R[i].key!=k)//从表头往后找
	i++;

   if (i>=n)//未找到返回0
	return 0;
   else 
	return i+1;//找到返回逻辑序号i+1
}

若k在表中,顺序查找方法查找成功的平均比较次数约为表长的一半:

  • 若k值不在表中,则必须进行n次比较之后才能确定查找失败
1.21二分查找

折半查找又称二分查找,是一种效率较高的查找方法。但是,折半查找要求线性表是有序表,即表中的元素按关键字有序。

  • 基本思路
    • 设R[low...high]是当前的查找区间,首先确定该区间的中点位置mid=⌊(low+ high)/2⌋, 然后将待查的k值与R[mid].key比较:
    • (1)若k=R[mid].key,则查找成功并返回该元素的逻辑序号。
    • (2)若k<R[mid].key,则由表的有序性可知R[mid...high].key均大于k,因此若表中存在关键字等于k的元素,则该元素必定是在位置mid左边的子表R[low...mid-1]中,故新的查找区间是左子表R[low...mid-1]。
    • (3)若k> R[mid].key,则关键字为k的元素必在mid的右子表R[mid+ 1...high]中,即新的查找区间是右子表R[mid+1...high]。下一次查找是针对新的查找区间进行的。
int BinSearch(RecType R[],int n,KeyType k)
{ 
       int low=0,high=n-1,mid;

       while (low<=high)//当前区间存在元素时循环
       {	
         mid=(low+high)/2;

	 if (R[mid].key==k)//查找成功返回其逻辑序号mid+1
	      return mid+1;

	 if (k<R[mid].key)//继续在R[low...mid-1]中查找
	      high=mid-1;
	 else
	      low=mid+1;//继续在R[mid+1...high]中查找
       }

      return 0;//未找到时返回0(查找失败)
}

查找成功:

查找失败:

1.3 二叉搜索树

1.3.1 如何构建二叉搜索树(操作)
结合一组数据介绍构建过程,及二叉搜索树的ASL成功和不成功的计算方法。

已数据(5,2,1,6,7,4,8,3,9)为例


如何在二叉搜索树做插入、删除。

  • 删除
    分为如下情况:
    1)被删除的节点是叶子节点。

2)被删除的节点只有左子树或者只有右子树,用其左子树或者右子树代替它

3)被删除的节点既有左子树,也有右子树,可以从其左子树中选最大的结点代替它,或者时从右子树中选最小的结点代替它

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

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

构建代码:

BSTree CreatBST(KeyType a[],int n)//返回树根指针
{      
       BSTree bt=NULL;//初始时bt为空树
       int i=0;

       while (i<n) 
       {    
          InsertBST(bt,a[i]);//将a[i]插入二叉排序树T中
         i++;
       }

       return bt;//返回建立的二叉排序树的根指针
} 

在二叉排序树中插入一个关键字为k的结点要保证插入后仍满足BST性质。其插入过程是:若二叉排序树bt为空,则创建一个key域为
k的结点,将它作为根结点﹔否则将k和根结点的关键字比较,若两者相等﹐则说明树中已有此关键字k ,无须插人,直接返回假;
若kkey,则将k插入根结点的左子树中,否则将它插入右子树中。对应的递归算法InsertBST()如下:

int InsertBST(BSTree &p,KeyType k)	
{  
   if (p==NULL)//原树为空   
   {      
      p=new BSTNode;
      p->key=k;
      p->lchild=p->rchild=NULL;
      return 1;
   }
   else if  (k==p->key) //相同关键字的节点0
      return 0;
   else if (k<p->key) 
      return InsertBST(p->lchild,k);//插入到左子树
   else  
      return InsertBST(p->rchild,k);//插入到右子树
 }

删除代码:

BinTree Delete( BinTree BST, ElementType X )
{
    Position temp;
    if(!BST)                                            //如果最终树为空,说明没有
        printf("Not Found\n");
    else
    {
        if(X < BST->Data)                               //从左子树递归删除
            BST->Left = Delete(BST->Left,X);
        else if(X > BST->Data)                          //从右子树递归删除
            BST->Right = Delete(BST->Right,X);
        else                                            //如果当前BST就是要删除的节点
        {
              if(BST->Left && BST->Right)               //要被删除的节点有左右两个孩子,就从右子树中找最小的数替换删除的节点
              {
                temp = FindMin(BST->Right);             //找最小
                BST->Data = temp->Data;                 //替换删除的节点
                BST->Right = Delete(BST->Right,temp->Data);//删除拿来替换的那个节点
              }
              else
              {
                  temp = BST;
                  if(!BST->Left)                        //只有右节点
                      BST = BST->Right;                 //直接赋值就可以
                  else if(!BST->Right)                  //只有左节点
                      BST = BST->Left;                  //直接赋值就可以
                  free(temp);                   
              }
        }
    }
    return BST;                                         //返回删除后的二叉树
}
2.分析代码的时间复杂度

如果二叉排序树是平衡的,则n个节点的二叉排序树的高度为,其查找效率为,近似于折半查找。如果二叉排序树完全不
平衡,则其深度可达到n,查找效率为O(n),退化为顺序查找。一般的,二叉排序树的查找性能在到O(n)之间。因此,
为了获得较好的查找性能,就要构造一棵平衡的二叉排序树。

3.为什么要用递归实现插入、删除?递归优势体现在代码哪里?

时间复杂度低,便于保留父子关系,删除和插入,找到父亲和孩子节点。

1.4 AVL树

  • AVL树的特点是:“AVL树中任何结点的两个子树的高度最大差别为1”
1.41 AVL树的4种调整做法。

LL型

RR型

LR型

RL型

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

1.43 STL容器map的特点、用法。
  • map映射容器的元素数据是由一个键值和一个映射数据组成的,键值与映照数据之间具有一一映照的关系。
    map容器的数据结构也采用红黑树来实现的,插入元素的键值不允许重复,比较函数只对元素的键值进行

  • 头文件
    #include<map>

  • 基本操作

begin() 返回指向 map 头部的迭代器
clear() 删除所有元素
count() 返回指定元素出现的次数
empty() 如果 map 为空则返回 true
end() 返回指向 map 末尾的迭代器
erase() 删除一个元素
find() 查找一个元素
insert() 插入元素
key_comp() 返回比较元素 key 的函数
lower_bound() 返回键值>=给定元素的第一个位置
max_size() 返回可以容纳的最大元素个数
rbegin() 返回一个指向 map 尾部的逆向迭代器
rend() 返回一个指向 map 头部的逆向迭代器
size() 返回 map 中元素的个数
swap() 交换两个 map
upper_bound() 返回键值>给定元素的第一个位置
value_comp() 返回比较元素 value 的函数
  • 定义一个map容器
    map<int,char>mp;//定义map容器
  • 插入
    普通插入
    mp[1]='a'; mp[1]='b';//key不允许重复,再次插入相当于修改value的值 mp[2]='a'; mp[3]='b'; mp[4]='c';
  • insert插入
    mp.insert(map<int,char>::value_type(5,'d'));
  • 删除
    mp.erase('b');//通过关键字key删除元素

1.5 B-树和B+树

B-树和AVL树区别,其要解决什么问题?
B-树定义。结合数据介绍B-树的插入、删除的操作,尤其是节点的合并、分裂的情况

1.51 B-树结构体定义
#define MAXM 10		  //定义B-树的最大的阶数
typedef int KeyType;    //KeyType为关键字类型
typedef struct node      //B-树节点类型定义
{
	int keynum; 	  //节点当前拥有的关键字的个数
	KeyType key[MAXM];  //[1..keynum]存放关键字,[0]不用
	struct node* parent;	    //双亲节点指针
	struct node* ptr[MAXM];//孩子节点指针数组[0..keynum]
} BTNode;
1.52 B-树的定义:
  • 每个节点最多有m-1个关键字(可以存有的键值对)。
  • 根节点最少可以只有1个关键字。
  • 非根节点至少有m/2个关键字。
  • 每个节点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。
  • 所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
  • 每个节点都存有索引和数据,也就是对应的key和value。
1.53B-树的插入
  • 向一棵m阶B-树中插入关键字,插入的位置一定在叶子节点层,有两种情况:
    • 1.该叶子节点的关键字个数n<m-1,不修改指针,直接插入关键字到此节点中;
    • 2.该叶子节点的关键字个数n=m-1,则需进行“节点分裂”,此时若该叶子节点没有双亲节点,则新建一个双亲节点,树的高度增加一层,且双
      亲节点的值为叶子节点插入关键字k后的中间位置关键字。若该叶子节点有双亲节点,则将待插入的关键字k插入到双亲节点中
1.54 B-树的删除

删除关键字k分两种情况:

  • 1.在叶子节点层上删除关键字k;

  • 2.在非叶子节点层上删除关键字k。对于一棵m阶B-树,非根、非叶子节点的关键字最少个数Min为 m/2取上限再减一

  • B-树非叶子节点删除:

    • 1.从此节点的子树节点中借调最大或最小关键字代替要删除的关键字;
    • 2.子树中删除借调的关键字;
    • 3.若子树节点关键字个数小于最小值,重复步骤1;
    • 4.若删除关键字在叶子节点层,按叶子节点删除操作法
  • B-树叶子节点删除:B-树的叶子节点b上删除关键字有如下三种情况:

    • 1.若b节点的关键字个数大于Min,则可直接删去该关键字;
    • 2.若b节点的关键字个数等于Min,且其兄弟节点的关键字个数大于Min,则可以从兄弟节点借。此时兄弟节
      点最小关键字上移双亲节点,而双亲节点中大于删除关键字的关键字下移删除节点;
    • 3.若b节点的关键字个数等于Min,且其兄弟节点的关键字个数等于Min。此时先删除关键字,兄弟节点及删
      除关键字节点、双亲节点中分割两者的关键字再合并为一个新叶子节点,若双亲节点关键字个数小于Min,重复上一合并节点的步骤
1.55 B+树定义
  • B+树是大型索引文件的标准组织方式,支持顺序查找和分块索引,一棵 m阶B+树满足以下六个条件
    • 1.每个分支节点最多有m棵子树
    • 2.根节点或者没有子树,或者至少有两棵子树
    • 3.除根节点外,其他每个分支节点的子树棵数至少为m/2取上限
    • 4.有n棵子树的节点有n个关键字
    • 5.所有叶子节点包含全部关键字及指向相应记录的指针,叶子节点按关键字大小顺序链接,叶子节点是直接指向数据文件中的记录
    • 6.所有分支节点包含子节点最大关键字及指向子节点的指针

1.6 散列查找。

  • 根据设定的哈希函数 H(key)和所选中的处理冲突的方法,将一组关键字映射到一个有限的、地址连续的地址集 (区间) 上,并以关键
    字在地址集中的“映像”作为相应记录在表中的存储位置,如此构造所得的查找表称之为“哈希表”。

  • 哈希函数h(key):把关键字为k,的对象存放在相应的哈希地址中

  • 哈希表:存储数据记录的长度为m (m多n)的连续内存单元

  • 构造哈希函数的方法

    • 1直接定址法:直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。直接定址法的哈希函数h(k)为:h(k)=k+c。这种方法是不会出现哈希冲突的,但是,对于数据的格式要求太大了,不适用于所有的情况,并且,如果数据分布不连续的话,会造成内春单元的浪费。这实际上是解决方法上面的差异。
    • 除留余数法:除留余数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。哈希函数h(k)为:h(k)=k mod p (mod为求余运算,p≤m)这里的p最好是接近于哈希表长度的素数,这样会更大程度上减少哈希冲突的概率。 这是我们最常见的方法。
  • 哈希冲突
    对于两个关键字分别为ki ,和kj,( i≠j)的记录有ki,≠kj,,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)

哈希表设计主要需要解决哈希冲突。实际中哈希冲突是难以辟免的,主要与3个因素有关:

  • 1.哈希表长度
    • 与装填因子有关。
    • 装填因子α=存储的记录个数/哈希表的大小=n/m
    • α越小,冲突可能性就越小;α越大(最大可取1),冲突的可能性就越大。控制在0.6~0.9的范围内
  • 2.与所采用的哈希函数有关。
  • 3.与解决冲突方法有关。
解决冲突的方法
  • 开放定址法:冲突时寻找新的空闲哈希地址。
    • 线性探测法:线性探查法的数学递推描述公式为: d0=h(k) di=(di-1+1) mod m (1≤i≤m-1)。遇见冲突将元素向后移动一个位置看看该地址是否为空,空的话就占了它,记录移动次数,不空的话继续向下搜索,直到找到位置。

    • 平方探测法:平方探查法的数学描述公式为: d0=h(k) di=(d0± i2) mod m (1≤i≤m-1),平方探查法是一种较好的处理冲突的方法,可以避免出现堆积现象。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元。

    • 哈希链——链地址法:拉链法是把所有的同义词用单链表链接起来的方法。


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

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

2.1.1 伪代码(贴代码,本题0分)
typedef struct BNode
{
	int data;
	struct BNode* left;
	struct BNode* right;
}BNode, * BinTree;
全局变量n
main
{
  for i to n
    输入x
    插入函数Insert
  endfor
  if (Level(BST))
    输出yes
  else
    输出no
}
Insert(BinTree BST, int X)
{
  if bst为空
    生成新节点然后返回bst
  if bst的元素等于x
    返回bst
  else if bst的元素大于x
    BST->right = Insert(BST->right, X);
  else if bst的元素小于x
    BST->left = Insert(BST->left, X);
}
Level(BinTree BST)//层次遍历
{
  count=0,a=1;
  queue<BinTree> Q;
  BinTree temp;
  Q.push(BST);
  while 当Q为空停止循环
    将Q的第一个元素赋值给temp
    if temp不为空
      count加一
      输出temp的元素
    endif
    else
      if count不等于总元素N
        a=0;
       endif
        continue
     endelse
    temp的左右子树进队列
  endwhile
  返回a
}
2.1.2 提交列表

2.1.3 本题知识点

建树,树的插入,树的层次遍历,队列的使用

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

2.2.1 伪代码(贴代码,本题0分)
main
{
	初始化哈希链
	输入数据 scanf("%d %d", &N, &K);
	for 1 to n
		输入数据 scanf("%s %d", &str[0], &distance);
		if 飞行距离distance小于最小飞行距离k
			distance=k
		endif
		插入函数插入数据到哈希链中
	endfor
	int M;//要查询的人数
	string inquire;
	scanf("%d", &M);
	for 1 to m
		查询人的身份证号码
	endfor

}
InsertHash 插入数据到哈希链中
{
	取哈希地址
	while 遍历链,看它是否飞行过
		if 有该人的飞行记录
			就把距离加上去
		endif
	endwhile
	用头插法把新数据插入链中
}
Inquire 查询是否该人
{
	取哈希地址
	while 遍历链直到链为空
		if 看看链中是否有要找的人
			有就返回并输出距离
		endif
	endwhile
	没找到就输出 No Info
}
2.2.2 提交列表

2.2.3 本题知识点

哈希链的创建,初始化,查找和插入

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

2.3.1 伪代码(贴代码,本题0分)
for 1 to n
	输入字符串
	while 遍历文件直到输入#
		for 判断单个文件
			if 判断是否字母
				如果单词没有达到上限就将单词加入到末尾
			endif
		endfor
		输入下一个字符串
	endwhile
endfor
输入m
for 1 to m
	输入文件
	for 从头到尾遍历文件
		if 单词再两个文件都出现过
			a和b进行累加
		else if 单词再其中一个文件出现过
			b单独累加
		end
	endfor
endfor
输出a/b


2.3.2 提交列表

2.3.3 本题知识点

字符串的遍历,string的使用