数据结构 查找

查找和排序是数据处理系统中最重要的两个操作,其次是插入、删除

静态查找:

在查找时只对数据元素进行查询或检索,查找表称为静态查找表。

动态查找:

在实施查找的同时,插入查找表中不存在的记录,或从查找表中删除已存在的某个记录

三种查找方法:

  • 顺序表和链表的查找:将K(关键字)与查找表中记录的关键字逐个进行比较,找到要查找的记录
  • 散列表的查找:根据给定的K值直接访问查找表,从而找到要查找的记录
  • 索引查找表的查找:首先根据索引确定带查找记录所在的快,然后从快中找到要查找的记录
  • 评价查找方法的指标 ASL=sum(PixCi)
    • Pi:查找第i个记录的概率,一般都是1/n;
    • Ci:查找第i个记录需要进行比较的次数;

顺序查找

 逐个比较,直到找到元素,输出结果

在编写时,常把i=0的数据设为要查找的元素,可减少代码与时间

#include <iostream>
using namespace std;
typedef float keytype;
typedef int keytype;
typedef char keytype;
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
#define maxsize 6
typedef struct SSTable
{
    keytype elem[maxsize];
    int length;    
} SSTable;
int Search_Seq(SSTable ST,keytype key)
{
    ST.elem[0] = key;
    for(int i = ST.length;i>0&&(!EQ(ST.elem[i],key));--i)//;代表空语句
    return i;
}

平均查找长度ASL

ASLss=(n+1)/2

查找不成功时的平均查找长度ASL:

ASLss=3(n+1)/4

折半查找(二分法查找)

要求待查找的表必须是按关键字大小有序排列的顺序表
折半查找的思想:

  • 将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;
  • 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
  • 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。
int Search_Bin(SSTable ST,int key)
{
    int low = 1,high = ST.length;
    while(low<=high)
    {
        int mid = (low+high)/2;//c++中对纯int类型进行计算会把结果向下取整(只留结果的整数部分,因此可以不用区分奇偶)
        if(ST.elem[mid]==key)
            return mid;
        if(ST.elem[mid]>key)//缩小排查范围
            high=mid-1;
        else
            low = mid+1;
    };
    return 0;
}

判定树(比较树):二分查找过程可用二叉树来描述,把当前查找区间的中间位置上的记录作为根,左子表和右子表中的记录分别作为根的左子树和右子树。

 折半查找成功时的平均查找长度ASL

假定表的长度n=2^h-1,则相应判定树必为深度是h的满二叉树h=log(n+1)。又假设每个记录的查找概率相等,则折半查找成功时的平均查找长度为:

 

 在查找成功时会找到图中某个圆形结点则成功时的平均查找长度:

 在查找不成功时,会找到图中某个方形结点,则不成功时的平均查找长度:

 

索引顺序查找(分块查找)

一种性能介于顺序查找和二分查找间的查找放法

  • 将表[1..n-1]均分为b块前b-1块中记录个数为s= n/b最后一块即第b块的记录数小于等于s;
  • 每一块中的关键字不一定有序,但前一块中的最大关键字必须小于后一块中的最小关键字,即要“分块有序”;
  • 抽取各块中的最大关键字及其起始位置构成一个索引表ID[b]。由于表RIn]是分块有序的,所以索引表是一个递增有序表

 一共有b块,每块s个元素,查找概率相等,每个索引项的查找概率为1/b,块中每个元素的查找概率为1/s

ASL=(sqrt(n)+1)

顺序查找法确定待查元素所在块

ASL=1/2*(n/s+s)+1

用折半查找法确定待查元素所在快

ASL=log2(n/s+1)+s/2

 总结

动态查找

表结构本身在查找过程中动态生成,即对于给定值key,若表中存在关键字等于key的记录,则查找成功,否则插入关键字等于key的记录。

当查找表以线性表的形式组织时,若对查找表进行插入、删除、或排序操作,就必须移动大量的记录;当记录数很多时,这种移动的代价很大。

利用树的形式组织查找表,可以对查找表进行动态高效的查找

二叉排序树(BST)

二叉排序树:空树或满足以下性质的二叉树

  • 若左子树不为空,则左子树上所有的值都小于根节点的值;
  • 若右子树不为空,则右子树上所有结点的值都大于根节点的值;
  • 左、右子树分别是二叉排序树。

中序遍历一棵二叉排序树,所得的结点序列是一个递增序列

如何构造?

  • 以所要排序序列的起始key为根节点,向后遍历;
  • 拿到一个新数,与根节点进行比较;小的往左孩子走,大的往右孩子走;碰到孩子结点继续执行比较、走操作,直到该数落到子叶上。

BST树查找(无插入)

#include <iostream>
using namespace std;
typedef float keytype;
typedef int keytype;
typedef char keytype;
typedef int datatype;
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
typedef struct Node
{
    keytype key;
    datatype data;
    struct Node *Lchild,*Rchild;
}BSTNode;

BSTNode* searchBST(BSTNode *T,keytype key)
{
    if(!T||EQ(key,T->key))
        return T;//结束条件
    else if(LT(key,T->key))
        return searchBST(T->Lchild,key);
    else    
        return searchBST(T->Rchild,key);
}

BST树插入

#include <iostream>
using namespace std;
typedef float keytype;
typedef int keytype;
typedef char keytype;
typedef int datatype;
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
typedef struct Node
{
    keytype key;
    datatype data;
    struct Node *Lchild,*Rchild;
}BSTNode;

int InsertBST(BSTNode *T,keytype key)
{   
    if(T==NULL)
    {   //此根结点为空,插入记录
        T = new BSTNode;//如果为空树,分配空间 
        T->key = key;
        T->Lchild = T->Rchild = NULL;
        return 1;
    }
    else if (key==T->key)
    {//存在相同关键字的结点,返回0
        return 0;
    }    
    else if (key<T->key)
    {   //如果比此根节点关键字小,往左边走;
        return InsertBST(T->Lchild,key);   
    }
    else if (key>T->key)
    {   //如果比此根节点关键字大,往右边走
        return InsertBST(T->Rchild,key);
    };
}

BST树删除

BST树的删除操作可以分为以下三个类型讨论。设要删除的结点为p,p的双亲结点为s

当结点为叶子结点时,可直接删除

当结点为非叶子结点,只有一个孩子时,另用一个指针指向p,然后直接把s指向p结点的指针指向p的孩子结点即可。

当结点为非子叶结点,既有左孩子又有右孩子时。,因此p的右孩子需要

方法1:

将p的左孩子改为p父节点f的左孩子(如果p是父节点的右孩子,就将p的左孩子作为父节点的右孩子),p的右孩子由于都是大于p的,p又大于c的所有孩子,因此p的右孩子大于c的所有孩子,应该放到c树最右最底层的位置s

        

 方法2:

将要删除的结点p的左孩子c的最右最下孩子s代替p,然后把s删除,如果s有左孩子就把该孩子放到s原本的位置上。可以看出,因为s总是大于c的且是c孩子里最大的,且一定小于p的右孩子,代替p的位置没有任何问题;其次当s有左孩子时,不算s,它也是c孩子里最大的,直接可以代替s的位置;s不会有右孩子,因为如果有,s就不是p左孩子c的最右最下孩子。

 

最差情况:单支树,ASL为(n+1)/2和顺序查找相同

最好情况:与这般查找的判定树形态相同,ASL和log2n成正比

层数i,每层元素个数n ASL=sum(i*n)/sum(n)

 平衡二叉树、2-3树到红黑树

平衡二叉树

BST查找效率稿,但平均查找长度受形态影响较大,形态比较均匀树查找效率很好,形态明显偏向某一方向时效率大大下降;因此希望有更好的二叉排序树,使得其形态总是均匀的,查找时能得到最好的效率,这就是平衡二叉排序树

平衡二叉树或是空树,或满足以下性质的二叉树

  • 左子树和右子树深度之差的绝对值不大于1
  • 左子树和右子树也都是平衡二叉树

平衡因子:

二叉树上左子树的深度减去右子树的深度

因此平衡二叉树上每个结点的平衡因子只能是-1,0,1

深度为h的平衡二叉排序树查找的平均时间复杂度为O(log2n)

一般的二叉排序树是不平衡的,若能通过某种方法使其既保持有序性,又具有平衡性,就找到了构造平衡二叉排序树的方法

2-3树

  • 2节点:具有2个孩子的节点,本身有1个元素
  • 3节点:具有3个孩子的节点,本身有2个元素
  • 满足二叉搜索树的基本性质
  • 节点可以存放一个或两个元素
  • 节点大于左孩子,小于右孩子
  • 两个元素的节点有三个子节点,分别在两个元素的左中右
  • 2-3树是一棵绝对平衡的树,从根节点到任意一个叶子子节点经过的结点数量是相同的结点的左子树或右子树也是2-3树

2-3树的构造和插入

和二叉排序树一样,遍历序列,然后逐次和根进行比较,放到子叶上。

不同的是,2-3树有一个向上“挤”的过程

  • 如果未命中查找结束于2-节点,直接将2-节点替换为3-节点,并将待插入元素添加到其中
  • 如果命中查找结束于3-节点,先临时将其成为4-节点,把待插入元素添加到其中,然后将4-节点转化为3个2-节点中间的节点成为左右节点的父节点。如果之前临时4-节点有父节点,就会变成向一个父节点为2-节点的3-节点中插入元素,中间节点与父节点为2-节点的合并

又例如 

红黑树

红黑树可以看作是由2-3树转换过来的,所有3节点的左值都是红节点。与2-3树类似,根节点到达所有节点经过的黑色节点数相等,节点大于左孩子,小于右孩子

 

  • 性质1.结点是红色或黑色
  • 性质2.根结点是黑色
  • 性质3.每个红色结点的两个子结点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色结点)
  • 性质4.从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点

猜你喜欢

转载自blog.csdn.net/Gelercat/article/details/127989234