交换机转发实验(回顾哈希表实现)
哈希表的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; }