查找博客作业

1.本周学习总结(0-5分)

1.1 查找的性能指标

ASL,即平均查找长度,在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。
计算公式:查找博客作业
其中n为查找表中元素个数,pi为查找第i个元素的概率,通常假设每个元素查找概率相同,pi=1/n,Ci是找到第i个元素的比较次数。
ASL的计算与移动次数和比较次数有关,决定了该查找算法的时间复杂度。

顺序查找

查找方式为从头扫到尾,找到待查找元素即查找成功,若到尾部没有找到,说明查找失败
Ci(第i个元素的比较次数)在于这个元素在查找表中的位置,如第0号元素就需要比较一次,第一号元素比较2次......第n号元素要比较n+1次。所以Ci=i;
当待查找元素不在查找表中时,也就是扫描整个表都没有找到,即比较了n次,查找失败。
查找成功:查找博客作业
查找不成功:查找博客作业

二分查找

前提是有序表,利用有序表构建二叉树,中间数为根,左小右大,即为判定树。
查找方式为(找k),先与树根结点进行比较,若k小于根,则转向左子树继续比较,若k大于根,则转向右子树,递归进行上述过程,直到查找成功或查找失败

ASL成功=查找博客作业
ASL不成功=查找博客作业

1.2 静态查找

顺序查找

定义:在一个已知无(或有序)序 队列中找出与给定关键字相同的数的具体位置。原理是让关键字与 队列中的数从最后一个开始逐个比较,直到找出与给定关键字相同的数为止,它的缺点是效率低下。

int sq_search(keytype keyp[],int n,keytype key)
{
   int i;
   for(i=0; i<n; i++)
    if(key[i] == key)
      return i;//查找成功
   return -1;//查找失败
}

时间复杂度为:O(n)
顺序查找的优缺点:
缺点:查找效率较低。
优点:算法简单而且使用面广。

二分查找

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

int binary_search(int key,int a[],int n) //自定义函数binary_search()
{
    int low,high,mid,count=0,count1=0;
    low=0;
    high=n-1;
    while(low<high)    //査找范围不为0时执行循环体语句
    {
        count++;    //count记录査找次数
        mid=(low+high)/2;    //求中间位置
        if(key<a[mid])    //key小于中间值时
            high=mid-1;    //确定左子表范围
        else if(key>a[mid])    //key 大于中间值时
            low=mid+1;    //确定右子表范围
        else if(key==a[mid])    //当key等于中间值时,证明查找成功
        {
            printf("查找成功!\n 查找 %d 次!a[%d]=%d",count,mid,key);    //输出査找次数及所査找元素在数组中的位置
            count1++;    //count1记录查找成功次数
            break;
        }
    }
    if(count1==0)    //判断是否查找失敗
        printf("查找失敗!");    //査找失敗输出no found
    return 0;
}

时间复杂度为:O(logn)
二分查找的优缺点:
优点:折半查找的时间复杂度为O(logn),远远好于顺序查找的O(n)。
缺点:虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。既使采用高效率的排序方法也要花费O(nlgn)的时间。

1.3 二叉搜索树

二叉排序树又称为二叉搜索树,它是一种特殊结构的二叉树,其定义为:二叉树排序树或者是一棵空树,或者是具有如下性质的二叉树:
(1)若它的左子树非空,则左子树上所有结点的值均小于根结点的值;
(2)若它的右子树非空,则右子树上所有结点的值均大于根结点的值;
(3)它的左右子树也分别为二叉排序树

1.3.1 如何构建二叉搜索树

现在比如说我要将 7 4 5 6 1 8 9这七个数放到一个二叉排序树中
首先,将7放入一个节点:
查找博客作业
下面放第二个数字 4 ,这个数字在放的时候需要和我们的根节点比较大小,比根节点大就放在右边,比它小就放在左边,4比7小因此应该放在左边。
查找博客作业
接下来放5,将5和7比较,发现比7小,因此应该放在左边,但这时左边已经有一个4了,那么我们需要把5和4进行比较,5大于4,因此应该放在4的右边。
查找博客作业
接下来插6,6和7比,比7小,再和4比,比4大,再和5比,比5大,因此应该放在5的右边。
查找博客作业
接下来放1,应该就放在4的左边。
查找博客作业
接下来放8,8比7大,应该放在7的右边。
查找博客作业
最后一个9,就应该放在8的右边。
查找博客作业
至此,就将这几个数放在二叉排序树中了。

ASL计算

每层的节点数×查找次数(即高度)进行求和即可得到成功的ASL;
每层中空节点数×查找父亲节点的次数(高度-1)进行求和即可得不成功的ASL。

在二叉搜索树中删除数据

1.仅有一颗子树的情况其实也非常简单,只需要将其唯一的子树接到其原父亲结点处,即原本指向被删除结点的指针改为指向被删除结点的子树。
2.有两颗子树:这种情况就有些特殊了,如果要保证删除后二叉搜索树的相对位置不变(即中序遍历顺序不变),那有两种方法:
(1)使待删除结点的左子树代替被删除的结点,将被删除结点的右子树放置于被删除结点的左子树的最右边。
(2)用待删除结点的直接前驱或直接后继(指的是遍历顺序的)代替被删除的结点,然后再删除用以替代的原节点。

1.3.2 如何构建二叉搜索树(代码)

构建二叉搜索树

Node *insert(Node *rt,int x)
{
    if(rt==NULL)
    {
        //rt = Node();
        Node *q = new Node;
        q->val=x;
        q->left=q->right=NULL;
        return q;
    }
    if(rt->val>x)
    {
        rt->left = insert(rt->left,x);
    }
    else
    {
        rt->right = insert(rt->right,x);
    }
    return rt;
}

删除代码

删除比较麻烦,需要分三种情况讨论。
(1)删除叶节点,直接将其删除即可
(2)删除的节点只有左子树或者只有右子树,则让子树取代它
(3)删除的节点既有左子树又有右子树,则在左子树中找到值最大的结节,让其取代它。

Node *remove(Node *rt,int x)
{
    if(rt==NULL) return NULL;
    if(rt->val>x) //先找到被删除节点
    {
        rt->left = remove(rt->left,x);
    }
    else if(rt->val<x)
    {
        rt->right = remove(rt->right,x);
    }
    else 
    {
        if(rt->left&&rt->right) //左右子树都不为空
        {
            Node* tmp = rt->left; //找左子树的最大值,只要一直往右找即可
            while (tmp->right)
            {
                tmp = tmp->right;
            }
            //cout<<tmp->val<<endl;
            int v = rt->val;
            rt->val = tmp->val;
            tmp->val = v;//替换
            rt->left = remove(rt->left,v); //在左子树中删除
        }
        else if(rt->left==NULL&&rt->right==NULL) //叶节点,直接赋值为空
        {
            rt = NULL;
        }
        else
        {
            if(rt->left) //
            {
                rt = rt->left;
            }
            else
            {
                rt = rt->right;
            }
        }   
    }
    return rt;
}

时间复杂度:最好:O(logn),最差:O(n)
用递归实现插入、删除的优势:保留父子关系,便于删除和插入顺利找到父亲和孩子。

1.4 AVL树

二叉排序树中查找的操作执行时间与树形有关,在最坏情况下执行的时间为O(n)(单边树时)。为了提高查找效率,在既能保持BST性质,又能保证树的高度在任何情况下为log2,这样在查找过程中,即使是最坏情况下,执行的时间也还是O(log2),这样的二叉树称为平衡二叉树。
一棵平衡二叉树中,每个结点的左,右子树高度最多相差1。在算法中,我们通过平衡因子来具体实现平衡二叉树的定义。一个结点的平衡因子是该结点左子树的高度减去右子树的高度(或者右子树的高度减去左子树的高度)。如果一棵二叉树中的所有结点平衡因子的绝对值都不超过1,那么这颗二叉树就是平衡二叉树
平衡二叉树中插入结点的过程
若向平衡二叉树中插入一个新结点(总是作为叶子结点插入)后破坏了平衡性,首先从该新插入的结向根结点方向查找第一个失衡的结点,然后进行调整。
LL型调整
LL型失衡:麻烦结点在不平衡发现者的左子树的左子树上,因而叫LL型插入,需要LL型调整;
LL型调整:把不平衡发现者的左儿子提到不平衡发现者的位置,使他成为新的根,而不平衡发现者则插入到之前他左儿子的右儿子上,新根之前的右儿子插入到不平衡发现者的左儿子上面。
具体图示如下:
查找博客作业

RR型调整
RR型失衡:麻烦结点在不平衡发现者的右子树的右子树上,因而叫RR型插入,需要RR型调整。
RR型调整:把不平衡发现者的右儿子提到不平衡发现者的位置,使他成为新的根,而不平衡发现者则插入到之前他右儿子的左儿子上,新根之前的左儿子插入到不平衡发现者的右儿子上面。
具体图示如下:
查找博客作业

LR型调整
LR型失衡:麻烦结点在不平衡发现者的左子树的右子树上,因而叫LR型插入,需要LR型调整。
LR型调整:把不平衡发现者的左儿子的右儿子提到不平衡发现者的位置,使他成为新的根,不平衡发现者则插入到新根的右儿子上,而不平衡发现者的左儿子则插入到新根的左儿子上面,新根之前的右儿子则插入到不平衡发现者的左儿子上面。
具体图示如下:
查找博客作业

RL型调整
RL型失衡:麻烦节点在不平衡发现者的右子树的左子树上,因而叫RL型插入,需要RL型调整。
RL型调整:把不平衡发现者右儿子的左儿子提到不平衡发现者的位置,使他成为新的根,不平衡发现者则插入到新根的左儿子上,而不平衡发现者的右儿子则插入到新根的右儿子上面,新根之前的左儿子则插入到不平衡发现者的右儿子上面。
具体图示如下:
查找博客作业

1.5 B-树和B+树

B-树

B-树一个节点可以放多个关键字,降低树的高度。可放外存,适合大数据量查找,一棵m阶B-树符合以下特性:
1.每个结点至多m个孩子结点,至多有m-1个关键字,除根节点外,其他节点至少有m/2个孩子结点,至少有m/2-1个关键字;
2.若根节点不是叶子结点,根节点至少两个孩子结点;
3.叶子结点一定都在最后一层,叶子结点下面还有一层是虚设的外部结点————就是表示查找失败的结点,不含任何信息,在计算B-树高度时要计入最底层的外部结点;
结构体定义:

#define MAXM 10
typedef node* BNode;
typedef struct node
{
	int keynum;
	int key[MAXM];
	BNode parent;
	BNode ptr[MAXM];
}BTNode;
int m;
int MAX;
int MIN;

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[i],则沿着指针ptr[i]所指的子树进程查找;
5.查找某个结点,若相应指针为空,落入一个外部结点,表示查找失败;类似判断树和二叉排序树的查找。

B-树的插入

往B-树中插入一个结点,插入位置必定在叶子结点层,但是因为B-树中对关键字个数有限制,所以,插入情况分为以下两种:
1.关键字个数n<m-1,不用调整
2.关键字个数n=m-1,进行分裂;如果分裂完之后,中间关键字进入的结点中关键字个数达到n=m-1,就继续进行分裂
查找博客作业

B+树

一棵m阶b+树满足条件:
1.每个分支节点至多有m棵子树;
2.根结点或者没有子树,或者至少两棵子树;
3.除根结点,其他每个分支结点至少有m/2棵子树;
4.有n棵子树的结点有n个关键字;
5.所有叶子结点包含全部关键字及指向相应记录的指针;

叶子结点按关键字大小顺序链接;
叶子结点时直接指向数据文件的记录;
6.所有分支结点(可以看成是分块索引的索引表),包含子节点最大关键字及指向子节点的指针;

B+树的查找

因为所有的叶子结点链接起来成为一个线性链表,可直接总最小关键字开始进行顺序查找所有叶子节点;

B-树和B+树的区别

这都是由于B+树和B-具有这不同的存储结构所造成的区别,以一个m阶树为例。
1.关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B-树虽然也有m个子结点,但是其只拥有m-1个关键字。
2.存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B-树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。
3.分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。
4.查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。

1.6 散列查找

哈希表又称为散列表,是除顺序表存储结构,链接表存储结构和索引表存储结构之外的又一种存储线性表的存储结构,其基本思路是,设要存储的元素个数为n,设置一个长度为m的连续内存单元,以每个元素的关键字ki(i取值为0~n-1)为自变量,通过一个称为哈希函数的函数h(ki),把ki映射为内存单元的地址(或下标),并把该元素存储在这个内存单元中,这个地址也称为哈希地址。这样构造的线性存储结构就是哈希表。
在建哈希表示可能存在两个关键字虽然不相同,但是其哈希地址却一样的情况,这种现象叫做哈希冲突。通常把具有不同关键字却有相同哈希地址的元素称为同义词。

哈希表构造

直接定址法

直接定址法是以关键字k本身或关键字加上某个数值常量C作为哈希链地址的方法,哈希函数为:h(k)=k+c
优势:计算简单,不可能有冲突发生;
缺点:关键字分布不连续,将造成内存单元的大量浪费。

除留取余数法

用关键字k除以某个不大于哈希表长度m的数p所得的余数作为哈希地址,h(k)=k%p(这里的p最好为不大于m的质数,减少冲突的可能性)
优势:计算简单,适用范围广泛;
缺点:容易发生哈希冲突。如果在上面的例子中插入14:h(14)=14%7=0 于7发生了哈希冲突。

哈希冲突的解决

开放定址法

在出现哈希冲突时,再哈希表中找到一个新的空闲位置存放元素,例如:要存放关键字k,d=h(k),而地址d已经被其他元素占用了,那么就在d地址附近寻找空闲位置进行填入。开放定址法分为线性探测和平方探测等

线性探测

线性探测是从发生冲突的地址d0开始,依次探测d0的下一个地址(当到达哈希表表尾时,下一个探测地址为表首地址0),直到找到一个空闲的位置单元为止,探测序列为:d=(d+i)%m,所以,我们在设置哈希表的长度时一定要大等于元素个数,保证每个数据都能找到一个空闲单元插入
查找博客作业

拉链法

拉链法是把所有的同义词用单链表链接起来的方法,所有哈希地址为i元素对应的结点构成一个单链表,哈希表地址空间为0~m-1,地址为i的单元是一个指向对应单链表的头结点。这种方法中,哈希表的每个单元中存放的不再是元素本身,而是相应同一词单链表的首结点指针。
优点:
处理冲突简单,无堆积现象,非同义词不会发生冲突;
结点空间时动态申请的,空间利用效率高;
在链表中,删除结点操作方便。

2.PTA题目介绍

2.1 是否完全二叉搜索树

2.1.1 伪代码

函数:
void Insert(BinTree*& BST, int k);//把结点k插入二叉排列树中
bool IsCompleteBinaryTree(BinTree*& BST, int n);//判断是否为完全二叉树
void LevelOutput(BinTree *BST);//层次遍历输出

构造二叉搜索树://注意:题目要求左子树键值大,右子树键值小
    如果BST为空
        建立新结点
    如果结点k<BST->key 
        递归进入右子树
    如果结点k>BST->key
        递归进入左子树  

判断是否为完全二叉树://即判断是否所有结点都已经遍历
   queue存储树结点的队列
   定义变量num来判断是否所有结点都已经遍历
   if 树空
      也算作是完全二叉树,返回true
   end if
   先把树的根节点放入队列
   while 1
       取队首node
       如果为空则跳出循环
       不为空则让左右子树进栈,先左后右
       出栈队首并使得输出个数num增1
   end while
   判断num的值,等于节点个数n则说明在遇到空节点前树节点遍历完成,返回true,反之,则返回false

层次遍历输出类似于判断是否为完全二叉树的函数,其实两个函数可以结合

2.1.2本题知识点

1.完全二叉树的结构
2.二叉树的层次遍历
3.二叉搜索树的插入操作

2.2 航空公司VIP客户查询

2.2.1 伪代码

声明一个map容器且对应类型都是long long的名称为vip
声明一个迭代器ster
定义整型变量IDNum存省份证号
定义字符型变量tail存身份证最后一位
定义distance存里程数
输入n,k//n代表n组数,k代表最低里程
while n-- do
    输入IDNum,tail,distance
    若tail为x则将IDNum置为负数
    iter为IDNum在map中的位置
    if 此IDNum不在vip中 do
        若distance小于最低里程k,则置为k
        将IDNum和distance的对应存入vip中
    else do    //IDNum存在
        若distance小于最低里程k,则置为k
        在该用户拥有的里程数基础上加上distance
    end if
end while
输入m
while m-- do//查找身份证所包含的里程数
    输入IDNum和tail
    若tail为x则将IDNum置为负数
    找IDNum在vip中的位置
    找到输出里程数,找不到输出No Info
end while

2.2.2本题的知识点

1.哈希链的构造创建和寻找,建链前需要对哈希链进行初始化和申请空间。
2.哈希链拉链法的使用
3.map库的使用

2.3 基于词频的文件相似度

2.3.1伪代码

从单词入手
定义int类型变量的两个文件内编号 file_a,file_b作为待查找的文件编号
定义变量类型为<string,int[]>的map容器构建单词索引表
根据单词在单词索引表中构建映射
for i=0 to 文件组数
    输入文件编号
    for iterator=map.begin() to !map.end()遍历单词
        if 单词在两个文件都出现过
            修正重复单词数,合计单词数
        else if 单词在其中一个文件中出现过
            修正合计单词数
        end if
    end for
end for
计算输出文件相似度

相关文章:

  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2022-12-23
  • 2021-12-14
  • 2022-12-23
相关资源
相似解决方案