【数据结构7-1-查找-线性-二分法-二叉树-哈希表】

news2024/9/20 9:44:52

目录

  • 1 查找基本概念
  • 2 线性表的查找
    • 2.1 顺序查找
    • 2.2 二分法查找
    • 2.3 分块查找
  • 3 树表的查询
    • 3.1 二叉排序树
      • 3.1.1 定义
      • 3.1.2 二叉树的建立、遍历、查找、增加、删除:
      • 3.1.3 代码实现:
    • 3.2 平衡二叉树
      • 3.2.1 平横因子
      • 3.2.2 不平横树的调整-左旋
      • 3.2.3 不平横树的调整-右旋
      • 3.2.4 当插入节点出现失衡因子如何旋转
      • 3.2.4 某位UP主的详细视频讲解
    • 3.3 B-树
      • 3.3.1 B-树的定义
      • 3.3.2 题目练习
      • 3.3.3 B-树的查找、插入、删除
    • 3.4 B+树
  • 4 散列表查找(哈希表查找)
    • 4.1 基本术语和概念
    • 4.2 散列函数的构造
    • 4.3 哈希表的创建,插入,查找
      • 4.3.1 程序实现
      • 4.3.2 程序结果:

常用的查找及代码程序

1 查找基本概念

  查找是在数据集合中寻找特定元素或满足特定条件的元素的过程。它是一种常见的数据操作。

2 线性表的查找

2.1 顺序查找

  顺序查找(Sequential Search)的查找过程为:从表的一端开始,依次将记录的关键字和给定值进行比较,若某个记录的关键字和给定值相等,则查找成功;反之,若扫描整个表后,仍未找到关键字和给定值相等的记录,则查找失败。
  顺序查找方法既适用于线性表的顺序存储结构,又适用千线性表的链式存储结构。下面只介绍以顺序表作为存储结构时实现的顺序查找算法。顺序查找比较简单,但是费时;效率低;
  简单看一下下面两个算法的区别:arr【0】不用于存储数据;

// 顺序查找函数
int search(int arr[], int size, int key) {
    // 从数组的最后一个元素开始查找
    for (int i = size - 1; i >= 1; --i) {
        if (arr[i] == key) {
            return i;
        }
    }
    // 未找到元素
    return 0;
}

改进后:不用每次都进行循环是否结束的查找,也就是i>=1?

// 顺序查找函数
int search(int arr[], int size, int key) {
    // 设置监视哨
    arr[0] = key;
    // 从数组的最后一个元素开始查找
    for (int i = size; arr[i]!=key; --i);
    // 未找到元素
    return i;
}

2.2 二分法查找

  它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构, 而且表中元素按关键字有序排列;
  例如查找30的数据:( 5, 16, 20, 27, 3 0, 36, 44, 55, 60, 67, 71)
  思考如果low=mid而不是low=mid+1后结果是什么?
  需要注意的是,循环执行的条件是low< =high,而不是low<high,因为low=high时,查找区间还有最后一个结点, 还要进一步比较 。

// 二分法查找函数
int main()
{
    int high, mid, low, t;
    int str[] = {5, 16, 20, 27, 30, 36, 44, 55, 60, 67, 71};
    high = 10;
    low = 0;
    mid = (high + low) / 2;
    t = 71;
    while (low <= high){
        mid = (high + low) / 2;
        if (str[mid] >= t){
            if (str[mid] == t){
                printf("YES\n");
                return 0;
            }
            else{
                high = mid;
            }
        }
        else{
            low = mid + 1; // 思考如果low=mid后结果是什么?如果
                           // t=71,会在后面陷入死循环:high=10,low=9,mid=9;一直循环
            }
    }
    return 0;
}

  折半查找的优点是:比较次数少,查找效率高。其缺点是:对表结构要求高,只能用于顺序存储的有序表。查找前需要排序,而排序本身是一种费时的运算。同时为了保持顺序表的有序性,对有序表进行插入和删除时,平均比较和移动表中一半元素,这也是一种费时的运算。因此,折半查找不适用于数据元素经常变动的线性表。

2.3 分块查找

  分块查找的优点是:在表中插入和删除数据元素时,只要找到该元素对应的块,就可以在该块内进行插入和删除运算。 由于块内是无序的,故插入和删除比较容易,无需进行大量移动。 如果线性表既要快速查找又经常动态变化,则可采用分块查找。
  其缺点是:要增加一个索引表的存
储空间并对初始索引表进行排序运算。
  其基本的思想和这里面差不多;如下图:

3 树表的查询

  前面介绍的3 种查找方法都是用线性表作为查找表的组织形式,其中折半查找效率较高。但由千折半查找要求表中记录按关键字有序排列,且不能用链表做存储结构,因此,当表的插入或删除操作频繁时,为维护表的有序性,需要移动表中很多记录。这种由移动记录引起的额外时间开销,就会抵消折半查找的优点。 所以,线性表的查找更适用千静态查找表,若要对动态查找表进行高效率的查找,可采用几种特殊的二叉树作为查找表的组织形式,在此将它们统称为树表。 本节将介绍在这些树表上进行查找和修改操作的方法

3.1 二叉排序树

3.1.1 定义

  二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:二叉排序树,又称二叉查找树,它或者是一棵空树,或者是具有以下性质的二叉树:

  1. 若左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  2. 若右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  3. 左、右子树也分别为二叉排序树。
    如下图所示:

    若中序遍历图7.5(a), 则可得到一个按数值 大小排序的递增序列:
                  3, 12, 24, 37, 45, 53, 61, 78, 90, 100

3.1.2 二叉树的建立、遍历、查找、增加、删除:

  若中序遍历图7.5(a), 则可得到一个按数值 大小排序的递增序列:具体详情可以参考我下面的这篇内容:主要是包括二叉排序树(建立、遍历、查找、增加、删除)并给出了详细的C运行代码;链接:

3.1.3 代码实现:

  二叉排序树(建立、遍历、查找、增加、删除)并给出了详细的C运行代码;链接

3.2 平衡二叉树

  二叉排序树查找算法的性能取决于二叉树的结构,而 二叉排序树的形状则取决于其数据集。如果数据呈有序排列,则二叉排序树是线性的,查找的时间复杂度为O(n); 反之,如果二叉排序树的结构合理,则查找速度较快,查找的时间复杂度为 O(lo2n)。事实上,树的高度越小,查找 g速度越快。因此,希望二叉树的高度尽可能小。本节将讨论一种特殊类型的二叉排序树,称为平衡二叉树 (Balance d Binary Tree或Height-Balanced·Tree), 因由前苏联数学家Adelson-Velskii和Land i s提出,所以又称AVL树。

平衡二叉树或者是空树,或者是具有如下特征的二叉排序树:

  1. (1 )左子树和右子树的深度之差绝对值不超过1;
  2. (2)左子树和右子树也是平衡二叉树

3.2.1 平横因子

  平衡因子=左子树高度-右子树高度:如下图所示:

3.2.2 不平横树的调整-左旋

  在旋转过程中,冲突的是9的左孩6,这里记一句话左旋--冲突左孩变右孩;如下图左旋过程

而且旋转过后,中序遍历的话,两者是等价的,但是树的高度却变低了;如下图:

3.2.3 不平横树的调整-右旋

  在旋转过程中,冲突的是14的左孩9,这里记一句话右旋--冲突右孩变左孩;如下图左旋过程

而且旋转过后,中序遍历的话,两者是等价的,但是树的高度却变低了;如下图:

3.2.4 当插入节点出现失衡因子如何旋转

  调整方法是:找到离插入结点最近且平衡因子绝对值超过1的祖先结点, 以该结点为根的子树称为最小不平衡子树, 可将重新平衡的范围局限于这棵子树,如下图所示,一共四种情况:
  巧记一下:LL是右旋,RR是左旋,也就是纯的则相反旋转:L(left),R(right)
  巧记一下:LR是右旋,先左后右,RL,先右后左,也就是混的按照字母:L(left),R(right)
在这里插入图片描述

3.2.4 某位UP主的详细视频讲解

关于平衡二叉树的详细信息,这个视频讲的异常清晰:链接

3.3 B-树

  前面介绍的查找方法均适用千存储在计算机内存中较小的文件,统称为内查找法。若文件很大且存放于外存进行查找时,这些查找方法就不适用了。内查找法都以结点为单位进行查找,这样需要反复地进行内、外存的交换,是很费时的。1970年,R.Bayer和£.Mccreight提出了一种适用于外查找的平衡多叉树-B-树,磁盘管理系统中的目录管理,以及数据库系统中的索引组织多数都采用B-树这种数据结构。
  B树就是一个有序的多路查询树

3.3.1 B-树的定义

  • 对于m叉树,每个节点最多有m个孩子,其中最多有m-1个关键字;(人5个手指最多4条缝)
  • 每个节点的内容如下:例如对于四叉树,3个关键字,即其存储结构:
n:多少关键字P0指针k1关键字P1指针k2关键字P2指针k3关键字P3指针
  • 每个节点的内容如下:例如对于四叉树,3个关键字,即其存储结构:
  • 谨记m阶子树,最多有m-1个关键字,而对于每个节点最少要有[m/2]-1个关键字,其中[m/2]是向上取整;
  • 关键字就是把内容进行了分块,如同索引,例如:2--- 5,两个关键字就分量三个区域,0-2,2-5,5--三个范围,也就是对于四叉树,最多是3个关键字;

3.3.2 题目练习

  真题练习:

3.3.3 B-树的查找、插入、删除

  代码实现——C:链接

/*
 * @Author: Xyh4ng
 * @Date: 2023-01-02 20:24:30
 * @LastEditors: Xyh4ng
 * @LastEditTime: 2023-01-05 20:17:06
 * @Description:
 * Copyright (c) 2023 by Xyh4ng 503177404@qq.com, All Rights Reserved.
 */
#include <stdio.h>
#include <stdlib.h>

#define M 3 // B树的阶
#define MIN_KEYNUM (M + 1) / 2 - 1

typedef struct BTreeNode
{
    int keyNum;               // 结点中关键字的数量
    struct BTreeNode *parent; // 指向双亲结点
    struct Node               // 存放关键字以及其孩子节点指针,正常结点最多存放m个孩子,但在插入判断时会多存放一个
    {
        int key;
        struct BTreeNode *ptr;
    } node[M + 1]; // key的0号单元未使用
} BTreeNode, *BTree;

typedef struct Result
{
    int tag;       // 查找成功的标志
    BTreeNode *pt; // 指向查找到的结点
    int i;         // 结点在关键字中的序号
} Result;

int Search(BTree T, int K)
{
    int i = 0;
    for (int j = 1; j <= T->keyNum; j++)
    {
        if (T->node[j].key <= K)
        {
            i = j;
        }
    }
    return i;
}

/* 在m阶B树T上查找关键字K,返回结果(pt,i,tag)。若查找成功,则特征值tag=1,指针pt所指结点中第i个关键字等于K;否则特征值tag=0,等于K的关键字应插入在指针Pt所指结点中第i和第i+1个关键字之间。 */
Result SearchBTree(BTree T, int K)
{
    BTree p = T, q = NULL; /*  初始化,p指向待查结点,q指向p的双亲  */
    int found = 0;
    int index = 0;
    Result r;
    while (p && !found)
    {
        index = Search(p, K); // p->node[index].key ≤ K < p->node[index+1].key
        if (index > 0 && p->node[index].key == K)
            found = 1;
        else
        {
            q = p;
            p = p->node[index].ptr;
        }
    }
    r.i = index;
    if (found) // 查找成功
    {
        r.tag = 1;
        r.pt = p;
    }
    else
    {
        r.tag = 0;
        r.pt = q;
    }
    return r;
}

void Insert(BTree *q, int key, BTree ap, int i)
{
    for (int j = (*q)->keyNum; j > i; j--) // 空出(*q)->node[i+1]
    {
        (*q)->node[j + 1] = (*q)->node[j];
    }
    (*q)->node[i + 1].key = key;
    (*q)->node[i + 1].ptr = ap;
    (*q)->keyNum++;
}

// 将结点q分裂成两个结点,mid之前的结点保留,mid之后结点移入新生结点ap
void Split(BTree *q, BTree *ap)
{
    int mid = (M + 1) / 2;
    *ap = (BTree)malloc(sizeof(BTreeNode));
    (*ap)->node[0].ptr = (*q)->node[mid].ptr;
    if ((*ap)->node[0].ptr)
    {
        (*ap)->node[0].ptr->parent = *ap;
    }

    for (int i = mid + 1; i <= M; i++)
    {
        (*ap)->node[i - mid] = (*q)->node[i];
        if ((*ap)->node[i - mid].ptr)
        {
            (*ap)->node[i - mid].ptr->parent = *ap;
        }
    }
    (*ap)->keyNum = M - mid;
    (*ap)->parent = (*q)->parent;
    (*q)->keyNum = mid - 1;
}

// 生成含信息(T,r,ap)的新的根结点&T,原T和ap为子树指针
void NewRoot(BTree *T, int key, BTree ap)
{
    BTree p;
    p = (BTree)malloc(sizeof(BTreeNode));
    p->node[0].ptr = *T; // 根结点孩子数最小为2,则将T作为左孩子,ap作为右孩子
    *T = p;
    if ((*T)->node[0].ptr)
    {
        (*T)->node[0].ptr->parent = *T;
    }
    (*T)->parent = NULL;
    (*T)->keyNum = 1;
    (*T)->node[1].key = key;
    (*T)->node[1].ptr = ap;
    if ((*T)->node[1].ptr)
    {
        (*T)->node[1].ptr->parent = *T;
    }
}

/*  在m阶B树T上结点*q的key[i]与key[i+1]之间插入关键字K的指针r。若引起结点过大,则沿双亲链进行必要的结点分裂调整,使T仍是m阶B树。 */
void InseartBTree(BTree *T, int key, BTree q, int i)
{
    BTree ap = NULL;
    int finished = 0;
    int rx = key; // 需要插入的关键字的值
    int mid;
    while (q && !finished)
    {
        Insert(&q, rx, ap, i);
        if (q->keyNum < M)
            finished = 1;
        else // 结点关键字数超出规定
        {
            int mid = (M + 1) / 2; // 结点的中间关键字序号
            rx = q->node[mid].key;
            Split(&q, &ap); // 将q->key[mid+1..M],q->ptr[mid..M]移入新结点*ap
            q = q->parent;
            if (q)
                i = Search(q, rx);
        }
    }
    if (!finished)
    {
        NewRoot(T, rx, ap);
    }
}

void Delete(BTree *q, int index)
{
    for (int i = index; i <= (*q)->keyNum; i++)
    {
        (*q)->node[index] = (*q)->node[index + 1];
    }
    (*q)->keyNum--;
}

void LeftRotation(BTree *q, BTree *p, int i)
{
    // 将父亲结点转移至q结点的末尾
    (*q)->keyNum++;
    (*q)->node[(*q)->keyNum].key = (*p)->node[i + 1].key;
    // 将q结点的右兄弟的第一个关键字转移至父亲结点的分隔符位置
    BTree rightBroPtr = (*p)->node[i + 1].ptr;
    (*p)->node[i + 1].key = rightBroPtr->node[1].key;
    // 将右结点的关键字前移
    for (int j = 1; j < rightBroPtr->keyNum; j++)
    {
        rightBroPtr->node[j] = rightBroPtr->node[j + 1];
    }
    rightBroPtr->keyNum--;
}

void RightRotation(BTree *q, BTree *p, int i)
{
    // 将q结点向后移动空出第一个关键字的位置
    for (int j = (*q)->keyNum; j >= 1; j--)
    {
        (*q)->node[j + 1] = (*q)->node[j];
    }
    // 将父亲结点移动至q结点的第一个关键字的位置
    (*q)->node[1].key = (*p)->node[i].key;
    (*q)->node[1].ptr = NULL;
    (*q)->keyNum++;
    // 将左兄弟结点的最后一个关键字移动至父亲结点的分隔符位置
    BTree leftBroPtr = (*p)->node[i - 1].ptr;
    (*p)->node[i].key = leftBroPtr->node[leftBroPtr->keyNum].key;
    leftBroPtr->keyNum--;
}

void BalanceCheck(BTree *q, int key);

void MergeNode(BTree *q, BTree *p, int i)
{
    BTree rightBroPtr = NULL, leftBroPtr = NULL;
    if (i + 1 <= (*p)->keyNum)
    {
        rightBroPtr = (*p)->node[i + 1].ptr;
    }
    if (i - 1 >= 0)
    {
        leftBroPtr = (*p)->node[i - 1].ptr;
    }
    if (rightBroPtr)
    {
        // 将父亲结点的分隔符移动至q结点的最后
        (*q)->keyNum++;
        (*q)->node[(*q)->keyNum].key = (*p)->node[i + 1].key;
        // 将右兄弟结点都移动到q结点上
        (*q)->node[(*q)->keyNum].ptr = rightBroPtr->node[0].ptr;
        for (int j = 1; j <= rightBroPtr->keyNum; j++)
        {
            (*q)->keyNum++;
            (*q)->node[(*q)->keyNum] = rightBroPtr->node[j];
        }
        // 将父亲结点的分隔符删除
        int key = (*p)->node[i + 1].key;
        for (int j = i + 1; j < (*p)->keyNum; j++)
        {
            (*p)->node[j] = (*p)->node[j + 1];
        }
        (*p)->keyNum--;
        if (!(*p)->parent && !(*p)->keyNum)
        {
            // 判断父亲结点是否为根结点,且关键字为空
            // 让q结点作为根结点
            (*q)->parent = NULL;
            (*p) = (*q);
        }
        BalanceCheck(p, key);
    }
    else if (leftBroPtr)
    {
        // 将父亲结点的分隔符移动至左兄弟结点的最后
        leftBroPtr->keyNum++;
        leftBroPtr->node[leftBroPtr->keyNum].key = (*p)->node[i].key;
        // 将q结点都移动到左兄弟结点上
        leftBroPtr->node[leftBroPtr->keyNum].ptr = (*q)->node[0].ptr;
        for (int j = 1; j <= (*q)->keyNum; j++)
        {
            leftBroPtr->keyNum++;
            leftBroPtr->node[leftBroPtr->keyNum] = (*q)->node[j];
        }
        // 将父亲结点的分隔符删除
        int key = (*p)->node[i].key;
        for (int j = i; j < (*p)->keyNum; j++)
        {
            (*p)->node[j] = (*p)->node[j + 1];
        }
        (*p)->keyNum--;
        if (!(*p)->parent && !(*p)->keyNum)
        {
            // 判断父亲结点是否为根结点,且关键字为空
            // 让q结点作为根结点
            (*q)->parent = NULL;
            (*p) = (*q);
        }
        BalanceCheck(p, key);
    }
}

void BalanceCheck(BTree *q, int key)
{
    if ((*q)->keyNum < MIN_KEYNUM) // 该结点不满足最小关键字数目要求
    {

        BTree p = (*q)->parent;
        int i = Search(p, key);                                            // 找到q结点在父亲结点中的索引
        if (i + 1 <= p->keyNum && p->node[i + 1].ptr->keyNum > MIN_KEYNUM) // 看q结点的右兄弟是否存在多余结点
        {
            LeftRotation(q, &p, i);
        }
        else if (i - 1 >= 0 && p->node[i - 1].ptr->keyNum > MIN_KEYNUM) // 看q结点的左兄弟是否存在多余结点
        {
            RightRotation(q, &p, i);
        }
        else // q结点的左右兄弟都不存在多余结点
        {
            // 将q结点和其左右兄弟的其中一个以及父亲结点中的分隔符合并
            MergeNode(q, &p, i);
        }
    }
}

void MergeBro(BTree *left, BTree *right)
{
    if (!(*left)->node[((*left)->keyNum)].ptr)
    {
        // 如果左子树为叶子结点
        (*left)->node[(*left)->keyNum].ptr = (*right)->node[0].ptr;
        for (int j = 1; j <= (*right)->keyNum; j++)
        {
            (*left)->keyNum++;
            (*left)->node[(*left)->keyNum] = (*right)->node[j];
        }
    }
    else
    {
        // 左子树不是叶子结点,则先将左子树最后一个子结点和右子树第一个子结点合并
        MergeBro(&(*left)->node[(*left)->keyNum].ptr, &(*right)->node[0].ptr);
        for (int j = 1; j <= (*right)->keyNum; j++)
        {
            (*left)->keyNum++;
            (*left)->node[(*left)->keyNum] = (*right)->node[j];
        }
    }
    // 合并完对左子树关键字数目进行判断
    if ((*left)->keyNum >= M)
    {
        int mid = (M + 1) / 2; // 结点的中间关键字序号
        int rx = (*left)->node[mid].key;
        BTree ap = NULL;
        Split(&(*left), &ap); // 将q->key[mid+1..M],q->ptr[mid..M]移入新结点*ap
        BTree p = (*left)->parent;
        int i = Search(p, rx);
        Insert(&p, rx, ap, i);
    }
}

void DeleteBTreeNode(BTree *T, int key)
{
    Result res = SearchBTree(*T, key);
    if (res.tag) // 查找成功
    {
        // 判断该结点是否是叶子结点
        if (!res.pt->node[res.i].ptr)
        {
            // 若是叶子结点,则直接删除,然后对该结点进行平衡判断
            Delete(&res.pt, res.i);
            BalanceCheck(&res.pt, key);
        }
        else
        {
            // 若不是叶子节点
            BTree leftChildPtr = res.pt->node[res.i - 1].ptr;
            BTree rightChildPtr = res.pt->node[res.i].ptr;
            if (leftChildPtr->keyNum > MIN_KEYNUM) // 左子树富有,则将左子树中提取最大值放到该结点中替换要删除的关键字
            {
                res.pt->node[res.i].key = leftChildPtr->node[leftChildPtr->keyNum].key;
                leftChildPtr->keyNum--;
            }
            else if (rightChildPtr->keyNum > MIN_KEYNUM) // 右子树富有,则将右子树中提取最小值放到该结点中替换要删除的关键字
            {
                res.pt->node[res.i].key = rightChildPtr->node[1].key;
                for (int j = 1; j < rightChildPtr->keyNum; j++)
                {
                    rightChildPtr->node[j] = rightChildPtr->node[j + 1];
                }
                rightChildPtr->keyNum--;
            }
            else // 左右子树都不富有,则合并左右子树
            {
                MergeBro(&leftChildPtr, &rightChildPtr);
                // 删除结点的关键字
                res.i = Search(res.pt, key); // 合并结点可能会改变结点中关键字的次序,重新查序
                for (int j = res.i; j < res.pt->keyNum; j++)
                {
                    res.pt->node[j] = res.pt->node[j + 1];
                }
                res.pt->keyNum--;
                // 对结点进行平衡判断
                BalanceCheck(&res.pt, key);
            }
        }
    }
    else // 查找失败
    {
        printf("您删除的元素不存在");
    }
}

int main()
{
    int r[16] = {22, 16, 41, 58, 8, 11, 12, 16, 17, 9, 23, 13, 52, 58, 59, 61};
    BTree T = NULL;
    Result s;
    int i;
    for (int i = 0; i < 16; i++)
    {
        s = SearchBTree(T, r[i]);
        if (!s.tag)
            InseartBTree(&T, r[i], s.pt, s.i);
    }

    while (1)
    {
        printf("\n请输入要删除的关键字: ");
        scanf("%d", &i);
        DeleteBTreeNode(&T, i);
    }
}

3.4 B+树

  B+ 树是一种 B-树的变形树,更适合用于文件索引系统。严格来讲,它已不符合第 5 章中定
义的树了。这里仅进行概念的了解,详细信息可后序用到在进行熟悉。

4 散列表查找(哈希表查找)

  前面讨论了基于线性表、树表结构的查找方法,这类查找方法都是以关键字的比较为基础的。散
列查找法(哈希查找) 的思想,它通过对元素的关键字值进行某种运算,直接求出元素的地址, 即使用关键字到地址的直接转换方法,而不需要反复比较。因此,散列查找法又叫杂凑法或散列法。

4.1 基本术语和概念

  1. 散列函数和地址:用于计算数据元素的哈希值。在记录的存储位置p和其关键字key 之间建立一个确定的对应关系H, 使 p=H( key ), 称这个对应关系H为散列函数,p为散列地址
  2. 散列表:存储数据元素的结构。一个有限连续的地址空间,用以存储按散列函数计算得到相应散列地址的数据记录。通常散列表的存储空间是一个一维数组,散列地址是数组的下标。
  3. 冲突和同义词:不同数据元素计算出的哈希值相同的情况。,那么这相同的哈希值就是同义词;
  4. 解决冲突的方法:如开放定址法、链地址法等。

4.2 散列函数的构造

  常用的方法分类:

  1. 数字分析法
  2. 平方取中法
  3. 折叠法
  4. 除留余数法,如下图:

4.3 哈希表的创建,插入,查找

4.3.1 程序实现

  这里哈希函数采用除留余数法,解决冲突的方法采用开放定址线性探测法.
  这里如果中文输出乱码可以参考下列文章. 链接:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define HASIZE 17
// 哈希表进行初始化时要保证其初始值不为要查询的值,
// 下面的值就是进行初始化的表值,一般不会重复
#define NULLKEY -32768

typedef struct hashtable
{
    int *key;
    int count; // 当前元素的个数
} HashTable;

// 哈希表的初始化
int InitHashtable(HashTable *H)
{
    H->count = HASIZE;
    H->key = (int *)malloc(sizeof(int) * HASIZE);
    if (!H->key)
    {
        return -1; // 空间分配失败
    }
    // 初始化
    for (int i = 0; i < HASIZE; i++)
    {
        (H->key)[i] = NULLKEY;
    }
    return 0; // 返回0代表初始化成功
}
// 使用除余留数法
int Hash(int key)
{
    return key % HASIZE;
}

// 插入函数
void HashInsert(HashTable *H, int key)
{
    // 先定义一个地址
    int addr;
    addr = Hash(key);
    // 第一种情况就是一次插入就完成,也就是o(1)
    // 第二种情况就是有了冲突,也就是第一次取的
    // 模不为初始值,这时候就要进行,开放地址线性探测法
    // 这两种相同点就是最后都要把key值插入表中,因此可以稍微合并一下,对下面的代码进行改进
    // if ((H->key)[addr] == NULLKEY)
    // {
    //     (H->key)[addr] = key;
    // }
    // else
    // {
    //     while ((H->key)[addr] != NULLKEY)
    //     {
    //         // addr = Hash(addr+1);//或者
    //         addr = (addr + 1) % HASIZE;
    //     }
    //     // 循环结束的条件是找到了空位,进行插入
    //     (H->key)[addr] = key;
    // }
    // 改进代码情况
    while ((H->key)[addr] != NULLKEY)
    {
        // addr = Hash(addr+1);//或者
        addr = (addr + 1) % HASIZE;
    }
    // 循环结束的条件是找到了空位,进行插入
    (H->key)[addr] = key;
}

// 搜索函数,传入一个地址,如果找到的话就让这个地址指向数据
int HashSerach(HashTable H, int key, int *addr)
{
    *addr = Hash(key);
    while (H.key[*addr] != key)
    {
        // 有两种情况是没有找到
        // 第一就是遇到了NULLKEY,第二种就是循环到了自己本身
        *addr = (*addr + 1) % HASIZE;
        if (H.key[*addr] == NULLKEY || H.key[*addr] == key)
        {
            // 返回-1代表查询失败
            return -1;
        }
    }
    // 返回0代表查询成功,并且addr指针指向目标数据
    return 0;
}

int main()
{
    int arr[15] = {4, 6, 76, 89, 3, 43, 45, 657, 87, 879, 65, 342, 42, 34, 242};
    HashTable H;
    int *adrr = NULL;
    int j;
    int i;
    adrr = &j;
    InitHashtable(&H);
    printf("打印原数组:\n");
    for (int i = 0; i < 15; i++)
    {
        printf("%d,", arr[i]);
        HashInsert(&H, arr[i]);
    } // 插入完毕
    // 打印出来:
    printf("\n打印插入的哈希表\n");
    for (int i = 0; i < 15; i++)
    {
        printf("%d,", H.key[i]);
    } // 插入完毕

    // 哈希搜索,查找0是否在哈希表中,否的话返回-1
    printf("\n哈希搜索,查找0是否在哈希表中,否的话返回-1");
    i = HashSerach(H, 0, adrr);
    printf("\n%d", i);
    printf("\n哈希搜索,查找43是否在哈希表中,是的话返回0");
    i = HashSerach(H, 43, adrr);
    printf("\n%d", i);

    return 0;
}

4.3.2 程序结果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1633461.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity 数字字符串逗号千分位

使用InputField时处理输入的数字型字符串千分位自动添加逗号&#xff0c;且自动保留两位有效数字 输入&#xff1a;123 输出&#xff1a;123.00 输入&#xff1a;12345 输出&#xff1a;12,345.00 代码非常简单 using UnityEngine; using TMPro;public class …

[机器学习系列]深入解析K-Means聚类算法:理论、实践与优化

目录 一、KMeans (一)Kmeans简介 (二)Kmeans作用和优点 (三)Kmeans局限和缺点 (四)Kmeans步骤 (五)如何选取最佳的K值的三种方法 (六)手肘法和目标函数的变化两种确定K值方法的区别 (七)如何选取第一次迭代的K个类中心------KMeans方法 (八)KMeans的常用参数介绍 二、…

【C语言刷题系列】对数字添加逗号

目录 一、问题描述 二、解题思路 三、源代码 拓展&#xff1a; 个人主页&#xff1a; 倔强的石头的博客 系列专栏 &#xff1a;C语言指南 C语言刷题系列 一、问题描述 二、解题思路 题目的要求&#xff0c;即对于一个较大的整数&#xff0c;每三位数字之间添加…

【go项目01_学习记录day01】

博客系统 1 vscode开发go项目插件推荐1.1 CtrlShiftP&#xff08;俗称万能键&#xff09; &#xff1a;打开命令面板。在打开的输入框内&#xff0c;可以输入任何命令。1.2 开发时&#xff0c;我们需要经常查阅 Go 语言官方文档&#xff0c;可惜因国内访问外网不稳定&#xff0…

自己手写了一个大模型RAG项目-05.基于知识库的大模型问答

大家好&#xff0c;我是程序锅。 github上的代码封装程度高&#xff0c;不利于小白学习入门。 常规的大模型RAG框架有langchain等&#xff0c;但是langchain等框架源码理解困难&#xff0c;debug源码上手难度大。 因此&#xff0c;我写了一个人人都能看懂、人人都能修改的大…

03-JAVA设计模式-解析器模式

解释器模式 什么是解析器模式 在Java中&#xff0c;解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为设计模式&#xff0c;它给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;该解释器使用该表示来解释语言中的句子…

unity3d使用3D WebView播放网页中的视频

Unity2021.3.35f1&#xff0c;硬件ESP32-Cam&#xff0c;3D WebView插件 1.新建工程&#xff0c;导入3D WebView for Winfows和3D WebView for Android 2.打开场景Assets\Vuplex\WebView\Demos\Scenes\2_CanvasWebViewDemo 3.修改Canvas的Render Mode为Screen Space-Camera&am…

30秒出服装设计稿,森马用Serverless+AIGC 整“新活”!

“创新项目如何去赋能我们的业务&#xff0c;这件事情在森马很重要。阿里云函数计算帮我们屏蔽掉了想把AI落地到实际业务场景中 GPU 算力资源储备、采购成本、技术门槛等很多难题&#xff0c;从而迅速做出决策&#xff0c;快人一步站在正确的起点&#xff0c;体验新技术对整个服…

『春招实习』2023年3月春招实习求职经历

『春招实习』2023年3月春招实习求职经历 简介货拉拉一面杭州吉里一面传墨科技一面 简介 3月初我便开始陆续投递简历&#xff0c;直观的感受就是【投递的太晚了】&#xff0c;很多公司很早就开始招聘实习生了。 但是自己一直在担心没准备好&#xff0c;所以就想着再准备一天就投…

数据结构单链表”质检员“*2

1.随机链表的逻辑结构复制 原题链接&#xff1a; . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode-cn.com/problems/copy-list-with…

机器学习:深入解析SVM的核心概念(问题与解答篇)【三、核函数】

核函数 **问题一&#xff1a;为什么说是有限维就一定存在高维空间可分呢&#xff1f;**原始空间与特征空间为什么映射到高维空间可以实现可分核函数的作用 **问题二&#xff1a;最终怎么得到函数**从对偶问题到决策函数的步骤&#xff1a;结论 **问题三&#xff1a;为什么说特征…

Kafka 3.x.x 入门到精通(03)——Kafka基础生产消息

Kafka 3.x.x 入门到精通&#xff08;03&#xff09;——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.2 集群启动2.3 创建主题2.4 生产消息2.4.1 生产消息的基本步骤2.4.2 生产消息的基本代码2.4.3 发送消息2.4.3.1 拦截器2.4.3.1.1 增加拦截器类2.4.3.1.2 配置拦截器 2.4.3…

c#数据库:1.c#创建并连接数据库

安装软件:SQL Server Management Studio Management Studio Visual Studio 2022 启动服务: 打开SQL Server Management Studio Management Studio ,连接到服务器(GUANZU是我的计算机名) 新建数据库,随便起个名字叫aq: c#代码: using System; using System.Collections.Gener…

UG NX二次开发(C++)-获取模型中所有的拉伸(Extrude)特征

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 1、前言2、在UG 12中创建几个拉伸特征3、UFun中获取对象类型4、通过NXOpen过渡5.测试结果1、前言 在采用UG NX二次开发时,有时需要在模型中获取特定的对象,比如拉伸特征、关联特征等等。但是通过…

大数据分析与应用实验(黑龙江大学)

实验一 Hadoop伪分布式实验环境搭建与WordCount程序 一、实验目的 1、学习搭建Hadoop伪分布式实验环境 2、在伪分布式实验环境下运行WordCount程序 二、实验内容 1、搭建Hadoop伪分布式实验环境&#xff0c;并安装Eclipse。 2、在Eclipse环境下&#xff0c;编写并执行Wor…

面试题:分布式消息中间件 MQ

MQ官网文档&#xff1a; RabbitMQ&#xff1a;https://www.rabbitmq.com/docs RocketMQ&#xff1a;https://rocketmq.apache.org/zh/docs/ Kafka&#xff1a;https://kafka.apache.org/documentation/ DDMQ&#xff1a;https://base.xiaojukeji.com/docs/ddmq 面试题&#xff…

【LeetCode周赛】第 395 场周赛

目录 3131. 找出与数组相加的整数 I 简单3132. 找出与数组相加的整数 II 中等3133. 数组最后一个元素的最小值 中等3134. 找出唯一性数组的中位数 困难 3131. 找出与数组相加的整数 I 简单 3131. 找出与数组相加的整数 I 分析&#xff1a; 将两个数组中最小的两个值相减即可。…

你的动漫AI女友 Anime gf :自定义创建各种独特个性、语言风格的虚拟角色

一个本地且开源的 CharacterAI 替代工具 Anime gf&#xff0c;提供了一个用户友好的界面&#xff0c;允许用户在桌面上与虚拟角色互动。你可以自定义创建各种角色&#xff0c;让每个虚拟角色都有自己的独特个性和语言风格&#xff0c;可以接入OpenAI、Anthropic、Mistral和 Tog…

Faststone Capture:一触即发的效率革命【AI写作】

首先&#xff0c;这篇文章是基于笔尖AI写作进行文章创作的&#xff0c;喜欢的宝子&#xff0c;也可以去体验下&#xff0c;解放双手&#xff0c;上班直接摸鱼~ 按照惯例&#xff0c;先介绍下这款笔尖AI写作&#xff0c;宝子也可以直接下滑跳过看正文~ 笔尖Ai写作&#xff1a;…

XY_RE复现(二)

一&#xff0c;何须相思煮余年 0x55 0x8b 0xec 0x81 0xec 0xa8 0x0 0x0 0x0 0xa1 0x0 0x40 0x41 0x0 0x33 0xc5 0x89 0x45 0xfc 0x68 0x9c 0x0 0x0 0x0 0x6a 0x0 0x8d 0x85 0x60 0xff 0xff 0xff 0x50 0xe8 0x7a 0xc 0x0 0x0 0x83 0xc4…