交换机转发实验(回顾哈希表实现)

交换机转发实验(回顾哈希表实现)

哈希表的C语言实现

哈希函数为对哈希表长取余

哈希表算法实现
# include <stdio.h>
# include <stdlib.h>

// 宏定义
//数据类型重定义
# define uint8_t unsigned char
# define uint16_t unsigned short
# define uint32_t unsigned long

// 哈希表长度
# define HASH_TABLE_LEN 100
数据结构

哈希表由结构体数据和索引链表来表示,首先是索引和对应的指针,然后指针连成链表

// 链表结点
typedef struct _Link_Node
{
    uint16_t id;
    uint16_t data;
    struct _Link_Node *next;
}Link_Node,*Link_Node_Ptr;
// 链表结点由id,data和指向下一个链表元素的指针构成
// [id | data | ---]--> [id| data| --]--> [id | data | NULL]

// 哈希表头
/*哈希表头略有不同,这里其实只需要一个指针,而哈希表头元素的具体位置可以由
数组的下标元素来指示*/
typedef struct _Hash_Header
{
    struct  _Link_Node *next;
}Hash_Header,*Hash_Header_ptr;

全局变量

// 一个完整的哈希表
Hash_Header_Ptr Hash_Table[HASH_TABLE_LEN];

这里定义了一个以哈希表头结构体为元素的数组,数组的长度为100,因为需要表示数组,所以类似于char* string 等价于 char string[]。

函数
  • 哈希表函数,用哈希函数生成id对应的哈希表中的位置
  • 函数功能: 输入 id, 返回 位置 pos
uint8_t hash_func(uint16_t id)
{
    uint8_t pos = 0;
    pos = id % HASH_TABLE_LEN;
    return pos;
}

至此,是可以用hash.h封装的一个哈希表的数据类型,具体对这个表的增、删、改、查需要注意。

初始化函数

初始化链表节点
Link_Node_Ptr init_link_node(void)
{
    Link_Node_ptr node;
    /* 这个函数是对哈希数组(我们把表头元素构成的数组称为哈希数组,把后面所缀的散列称为链表结点,现在这个函数的作用是初始化一个链表结点,包括id|data|指向下一个元素的指针
    Link_Node_ptr表示指向链表结点的指针*/
    // 申请结点,然后设置尾指针为NULL
    node = (Link_Node_ptr)malloc(sizeof(Link_Node));
    // 初始化长度为 0
    node -> next = NULL;
    return node;
}
初始化哈希表头结点
Hash_Header_Ptr init_hash_header_node(void)
{
    Hash_Header_ptr node;
    // 申请结点
    node = (Hash_Header_Ptr)malloc(sizeof(Hash_Header));
    // 初始化长度为 0
    node ->next = NULL;
    return node;
}
哈希表初始化
void init_hash_table(void)
{
    uint8_t i = 0;
    for(i = 0; i < HASH_TABLE_LEN;i++)
    {
        Hash_Table[i] = init_hash_header_node();
        /*这一句目前感觉不需要,属于重复说明,首先我们之前定义了一个全局变量,				HASH_TABLE,采用初始化哈希表头,为每一个数据元素(结构体)申请空间,
        就是为每一个数组元素申请空间,指针赋值为NULL,最后返回node
        所以上面两句可以理解为
        Hash_Table[i] = node;
        node ->next = NULL;
        第二句应该是不需要的*/
        Hash_Table[i]->next = NULL;
    }
}

初始化操作完成

  • 在哈希表增加结点
/*增加结点属于在哈希表的链表末尾进行数据增加,链表末尾需要判断是第一个直接与哈希表头连接的结点,还是已经存在其它的结点*/
void append_link_node(Link_Node_Ptr new_node)
{
    Link_Node_Ptr node;
    uint8_t pos = 0;
    // 新结点下一个指向为空
    new_node ->next = NULL;
    // 用哈希函数获取位置
    pos = hash_func(new_node->id);
    
    // 判断是否为直接与哈希表头相连
    if(Hash_Table[pos]->next == NULL){
        Hash_Table[pos]->next = new_node;
    }
    else{
        //需要找到该位置所对应的最后一个结点
        node = Hash_Table[pos]->next;
        // 进行链表的遍历
        while(node ->next != NULL){
            node = node->next;
        }
        // 插入
        node ->next = new_node;
    }
}
  • 在哈希表查询结点
/*在哈希表查询结点:
1.找到在哈希表某处的单链表中,并开始遍历
2.返回的是查询结点的前一个结点指针,这样便于删除操作
输入 pos : 哈希表数组元素下标,从0开始计数
id : 需要查询的结点id 
root: 如果是第一个链表结点,则*root = 1, 否则为0
返回: 所需查询的结点的前一个结点指针,如果是第一个链表结点,则返回第一个链表结点
失败则返回0*/
Link_Node_Ptr search_link_node(uint8_t id,uint8_t *root)
{
    Link_Node_ptr node;
    uint8_t pos = 0;
    // 用哈希函数获得位置
    pos = hash_func(id);
    
    // 获取第一个链表结点
    node = Hash_Table[pos]->next;
    // 判断单链表是否存在
    if(node ==  NULL){
        return 0;
    }
    // 判断是否为第一个链表结点
    if(node ->id == id){
        *root = 1;
         return node;
    }
    else{
        // 不是第一个链表结点,遍历
        *root = 0;
        while(node ->next != NULL){
            if(node->next->id == id){
                return node;
            }
            else{
                node = node->next;
            }
        }
        return 0;
    }
}
  • 在哈希表删除结点
  • 删除的不是当前节点,而是输入节点的后一个节点
  • 输入: node:删除此节点后面的一个节点 new_node:新节点
void delete_link_node(Link_Node_Ptr node)
{
    Link_Node_Ptr delete_node;
    // 重定向需要删除的前一个结点
    delete_node = node -> next;
    node -> next = delete_node ->next;
    // 删除结点
    free(delete_node);
}
  • 删除第一个链表结点

    void delete_link_root_node(Link_Node_Ptr node)
    {
        uint8_t pos = 0;
        // 用哈希函数获得位置
        pos = hash_func(node->id);
        // 哈希表头清空
        if(node != NULL){
            Hash_Table[pos]->next = node ->next;
            //删除结点
            free(node);
            node = NULL;
        }
    }
    

    这里感觉写法有点问题,一般free以后不需要再赋值NULL

    // 试水: 删除第一个链表结点
    void delete_link_root_node(Link_Node_Ptr node)
    {
        uint8_t pos = 0;
        pos = hash_func(node->id);
        if(node != NULL){
            Hash_Table[pos]->next = node->next;
            free(node);
        }
        else{
            Hash_Table[pos]->next = NULL;
        }
    }
    
    计数
    // 获得哈希表中所有的结点数
    uint16_t get_node_num(void)
    {
        Link_Node_Ptr node;
        uint16_t i = 0;
        uint16_t num = 0;
        // 遍历
        for(i = 0 ; i < HASH_TABLE_LEN;i++){
            // 获取第一个链表结点
            node = Hash_Table[i]->next;
            while(node !=NULL){
                node = node->next;
                num++;
            }
        }
        return num;
    }
    
    • 从哈希表中获得对应节点的序号
    • 参数: index: 序号: 从1 开始,最大值为节点总数值
    • root: 如果是根节点,则*root = 1,否则为 0
    • 返回: 所需查询的节点的前一个结点指针,如果是第一个链表节点,则返回第一个链表节点
    // 查找第index个结点,并返回指向该结点的指针
    Link_Node_Ptr get_node_from_index(uint16_t index,uint8_t *root)
    {
        Link_Node_Ptr node;
       	uint16_t i = 0;
        uint16_t num = 0;
        
        // 遍历
        for( i = 0; i < HASH_TABLE_LEN; i++)
        {
            // 获取第一个链表节点
            node = Hash_Table[i]->next;
            // 判断单链表是否存在
            if(node == NULL)
            {
                continue;
            }
            // 表示对于 Hash_Table中的[下标| NULL]后面的代码不再执行
            // 执行i++操作,直到找到第一个单链表存在,使得num++
            num++;
            /* 这个时候我们逃脱了第一个if判断,这个哈希表的代码实现不喜欢使用else,而是尽量用if,contiune执行操作,上面我们排除了所有表头next成员为NULL的部分,得到第一个非空的表头对应的链表,并且已经执行了num++的操作,下面需要考虑的是存在单链表的
            分两种情况判断:单链表是第一个直接和表头相连的结点(或者说根结点)
            还是普通的结点*/
            if(num == index)
            {
                *root = 1;
                return node;
            }
            /*遇到根结点,恰好下标符合,设置root并返回根结点
            否则继续进行判断*/
            while(node->next!=NULL)
            {
                num++;
                /*这里先行加了一个,所以最后实际上num应该是 index-1 
                这样提前进行 num++ 的好处在于,返回第index - 1 的结点的指针
                在最后删除操作的时候得到便利*/
                if(num == index)
                {
                    *root = 0;
                    return node;
                }
                node = node ->next;
            } 
        }
        return 0;
    }
    
    全删
    • 删除hash表中所有的结点,,但是仍保留单链表结构
    void drop_hash()
    {
        // 类似list_for_each_entry,删除时设置两个指针,指向当前和当前的后一个
        Link_Node_Ptr node;
        uint16_t i = 0;
        Link_Node_Ptr node_next;
        // 遍历
        for(i = 0; i < HASH_TABLE_LEN;i++)
        {
            //获取单链表
            node = Hash_Table[i]->next;
            
            while(1)
            {
                // 判断是否存在单链表
                if(node == NULL)
                {
                    // 不存在,这一步我依然认为没必要
                    Hash_Table[i]->next = NULL;
                    break;
                }
                
                // 下一个结点,或者说第二个...普通结点
                node_next = node->next;
                free(node);
                node = node_next;
                /*进行一个迭代,首先node = Hash_Table[i]->next指向下一个结点
                然后根据node_next = node ->next获取后面的结点信息,
                得到结点信息之后,就可以free(node)
                并且对后一个结点node_next重新开始下一轮判断*/
            }
        }
    }
    
    打印
    // 输出所有结点
    void printf_hash()
    {
        Link_Node_Ptr node;
        uint8_t root = 0;
        uint8_t i = 0;
        uint8_t num = 0;
        
        printf("------------------打印hash表-----------------\n")
        
        num = get_node_num();
        for(i = 1; i <= num;i++)
        {	/*因为get_node_from_index取的实际是第 i-1 个结点,所以从 1 开始
        	先传入 0,如果需要改变,则为1*/
            node = get_node_from_index(i,&root);
            if(node != 0)
            {
                // 拿到了返回的对应结点
                if(root)
                {
                    printf("根节点:节点号%d,id为%d\n",i,node->id);
                }
                else
                {
                    printf("普通节点:节点号%d,id为%d\n",i,node->id);
                }
            }
        }
    }
    
    主函数
    • 实现对哈希表的新建、建立节点、查询及增加、删除节点的操作

      int main()
      {
          Link_Node_Ptr node;
          uint8_t temp = 0;
          uint8_t root = 0;
          uint8_t i = 0;
          
          init_hash_table();
          //初始化,全局变量Hash_Table
          //插入数据 id = 1,data = 2
          node = init_link_node();
          node-> id = 1;
          node -> data = 2;
          append_link_node(node);
          
          // 查询节点数
          printf("1.节点数为%d\n",get_node_num());
          
          //插入数据 id = 1002,data = 1001
          node = init_link_node();
          node ->id =1002;
          node -> data = 1001;
          append_link_node(node);
          /*其实init_link_node的操作可以直接写在main函数里,这里反复调用的一个好处是,可以直接复用node变量名*/
          node = init_link_node();
          node->id = 10000;
          node ->data = 10001;
          append_link_node(node);
          
          node = init_link_node();
          node->id = 1000;
          node ->data = 10001;
          append_link_node(node);
          
          node = init_link_node();
          node ->id = 2;
          node ->data = 10001;
          append_link_node(node);
          
          // 查询节点数
          printf("2.节点数为%d\n",get_node_num());
          
          // 查询 id = 1000 并删除
          node = search_link_node(1000,&temp);
          if(node! = 0)
          {
              if(temp == 0)
              {
                  printf("删除普通节点:所查询id的值为%d,数据为%d\n",node->next->id,node->next->data);
                  
                  // 删除
                  delete_link_node(node);
              }
              else
              {
                  // 是根结点
                  printf("删除根节点:所查询id的值为%d,数据为%d\n",node->id,node->data);
                  delete_link_root_node(node);
              }
          }
          else
          {
              printf("查询失败!\n");
          }
          
          // 纯查询操作,id = 1001
          node = search_link_node(1001,&temp);
          if(node!=0)
          {
              if(temp == 0)
              {
                  printf("所需查询id的值为%d\n",node->next->data);
              }
              else
              {
                  printf("所需查询id的值为%d\n",node->data);
              }
          }
          else
          {
              printf("查询失败\n");
          }
          
          // 查询节点数
          printf("节点数为%d\n",get_node_num());
          printf_hash();
          getchar();
          return 0;    
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值