当前位置: 首页 > article >正文

数据结构-----哈希表和内核链表

哈希表(Hash Table)


一、哈希表基本概念

1. 核心定义
  • 哈希函数:将关键字映射到存储位置的函数
  • 哈希冲突:不同关键字映射到相同位置的现象
  • 装载因子:α = 元素数量 / 表长度(衡量空间利用率)
2. 重要特性
特性说明
平均时间复杂度查找/插入/删除:O(1)
最坏时间复杂度所有操作退化到O(n)
空间复杂度O(n)

二、哈希函数设计原则

1. 优质哈希函数特征
  1. 计算简单
  2. 散列均匀
  3. 冲突概率低
2. 常见哈希函数类型
类型公式适用场景
除留余数法h(k) = k % p(p为质数)整数关键字
平方取中法取k²中间几位数不知分布的关键字
折叠法分割相加后取模较长数字关键字

三、完整实现示例

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


typedef int DATATYPE;


typedef struct
{
    DATATYPE *head;
    int tlen;
} HSTable;


HSTable *HSInit(int len)
{
    HSTable *hs = malloc(sizeof(HSTable));
    if (NULL == hs)
    {
        perror("HSInit malloc error");
        return NULL;
    }


    hs->head = malloc(sizeof(HSTable) * len);
    if (NULL == hs->head)
    {
        perror("HSInit malloc2 error");
        return NULL;
    }
    hs->tlen = len;
    memset(hs->head, -1, sizeof(HSTable) * len);
    return hs;
}


int HSFun(HSTable *hs, DATATYPE *data)
{
    return *data % hs->tlen;
}


int HSTableInsert(HSTable *hs, DATATYPE *data)
{
    int inx = HSFun(hs, data);
    while (hs->head[inx] != -1)
    {
        printf("data %d confilt pos :%d\n", *data, inx);
        inx = (inx + 1) % hs->tlen;
    }
    memcpy(&hs->head[inx], data, sizeof(DATATYPE));
    return 0;
}


int HSTableSearch(HSTable *hs, DATATYPE *data)
{
    int inx = HSFun(hs, data);
    int oldinx = inx;
    while (hs->head[inx] != *data)
    {
        inx = (inx + 1) % hs->tlen;
        if (oldinx == inx)
        {
            return -1;
        }
    }
    return inx;
}


int main(int argc, char const *argv[])
{
    int array[] = {12, 34, 56, 78, 91, 23, 45, 67, 89, 10};
    HSTable *hs = HSInit(10);


    for (int i = 0; i < 10; i++)
    {
        HSTableInsert(hs, &array[i]);
    }


    DATATYPE data = 34;
    int idx = HSTableSearch(hs, &data);
    if (-1 == idx)
    {
        printf("can't find\n");
    }
    else
    {
        printf("find %d\n", hs->head[idx]);
    }


    return 0;
}

内核风格链表(klist)


一、链表核心结构

1. 节点定义
typedef struct __klist {
    struct __klist *next;
    struct __klist *prev;
} KLIST;
2. 数据容器
typedef struct {
    int id;
    char name[40];
    KLIST node;  // 嵌入链表节点
} PER;

二、核心操作实现

1. 链表初始化
void klist_init(KLIST* head) {
    head->prev = head;
    head->next = head;
}

示意图

       head
┌─prev ────┐
│          │
└─next ────┘
2. 通用插入操作
void klist_add(KLIST* newnode, KLIST* prev, KLIST* next) {
    newnode->next = next;
    newnode->prev = prev;
    prev->next = newnode;
    next->prev = newnode;
}

// 头部插入
void klist_add_head(KLIST* head, KLIST* newnode) {
    klist_add(newnode, head, head->next);
}

// 尾部插入
void klist_add_tail(KLIST* head, KLIST* newnode) {
    klist_add(newnode, head->prev, head);
}

三、关键宏解析

1. 容器获取宏
#define offset(type, mem) ((size_t)&((type*)0)->mem)
#define containerof(ptr, type, mem) ({ \
    const typeof(((type*)0)->mem)* _mptr = (ptr); \
    (type*)((char*)_mptr - offset(type, mem)); })

原理图解

PER结构体内存布局
+---------------+ +---------------+
| id (4字节)    | | name (40字节) |
+---------------+ +---------------+
| node.next     | | node.prev     |
+---------------+ +---------------+
2. 安全遍历宏
#define klist_for_each(p, n, head, mem) \
    for(p = containerof((head)->next, typeof(*p), mem), \
        n = containerof(p->mem.next, typeof(*p), mem); \
        &p->mem != (head); \
        p = n, n = containerof(n->mem.next, typeof(*n), mem))

四、应用层接口

1. 添加人员信息
int add_per(int id, char* name, KLIST* head) {
    PER* per = malloc(sizeof(PER));
    if (!per) return 1;
    
    per->id = id;
    strcpy(per->name, name);
    klist_add_tail(head, &per->node);
    return 0;
}
2. 显示所有人员
int show_per(KLIST* head) {
    PER *p, *n;
    klist_for_each(p, n, head, node) {
        printf("ID: %-5d Name: %s\n", p->id, p->name);
    }
    return 0;
}
3. 删除指定人员
int del_per(KLIST* head, int id) {
    PER *p, *n;
    klist_for_each(p, n, head, node) {
        if (p->id == id) {
            klist_del(p->node.prev, p->node.next);
            free(p);
            break;  // 删除后立即退出
        }
    }
    return 0;
}

五、性能对比

操作时间复杂度特点
插入头部O(1)直接修改头节点指针
插入尾部O(1)通过tail指针直接访问
随机访问O(n)需要遍历链表
删除节点O(1)已知节点位置时直接操作指针

六、containerOf 与 offsetof 宏补充


一、核心概念解析

1. offsetof 宏
#include <stddef.h>
#define offsetof(type, member) ((size_t)&(((type *)0)->member))
  • 功能:计算结构体成员相对于结构体首地址的字节偏移量
  • 原理
    通过将结构体指针强制类型转换为 0 地址指针,获取成员的"虚拟地址",其数值即为偏移量
2. containerOf 宏
#define containerOf(ptr, type, member) \
    ((type *)((char *)(ptr) - offsetof(type, member)))
  • 功能:通过成员指针反推外层结构体指针
  • 原理
    成员指针地址减去该成员的偏移量,得到结构体起始地址

二、内存布局图示

1. 结构体内存模型
+-------------------+ <-- 结构体起始地址
| member1 (4字节)   |
+-------------------+
| member2 (1字节)   |
+-------------------+
| member3 (8字节)   |
+-------------------+
2. containerOf 工作流程
假设:
- 结构体类型为 MyStruct
- 成员指针 ptr 指向 member3
- member3 偏移量为 5 字节

ptr 地址:0x1005
结构体地址 = 0x1005 - 5 = 0x1000

三、完整代码示例

1. 数据结构定义
#include <stdio.h>
#include <stddef.h>

typedef struct list_head {
    struct list_head *prev, *next;
} LIST_HEAD;

typedef struct task_struct {
    int pid;
    char state;
    LIST_HEAD tasks;  // 嵌入链表节点
} PROCESS;
2. 宏实现
#define container_of(ptr, type, member) ({ \
    const typeof(((type *)0)->member) *__mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); })

#define list_entry(ptr) container_of(ptr, PROCESS, tasks)
3. 使用示例
void process_task(LIST_HEAD *task_node) {
    // 通过链表节点获取进程结构体
    PROCESS *proc = list_entry(task_node);
    
    printf("PID: %d, State: %c\n", proc->pid, proc->state);
}

int main() {
    PROCESS p = {.pid = 1001, .state = 'R'};
    process_task(&p.tasks);  // 输出:PID: 1001, State: R
    return 0;
}

四、关键特性对比

特性offsetofcontainerOf
输入参数结构体类型 + 成员名成员指针 + 结构体类型 + 成员名
输出结果字节偏移量(size_t)外层结构体指针
计算时机编译时运行时
标准支持C89 标准GNU 扩展

五、应用场景分析

1. Linux 内核链表
// 内核源码示例(linux/list.h)
struct list_head {
    struct list_head *next, *prev;
};

#define list_entry(ptr, type, member) \
    container_of(ptr, type, member)
2. 设备驱动开发
typedef struct usb_device {
    int vendor_id;
    struct list_head device_list;  // 设备链表节点
} USB_DEV;

void scan_devices(struct list_head *dev_node) {
    USB_DEV *dev = container_of(dev_node, USB_DEV, device_list);
    // 访问设备详细信息
}

http://www.kler.cn/a/597525.html

相关文章:

  • Unity热更新方案HybridCLR+YooAsset,从零开始,保姆级教程,纯c#开发热更
  • 2024年十大开源SLAM算法整理
  • 点亮STM32最小系统板LED灯
  • 从零开始:使用 Cython + JNI 在 Android 上运行 Python 算法
  • 内网渗透(CSMSF) 构建内网代理的全面指南:Cobalt Strike 与 Metasploit Framework 深度解析
  • pfsense部署三(snort各版块使用)
  • 渗透测试工具推荐 | BurpSuite的常用功能——抓包
  • centos7安装单机zookeeper
  • WEB攻防- PHP反序列化属性权限特征原生类 TIPS字符串逃逸CVE 绕过漏洞
  • 「JavaScript深入」WebSocket:高效的双向实时通信技术
  • Java 用二维数组输出三角形排列的字母
  • 盘泰UV种植体:抗老化新科技,焕发种植牙新活力
  • 当科技业成为系统性压榨的绞肉机
  • 【鸿蒙开发】Hi3861学习笔记- WIFI应用STA连接网络
  • nvm 安装某个node.js版本后不能使用或者报错,或不能使用npm的问题
  • 【IDEA】IDEA常用快捷键(适应包括xml所有类型文件)
  • stm32f103 boot引脚
  • 【java面试】线程篇
  • Java——Random库
  • Godot读取json配置文件