笔者对于这部分的总结并不完全,选择了一些基础知识进行总结。(如平衡二叉树,B树,B+树,红黑树等更复杂的知识均未涉及)

查找

查找基本概念
  1. 在查找表中只做查找操作,而不改动表中数据元素,称此类查找表为静态查找表;反之,在查找表中做查找操作的同时进行插入数据或者删除数据的操作,称此类表为动态查找表
  2. 平均查找长度:把平均需要和给定值k进行比较的关键字此数称为平均查找长度(ASL
线性表的查找

用于查找的顺序表用数组表示,该数组元素的类型声明如下:

typedef int KeyType; //关键字类型
typedef struct
{
    KeyType key;   //关键字项
    InfoType data; //其他数据项
} RecType;         //查找元素的类型
顺序查找

静态查找表用顺序存储结构表示时,顺序查找的查找过程为:从表中的最后一个数据元素开始,逐个同记录的关键字做比较,如果匹配成功,则查找成功;反之,如果直到表中第一个关键字查找完也没有成功匹配,则查找失败。

int SeqSearch(RecType 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
}

可以在上述算法中加入一个关键字为k的记录,这样查找时不用再判断i是否越界,提高了查找速度。对应算法如下:

int SeqSearch1(RecType R[], int n, KeyType k)
{
    int i = 0;
    R[n].key = k; //在R的末尾增加了一个关键字为k的记录
    while (R[i].key != k) //从表头往后找
        i++;
    if (i == n) //若没有找到返回0
        return 0;
    else
        return i + 1; //找到就返回i+1
}
Tips

平均查找长度:(n+1)/2

时间复杂度:O(n) (n为查找表中元素的个数)

优点:

  • 算法简单
  • 对表的结构没有要求,对关键字是否有序没有要求

缺点:

当n较大时查找效率低

折半查找

折半查找,也称二分查找,在某些情况下相比于顺序查找,使用折半查找算法的效率更高。但是该算法的使用的前提是静态查找表中的数据必须是有序的

int BinSearch(RecType R[], int n, KeyType k)
{
    int low = 0, high = n - 1, mid;
    while (low <= high)
    {
        mid = (low + high) / 2;
        if (k == R[mid].key)  //查找成功直接返回mid+1
            return mid + 1;
        if (k < R[mid].key) //在R[low..mid-1]中查找
            high = mid - 1;
        else             //在R[mid+1..high]中查找
            low = mid + 1;
    }
    return 0;  //查找失败,返回0
}
Tips

平均查找长度:log2(n+1)-1

时间复杂度:O(log2n) (n为查找表中元素的个数)

优点:

查找效率高

缺点:

  • 要求关键字有序
  • 要求存储结构具有随机存取的特征,只适用于顺序表不适用于链式存储结构
树表的查找
二叉排序树

二叉排序树要么是空二叉树,要么具有如下特点:

二叉排序树中,如果其根结点有左子树,那么左子树上所有结点的值都小于根结点的值;

二叉排序树中,如果其根结点有右子树,那么右子树上所有结点的值都大小根结点的值;

二叉排序树的左右子树也要求都是二叉排序树。

因此二叉排序树的中序序列是一个递增的有序序列

二叉排序树的结点类型声明如下:

typedef struct node //元素类型
{
    KeyType key;                  //关键字项
    InfoType data;                //其他数据项
    struct node *lchild, *rchild; //左、右孩子指针
} BSTNode;
二叉排序树的插入和创建

插入对应算法:

bool InsertBST(BSTNode *&bt, KeyType k)
{
    if (bt == NULL) //如果原树为空,新插入的结点为根节点,返回true
    {
        bt = (BSTNode *)malloc(sizeof(BSTNode));
        bt->key = k;
        bt->lchild = bt->rchild = NULL;
        return true;
    }
    else if (k == bt->key) //如果存在相同的关键字的结点,返回false
        return false;
    else if (k < bt->key)
​
        return InsertBST(bt->lchild, k); //插入到左子树中
    else
        return InsertBST(bt->rchild, k); //插入到右子树中
}

创建对应算法:

BSTNode *CreateBST(KeyType a[], int n)
{
    BSTNode *bt = NULL; //初始时bt是空树
    int i = 0;
    while (i < n)
    {
        InsertBST(bt, a[i]); //调用插入函数将a[i]插入到bt中
        i++;
    }
    return bt; //返回排序二叉树的根指针
}
二叉排序树的查找

二叉排序树中查找某关键字时在二叉排序树不为空树的前提下,首先将被查找值同树的根结点进行比较,会有 3 种不同的结果:

  • 如果相等,查找成功;
  • 如果比较结果为根结点的关键字值较大,则说明该关键字可能存在其左子树中;
  • 如果比较结果为根结点的关键字值较小,则说明该关键字可能存在其右子树中。查找实现算法如下:
BSTNode *SearchBST(BSTNode *bt, KeyType k)
{
    //如果递归过程中bt为空,则查找结果,返回NULL;或者查找成功,返回指向该关键字的指针
    if (bt == NULL || k == bt->key)
    {
        return bt;
    }
    else if (k < bt->key)
    {
        //递归遍历其左孩子
        return SearchBST(bt->lchild, k);
    }
    else
    {
        //递归遍历其右孩子
        return SearchBST(bt->rchild, k);
    }
}
Tips
  • n个不同关键字构成的不同的二叉排序树有C(n,2n)/n+1
  • 二叉树排序树中的结点作为内部结点,可以添加相应的外部结点。具有n个结点的二叉排序树,其外部结点的个数为n+1。查找失败一定是落在外部结点上。
  • 它的时间复杂度也是O(log2n)
  • 二叉树查找效率和二叉排序树的高度有关
  • 二叉排序树中插入一个结点所需的关键字比较次数最多是树的高度。
  • 向一棵二叉排序树中插入一个结点均是以叶子结点插入的。

对于给定的二叉排序树结点p,找出其左子树中最大结点和右子树中最小结点

void maxminnode(BSTNode *p)
{
    if (p != NULL)
    {
        if (p->lchild != NULL)
            printf("左子树的最大结点为:%d\n", maxnode(p->lchild));
        if (p->rchild != NULL)
            printf("右子树的最小结点为:%d\n", minnode(p->rchild));
    }
}
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);
    }
}
二叉排序树的删除

假设要删除的为结点 p,则对于二叉排序树来说,需要根据结点 p 所在不同的位置作不同的操作,有以下 3 种可能:

1、结点 p 为叶子结点,此时只需要删除该结点,并修改其双亲结点的指针即可; 2、结点 p 只有左子树或者只有右子树,如果 p 是其双亲节点的左孩子,则直接将 p 节点的左子树或右子树作为其双亲节点的左子树;反之也是如此,如果 p 是其双亲节点的右孩子,则直接将 p 节点的左子树或右子树作为其双亲节点的右子树; 3、结点 p 左右子树都有,可以从其左子树中选择关键字最大的结点r,用结点r的值代替结点p的值,并删除结点r。

删除排序二叉树bt中关键字为k的结点的算法如下:

bool DeleteBST(BSTNode *&bt, KeyType k)
{
    if (bt == NULL) //bt是空树,删除失败
    {
        return false;
    }
    else
    {
        if (k < bt->key)
            return DeleteBST(bt->lchild, k); //递归在左子树中删除为k的结点
        else if (k > bt->key)
​
            return DeleteBST(bt->rchild, k); //递归在右子树中删除为k的结点
​
        else //如果找到了要删除的结点bt
        {
            Delete(bt); //调用Delete函数删除结点bt
            return true;
        }
    }
}
void Delete(BSTNode *&p)
{
    BSTNode *q;
    if (p->rchild == NULL) //结点没有右子树(含为叶子节点的情况)
    {
        q = p;
        p = p->lchild; //用p的左孩子代替它
        free(q);
    }
    else if (p->lchild == NULL) //结点没有左子树
    {
        q = p;
        p = p->lchild; //用p的右孩子代替它
        free(q);
    }
    else
        Delete1(p, p->lchild); //p既有左子树又有右子树的情况调用Delete1函数
}
void Delete1(BSTNode *p, BSTNode *&r) //p有左右子树,且r是p的左孩子
{
    BSTNode *q;
    if (r->rchild != NULL) //递归寻找r的最右下结点
        Delete1(p, r->rchild);
    else
    {
        p->key = r->key; //将结点r的值存放到结点p中
        p->data = r->data;
        q = r;         //将r赋给q
        r = r->lchild; //用r的左孩子代替r
        free(q);       //释放结点q的空间
    }
}

注意:若先删除二叉排序树中的某个结点,再重新插入该结点,不一定得到原来的二叉排序树。