查找
这个作业属于哪个班级 | 数据结构--网络2011/2012 |
---|---|
这个作业的地址 | DS博客作业05--查找 |
这个作业的目标 | 学习查找的相关结构 |
姓名 | 王小雨 |
0.PTA得分截图
1.本周学习总结
1.1查找的性能指标
关键字的平均比较次数,也叫平均搜索长度ASL
成功时ASL是指找到中任一记录平均需要的关键字比较次数
1.2静态查找
顺序查找
表中有与k相等的关键字时,比较次数
表中无与k相等的关键字时,比较次数为表长度
typedef struct{
KeyType key;//关键字
InfoType data;//其他数据
}NodeType;
typedef NodeType SeqList[max];//顺序表
int SeqSearch(SeqList R,int n,KeyType k)
{
int i=0;
while(i<n&&R[i].key!=k) i++;
if(i>=n) return 0;//没找到
else return i+1;//找到
}
顺序查找的ASL:成功时(n+1)/2;不成功时n
二分查找
要求线性表中的结点已经按关键字值的递增或递减顺序排列
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) return mid+1;
if(k<R[mid].key) BinSearch1(R,low,mid-1,k);
else BinSearch1(R,mid+1,high,k);
}
else return 0;
}
判定树
折半查找ASL:[log2(n+1)]
索引
索引存储结构=数据表+索引表
索引项一般形式:关键字+地址
分块查找
key中为块中最大关键字,link中为该块的起始地址
先在索引表中查找,第一个大于80的,然后在数据表中对应块的起始位置查找
1.3二叉搜索树
如何构建二叉搜索树
特征:若它的左子树不空,则左子树上所有结点的值均小于根节点的值
若它的右子树不空,则右子树上所有结点的值均大于根节点的值
二叉排序树中没有相同关键字的结点
中序遍历二叉排序树得到的结果是一个关键字递增的有序序列
typedef struct node{
KeyType key;//关键字
InfoType data;//其他数据域
struct node *lchild,*rchild;
}BSTNode,*BSTree;
BSTNode *SearchBST(BSTNode *bt,KeyType k)//递归
{
if(bt==BULL)||bt->ket==k) return bt;//递归口
if(k<bbt->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;//没找到
}
二叉排序树的插入
插入一定在叶节点上
int InsertBST(BSTree &p,KeyType k)
{
if(p==NULL)//树空,创建一个key域为key的结点
{
p=new BSTNode;
p->key=k;p->lchild=NULL;p->rchild=NULL;
return 1;
}
else if(k==p->key) return 0;//树中已有该关键字k,无须插入
else if(k<p->key) return InsertBST(p->lchild,k);//k插入根节点的左子树中
else return InsertBST(p->rchild,k);
}
创建二叉搜索树
BSTNode *CreatBST(KeyType A[],int n)//返回树根指针
{
BSTNode *bt=NULL;//初始化
int i=0;
while(i<n)
{
InsertBST(bt,A[i]);
i++;
}
return bt;//返回建立的根指针
}
二叉排序树的生成
任何结点插入时,都要从根节点开始比较
不同插入次序的序列生成不同形态的二叉排序树
求二叉排序树的最大结点和最小结点
二叉排序树特点:
中序遍历的序列为递增序列;
根节点最左下的结点是关键字最小的;
根节点最右下的结点是关键字最大的
KeyType maxnode(BSTNode *p)//最大
{
while(p->rchild!=NULL) p=p->rchild;
return (p->data);
}
KeyType minnode(BSTNode *p)//最小
{
while(p->lchild!=NULL) p=p->lchild;
return (p->data);
}
二叉排序树结点删除
一.被删的是叶子节点
直接删去该结点:其双亲结点中相应指针域值改为空
二.被删的结点只有左子树或只有右子树
用其左子树或其右子树代替它:其双亲结点中相应指针域值改为被删结点的左子树或右子树
三.被删结点既有左子树也有右子树
用其前驱取代它,其前驱为左子树中最大结点;或用其后继取代他,其后继为右子树中最小结点
int DeleteBST(BSTNode *&bt,KeyType k)
{
if(bt->key<k) DeleteBST(bt->lchild,k)
if(bt->key>k) DeleteBST(bt->rchild,k)
if(bt->key==k)
{
if(bt右孩子空)保存bt左孩子node,删除bt,bt=node
if(bt左孩子空)保存bt右孩子node,删除bt,bt=node
if(bt有左右孩子)
{
递归查找bt左子树最右孩子r
bt->key=r->key
保存r左孩子node,删除r //r为最右孩子,所以一定有左孩子
r=node
}
}
删除关键字k
int DeleteBST(BSTNode &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);
else
{
Delete(bt);//找到,删除bt
return 1;
}
}
}
void Delete(BSTree &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)//既有左子树也有右子树的删除
{//r为p的左孩子
BSTNode *q;
if(r->rchild!=NULL) Delete1(p,r->rchild);//递归找到p的左子树中最大即最右的结点
else//此时r为p的左子树中最右的结点
{
p->key=r->key;
q=r;r=r->lchild;delete q;
}
}
1.4AVL树
平衡二叉树AVL树
所有结点的左右子树深度之差绝对值小于等于1
平衡因子:该节点左右子树的高度差
结点类型
typedef struct node{
KeyType key;//关键字
int bf;//平衡因子
InfoType data;//其他数据
struct node *lchild,*rchild;
}BSTNode;
平衡旋转
在AVL树中插入一个结点,可能导致失衡,所以要调整树结构,调整过程叫做平衡旋转
LL型
RR型
LR型
RL型
例:将(3,4,5,9,7)构成一棵平衡二叉排序树
1.5B-树和B+树
一个结点中可以放多个关键字,降低树的高度
有B-和B+树
B-树又称多路平衡查找树
B-树
定义:一棵m阶B-树或一棵空树,或是满足下列要求的m叉树:每个结点最多m个孩子结点(最多m-1个关键字);除根节点外,其他结点最少[m/2]向上取整个孩子结点,即最少[m/2]向上取整-1个关键字;若根节点不是叶子节点,根节点至少两个孩子节点
每个结点的结构为:
n为关键字个数,n+1个孩子指针
结点中关键字按大小排序
非根节点孩子个数:最大m 最小[m/2]向上取整
非根节点关键字个数:最大m-1 最小[m/2]向上取整-1
根节点最少两个孩子
结点类型定义
typedef int keytype;
typedef struct node{
int keynum;
keytype key[maxm];//关键字个数
struct node *parent;
struct node *ptr[maxm];
}BTNode;
B-树特点:
所有结点的平衡因子都等于0,所有外部节点都在同一层上;
计算B-树高度时,需计入最底层的外部节点;
外部结点为空,一棵B树中共n个关键字,则外部结点共n+1个
B-树的查找
将要查找的k与根节点中的key[i]进行比较
若k=key[i],则查找成功
若k<key[i],则沿着指针ptr[0]所指的子树继续查找
若key[i]<k<key[i+1],则沿着指针ptr[i]所指的子树继续查找
若k>key[n],则沿着指针ptr[n]所指的子树继续查找
B-树的插入
插入位置一定在叶子节点层
该结点关键字个数<m-1,直接插
该结点关键字个数=m-1,则需进行结点分裂
若原来没有双亲,则新建一个双亲结点,ki为该双亲,树高度加一
若原来有双亲,则将ki加入到该双亲结点中
B-树的删除
结点中关键字个数>[m/2]向上-1,直接删除
结点中关键字个数=[m/2]向上-1:从兄弟结点借关键字;若借不了,则进行结点合并
非叶子结点上删除关键字k:
从该结点的子树结点中借左子树中最大的或右子树中最小的关键字key代替k,删除原来的key
叶子节点删除:
向兄弟借:兄弟结点中最小关键字上移到双亲结点;双亲结点中大于删除关键字的关键字下移到删除节点
向兄弟借不了:先删除关键字,再把兄弟节点及删除关键字节点及双亲结点中分割二者的关键字合并成一个新的叶子结点
B+树
每个分支结点最多有m棵子树;
根节点没有子树或最少两棵子树;
除根节点,其他每个分支结点最少有[m/2]向上棵子树
有n棵子树的结点有n个关键字
B+树的查找
直接从最小关键字开始进行顺序查找所有叶节点构成的线性链表;从根节点出发一直到找到为止
1.6散列查找
哈希表的查找
哈希表又称散列表,是除顺序表,链表,索引表之外的又一种存储线性表的存储结构
通过某种函数关系得到各个关键字的存储地址
哈希冲突:两个关键字的地址可能会一样
装填因子a=存储的记录个数/哈希表的大小=n/m;a越小,冲突可能性越小
哈希函数构造方法
一.直接定址法
以关键字k本身或关键字加上常量c作为哈希地址
哈希函数:h(k)=k+c;
二.除留余数法
用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址
哈希函数:h(k)=k mod p(mod为取余,p<=m,p取素数)
解决哈希冲突
一.开放定址法
定义:冲突时找一个新的空的哈希地址
1.线性探查法
找被占用位置的后一个空位置
2.平方探查法
找被占用位置的前后方空位置:d+1,d-1.d+4,d-4
成功=总探测次数/关键字个数
不成功=每个位置移到空所需的探测次数之和/p
哈希表的运算
哈希表结构体
#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)
{
计算k的哈希地址adr=k%p;
若ha[adr]=k,则已存在k,不需要插入
while(ha[adr]不为空 或 ha[adr]不等于删除标记)
{
线性探查k的位置,即adr=adr+1;
计算探测次数count
}
ha[adr]=k;
哈希表长度+1
}
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++;//哈希表长度
}
哈希表查找
开放定址法
d为k的哈希地址
while(ha[d]!=空&&ha[d]!=k)
d=用某种探查法找下一个地址
if(ha[d]==空)
return 失败标记
else
return ha[d];
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;
else return -1;
}
哈希表删除
不能真正删除,做个删除标记DELKEY
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--;//长度-1
return 1;
}
else return 0;
}
二.拉链法
把地址相同的关键字用单链表连接起来,头节点用数组存储,一条链上的关键字用头插法插入
成功=每层的个数和/关键字个数
不成功=每条链中有1个结点的的个数+有2个结点的个数+.../p
哈希链结构体
typedef struct HashNode{
int key;
struct HashNode *next;
}HashNode,*HashTable;
HashTable he[max];
建哈希链
void CreateHash(HashTable ht[],int n)
{
建n条带头节点的哈希链,初始化链表;
for i=1 to k;
输入数据data;
InsertHash(ht,data)
}
插入
InsertHash(ht,data)
{
计算哈希地址adr=data%p;
查找链ha[adr],存在则不插入
不存在:
data生成数据节点node;
头插法插入链ht[adr];
}
删除
InsertHash(ht,data)
{
计算哈希地址adr=data%p;
若链ht[adr]不为空:
查找链ht[adr],找到删除
}
查找
d为地址
p=ha[d]->next;//指向第d条链的表头节点
while(p!=NULL%%p->key!=k)//在ha[d]的单链表中查找
p=p->next;
if(p=NULL)
return 失败标记
else
return p所指结点
2.PTA题目介绍
2.1是否完全二叉搜索树
完全二叉树的特点是只有最后一层有的空的情况。当我们层次遍历树的时候,最后一层才有空结点且在右边反映到结果上就是我们的所有空结点都在最后。一旦出现空结点,后面就不会有其他结点
2.2.1伪代码
主函数
for i->num
调用InserterBST函数,插入数据
调用PrintBT函数来
if(是完全二叉树)
printf("YES");
else
Printf("NO");
队列Q
if (bt不为空)
进队
end if
while(队Q不为空)
curnode=队头
if (curnode != NULL)
输出节点
end if
出栈队头
if curnode不为空
两个孩子进栈
end if
if curnode为空&&队头不为空
return false
end if
2.1.2提交列表
2.1.3知识点
1.完全二叉树的特点
2.中序遍历二叉树得到的是一个递增序列,依据该特点可验证二叉搜索树建立是否正确
2.2航空公司VIP客户查询
2.2.1伪代码
//主函数
调用CreateHash函数,建立哈希表
for i->要查询的数量
输入身份证号
adr=函数(取地址)
调用函数FindHash
if 有相同的会员号
输出公里数
else
输出“No Info”
end for
//建函数CreateHash
for i->所有人
输入会员号和公里
adr=函数(取地址)
if 地址内为空
插入会员号和公里数
end if
else
while 本地址内所有的会员号
if 找到相同的
H[i].dis+=公里
end if
else
没找到
return -1
end else
2.2.2提交列表
2.2.3知识点
1.用map会超时,用链表的时间更低
2.哈希链表的长度要适用于数据