键树_Trie树_介绍和C语言实现_20230511

news2025/1/11 5:57:42

键树_Trie树形式_树介绍及C语言实现

  1. 前言

上一篇提到键树有两种不同的表示方法,它们分别是双链树和Trie树,在上文中对双链树的数据结构以及在键树上的C语言实现做了详细的分析与讨论。如若键树中的结点的度较大,则采用Trie树结构较双链结构更为合适。本文将介绍Trie树的数据结构基本特征,在此基础上,将实现Trie树的插入、删除和查找等基本操作。本文严格采用《数据结构》(严蔚敏)对Trie树的约束定义,请读者特别留意其前提条件。

  1. Trie树介绍

用树的多重链表来表示键树,则树的每个结点中应含有d个指针域,此时的键树就成为称作Trie树,如果结点中仅含有数字,规定d的值为11;如果结点中仅含有大写字母或小写字母,规定d的值为27,d定义为结点的最大度(子树指针数量)。

若重键树中某个结点到叶子结点的路径上的每个结点都只有一个孩子,则可将该路径上的所有结点压缩为一个“叶子结点”,且在该叶子结点储存关键字既指向记录的指针的信息等。

从结点Z到结点$单支树,相应的Trie树中就只有含有一个关键字ZHAO及相关记录信息的叶子结点。对于关键字CHEN,从结点C到结点$,结点H和E都不是单支树,但是结点N和结点$为单支树,所以μ直接指向叶子结点CHEN$。对于关键字CHA,从结点C到结点 $,每个结点都不是单支树,最后代表$字符的φ指针指向最终的叶子结点及相关的记录信息。

因此在Trie中有两种结点,分支结点和叶子结点,分支结点中含有d个指针域和一个指示该节点中非空指针域的个数的整数;叶子结点中含有关键字与指向记录的指针域。 显而易见,在分支结点中,不设定数据域,每个分支结点所表示的字符均有其父节点的指针位置代表的字符决定,比如β结点所代表的的字符为根节点中β结点所在位置的字符,也就是β代表的字符为C。值得一提的是,可以采用线性哈希函数实现字符和其代表结点的对应关系,本文中采用int ord(char ch)作为哈希函数,实现字符和指针之间映射关系。

在这里插入图片描述

  1. Trie树常见操作

3.1 查找操作

从上图中可以看出,在查找成功时走了一条从根节点到叶子结点的路径。例如在上图中查找CHEN, 从根节点出发,经过β结点,然后再到达δ结点,最后来到μ结点,μ结点指向叶子结点,叶子结点的关键字与CHEN匹配,从而判定查找成功。

如果要查找关键字CHI,类似地,从根节点出发,经过β结点,然后到达δ结点,在δ结点中,I字符指向的结点为空,从而判定结点查询失败。

3.2 插入操作

由于在特定的条件下可将路径上所有的结点压缩为一个叶子结点,这就使得插入操作显得比较复杂。插入过程中,分为两种不同的情形,从根节点出发,开始比对待插入的关键字字符代表的结点是否存在,如果指针存在,则不断往下搜索,直至指针为空或指针指向叶子结点。为了描述方便,假定新插入的关键字与现有树中的关键字都不相同。

第一类所代表为p指针为空的情形,要求在如下的子树中插入关键字CHAO, 它代表第一类插入情形,从根节点出发,关键字C代表的结点已存在,β指针代表结点C,按照前述讨论,继续往下搜索H结点,δ指针代表结点H,之后来到代表A的λ结点,在λ结点中,发现代表O的结点为空,这时候直接创建一个叶子结点,在O的位置处储存叶子结点的指针ω。

插入前:

在这里插入图片描述

插入后:

在这里插入图片描述

第二种情况,相对而言稍微复杂,p指针指向为叶子结点,具体看一个例子。在下面的Trie树中插入关键字CAKMO$。

在这里插入图片描述

首先从根节点出发,查找C对应的为β结点,在查找A对应的结点为γ结点,γ结点对应为叶子结点,此叶子结点的关键字信息为CAKMI$ , 通过比较发现,一直到M,二者的关键字字符都相同,可以理解为单树,然后分别为O和I,有两个分支结点,最后$字符相同。

通过上述阐述,不难发现,如果遇到叶子结点,在插入新节点过程中,实际上需要比较已有的叶子结点和待插入结点的关键字的相关信息,如果相同位置代表的字符相同,则直接创建分支结点,分支结点中的d为1,重复前述过程直至两个关键字当中的字符不相同,此时需要创建两个叶子结点(当然也可以选择保存原有的叶子结点,直接赋值),总结上述的过程,整个过程包括:

  • 分支结点替换叶子结点(γ结点为分支节点,替换之前的叶子结点)
  • 创建分支结点(ε,ζ
  • 创建叶子结点(η,θ),η,θ分别代表原有叶子结点和现插入的叶子结点

完成关键字CAKMI$插入后,新Trie树结构为,

在这里插入图片描述

3.3 删除操作

删除操作与插入操作类似,首先需要找到待删除关键字所在的叶子结点,如果结点度大于2,那么直接删除此叶子结点即可。

比如要删除关键字CHAO$,直接在λ结点删除ω结点即可,删除后对整个Trie树没有任何影响。

在这里插入图片描述

如果删除关键字的度d为2,那么就要向上回退直至某个结点的度≥2为止,由于无法确定回退高度,选择采用递归删除为最佳的程序实现方式。

那么是否存在删除关键字的度d为1的情况呢?这个问题留给大家去思考。根据Trie树的定义,答案显然是否定的,如果度为1,那么就是独树,它可以向上继续压缩直至不是独树。

  1. 程序实现

根据上述描述,程序实现是水到渠成的事情,在此不再赘述。

4.1 头文件定义

/**
 * @file TrieTree.h
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2023-05-10
 *
 * @copyright Copyright (c) 2023
 *
 */

#ifndef TRIETREE_H
#define TRIETREE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))

#define MAXKEYLEN 16
#define N         27

typedef char KeyType;
typedef char* Record;


typedef struct  SElemType
{
    KeyType key;
    Record  value;
}SElemType;

//Dynamic Search Table
typedef struct DSTable
{
    SElemType *elem;
    int        len;
} DSTable;

void CreateTable(FILE *fp, DSTable *st);


typedef struct KeysType
{
    char ch[MAXKEYLEN];
    int num;
}KeysType;

typedef enum NodeKind
{
    LEAF,
    BRANCH
} NodeKind;

/*
typedef struct DLTNode
{
    char symbol;
    NodeKind kind;
    struct DLTNode *next;
    union 
    {
        Record infoptr;
        struct DLTNode *first;
    };
    
}DLTNode, *DLTree;

*/

typedef struct TrieNode
{
   NodeKind kind;
   //anonymous union structure
   union
   {
        struct 
        {
            KeysType keys;
            Record   infoptr;
        } lf; //leaf=lf

        struct
        {
            struct TrieNode *ptr[N];
            int              num;
        }bh; //branch=bh   
   };
   
}TrieNode, *TrieTree;

/**
 * @brief Create a trie tree object
 * 
 * @param trie Pointer to Trie tree
 */
void create_trie(TrieTree *trie);

/**
 * @brief Insert a new keys into the Trie tree
 * 
 * @param trie Trie tree
 * @param keys Keys to be inserted
 */
void insert_trie(TrieTree root, KeysType keys);


/**
 * @brief Delete the keys from the trie tree
 * 
 * @param trie Target trie tree
 * @param keys KeysType 
 */
int delete_trie(TrieTree root, KeysType *keys, int i,TrieTree *temp_node);

/**
 * @brief Search the keys from KeysType
 * 
 * @param trie Trie key type
 * @param keys To be searched keys
 * @return Record Return record if execute successfully
 */
Record search_trie(TrieTree root, KeysType keys);

int ord(char ch);

void create_leaf_node(TrieTree p, KeysType keys,int i);

void create_branch_node(TrieTree p,int i);

#endif

4.2 函数实现文件

/**
 * @file TrieTree.c
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2023-05-10
 *
 * @copyright Copyright (c) 2023
 *
 */
#ifndef TRIETREE_C
#define TRIETREE_C
#include "TrieTree.h"

void CreateTable(FILE *fp, DSTable *st)
{
    int n;
    char str[MAXKEYLEN];
    int i;


    n=0;
    // 当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
    while (fgets(str, MAXKEYLEN, fp) != NULL)
    {
        n++;
    }

    fseek(fp,0,SEEK_SET);

    st->len=n;
    st->elem=(SElemType *)malloc(sizeof(SElemType)*(n+1));

    for(i=1;i<=n;i++)
    {
        st->elem[i].value = (Record)malloc(sizeof(char) * MAXKEYLEN);
        memset(st->elem[i].value, 0, sizeof(char) * MAXKEYLEN);
        fscanf(fp,"%c %s%*c",&(st->elem[i].key),st->elem[i].value);
    }

    return;    
}

void create_trie(TrieTree *trie)
{
    *trie=(TrieTree)malloc(sizeof(TrieNode));
    (*trie)->kind=BRANCH;
    (*trie)->bh.num=0;
    memset((*trie)->bh.ptr,0,sizeof(TrieNode *)*N);

    return;
}

void insert_trie(TrieTree root, KeysType keys)
{
    TrieTree p;
    TrieTree pre_p;

    TrieTree new_leaf_node;
    TrieTree q;
    KeysType exist_keys;

    int      i;
    int      j;

    p=root;
    pre_p=NULL;
    i=0;

    //find the insert location;
    while(p && p->kind==BRANCH && i<keys.num)
    {
        pre_p = p;
        p=p->bh.ptr[ord(keys.ch[i])];        
        i++;
    }

    i-=1;
    j = i;

    if(p&&p->kind==LEAF)
    {
        exist_keys = p->lf.keys;
        while(i<keys.num && j<exist_keys.num &&keys.ch[i]==exist_keys.ch[j])
        {

            //Execute replacement action to replace leaf with branch
            create_branch_node(pre_p, ord(keys.ch[i])); // it would be NULL at ptr[ord(keys.ch[i])]
            // pre_p will point to newly-created node(replace the old leaf with new branch)
            pre_p=pre_p->bh.ptr[ord(keys.ch[i])]; 
            
            i++;
            j++;  
        }
        // reset to zero before inserting the created leafs
        pre_p->bh.num-=1; 
        create_leaf_node(pre_p, exist_keys, j); //create the exist_keys leaf node
    }
    
    create_leaf_node(pre_p, keys, i); //create the new keys leaf node

    return;
}

//use recurision to achieve the function
int delete_trie(TrieTree root, KeysType *keys, int i, TrieTree *temp_node)
{

    int temp;
    
    if (root->kind == LEAF)
    {
        *temp_node=root;
         return 1; //continue action after stack frame pops up
    }
    else
    {
        temp=delete_trie(root->bh.ptr[ord((*keys).ch[i])],keys,i+1,temp_node);
        if(temp)
        {
            if (root->bh.ptr[ord((*keys).ch[i])] == *temp_node)
            {
                root->bh.num--;
                root->bh.ptr[ord((*keys).ch[i])] == NULL; //delete the target leaf
                if (root->bh.num == 1) // if there is still one remaining, just search it
                {
                    for (int j = 0; j < N; j++)
                    {
                        if (root->bh.ptr[j] && root->bh.ptr[j]->kind == LEAF)
                        {
                            *keys = root->bh.ptr[j]->lf.keys;
                            *temp_node = root->bh.ptr[j];
                            return 1; //search successfully and return 1
                        }
                    }
                }
               
                return 0;  // otherwise, return 0 and no actions will be taken when stack pop up
            }
            else if(root->bh.num==1) // if the immediate parent only has one pointer, continue upward search
            {
                return 1;
            }
            else //when root->bh.num>=2, the other leaf will be installed and action ended
            {
                root->bh.ptr[ord((*keys).ch[i])]=*temp_node;
                return 0;
            }
        }
        else //no actions will be taken, just return 0
        {
            return 0;
        } 
    }
}

Record search_trie(TrieTree root, KeysType keys)
{
    int i;
    TrieTree p;

    i=0;
    p=root;

    while(p && p->kind==BRANCH && i<keys.num)
    {
        p=p->bh.ptr[ord(keys.ch[i])];
        i++;
    }

    if(p && p->kind==LEAF)
    {
        return p->lf.infoptr;
    }
    else
    {
        return NULL;
    }
}

//----------------------------------//


int ord(char ch)
{
    return (ch=='$'?0:ch-'A'+1);
}

void create_leaf_node(TrieTree p, KeysType keys,int i)
{
    TrieNode * new_node;
    Record     str;

    new_node = (TrieTree)malloc(sizeof(TrieNode));
    new_node->kind = LEAF;

    str = (char *)malloc(sizeof(char) * (keys.num + 1));
    memset(str, 0, sizeof(char) * (keys.num + 1));
    strncpy(str, keys.ch, keys.num);

    new_node->lf.keys = keys;
    new_node->lf.infoptr = str;

    p->bh.ptr[ord(keys.ch[i])]=new_node;
    p->bh.num+=1;

    return;
}

void create_branch_node(TrieTree p, int i)
{
    TrieNode *new_node;

    new_node = (TrieTree)malloc(sizeof(TrieNode));
    new_node->kind = BRANCH;
    memset(new_node->bh.ptr,0,sizeof(TrieTree)*N);
    new_node->bh.num=1;

    p->bh.ptr[i] = new_node;
 
}

#endif

4.3 测试文件

/**
 * @file TrieTree_main.c
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2023-05-10
 *
 * @copyright Copyright (c) 2023
 *
 */
#ifndef DLTREE_MAIN_C
#define DLTREE_MAIN_C
#include "TrieTree.c"

int main(void)
{
    int i;
    int j;
    int len;
    FILE *fp;
    DSTable st;
    TrieTree root;
    TrieTree temp_node;
    KeysType keys;
    KeysType del_keys;

    fp=fopen("data.txt","r");
    CreateTable(fp,&st);

    create_trie(&root);

    for(i=1;i<=st.len;i++)
    {
        len=strlen(st.elem[i].value);
        memset(keys.ch, 0, sizeof(char) * MAXKEYLEN);
        strcpy(keys.ch,st.elem[i].value);
        keys.num=len;
        if(i==2)
        {
            del_keys=keys;
        }
        insert_trie(root,keys);
    }

    delete_trie(root,&del_keys,0,&temp_node);

    printf("Inserting is done\n");
    

    printf("\n");

    getchar();
    fclose(fp);
    return EXIT_SUCCESS;
}


#endif

4.5 数据源文件(data.txt)

1 CAI$
2 CAO$
3 CHA$
4 CHANG$
5 CHAO$
6 CHEN$
7 ZHAO$
  1. 小结

通过C语言实现Trie树,对Trie的结构有了更深入的了解,同时加深了指针在程序中的应用,通过重复操作分析,抽象出创建分支结点和叶子结点的子函数,使程序可读性增强。

参考资料:

  1. 《数据结构》 严蔚敏

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

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

相关文章

【软件工程】期末复习总结(通俗易懂,学不会来打我)

【软件工程】期末复习总结&#xff08;通俗易懂&#xff0c;学不会来打我&#xff09; 第一章 1.1 软件工程的发展历程 1.1.1 软件危机&#xff08;日子没法过了&#xff09; 软件危机&#xff08;Software Crisis&#xff09;是指在计算机软件开发、运行、维护和管理过程中…

126-Linux_git安装及使用

文章目录 一.git基本概念1.什么是git2.git的特点3.git工作流程4.文件的四种状态 二.git的安装1.在ubuntu上测试有没有安装2.使用命令 sudo apt install git 进行安装3.安装后查看版本,检查是否安装成功 三.git的使用1.git常用命令(1)创建一个目录(2)使用git init 命令将其变为一…

Netty编程入门超级详细,有这篇就足够了

目录 前言一、简介二、为什么使用Netty2.1 NIO的缺点2.2 Netty的优点 三、架构图四、永远的Hello Word4.1引入Maven依赖4.2 创建服务端启动类4.3 创建服务端处理器4.4 创建客户端启动类4.5 创建客户端处理器4.6 测试 五、Netty的特性与重要组件5.1 taskQueue任务队列5.2 schedu…

MyBatis的CRUD

0-基础知识 id&#xff1a;唯一标识 type&#xff1a;映射的类型&#xff0c;支持别名 java里的命名规则是驼峰&#xff0c;而sql里面是下划线&#xff0c;如何对数据库表的字段起别名&#xff1f; 数据库表的字段名称和实体类的属性名称 不一样&#xff0c;则不能自动封装数据…

算法套路十五——最长公共子序列LCS

算法套路十五——最长公共子序列LCS 算法示例&#xff1a;LeetCode1143. 最长公共子序列 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&am…

Windows系统配置Anaconda虚拟环境,并安装Numpy、Scipy和Matplotlib等模块方法

有些项目不是必须在Ubuntu系统下进行的&#xff0c;对大部分人来说更熟悉Window系统&#xff0c;且查阅电脑中相关文件和使用微信更方便&#xff0c;因此记录一下Windows系统配置Anaconda虚拟环境步骤和安装Numpy、Scipy及Matplotlib等模块方法。 一、Anaconda安装 Anaconda可以…

异步电机速度估计-模型参考自适应MRAS法(补充)

导读&#xff1a;前期文章已经介绍过模型参考自适应MRAS进行速度估计的方法&#xff0c;本期文章主要是对MRAS实现的细节做一下补充。 若需要文章的仿真模型&#xff0c;关注微信公众号&#xff1a;浅谈电机控制&#xff0c;获取。 一、MRAS知识点回顾 1.1 基本原理 MRAS 模…

Calico的BGP打通Kubernetes网络和局域网

1、项目背景 随着云原生技术的不断发展&#xff0c;容器化应用已成为企业构建云原生架构的重要方式。而随着集群规模不断扩大&#xff0c;跨主机通信的需求也越来越重要。在 Kubernetes 集群中&#xff0c;Pod 是最小的调度和管理单位&#xff0c;而网络也是 Kubernetes 中最重…

双链表——“数据结构与算法”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰又回来了&#xff0c;到了好久没有更新的数据结构与算法专栏&#xff0c;最近确实发现自己有很多不足&#xff0c;需要学习的内容也有很多&#xff0c;所以之后更新文章可能不会像之前那种一天一篇或者一天两篇啦&…

浅谈 Node.js

Node.js 是什么&#xff1f; Node.js 是一个开源、跨平台的 JavaScript 运行时环境。 官网&#xff1a;https://nodejs.org/zh-cn 更多精彩内容&#xff0c;请微信搜索“前端爱好者“&#xff0c; 戳我 查看 。 Node.js ≠ JavaScript Node.js中&#xff0c;没有BOM和DOM。…

【LLM】LangChain基础使用(构建LLM应用)

note LangChain应用开发框架&#xff0c;支持python和typescript语言&#xff1b;可以帮助生成prompt模板&#xff0c;并通过代理充当其他组件&#xff08;如提示模板、其他大语言模型、外部数据和其他工具&#xff09;的中央接口。LangChain可以直接与 OpenAI 的 text-davinc…

BGW协议(算数共享)

概述 BGW协议可以用于对域上包含加法、乘法、常数乘法门的算术电路求值&#xff0c;此协议强依赖Shamir秘密分享方案&#xff0c;利用其同态特性对各个秘密份额进行适当的处理&#xff0c;就可以在秘密值上进行安全计算。 加法门 算数加法共享&#xff08;两方&#xff09; …

c++ 友元介绍

友元的目的就是让一个函数或类访问另一个函数中的私有成员 友元函数 &#xff08;1&#xff09;普通函数作为友元函数 class 类名{friend 函数返回值类型 友元函数名(形参列表);//这个形参一般是此类的对象.... } 经过以上操作后&#xff0c;友元函数就可以访问此类中的私有…

Vue最新快速上手教程(狂神)

文章目录 前端核心分析1. 第一个Vue程序2. Vue基本语法3. Vue绑定事件4. Vue双向绑定5. 组件讲解6. Axios异步通信7. 计算属性8. 插槽slot9. 自定义事件内容分发10. 第一个vue-cli程序11. webpack学习使用12. vue-router路由13. vueelementUI14. 路由嵌套15. 参数传递及重定向1…

【JAVA】黑马程序员JAVA教程笔记 基础篇 Day 1

常用命令行DOS命令 Path环境变量 用途 1. 可以理解为系统中的一个大管家&#xff0c;记录了很多软件的完整路径。 2. 将常用的软件都交给Path环境变量&#xff0c;便于用命令行打开。 设置步骤 复制要使用的软件的存储地址右键点击 此电脑&#xff0c;打开属性点击 高级系统…

【2023最新】几乎涵盖你需要的Android性能优化的所有操作

前言 现如今&#xff0c;Android开发在市面上的需求不再像以前一样一人难求&#xff0c;僧多认识的情况直接导致整个行业对Android开发岗位的要求越来越高&#xff0c;Android 开发越来越规范&#xff0c;间接导致项目对质量要求的提升。启动优化、内存优化、App 崩溃监控等性…

【网络安全CTF】BUUCTF(Basic篇)

Linux Labs 解题思路&#xff1a;已给用户名密码&#xff0c;直接用ssh工具连接即可获取flag 查找flag在跟下 提交完成。 BUU LFI COURSE 1 访问链接&#xff1a;为php代码审计题&#xff0c;看题目要求构造GET请求读取文件 http://9a7d4988-99f9-4c29-88d8-600e19887723.n…

三极管知识大全

一、三极管的使用 一般可以当做开关管来使用&#xff0c;也可以利用三极管的放大特性&#xff0c;来搭建恒流源&#xff0c;恒压源等等&#xff0c; 三极管当做开关管来使用的话&#xff0c;三极管输出的是高、低、高、低的方波信号 BUCK电源的开关频率在65KHz&#xff0c;也…

【刷题笔记】另类加法+走方格的方案数

一、另类加法 题目&#xff1a; 牛客网链接&#xff1a;另类加法_牛客题霸_牛客网 描述 给定两个int A和B。编写一个函数返回AB的值&#xff0c;但不得使用或其他算数运算符。 测试样例&#xff1a;1&#xff0c;3 返回&#xff1a;4 解析&#xff1a; 因为无法使用算数运算符…

网易云音乐开发--完善video模块

解决多个视频同时播放问题 老样子&#xff0c;npm start开启服务器 说一下问题 现在打开多个视频会让他&#xff0c;同时播放&#xff0c;相当的吵闹。我们只需要一次只有一个视频播放 看文档&#xff0c;我们的思路是点击这个视频&#xff0c;关闭上次视频 我们传入这个id …