DS博客作业05--查找
0.展示PTA总分
1.本章学习总结
1.1 总结查找内容
查找的性能指标ASL
- 分析静态查找几种算法包括:顺序查找、二分查找的成功ASL和不成功ASL。
顺序查找
- 思路:从表的一端开始,顺序扫描线性表,依次将扫描到的关键字和给定值k相比较,若当前扫描到的关键字与k相等,则查找成功;若扫描结束后,仍未找到关键字等于k的记录,则查找失败。
- ASL:顺序查找方法在查找成功时的平均比较次数约为表长的一半。若k不在表中,则要进行n次比较后才能确定查找失败。
- ASL(成功)=(n+1)/2
- ASL(不成功)=n
- 代码
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
}
二分查找
- 思路:二分查找也称为折半查找,要求线性表中的节点必须己按关键字值的递增或递减顺序排列。
- 性能分析ASL(判定树):把当前查找区间的中间位置上的记录作为根;左子表和右子表中的记录分别作为根的左子树和右子树。称为描述二分查找的判定树或比较树。
- ASL成功=log(n+1)-1
- ASL不成功=log(n+1)
- 代码
常规算法:
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)
high=mid-1;
else
low=mid+1;
}
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;
}
分块查找
- 思路:
(1)将数据表R[0..n-1]均分为b块;
(2)表是“分块有序”;
(3)抽取各块中的最大关键字及其起始位置构成一个索引表IDX[0..b-1],即IDX[i](0≤i≤b-1)中存放着第i块的最大关键字及该块在表R中的起始位置。 - 性能:介于顺序查找和二分查找之间的查找方法
动态查找:二叉搜索树
概念:二叉排序树(简称BST)又称二叉查找(搜索)树,其定义为:二叉排序树或者是空树,或者是满足如下性质(BST性质)的二叉树:
①若它的左子树非空,则左子树上所有结点值(指关键字值)均小于根结点值
②若它的右子树非空,则右子树上所有结点值均大于根结点值
③左、右子树本身又各是一棵二叉排序树
特点:
①二叉排序树的中序序列是一个递增有序序列
②根结点的最左下结点是关键字最小的结点
③根结点的最右下结点是关键字最大的结点
二叉排序树的查找
思路
首先将给定的K值与二叉排序树的根结点的关键字进行比较:
①给定的K值小于BST的根结点的关键字:继续在该结点的左子树上进行查找
②给定的K值大于BST的根结点的关键字:继续在该结点的右子树上进行查找
③若相等:则查找成功
- 结构体定义
typedef struct node
{ KeyType key; //关键字项
InfoType data; //其他数据域
struct node *lchild,*rchild; //左右孩子指针
} BSTNode,*BSTree;
- 构建
BSTNode *CreatBST(KeyType A[],int n) //返回树根指针
{ BSTNode *bt=NULL; //初始时bt为空树
int i=0;
while (i<n)
{
InsertBST(bt,A[i]); //将A[i]插入二叉排序树T中
i++;
}
return bt; //返回建立的二叉排序树的根指针
}
- 查找
递归:
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
}
- 插入
- 思路
- (1)若二叉排序树T为空?
则创建一个key域为k的结点,将它作为根结点; - (2)否则将k和根结点的关键字比较,若两者相等?
则说明树中已有此关键字k,无须插入,直接返回0; - (3)若kkey?
则将k插入根结点的左子树中。 - (4)否则将它插入右子树中。
- 代码
BinTree Insert(BinTree BST, ElementType X)
{
if (BST == NULL) //空结点
{
BST = new BSTNode; //生成新结点
BST->Data = X;
BST->Left = BST->Right = NULL;
}
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)被删除的节点既有左子树,也有右子树:以其前驱替代之,然后再删除该前驱节点。前驱是左子树中最大的节点。也可以用其后继替代之,然后再删除该后继节点。后继是右子树中最小的节点。
- 代码:
int DeleteBST(BSTree &bt,KeyType 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); //删除*bt节点
return 1;
}
}
}
void Delete(BSTreee &p) //从二叉排序树中删除*p节点
{ BSTNode *q;
if (p->rchild==NULL) //*p节点没有右子树的情况
{
q=p; p=p->lchild;delete q;
}
else if (p->lchild==NULL) //*p节点没有左子树
{
q=p; p=p->rchild;delete q;
}
else Delete1(p,p->lchild);
//*p节点既有左子树又有右子树的情况
}
//即有左子树又有右子树的删除算法
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;
}
}
- 查找最大或最小结点
BinTree FindMin(BinTree BST)
{
if(BST!=NULL)
while (BST->Left != NULL)
{
BST = BST->Left;
}
return BST;
}
BinTree FindMax(BinTree BST)
{
if (BST != NULL)
while (BST->Right != NULL)
{
BST = BST->Right;
}
return BST;
}
AVL树
- 定义
- 左、右子树是平衡二叉树;
- 所有结点的左、右子树深度之差(平衡因子)的绝对值≤1
- 最坏情况下的时间也均为O(log2n)
- 结构体
typedef struct node //记录类型
{ KeyType key; //关键字项
int bf; //增加的平衡因子
InfoType data; //其他数据域
struct node *lchild,*rchild;//左右孩子指针
} BSTNode;
四种调整做法
LL型调整:
1.A的左孩子B右上旋转作为A的根节点
2.A节点右下旋转称为B的右孩子
3.B原右子树称为A左子树
RR型调整:若在A的右子树的右子树上插入结点,使A的平衡因子从-1增加至-2,需要进行一次逆时针旋转
1.A的右孩子B左上旋转作为A的根节点
2.A节点左下旋转称为B的左孩子
3.B原左子树称为A右子树
LR型调整:若在A的左子树的右子树上插入结点,使A的平衡因子从1增加至2,(以插入的结点C为旋转轴),先C进行逆时针旋转,A再顺时针旋转。
1.C向上旋转到A的位置,A作为C右孩子
2.C原左孩子作为B的右孩子
3.C原右孩子作为A的左孩子
保持二叉排序树有序
RL型调整:若在A的右子树的左子树上插入结点,使A的平衡因子从-1增加至-2,(以插入的结点C为旋转轴),先进行顺时针旋转,再逆时针旋转。
1.C向上旋转到A的位置,A作为C左孩子
2.C原左孩子作为A的右孩子
3.C原右孩子作为B的左孩子
保持二叉排序树有序
B-树和B+树
-
B-树:
-
定义:
一棵m阶B-树或者是一棵空树,或者是满足下列要求的m叉树:
每个节点至多m个孩子节点(至多有m-1个关键字)
除根节点外,其他节点至少有[m/2]个孩子节点(即至少有[m/2]-1个关键字)
若根节点不是叶子节点,根节点至少两个孩子节点 -
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;
- 查找结果返回类型
typedef struct {
BTNode *pt; // 指向找到的结点的指针
int i; // 1..m-1,在结点中的关键字序号
int tag; // 标志查找成功(=1)或失败(=0)
} Result; // 在B树的查找结果类型
- .查找
- 在一棵B-树上顺序查找关键字为k的方法为:
将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]所指的子树继续查找。
- 插入
在查找不成功之后,需进行插入。关键字插入的位置必定在叶子结点层,有下列几种情况:
该结点的关键字个数n<m-1,不修改指针;
该结点的关键字个数 n=m-1,则需进行“结点分裂”
节点分裂:
1.如果没有双亲结点,新建一个双亲结点,树的高度增加一层。
2.如果有双亲结点,将ki插入到双亲结点中。
删除
B+树
- 定义:索引文件组织中,经常使用B-树的变形B+树。 B+树是大型索引文件的标准组织方式。
- 特点:
每个分支节点至多有m棵子树。
根节点或者没有子树,或者至少有两棵子树
除根节点,其他每个分支节点至少有m/2棵子树
有n棵子树的节点有n个关键字。
所有叶子节点包含全部关键字及指向相应记录的指针
叶子节点按关键字大小顺序链接
叶子节点是直接指向数据文件中的记录。
所有分支节点(可看成是分块索引的索引表)
包含子节点最大关键字及指向子节点的指针。
散列查找
哈希表:
-
定义:又称散列表,是除顺序表存储结构、链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构。
哈希表是一种存储结构,它并非适合任何情况,主要适合记录的关键字与存储地址存在某种函数关系的数据。
哈希表与哈希地址
哈希函数:把关键字为ki的对象存放在相应的哈希地址中
哈希表:存储数据记录的长度为m(m≥n)的连续内存单元 -
哈希冲突
对于两个关键字分别为ki和kj(i≠j)的记录,有ki≠kj,但h(ki)=h(kj)。把这种现象叫做哈希冲突(同义词冲突)。
在哈希表存储结构的存储中,哈希冲突是很难避免的。 -
哈希表设计
哈希表设计主要需要解决哈希冲突。实际中哈希冲突是难以避免的,主要与3个因素有关: -
哈希表长度
与装填因子有关。
装填因子α=存储的记录个数/哈希表的大小=n/m
α越小,冲突可能性就越小;α越大(最大可取1),冲突的可能性就越大。控制在0.6~0.9的范围内
与所采用的哈希函数有关。
与解决冲突方法有关。 -
哈希函数构造方法
-
直接定址法
直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。
优点:简单,均匀,不会产生冲突;
缺点:关键字分布不连续将造成内存单元的大量浪费
数学函数:h(k)=h+c; -
数字分析法:
适合于所有关键字值都已知的情况,并需要对关键字中每一位的取值分布情况进行分析。 -
除留余数法
除留余数法是用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址的方法。
数学函数:h(k)=h%p;(p多为质点) -
哈希冲突解决方法
-
开放定址法
-
定义:开放地址法就是指:一旦发生了冲突就去寻找下一个空的哈希地址,只要哈希表足够大,空的散列地址总能找到,并将记录存入。
-
公式:
Hi=(H(key) + Di) mod m (i = 1,2,3,….,k k<=m-1)(线性探查法)
Hi=(H(key) +/- Di^2) mod m (i = 1,2,3,….,k k<=m-1)(平方探查法)
平方探查法是一种较好的处理冲突的方法,可以避免出现堆积现象。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元。 -
拉链法
拉链法是把所有的同义词用单链表链接起来的方法。 -
ASL
ASL成功=∑每个关键字比较次数/关键字个数
ASL不成功=∑关键字比较到空结点所需的次数/p -
开发地址法创建哈希表,结构体定义如下
#define NULLKEY -1 //定义空关键字
#define DELKEY -2 //定义被删关键字
typedef int KeyType; //关键字类型
typedef struct
{
KeyType key; //关键字域
int count; //探测次数域
}HashTable; //哈希表单位类型
- 哈希表的插入和建表如下
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++;
}
void CreatHT(HashTable ha,int &n,int m,int p,KeyType keys[],int nl)
{
for(int i=0;i<m;i++
{
ha[i].key=NULLKEY;
ha[i].count=0;
}
n=0;
for(i=0;i<nl;i++)
InsertHT(ha,p,keys[i],int &n);
}
- 用拉链法构造哈希表,称为哈希链,是一种顺序和链式相结合的存储结构,结构体定义如下
typedef int KeyType; //关键字类型
typedef struct node
{
KeyType key; //关键字域
struct node*next; //下一个结点指针
}NodeType; //单链表结点类型
typedef struct
{
NodeType *firstp; //首结点指针
}HashTable; //哈希表单元类型
- 拉链法建哈希表的插入建表
void InsertHT(HashTable ha[],int &n,int p,KeyType k) //将关键字k插入到哈希表中
{
int adr;
adr=k%p; //计算哈希函数值
NodeType *q;
q=(NodeType*)malloc(sizeof(NodeType));
q->key=k; //创建一个结点q,存放关键字k
q->next=NULL;
if(ha[adr].firstp==NULL)
ha[adr].firstp=q;
else //若单链表adr不为空
{
q->next=ha[adr].firstp; //采用头插法插入到ha[adr]的单链表中
ha[adr].firstp=q;
}
n++; //哈希表中结点的总个数增1
}
void CreatHT(HashTable ha[],int &n,int m,int p,KeyType keys[],int nl) //由关键字序列keys[0..nl-1]创建哈希表
{
for(int i=0;i<m;i++) //哈希表置初值
ha[i].firstp=NULL;
n=0;
for(i=0;i<nl;i++)
InsertHT(ha,n,p,keys[i]); //插入n个关键字
}
谈谈你对查找的认识及学习体会
对查找主要学了线性表查找、树表查找以及哈希表的查找。线性表查找主要是顺序查找、折半查找和分块查找,树表查找主要是二叉排序树、AVL树、B-树和B+树,哈希表查找主要是开发地址法建哈希表查找和拉链法建哈希表查找。无论哪种查找,都要涉及插入、创建、删除、查找和计算成功和不成功的ASL等操作。学习完查找之后,发现有这么多的查找方法,方便了以后从一系列数据中查找某个数据的过程,感觉难度和树、图都差不多。查找算法可以广泛应用到各种搜索引擎上,也可以用于用户查找个人信息等。
PTA题目介绍
2.1 7-1 是否完全二叉搜索树
2.1.1 该题的设计思路
- 本题是有关二叉搜索树的一道题,有两个概念,一个是完全二叉树,一个是二叉搜索树,完全二叉树的特点是叶子结点的上一层是满的,并且没有结点是有右孩子而无左孩子的,二叉搜索树的特点是所以结点的左孩子都比根结点小,右孩子都比根结点大,但本题给定的条件是相反的,所以要注意这一点。本题问题就是给你一组数据,让你建一棵二叉搜索树,然后判断是否是完全二叉搜索树。
基本思路就是建好树之后就层次遍历,当遍历到空结点的时候,赋值为-1给一个数组,其他结点的数据原样赋给一个数组,最后遍历数组,看是否有在出现小于0后又出现大于0的情况,若有,则输出NO,否则输出YES。时间复杂度为O(n)。
2.1.2该题的伪代码
bool IsCompleteTree(BSTree& T, int n)//判断是否为完全二叉树
{
初始化一个队列Q
if(根节点为空)
返回true
else
{
定义一个变量count计算访问了几个节点
将根节点入队
BSTree t;//初值为队首元素
while(t不为空)
{
将t的左孩子和右孩子入队
累计访问的节点
}
end while
}
if(所有节点都被访问过)
返回true
else
返回false
}
void LevelOrder(BSTree& T, int n)//层序遍历输出结点
{
初始化一个队列Q
定义一个数组data存储层序遍历的结果
将根节点入队
while(队列不为空)
{
取队首元素
给数组data赋值
if(左子树不空)
将左子树入队
if(右子树不空)
将右子树入队
}
输出层次遍历结果
}
void InsertBST(BSTree& T, int e)//向树中插入元素
{
if(初始树为空时)
给树中节点赋初值
else if(插入的数值比当前节点的值小)
{
将要插入的数值插入左子树
}
else if(插入的数值比当前节点的值大)
{
将要插入的数值插入右子树
}
}
void CreateBST(BSTree& T, int a[], int n)//创建二叉搜索树
{
树初始化为空
将输入的数值依次向树中插入
}
2.1.3提交列表
- 段错误:一开始写的时候创建二叉搜索树的函数有返回其根节点,但是在主函数里根节点是空的,后面函数调用就出错了。
- 部分正确:刚开始不清楚要满足什么条件才能判定是完全二叉树,怎么改都是答案错误,后来又换另一种方法,用一个变量记录访问过的节点,当程序执行完后,若所有节点都被访问过则说明是完全二叉树
2.1.4本题涉及的知识点
- (1)使用队列实现树的层次遍历
1.首先将根节点入队
2.遍历当前层所有节点,并用数组保存访问过的节点,然后将下一层的节点入队
3.最后得到的每一层节点的值依次输出 - (2)判断一棵树是否为完全二叉树
1.若当前访问节点左孩子为空,右孩子不为空,则该树不是完全二叉树
2.若当前访问节点左孩子不为空,右孩子为空;或者其左右孩子都为空,且该节点之后的队列中的节点都是叶子节点,则该树为完全二叉树
2.2题目2:QQ帐户的申请与登陆
2.2.1该题的设计思路
- 使用map容器存储账号与密码,可以处理QQ账号与密码一对一的数据处理,其账号与密码用数组方式插入。
2.2.2 该题的伪代码
void Check(int n, string str1, string str2)
{
数组str1存储账号,str2存储密码
for i=0 to n
{
输入一个字符ch
if(ch为N)
{
输入账号和密码
if(此账号已经注册过)
输出ERROR: Exist
else
将此账户密码保存入Q,输出New: OK
}
else if(ch为L)
{
输入账号和密码
if(此账号不存在)
输出ERROR: Not Exist
else
{
if(账号密码都匹配,登录成功)
输出Login: OK
else //帐户密码错误
输出ERROR: Wrong PW
}
}
}
end for
}
2.2.3 PTA提交列表
2.2.4涉及知识点
- 使用map得包含map类所在头文件#include"map"
- 所用到的map基本操作函数:find()--查找一个元素;end()--返回指向map末端的迭代器
- find()用于定位数据出现位置,当数据出现时返回数据所在位置的迭代器,若map中没有要查找的数据,则它返回的迭代器等于end()返回的迭代器。如本题中Q.find(str1) == Q.end()表示查找的账号不存在
二叉搜索树的最近公共祖先
2.3.1题解
- 数据表达:变量m表示待查询的节点对数,变量n表示二叉搜索树中节点个数。接着输入n个不同的整数,表示二叉搜索树先序遍历时每个节点的值。再输入m行数据,每行数据包括两个整数 u和v,用以表示二叉搜索树中两个不同节点的值。输出分为四种情况:1.若值为u或v的节点无法在二叉搜索树中查找到,则按照题面要求输出u或者v找不到;2.若值为u和值为v的节点都无法在二叉搜索树中查找到,则按照题面要求输出u和v找不到;3.若值为u和值为v的两个节点中,有一个节点是另一个节点的祖先节点,则按照题面要求输出;4.按照题面要求正常输出值为u和值为v的两个节点的最近的公共祖先节点
2.3.2伪代码
设置全局变量flag,判断值为u和值为v的两个节点是否是对方的祖先节点
int main()
{
输入n个整数,用数组Key保存所有节点的值,并调用Insert函数插入二叉搜索树的每一个节点
输入m对整数键值u和v,并调用Find函数在构建好的二叉搜索树中进行查找,依照查找结果根据题目要求输出
分别调用PreTraverse函数,判断值为u和值为v的两个节点是否是对方的祖先节点,若都不是,则置全局变量flag的值为1
if (flag)
{
for (j = 0; j < n; j++) //遍历整个数组Key
{
if(数组元素的值位于整数u和v之间)
then 按照题目要求输出两节点的最近公共祖先节点,break跳出循环
}
}
return 0;
}
BinTree Insert(BinTree BST, int X) //在二叉搜索树BST中插入值为X的节点
{
if (BST == NULL) //若树为空树
then 创建新节点,节点的值为X,节点的左右孩子置空
else if (BST->Data == X) //树中已有值为X的节点
then 返回树的根节点
else if (X < BST->Data)
then 递归调用Insert函数,将值为X的节点插入到左子树中
else
then 递归调用Insert函数,将值为X的节点插入到右子树中
返回树的根节点
}
BinTree Find(BinTree BST, int X) //在二叉搜索树中查找值为X的节点
{
while (BST != NULL) //当树不空时查找
{
if (BST->Data == X) //查找成功
then 返回根节点
else if (BST->Data > X)
then 根节点移到左孩子节点上,迭代查找
else
then 根节点移到右孩子节点上,迭代查找
}
return NULL; //未找到,返回NULL
}
void PreTraverse(BinTree BST, int n, int m) //根节点值为m,对这棵二叉搜索树进行先序遍历
{
if (BST == NULL) //若树为空树
then 全局变量flag的值置为1,直接返回
else
{
if (n == BST->Data) //找到值为n的节点
then 按照题目要求输出值为m的节点是值为n的节点的祖先节点
else if (n < BST->Data)
then 递归调用PreTraverse函数,在左子树中查找
else
then 递归调用PreTraverse函数,在右子树中查找
}
}
2.3.3提交列表
2.3.4知识点
-
1.构建二叉搜索树就是一个不断插入节点的过程,插入节点时,可借助递归使节点正确地插入到根节点的左子树或右子树上。而在二叉搜索树中查找特定元素时,可借助迭代,边移动根节点指针边查找特定元素
-
2.判断一个节点A是否是另一节点B的祖先节点时,可先调用查找函数在二叉搜索树中查找前一个节点A,再以此节点A为根节点进行先序遍历。若先序遍历的过程中某个节点的值等于后一个节点B的值,则说明节点A是节点B的祖先节点