0.PTA得分截图

1.本周学习总结

1.1 总结查找内容

1.1.1 查找的性能指标ASL静态查找:

  • ASL的定义:关键字的平均比较次数,也称平均搜索长度ASL。

    n:记录的个数
    pi:查找第i个记录的概率 ( 通常认为pi =1/n )
    ci:找到第i个记录所需的比较次数

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

线性表的查找方法:

1.顺序查找
  • 思路:从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k相比较,
    若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
  • 顺序表结构体定义:
#define MAXL <表中最多记录个数>
typedef struct 
{  KeyType key;  //KeyType为关键字的数据类型
   InfoType data;//其他数据
} NodeType;
 typedef NodeType SeqList[MAXL]; //顺序表

  • 顺序查找的算法:
    思路:在顺序表R[0..n-1]中查找关键字为k的元素,成功时返回找到的元素的逻辑序号,失败时返回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
}

时间复杂度:O(n)

  • 成功时的顺序查找的平均查找长度为:
  • 查找成功时的平均比较次数约为表长的一半。成功ASL=(n+1)/2
  • 查找不成功时的平均查找长度为n。不成功ASL=n
2.二分查找(折半查找)
  • 定义:二分查找也称为折半查找,要求线性表中的节点必须己按关键字值的递增或递减顺序排列。

  • 二分查找代码:

int BinSearch(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 + 1;
		if (k < R[mid].key)

		else
	}

	return 0;
}

  • 二分查找递归算法:
int BinSearch1(SeqList R, int low, int high, KeyType k)
{
	int mid;
	if (low <= high)		 //查找区间存在一个及以上元素
	{
		mid = (low + high) / 2;  //求中间位置
		if (R[mid].key == k) //查找成功返回其逻辑序号mid+1
			return mid + 1;
		if (R[mid].key > k)  //在R[low..mid-1]中递归查找
			BinSearch1(R, low, mid - 1, k);
		else		//在R[mid+1..high]中递归查找
			BinSearch1(R, mid + 1, high, k);
	}
        else
          return 0;
}

时间复杂度:O(log2n)

  • 二分查找的性能分析-判定树:
    定义:
把当前查找区间的中间位置上的记录作为根;
左子表和右子表中的记录分别作为根的左子树和右子树。
称为描述二分查找的判定树或比较树。
  • 折半查找的平均查找长度:
    1.树中第i层上的记录个数为2i-1,查找该层上的每个记录需要进行i次比较。
    2.当n比较大时,判定树看成内部节点的总数为n=2h-1、高度为h=log2(n+1)的满二叉树


    3.对于n个元素,二分查找,成功时最多的关键字比较次数为:log2(n+1) 
    不成功时关键字比较次数为:log2(n+1)  。

  • eg:

    ASL成功=每一层节点数层次数的总和/总结点数
    ASL不成功=其他不存在于树中的每一层节点数
    (层次数-1)/总结点数

3.分块查找:
  • 索引存储结构和分块查找:
    索引存储结构:存储数据同时,建立索引表。
    索引存储结构=数据表+索引表
    索引项的一般形式是:(关键字,地址)

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

  • 静态查找表的缺点:当表的插入或删除操作频繁时,为维护表有序性,需要移动表中很多记录。

  • 二分查找和分块查找只适用于静态查找表。

1.1.2 动态查找:二叉搜索树:

以二叉树或树作为表的组织形式, 称为树表。
表结构在查找过程中动态生成,适用动态查找。

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

二叉排序树可看做是一个有序表,所以在二叉排序树上进行查找,和二分查找类似,也是一个逐步缩小查找范围的过程。

* 二叉排序树上的查找:
  • 二叉排序树的结点类型:
typedef struct node 
{       KeyType key;            	  //关键字项
     InfoType data;          	  //其他数据域
        struct node *lchild,*rchild; 	  //左右孩子指针
}  BSTNode,*BSTree;

  • 二叉排序树查找递归代码:
BSTNode *SearchBST(BSTNode *bt,KeyType k)
 {  if (bt==NULL || bt->key==k)   
       return bt;
    if (k<bt->key)   return SearchBST(bt->lchild,k);
    else  return SearchBST(bt->rchild,k);
 }

  • 非递归代码:
BSTNode *SearchBST1(BSTNode *bt,KeyType k)
{  while (bt!=NULL)
   {
      if (k==bt->key)
          return bt;
      else if (k<bt->key)
          bt=bt->lchild;  //在左子树中迭代查找
      else
          bt=bt->rchild;  //在左子树中迭代查找
   }
   return NULL;   //没有找到返回NULL
}

* 二叉排序树的插入和生成:

在二叉排序树中插入一个关键字为k的新结点,要保证插入后仍满足BST性质.(插入过程:边查找边插入)

(1)若二叉排序树T为空:
        则创建一个key域为k的结点,将它作为根结点;
(2)否则将k和根结点的关键字比较,如果两者相等:
       则说明树中已有此关键字k,无须插入,直接返回0;
(3)若k<T->key:
       则将k插入根结点的左子树中。
(4)否则将它插入右子树中。

  • 二叉搜索树插入代码(递归):
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);  //插入到右子树中
 }

任何节点插入到二叉排序树时,都是以叶子节点插入的。

  • 在二叉排序树插入算法基础上的生成算法(创建过程就是遍历数组,调用插入函数):
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;  //返回建立的二叉排序树的根指针
} 
  • 不同插入次序的序列会生成不同形态的二叉排序树。

  • 二叉排序树的特点:

二叉排序树的中序序列是一个递增有序序列.
根结点的最左下结点是关键字最小的结点.
根结点的最右下结点是关键字最大的结点.

* 二叉排序树的节点删除:

分为三种情况:

1.被删除的节点是叶子节点:
直接删去该节点。其双亲节点中相应指针域的值改为“空”

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

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

  • 二叉排序树节点删除的代码:
    1.删除叶子结点:
int DeleteBST(BSTree &bt,KeyType k)  //在bt删除关键字为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);    //调用Delete(bt)函数删除bt结点
	       return 1;
           }
        }
} 

2.删除只有左子树或者只有右子树的节点:

void Delete(BSTree &p)   	 //从二叉排序树中删除p结点
{     
      BSTree q;
      if (p->rchild==NULL)        	//p节点没有右子树的情况
      {     
            q=p; p=p->lchild;		//用其左孩子替换它
            free(q);  
      }
      else if (p->lchild==NULL)    	//p节点没有左子树的情况
      {     
            q=p; p=p->rchild;	//用其右孩子替换它
            free(q);  
      }
      else Delete1(p,p->lchild);	
            //p节点既没有左子树又没有右子树的情况
}

3.删除既有左子树,也有右子树的节点:

void Delete1(BSTNode* p, BSTNode*& r)
//被删节点:p,p的左子树节点:r
{
	BSTNode* q;
	if (r->rchild != NULL)
		Delete1(p, r->rchild);//递归找最右下节点
	else  	//找到了最右下节点*r
	{    //将*r的关键字值赋给*p
		p->key = r->key;
		q = r; r = r->lchild;
		delete q;
	}
}

  • 二叉排序树查找的性能分析
    平均查找长度和二叉树的形态有关,即,
    最好:log2n(形态匀称,与二分查找的判定树相似)
    最坏: (n+1)/2(单支树)

1.1.3 AVL树的定义及4种调整做法:

  • 平衡二叉树:
左、右子树是平衡二叉树;
所有结点的左、右子树深度之差的绝对值≤ 1
树中每个结点的左、右子树深度之差(称为平衡因子BF)只能取:-1、0 或 1;
AVL树的平均查找长度和logn是同数量级的

  • AVL树的节点的类型:
typedef struct node     //记录类型
{ KeyType key;    	//关键字项
   int bf;			//增加的平衡因子
   InfoType data;     	//其他数据域
 struct node *lchild,*rchild;//左右孩子指针
} BSTNode;

  • 如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。
    我们称调整平衡过程为平衡旋转。

  • 平衡二叉树的四种平衡调整:
    1.LL型调整:

若在A的左子树的左子树上插入结点,使A失衡,平衡因子2,需要进行一次顺时针旋转。
1.A的左孩子B右上旋转作为A的根节点
2.A节点右下旋转称为B的右孩子
3.B原右子树称为A左子树

2.RR型调整:

若在A的右子树的右子树上插入结点,使A的平衡因子从-1增加至-2,需要进行一次逆时针旋转
1.A的右孩子B左上旋转作为A的根节点
2.A节点左下旋转称为B的左孩子
3.B原左子树称为A右子树

3.LR型调整:

若在A的左子树的右子树上插入结点,使A的平衡因子从1增加至2,(以插入的结点C为旋转轴),先C进行逆时针旋转,A再顺时针旋转。
1.C向上旋转到A的位置,A作为C右孩子
2.C原左孩子作为B的右孩子
3.C原右孩子作为A的左孩子

4.RL型调整:

若在A的右子树的左子树上插入结点,使A的平衡因子从-1增加至-2,(以插入的结点C(A的右孩子的左孩子)为旋转轴),先进行顺时针旋转,再逆时针旋转。
1.C向上旋转到A的位置,A作为C左孩子
2.C原左孩子作为A的右孩子
3.C原右孩子作为B的左孩子

  • 调整思路:
多个失衡点,从最下面失衡点开始调整
了解是哪种失衡,谁是失衡点
LL和RR是选择失衡点的L或R孩子旋转。LR和RL选择失衡点的相应的LR或RL孙子旋转。
每次调整后,检查是否二叉排序树。还有失衡点否。

1.1.4 B-树和B+树:

B-树和B+树:一个节点可放多个关键字,降低树的高度。可放外存,适合大数据量查找。如数据库中数据。

B-树:

  • 定义:
    B-树又称为多路平衡查找树。
    是一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
    1.每个节点至多m个孩子节点(至多有m-1个关键字)
    2.除根节点外,其他节点至少有m/2个孩子节点(即至少有m/2-1个关键字);
    3.若根节点不是叶子节点,根节点至少两个孩子节点。

  • 节点的结构为:

1.n为关键字个数,n+1孩子指针。
2.结点中按关键字大小顺序排列,ki<ki+1;
3.pi为该节点的孩子指针,满足以下条件:
         p0 指向节点关键字<k0 
         pi指向节点关键字大于等于[ki,ki+1]
         pn指向节点关键字>kn。

  • m阶B-树结点特点:
非根结点:孩子个数  
最小: m/2     最大:m
非根结点:关键字个数:
最小: m/2 -1  最大:m-1
根节点至少2个孩子:2~m

  • B-树特点:
1.B-树是所有结点的平衡因子均等于0的多路查找树。所有外部结点都在同一层上。
2.在计算B-树的高度时,需要计入最底层的外部结点
3.外部结点就是失败结点,指向它的指针为空,不含有任何信息,是虚设的。一棵B树中总有n个关键字,则外部结点个数为n+1。

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

  • 在B-树查找结果返回如下类型:
typedef struct {
  BTNode  *pt;     // 指向找到的结点的指针
  int  i;       // 1..m-1,在结点中的关键字序号
  int  tag;   // 标志查找成功(=1)或失败(=0)
} Result;             // 在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[n]?
      则沿着指针ptr[n]所指的子树继续查找。 
查找到某个叶结点,若相应指针为空,落入一个外部结点,表示查找失败。
* B-树的插入:

思路:
在查找不成功之后,需进行插入。关键字插入的位置必定在叶子结点层,有下列几种情况:
该结点的关键字个数n<m-1,不修改指针;
该结点的关键字个数 n=m-1,则需进行“结点分裂”
节点分裂(如果没有双亲结点,新建一个双亲结点,树的高度增加一层。如果有双亲结点,将ki插入到双亲结点中。)

eg:



* B-树的删除:

和插入的考虑相反,
结点中关键字的个数>m/2-1,直接删除
结点中关键字的个数=m/2-1
要从其左(或右)兄弟结点“借调”关键字
若其左和右兄弟结点均无关键字可借(结点中只有最少量的关键字),则必须进行结点的“合并”。

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

一。在叶子结点层上删除关键字k。

在B树的叶子结点b上删除关键字共有以下3种情况:
⑴假如b结点的关键字个数大于Min,说明删去该关键字后该结点仍满足B树的定义,则可直接删去该关键字。 
     
⑵假如b结点的关键字个数等于Min,说明删去关键字后该结点将不满足B树的定义。若可以从兄弟结点借。
1.兄弟结点最小关键字上移双亲结点
2.双亲结点大于删除关键字的关键字下移删除结点  

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

二。在非叶子结点层上删除关键字k。

1.从pi子树节点借调最大或最小关键字key代替ki。
2.pi子树中删除key
3.若子树节点关键字个数< m/2-1,重复步骤1
4.若删除关键字为叶子结点层,按叶子结点删除操作法

B+树:

  • 一棵m阶B+树满足下列条件:
    1.每个分支节点至多有m棵子树。
    2.根节点或者没有子树,或者至少有两棵子树
    3.除根节点,其他每个分支节点至少有m/2棵子树
    4.有n棵子树的节点有n个关键字。
    5.所有叶子节点包含全部关键字及指向相应记录的指针:
    叶子节点按关键字大小顺序链接
    叶子节点是直接指向数据文件中的记录。
    6.所有分支节点(可看成是分块索引的索引表):
    包含子节点最大关键字及指向子节点的指针。

  • B+树的查找:
    直接从最小关键字开始进行顺序查找所有叶节点链接成的线性链表。
    从B+树的根节点出发一直找到叶节点为止。

  • m阶的B+树和m阶的B-树的差异:

(1)非根结点关键字个数n不同、n取值范围不同
B+树中:一个节点n个孩子则对应n个关键字
  取值范围:m/2≤n≤m,根节点是1≤n≤m;
B-树中:一个节点n个孩子则对应n-1个关键字
  取值范围: m/2-1≤n≤m-1 ,根节点 1≤n≤m-1
  根节点孩子至少为2.
(2)叶子结点不一样
B+树所有叶子节点包含了全部关键字
B-树叶子节点包含的关键字与其他节点包含的关键字是不重复的。
(3)B+树中所有非叶子节点仅起到索引的作用,而在B-树中,每个关键字对应一个记录的存储地址
(4)通常在B+树上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,所有叶子节点链接成一个不定长的线性链表。
B-树和B+树都是平衡树。

1.1.5 散列查找:

哈希表:

  • 哈希表的基本概念:
    哈希表(Hash Table)又称散列表,是除顺序表存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构。
    哈希表是一种存储结构,它并非适合任何情况,主要适合记录的关键字与存储地址存在某种函数关系的数据。

  • 哈希函数和哈希地址:

    哈希函数h(key):把关键字为ki的对象存放在相应的哈希地址中
    哈希表:存储数据记录的长度为m(m≥n)的连续内存单元(线性表结构)

  • 哈希冲突:
    对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)。
    在哈希表存储结构的存储中,哈希冲突是很难避免的。

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

哈希函数构造方法:
    1. 直接定址法:
      直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。
      直接定址法的哈希函数h(k)为:h(k)=k+c
      优点:计算简单,并且不可能有冲突发生
      缺点:关键字分布不连续将造成内存单元的大量浪费
    1. 除留余数法:
      除留余数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
      哈希函数h(k)为:h(k)=k mod p (mod为求余运算,p≤m) (p最好是质数(素数)。)
  • 3.数字分析法:
    适合于所有关键字值都已知的情况,并需要对关键字中每一位的取值分布情况进行分析。
    取关键字最后两位作为哈希地址。

哈希冲突解决方法:
  • 开放定址法:
    冲突时找一个新的空闲的哈希地址。
    有两种实现方法:
    (1)线性探查法:
    线性探查法的数学递推描述公式为:
    d0=h(k)
    di=(di-1+1) mod m (1≤i≤m-1)
    线性探查法容易出现堆积:
    非同义词冲突:哈希函数值不相同的两个记录争夺同一个后继哈希地址 : 堆积(或聚集)现象。
    eg:
    如{16,74,60,90,46,17,31,29,88,77}。h=k mod 13


ASL成功=(2+1+1+1+1+4+1+1+1+1+1)/11=1.364
ASL不成功=(2+1+10+9+8+7+6+5+4+3+2+1+3)/13=4.692

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

哈希表上的运算:
  • 哈希表结构体:
#define 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){
    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; 
}
  • 哈希表删除算法:
int DeleteHT(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;
} 

拉链法:

拉链法是把所有的同义词用单链表链接起来的方法。

  • 哈希链结构体:
typedef struct HashNode{
   int  key;
   struct HashNode *next; 
}HashNode,* HashTable;
HashTable ht[MAX];

  • 堆区建哈希链做法:
struct Node//链表的结构体
{
	ElementType Element;
	struct ListNode *Next;
} ListNode, *List;
typedef struct 
{
	int TableSize;//哈希表的大小,即哈希表中链表的个数
	List  Lists;//链表指针数组,数组的每个元素为一个链表指针
}HashTable ;
HashTable *H;
H=new HashTable ;
H->Lists=new List[H->TableSize];
H->Lists[i]=new ListNode;
  • 哈希链插入和删除:
1.哈希链插入数据
InsertHash(ht,data) 
{
    计算哈希地址adr=data%P;
    查找链ht[adr],存在不插入。
    不存在:
        data生成数据节点node
        头插法插入链ht[adr]
}
2.哈希链删除数据
InsertHash(ht,data) 
{
    计算哈希地址adr=data%P;
    若链ht[adr]不为空:
          查找链ht[adr],找到删除
}

  • 拉链法哈希表查找k过程:
d=h(k);
p=ha[d]->next;//第d条链表头结点
while (p!=NULL && p->key!=k)
      p=p->next;   //在ha[d]的单链表中查找
if (p==NULL)
      return 失败标记;
else
    return p所指结点;

eg:
关键字序列为(16,74,60,43,54,90,46,31,29,88,77)
哈希链为:

成功ASL=(19+22)/11=1.182
不成功ASL=(0+0+1+2+1+1+0+1+1+1+1+0+2)/13=0.846

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

感觉查找中ASL不成功有点难计算。但是把外部节点都列出来就好了,老师也有意给我们多讲加深记忆;
查找根据存储结构的不同有好多种方式方法,判断效率的依据还是时间空间复杂度。
其实感觉二叉排序树最好理解了,然后开放定址法的两种方法感觉有点难理解。

2.PTA题目介绍

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

2.1.1 该题的设计思路:

输入n个正整数进行二叉搜索树的插入创建,二叉搜索树创建完毕后进行层次遍历,遍历到空时整棵树已经遍历完全就是完全二叉树;
完全二叉树:
除了最后一层,其他各层节点都达到最大个数,最后一层的节点可以不满但整体偏左。
例如题目输入:

或者

两个树的判断过程为:

时间复杂度:O(n²)

2.1.2 该题的具体代码:




2.1.3 该题的伪代码;

int main()
{
	输入整数个数n
		创建A[N];//存结点元素序列的数组
	为数组元素赋值
		BSTree Tree;
	CreateBST(Tree, A, n);//创建二叉搜索树

	if 为完全二叉搜索树
		//输出层序遍历的结果和“YES”
	else 不为完全二叉搜索树
		//输出层序遍历的结果和“NO”
}

bool Judge(BSTree& BT, int n)//判断是否为完全二叉树
{
	queue<BSTree> Q;
	if 二叉树为空树
		return true;
	else
		用Count来计算访问了几个结点
		Q.push(BT);
	BSTree t;
	while 队列遇到空节点时退出循环
		当前节点的左右孩子入队出队
		//此处是寻找空节点位置
		Count++;
	end while
		if 遍历完全后节点数 = Count总数
			是完全二叉树
		else
			不是完全二叉树
			end if
}
void Print(BSTree& BT, int n)//层序遍历输出结点
{
	while Q不为空队列
		将二叉树元素提取到data数组中
		if p有左孩子
			左孩子入队
		if p有右孩子
			右孩子入队
		众节点元素出队
	end while
	元素间隔一个空格层次输出数组中的元素
}
void InsertBST(BSTree& BT, int e)//向树中插入元素
{
	if 初始树为空时
		插入根节点e
	else if 键值大的插入左子树
		比根节点大的元素递归插入到左孩子位
	else if 键值小的插入右子树
		比根节点小的元素递归插入到右孩子位、
	end if
}
void CreateBST(BSTree& BT, int a[], int n)//创建二叉搜索树
{
	BT = NULL;//树初始化为空
	for i = 0 to i = n-1
		将数组中的元素插入到二叉树中
	end for
}

2.1.4 PTA提交列表

Q1:没注意看题干,直接还是按照二叉排序树左小右大的顺序来建树的
A1:建树时注意左大右小,比根节点大的元素递归构建为左孩子,比根节点小的元素递归构建为右孩子
Q2:我在判断是否为二叉排序树的函数中用来和节点数比较的变量Count忘记初始化了,就死循环了
A2:在遍历之前把Count初始化为0,根据遍历长度逐渐递增并最后和节点数进行比较

2.1.5 本题涉及的知识点

完全二叉树:
完全二叉树是特殊形态的满二叉树,在满二叉树的基础上,它的叶子结点可以不全满,且叶子结点整体偏左。
层次遍历:
借助队列来储存二叉树每一层的元素并依次输出。

2.2 7-4 整型关键字的散列映射

2.2.1 该题的设计思路

用除留余数法定义的散列函数将关键字映射到长度为P的散列表中。再用线性探测法解决冲突。
需要注意此题如果遇到重复元素并不会去找后面的空间储存。而是返回此元素在哈希表中的位置。
eg:

时间复杂度:O(n²)

2.2.2 该题的具体代码



2.2.3 该题的伪代码

初始化全局变量flag = 0;//输出时控制空格

void InsertHT(HashTable ha, int k, int p)
{
	adr代表下标
	if adr下标对应的地址未被占
		插入数据k到此地址
		探查次数为-1;
	else
		while 发生哈希冲突
			if 数据重复 break;
			否则找后面的空位置插入(adr = (adr + 1) % p)
	end if
}

void CreateHT(HashTable ha, int num[], int n, int p)
{

	初始化哈希表,状态都是未占,探寻次数为0
	for i = 0 to i = n-1
		插入数据到哈希表中
}
int main()
{
	输入数据个数和p求余数
	for i = 0 to i = n-1
		输入n个数据
	CreateHT(ha, num, n, p);
	return 0;
}

2.2.4 PTA提交列表

Q1:遇到重复数据时我以为要找后面的位置插入但其实不用
A1:重复数据出现时输出原有数据在哈希表中的位置
Q2:关键字最大的个数Maxsize我写1000比较小了
A2:关键字Maxsize设置为1010就可以运行了

2.2.5 本题涉及的知识点

哈希表的插入创建,它的除留余数法和线性探测法
线性探测法用于解决哈希冲突,在发生哈希冲突时寻找此位置后面的位置进行插入

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

2.3.1 该题的设计思路

建立哈希链,取乘客的身份证号后5位作为哈希地址进行存储,如果不是会员则添加,是会员就看里程数;

时间复杂度:O(n²)

2.3.2 该题的具体代码





2.3.3 该题的伪代码

//获得哈希表地址
	for i = 12 to i = 16
		取后五位数字作为哈希地址
	if 最后以为为x
	temp乘10再累加
	else
	直接转换为五位数;
	return temp;


//在哈希链中寻找用户信息

	node = h[temp]->next;
	while 当前用户有信息存储
		if 用户信息与存档相匹配
			return node;
		else 用户信息与存档不相匹配
			读取下一个信息;
	end while


//向哈希链中插入数据

	temp = GetID(ID);//获得信息·
	p = Find(h, ID, temp);//核对信息
	if 乘客是会员
		if 要走的距离小于最小里程数
			在本来的里程数上加上k算作最大里程
		else
			直接在原里程数上增加
		end if
	end if
	else 乘客不是会员
		新录入ta的信息成为会员
		根据里程大小计算总里程
		将新用户信息插入node中
	end else

创建哈希链
	int i;
	char ID[20];
	int dist;
	for i = 0 to i = n-1
		输入ID以及航程dist
		Insert(h, ID, dist, k);
	end for



int main()
{
		输入n, k;
	for i = 0 to i  MAXN-1
		为数组h申请动态空间
	end if
	Create(h, n, k);
	输入m
	for i = 0 to i = m-1
		scanf("%s", ID);
		获得哈希地址
		寻找乘客信息
		if 哈希地址合理
			输出里程数
		else 该乘客不是会员
		    输出错误
	end for
	return 0;
}

2.3.4 PTA提交列表

Q1:取后几位作为哈希地址的问题感觉有点难找,取的小了数据运行缓慢还溢出,然后大了就运算复杂
A1:取后5位作为哈希地址,如果最后一位为x则加10;
Q2:这个地方cin和cout为啥不如scanf和printf的原理我不太懂,然后哈希链的空间定义一次就行了
A2:使用scanf和printf来输入输出数据,哈希链空间不要重复申请,并且所有的哈希地址都要初始化,而不是i=0 to i<n。

2.3.5 本题涉及的知识点

哈希链的构造创建和寻找;
哈希链拉链法的使用;
关于哈希链使用空间还有哈希地址如何确立。