DS博客作业05--查找

0.PTA总分

1.本周学习总结

1.1 总结查找内容

顺序查找

数据用数组存储,查找元素时对数组进行遍历查找的操作。

  1. 查找成功ASL:每个数据的查找次数和/总个数
  2. 查找不成功ASL:每个数据均要遍历数组
for(i=0;i<n;i++)
{
      if(key==a[i])return true;
}
return false;

二分查找

又名折半查找
仅适用于数据有序排列的线性表
做法可简述为:每次取中间值比对后缩小区域空间,直到结束。(下面举例说明)

二分查找的ASL-通过判定树分析
以上述题的数据为例,所构成的判定树为:

其中:

  1. 成功ASL:每个数据查找次数和/总数据个数。
  2. 不成功ASL:查找到矩形次数和/总矩形个数。
int BinSearch(int data[], int n, int key)
{
    int low = 0;
    int high = n - 1;
    int mid;
    while (low <= high)			//当前区间存在元素时循环
    {
          mid = (low + high) / 2;
          if (data[mid] == key)		//查找成功	                  
                return mid + 1;		//返回数据所在位置
          if (key < data[mid])
                high = mid - 1;
          else
                low = mid + 1;
    }
    return -1;				//没找到
}

二叉搜索树

基于二叉树的一种数据结构
特点:

  1. 右子树上所有结点的值均大于它的根结点的值;
  2. 左子树上所有结点的值均小于它的根结点的值;
  3. 对其进行中序遍历的结果是递增数列;
    结构体:
typedef struct Node
{
    int Data;
    struct Node *Left;
    struct Node *Right;
}TNode,*BinTree;
  • 二叉搜索树的构建以及插入
    二叉树的构建通过循环插入N个数来完成
    插入的操作则是不断匹配插入值与节点值大小进行左右子树的遍历,直到节点为空。
BinTree Insert(BinTree BST, ElementType X)
{
	if (BST==NULL)//节点为空,插入
	{
		BST = (BinTree)malloc(sizeof(struct TNode));
		BST->Data = X;
		BST->Left = BST->Right = NULL;
	}
	else
	{
		if (X == BST->Data)return BST;//找到与插入值相同的节点,则返回。
		else if (X < BST->Data)//插入值小于节点,遍历左子树
		{
			BST->Left = Insert(BST->Left, X);
		}
		else if (X > BST->Data)//插入值大于节点,遍历右子树
		{
			BST->Right = Insert(BST->Right, X);
		}
	}
	return BST;
}
  • 二叉搜索树的删除
    删除操作较为繁琐,分为一下三点:
  1. 被删除的节点是叶子节点。 (叶子节点的删除较容易,直接删除即可)
  2. 被删除的节点只有左子树或者只有右子树。
  3. 被删除的节点既有左子树,也有右子树。

第二种情况:

  • 当删除的节点只有左子树或右子树时,则需将其父亲节点指向其子树即可。

    第三种情况:
  • 删除的节点既有左子树也有右子树时,则需找元素替换其值,再删除所找的元素的节点。而查找的规则则是:
    1. 从节点的右子树找最小值进行替换。
    2. 从节点的左子树找最大值进行替换。
  • 通过二叉搜索数的性质:树的最左节点即最小,最右节点即最大。则上述操作就较为简单了。
BinTree Delete(BinTree BST, int X)
{
	Position q;
	if (BST == NULL)//找不到元素X
	{
		printf("Not Found\n");
		return NULL;
	}
	else
	{
		if (X < BST->Data)                      //查找X元素所在位置
		{
			BST->Left = Delete(BST->Left, X);
		}
		else if (X > BST->Data)
		{
			BST->Right = Delete(BST->Right, X);
		}
		else                                    //找到X元素
		{
			if (BST->Left == NULL)
			{
				q = BST;
				BST = BST->Right;
				free (q);
			}
			else if (BST->Right == NULL)        //左或右子树为空
			{
				q = BST;
				BST = BST->Left;
				free (q);
			}
			else                                 //有左右子树
			{
				q = FindMax(BST->Left);      //在左子树找最右元素
				BST->Data = q->Data;
				BST->Left=Delete(BST->Left, q->Data);    //在左子树中删除所查找的元素
			}
		}
	}
	return BST;
}

AVL树

  • 在二叉搜索树的基础上,通过调整,使得其变成一个相对平衡的状态,即任一节点的左右子树高度差绝对值不超过 1
    调整的方法分为四种
  • LL调整:在某结点的左子树左子树插入节点而引起的失衡。
  • RR调整:在某结点的右子树右子树插入节点而引起的失衡。
  • LR调整:在某结点的左子树右子树插入结点而引起的失衡。
  • RL调整:在某结点的右子树左子树插入结点而引起的失衡。
    LL及RR调整


    LR及RL调整

B树

  • B-树
    定义:对于一棵m阶B-树,每个节点至多m个孩子节点且至多有m-1个关键字;除根节点外,其他节点至少有m/2个孩子节点;若根节点不是叶子节点,根节点至少两个孩子节点。

B-树的特点:

  1. 所有结点的平衡因子均为0;
  2. B-树分为内部节点及外部节点;
  3. B-树的叶子节点均在同一层;
  • B-树的查找
    类似于搜索二叉树的查找,只不过变成多叉树。

  • B-树的插入
    插入的位置一定在叶子节点层

  1. 当插入完节点关键字个数<m,无需分裂。
  2. 插入完节点关键字个数=m,则需分裂。
    分裂插入后取中间关键字,将其放入双亲节点。**
  • B-树的删除

    • 结点中关键字个数>(m/2)-1时,直接删除
    • 结点中关键字个数=(m/2)-1时:
      1. 需从其左或右兄弟结点借关键字。
      2. 若无关键字可借,则需合并结点。
        可借情况

        合并情况
  • B+树
    定义

  1. 对于一棵m阶B-树,每个节点至多m个孩子节点且至多有m-1个关键字
  2. 除根节点外,其他节点至少有m/2个孩子节点
  3. 若根节点不是叶子节点,根节点至少两个孩子节点。
  • (以上三点与B-树相同)
  1. 有n棵子树的节点有n个关键字。
  2. 所有叶子节点包含全部关键字及指向相应记录的指针并且叶子节点按关键字大小顺序链接。
  3. 所有分支节点包含子节点最大关键字及指向子节点的指针。

哈希

将关键字与地址产生一定的映射后,可以进行直接进行访问的存储结构。映射可表示为:H(k)=c
地址的构造方法分为三种:

  1. 直接定址法:在关键字本身加或减常数而得。H(k)=k+c;
  2. 除留余数法:用关键字k除以不大于哈希表长度的数p所得的余数作为地址:表示为H(k)=k mod p,其中p最好是质数;
  3. 数字分析法:当每个关键字用相同的规律,且都不同时
    常见做法为第二种
  • 哈希表
#define NULLKEY -1      //代表空
#define DELKEY -2      //代表删除
typedef char * InfoType ; 
typedef struct{
	int key;// 关键字域 
	InfoType data;//其他数据域 
	int count; //探查次数 
}HashTable[MaxSize]; 

在构建哈希表过程中,会产生地址冲突。解决方法:

  1. 线性探查法:当新求出地址下已有数据时,则往下寻找地址,直至内容为空。
  2. 平方探测法:当新求出地址d0下已有数据时,则新地址di=(d0± i^2) mod m

哈希表构建(解决冲突方法均为线性探查法)

void InsertHT(HashTable ha, int& n, KeyType k, int p)
{
	int adr, i;
	adr = k % p;                  //计算地址
	if (ha[adr].key == NULLKEY)    //地址为空,可插入数据
	{
		ha[adr].key = k; 
                ha[adr].count = 1;     //记录探测次数
	}
	else
	{
		i = 1;
		while (ha[adr].key != NULLKEY)
		{
			adr = (adr + 1) % p;
			i++;
		}//查找插入位置
		ha[adr].key = k;
                ha[adr].count = i;   //记录探测次数
	}
	n++;
}

ASL计算

  • 成功探测次数:每个关键字的成功探测次数即插入该关键字时,经历多少次的地址更新。

  • 不成功探测次数:表长的范围内,每个地址查找到空的次数。

    成功ASL:成功探测次数和/数据个数

    不成功ASL:不成功探测次数和/p

  • 哈希链
    哈希链无需考虑地址冲突问题。

typedef struct HashNode{
   int  key;
   struct HashNode *next; 
}HashNode,* HashTable;
HashTable ht[MAX];

每个地址均有一个头结点,遇到相同地址关键字时,只需采用头插法插入即可。

  • 成功探测次数:层数×每层结点个数和
  • 不成功探测次数:仅有i个结点个数和
    成功ASL

    不成功ASL

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

  • 查找算法个人感觉功能强大且用处广泛,其算法能使一堆杂乱无章的数据变得有序,从而利于处理,搜索引擎就是一个很好的例子。本章知识点中,理论知识以及操作部分占比较多,代码方面并不多。然而面对较多的理论知识及操作时,起初掌握并不太清楚,也导致做一些题目时,会由于某一部分不清楚的知识点而错误。但总体而言,有些内容之前也接触过了,所以整体学起来相比于之前并不会太吃力。也算是量的问题吧,做的题多了,目前的疑惑自然而然就迎刃而解了。

2.PTA题目介绍

2.1 7-1 是否完全二叉搜索树

2.1.1 该题的设计思路

完全二叉树的特性:通俗来讲,从上到下,从左往右遍历完全二叉树时,直到结束才会遇到空结点。
如图按照ABCD···顺序遍历直到尾才空

  • 根据以上特性,建成一颗完全二叉树后,进行一次层次遍历,若数据未遍历完就遇到空节点,说明非完全二叉搜索树

时间复杂度:建树时间O(nlogn)+层次遍历O(n)=O(nlogn)

2.1.2 该题的伪代码

根据输入数据建二叉搜索树;
树非空时q.push(T);
while(队列非空)
{
      取队头元素top;
      if(top==NULL)
      {
            若数据未遍历完,说明非完全二叉搜索树;
      }
      入队top左右孩子;
}
若遍历完无错误,说明是完全二叉搜索树;

代码

2.1.3 PTA提交列表

  • 起初开始编写时的想法是统计每层的数据个数再根据2^(n-1)判断,不过随后发现这并不行得通,因为只要不是满二叉树,到最后一层时根据此方法无法判断。所以就根据层次遍历时数据的连续性来编写代码。
  • 由于代码涉及的基本操作都是熟悉不能再熟悉了,所以只需稍作修改,测试样例无误后就提交PTA通过了。

2.1.4 本题设计的知识点

  1. 完全二叉树的基本性质,层次遍历时数据的连续性。
  2. 二叉搜索树的构建。
  3. 二叉树的层次遍历。

2.2 7-2 二叉搜索树的最近公共祖先

2.2.1 该题的设计思路

二叉树的性质:左子树的数据都比根节点小,右子树的数据都比根节点大。

  • 通过该性质,可知道对于一组数据U和V,若他们有公共祖先X,则X一定满足U<=X<=V或U>=X>=V。
    **所以只需从根结点开始遍历二叉搜索树,通过上述条件判断即选出公共祖先。
    时间复杂度:O(nlogn)

2.2.2 该题的伪代码

根据数据建成二叉搜索树;
输入查找次数n;
while(n--)
{
    cin>>U>>V;
    if(U或V任一一个不在数据数组中)continue;
    while(root)
    {
          若root均大于U,V
                root=root->Left;
          若root均小于U,V
                root=root->Right
          else root就是公共祖先
    }
}

代码

2.2.3 PTA提交列表

  • 一看到题目给出的前序序列再加上二叉搜索树,就想到了用前序跟中序建树。不过在老师讲解过后,发现这种方法跟直接通过前序建立二叉搜索树的结果是一样的,反而更为麻烦。这也算是新的知识点吧。
  • 由于采用的时vector数组,并不知道如何用类指针的方法操作数组,所以也上网查找了资料。发现采用迭代器——vector::iterator,可以很好的解决这个问题。所以整体编程并不算复杂。
  1. 部分错误是因为输出时的疏忽导致。

2.2.4 本题设计的知识点

  1. 二叉搜索树的中序遍历是有序的,
  2. 根据前序及中序可建立二叉树。
  3. 迭代器的使用以及vector数组的操作

2.3 7-5(哈希链) 航空公司VIP客户查询 (25分)

2.3.1 该题的设计思路

  • 通过身份证号计算哈希地址,再将其数据存入构建的哈希链中。
    思路较为简单,但由于测试点数据量庞大,并不容易通过

2.3.2 该题的伪代码

//结构体
typedef struct HashNode
{
	long long  key;//id
	int dis;//历程数
	struct HashNode* next;
}HashNode, * HashTable;
输入会员个数n;
while(n--)
{
      输入身份证id,里程distance;
      若id最后一位为x 则+10;
      adr=id%p;
      若 该id已记录 
            则ht[adr]->dis+=distance
      否则 头插法插入ht[adr]
}

代码

2.3.3 PTA提交列表

  1. 多种错误:包括运行超时跟答案错误。其中的运行超时的解决办法是我没想到的。由于我是在不知道如何解决超时问题,所以我上网查找了题目,发现大数据下的scanf跟cin有着较大的效率差,scanf是明显快于cin的(具体讨论见博客cin与scanf cout与printf效率问题)。更改成scanf后,超时问题果然就解决了。
  2. 答案错误:揪出答案错误的原因算是比较气人的了。由于题目的最小里程数是输入的数据,而我在编写过程中直接使用500忽略了K值,这导致我找了好久才发现这个问题。

2.3.4 本题设计的知识点

  1. 在大数据的输入下,scanf的效率明显高于cin
  2. 链表操作的头插法。
  3. 哈希链的基本操作。
posted @ 2020-05-20 18:23  1911-黄荣煌  阅读(298)  评论(0编辑  收藏  举报