代码改变世界

DS博客作业05p->--查找

2020-05-24 19:00  pluto1214  阅读(212)  评论(0编辑  收藏  举报

0.PTA得分截图

1.本周学习总结

一.静态查找

(1)顺序查找
概念:从表的一端按顺序扫描线性表,依次将扫描到的关键字和给定值k相比较,若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
ASL(success)=(n+1)/2,ASL(unsuccess)=表的长度=n

(2)二分查找
概念:二分查找也称为折半查找,要求线性表中的节点必须己按关键字值的递增或递减顺序排列。

int BinSearch(SeqList R,int n,int key)
{da
   int left=0,right=n-1,mid;
   while(left<=high)
   {
      mid=(left+right)/2;
      若key等于R[mid].data,return mid+1;
      若小于,right=mid-1;
      若大于,left=mid+1;
   }
   return 0;
}

二分查找的判定树

ASL(success)=每层圆形节点个数*层数的和/圆形节点的个数
ASL(unsuccess)=每层矩形节点个数*(层数-1)/矩形节点的个数
如上图,ASL(unsuccee)=(1*1+2*2+4*3+4*4)/11=3
ASL(unsuccess)=(4*3+8*4)/12=3.67

(3)分块查找
概念:(1)将数据表R[0..n-1]均分为b块;
(2)表是“分块有序”;
(3)抽取各块中的最大关键字及其起始位置构成一个索引表IDX[0..b-1],即IDX[i](0≤i≤b-1)中存放着第i块的最大关键字及该块在表R中的起始位置。

查找方法:先在上面的索引表中比较查找,再在下面的数据表中查找,例如查找80需比较8次

二.动态查找

(1)二叉排序树

概念:或者是一棵空树;
或者是具有如下特性的二叉树:
若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
它的左、右子树也都分别是二叉排序树。

特点:没有相同值的节点;中序遍历后得到一个递增有序的序列
如图为一棵二叉排序树

ASL的计算方法与上面二分查找的判定树类似
以上图为例,ASL(success)=(1+22+33+42+52)/10=3.2
ASL(unsuccess)=(12+43+24+45)/11=3.8

二叉搜索树的创建和插入
BinTree Create(KeyType A[],int n)
{
      BinTree BST=NULL;
      int i=0;
      for(i=0;i<n;i++)
      {
            Insert(BST,a[i])
      }
      return BST;
}
BinTree Insert( BinTree BST, ElementType x )
{
    if(!BST)//为空生成节点
    {
        BinTree p=(BinTree)malloc(sizeof(BinTree));
        p->Data=x;
        p->Left=p->Right=NULL;
        BST=p;
    }
    else if(x<BST->Data)//小于根节点则插入左子树
        BST->Left=Insert(BST->Left,x);
    else if(x>BST->Data)//大于根节点插入右子树
        BST->Right=Insert(BST->Right,x);
    return BST;
}

二叉搜索树的删除
BinTree Delete( BinTree BST, ElementType x )
{
    if(!BST)
    {
        printf("Not Found\n");
    }
    else 
    {
        if(x<BST->Data)//递归左子树找x
            BST->Left=Delete(BST->Left,x);
        else if(x>BST->Data)
            BST->Right=Delete(BST->Right,x);//递归右子树找x
        else if(x==BST->Data)
        {
            if(BST->Left&&BST->Right)//左右孩子都在,递归查找左子树最右孩子,用最右孩子替代x
            {
                BinTree t=FindMin(BST->Right);
                BST->Data=t->Data;
                BST->Right=Delete(BST->Right,BST->Data);
            }
            else
            {
                if(!BST->Left)BST=BST->Right;//只有左孩子,左孩子代替x
                else if(!BST->Right)BST=BST->Left;//只有右孩子,右孩子代替x
            }
        }
    }
    return BST;
}
查找
Position Find( BinTree BST, ElementType x )
{
    if(!BST)
        return NULL;
    if(BST->Data==x)
        return BST;
    else if(x<BST->Data)
        return Find(BST->Left,x);
    else if(x>BST->Data)
        return Find(BST->Right,x);
    return BST;
}
查找最大或最小节点
Position FindMin( BinTree BST )
{
    if(BST)
    {
       if(BST->Left)return FindMin(BST->Left);
       else return BST;
    }
}
Position FindMax( BinTree BST )
{
    if(BST)
    {
       if(BST->Right)return FindMax(BST->Right);
       else return BST;
    }
}

三.平衡二叉树

平衡二叉树的定义:

  • 每个节点的左右子树都是平衡二叉树
  • 左右子树的深度差的绝对值不超过1
    如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转。
  • LL型调整(由于左孩子的左子树插入节点引起失衡,需调整孩子节点)
  • RR型调整(由于右孩子的右子树插入节点引起失衡,需调整孩子节点)
  • LR型调整(由于左孩子的右子树插入节点引起失衡,需调整孙子节点)
  • RL型调整(由于右孩子的左子树插入节点引起失衡,需调整孙子节点)

四.B树

B-树的定义:

  • 每个节点至多m个孩子节点(至多有m-1个关键字)
  • 除根节点外,其他节点至少有m/2(向上取整)个孩子节点(即至少有m/2(向上取整)-1个关键字);
  • 若根节点不是叶子节点,根节点至少两个孩子节点

B+树的定义:

  • 每个分支节点至多有m棵子树。
  • 根节点或者没有子树,或者至少有两棵子树
  • 除根节点,其他每个分支节点至少有m/2(向上取整)棵子树
  • 有n棵子树的节点有n个关键字。 
  • 所有叶子节点包含全部关键字及指向相应记录的指针
    a.叶子节点按关键字大小顺序链接
    b.叶子节点是直接指向数据文件中的记录。
  • 所有分支节点(可看成是分块索引的索引表)
    a.包含子节点最大关键字及指向子节点的指针。

B-树的插入:
关键字插入的位置必定在叶子结点层,有下列几种情况:
(1)该结点的关键字个数n<m-1,不修改指针;
(2)该结点的关键字个数 n=m-1,则需进行“结点分裂”,取中间元素生成新节点,左边为左子树,右边为右子树

B-树的删除
(1)结点中关键字的个数>m/2取上整-1,直接删除
(2)结点中关键字的个数=m/2取上整-1
a. 要从其左(或右)兄弟结点“借调”关键字
b. 若其左和右兄弟结点均无关键字可借(结点中只有最少量的关键字),则必须进行结点的“合并”。

假如b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义。若可以从兄弟结点借。
1.兄弟结点最小关键字上移双亲结点
2.双亲结点大于删除关键字的关键字下移删除结点  

b结点的关键字个数等Min,兄弟节点关键字个数也等于Min
1.删除关键字
2.兄弟节点及删除关键字节点、双亲结点中分割二者关键字合并一个新叶子结点
3.若双亲结点关键字个数<=Min,重复2

五.哈希表

概念:哈希表又称散列表,是除顺序存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构

构造方法:

  1. 直接定址法
    直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。
  2. 除留余数法
    除留余数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
    哈希函数h(k)为:
    h(k)=k mod p (mod为求余运算,p≤m)
    p最好是质数(素数)
    代码如下
void CreateHT(HashTable ha, KeyType x[], int n, int m, int p)
{
	int i;
	int count = 0;

	for (i = 0; i < p; i++)//哈希表初始化
	{
		ha[i].count = 0;
		ha[i].key = NULLKEY;
	}
	for (i = 0; i < n; i++)//插入元素
	{
		InsertHT(ha,count,x[i],p);
	}
}
void InsertHT(HashTable ha, int& n, KeyType k, int p)
{
	int adr, i;

	adr = k % p;

	if (ha[adr].count == 0 || ha[adr].key == NULLKEY)//该地址为空,直接插入
	{
		ha[adr].key = k;
		ha[adr].count = 1;
	}
	else//解决哈希冲突
	{
		i = 1;
		while (ha[adr].count != 0 && ha[adr].key != NULLKEY)//查找插入的位置
		{
			adr = (adr + 1) % p;
			i++;
		}
		ha[adr].key = k;//插入k
		ha[adr].count = i;//查找位置的次数
	}
	n++;

}

哈希冲突(两个不同的关键字拥有相同的哈希地址)解决办法

  • 开放定址法:冲突时找一个新的空闲的哈希地址。
    线性探测法:线性探查法的数学递推描述公式为:
    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)
  • 拉链法:拉链法是把所有的同义词用单链表链接起来的方法。

建哈希链

void createHash(HashTable ha[], int length,int k)//建哈希表
{
	int i;
	char id[20];
	int id5;
	int fly;
	int m;

	for (i = 0; i < MAX; i++)//每条链初始化
	{
		ha[i].first = NULL;
	}

	for (i = 0; i < length; i++)
	{
		scanf("%s", id);
		scanf("%d", &fly);
		if (fly < k)
			fly = k;
		
		id5=translate(ha, id);
		insert(ha, id5, fly);//插入链中的节点
	}

	scanf("%d", &m);

	for (i = 0; i < m; i++)
	{
		scanf("%s", id);
		id5 = translate(ha, id);
		SearchHT(ha, id5);
	}
}
void insert(HashTable ha[], int id5, int fly)
{
	int adr;
	LinkList* p;
	LinkList* q;

	p = new LinkList;
	p->key = id5;
	p->fly = fly;
	p->next = NULL;

	adr = id5 % MAX;
	if (ha[adr].first == NULL)
	{
		ha[adr].first = p;
		return;
	}
	else
	{
		q = ha[adr].first;

		while (q != NULL)//找插入位置
		{
			if (q->key == id5)
			{
				q->fly = p->fly + q->fly;
				return;
			}
			q = q->next;
		}
		p->next = ha[adr].first;
		ha[adr].first = p;
	}
}
void SearchHT(HashTable ha[], int id5)//找链中的元素
{
	int adr;
	List p;

	adr = id5 % MAX;
	p = ha[adr].first;
	while (p != NULL)
	{
		if (p->key == id5)//匹配成功,找到
		{
			printf("%d\n", p->fly);
			return;
		}
		p = p->next;
	}
	if (p == NULL)//没找到
	{
		printf("No Info\n");
	}
}

1.2.谈谈你对查找的认识及学习体会。

  • 查找的内容比较多,但顺序查找那部分内容是以前的知识,还是相对比较好记
  • 查找的内容虽然多,但是没有图和树那么复杂,像AVL树和B树都比较好理解
  • PTA的题目查找一块更偏向于实际,认真思考还是比较有趣
  • 动态查找的内容涉及到对表的修改,所以比静态查找要复杂一些
  • 查找在大数据方面的算法尤为重要
  • 许多题目都需要画图,代码层面的反而应用的比前面章节的少

2.PTA题目介绍

2.1 题目1是否完全二叉搜索树

2.1.1 该题的设计思路

题目分析

  • 第一行输入节点个数n,第二含输入n个节点的值
  • 用插入的方法生成一棵二叉搜索树
  • 思考完全二叉树,什么是完全二叉树,其特点在代码方面的表示
  • 如下为完全二叉树的图像,其最后一层的节点的值应全部靠左,若有一节点没有右子树,则层次遍历在该节点后的节点都为叶子节点,否则不为完全二叉树

    时间复杂度为O(n)

2.1.2 该题的伪代码

int judge(Tree T)
{
      queue<Tree> q;
      Tree p;
      若T不空,入队列q;
      flag1=0;
      while(q不空)
      {
          p=q.front();
          q出栈;
          输出p->data;
         若p的左子树不为空,将左子树入队列q;
         若p的右子树不空,将右子树入队列q;
      }
      对p节点是否左子树和右子树进行判定;
      若没有右孩子,flag1=1;
      若没有左孩子但有右孩子,则肯定不为完全二叉树;
      若flah1=1,该节点不为叶子节点,则不为完全二叉树;
}

2.1.3 PTA提交列表


Q:完全二叉树的特点代码表示错误
错误代码if(p->left!=NULL&&p->rightNULL)
flag1=1
A:这样的代码是说一个节点没有右孩子但有左孩子,其后的节点都为叶子节点,flag1=1进入后面节点的判断分支
但满二叉树为完全二叉树的一种,其p->left
NULL,但其后的节点也全为叶子节点,判断条件修改为if(p->right==NULL)

2.1.4 本题设计的知识点

1.完全二叉树的概念和特点,完全二叉树最后一层的节点必须全部靠左,满二叉树也为完全二叉树
2.层次遍历的做法,需要队列来辅助遍历
3.二叉搜索树的创建实际上就是将一个一个节点插入

2.2 题目2整型关键字的散列映射

2.2.1 该题的设计思路

  • 正整数n为关键字个数,p为素数且为散列表的长度
  • 用除留余数法构建散列表
  • 线性探测法解决哈希冲突
  • 时间复杂度为O(n)

2.2.2 该题的伪代码

void InsertHT(HashTable ha, int& n, int k, int p)
{
	int adr, i;
	adr = k % p;
	if该地址为空,直接插入;
	else   //哈希冲突解决
	{
		i = 1;
		while (地址不空&&ha[adr].key != k)//线性探测找位置
		{
			adr = (adr + 1) % p;
			i++;
		}
		插入k;
	}
                n++;
	输出adr;
}

2.2.3 PTA提交列表


Q:重复数据发生错误,对于重复数据,不用再线性探测映射到散列表中,否则一个数据将会有两个地址
A:将while (ha[adr].count != 0 && ha[adr].key != NULLKEY )修改为while (ha[adr].count != 0 && ha[adr].key != NULLKEY && ha[adr].key != k)
Q:对于哈希表长度理解错误,将MAX=1000,即n元素的最大个数
A:但关键字的个数并不是散列表的长度,题目给到p为表长,且p>=n,于是max要大于1000,将1000改为10000通过

2.2.4 本题设计的知识点

  • 对于重复数据,不能再次用线性探测法找位置,否则一个关键字会有多个地址
  • 解决哈希冲突用线性探测法,修改地址为adr=(adr+1)%p;

2.3 题目3(map) QQ帐户的申请与登陆

2.3.1 该题的设计思路

  • 正整数n代表接下来给出的指令数
  • 每行给出指令,L为老用户,N为新用户,之后接上账号和密码
  • 题目要求用Map,可用map<string,string>将账户与密码关联
  • 时间复杂度为O(n)

2.3.2 该题的伪代码

int main()
{
	int n;//指令的行数
	char ch;
	string QQaccount;//账户
	string password;//密码
	map<string, int> exist;//判断账户是否存在
	map<string, string> infor;//判断登录是否合法
               
	for i=1ton
                {
                       输入指令,账户和密码;
                       若指令为N,
                              若exist[QQaccount]为1,输出用户存在
                              否则注册用户,输出注册成功
                       若指令为L
                              若exist[QQaccount]为0,该用户不存在
                              else if (infor[QQaccount] != password)  
                                     输出密码错误     
                              else if (infor[QQaccount] == password)
                                     输出登录成功
                }
	return 0;
}

2.3.3 PTA提交列表


Q:新用户注册时if(exist[QQaccount] == 0),输出ERROE:Exist错误
A:该初值本来就为0,调试时发现新用户进入该if分支,应改为exist[QQaccount] == 1

2.3.4 本题设计的知识点。

  • map容器可以将两个不同或相同类型的变量关联起来
  • 将账户和密码用map关联已判断登录是否合法
  • 将账户与int型变量关联,可判断该账号是否存在,存在为1,不存在为0