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

数据结构(五):顺序循环队列与哈希表

一、顺序循环队列

1. 基本概念

顺序循环队列本质上是一个使用数组实现的队列结构,其头尾指针分别用于标记出队(删除)与入队(添加)的位置。为了实现循环效果,队列头尾位置通过取模操作循环利用数组空间。

我们通常定义一个结构体表示循环队列:

  • head:队头下标

  • tail:队尾下标

  • pbase:指向连续内存空间的指针(模拟数组)

  • 其中 pbase[head] 表示队头元素,pbase[tail] 表示即将插入的位置

数据部分可以使用结构体封装,如:

struct Data {

char name[20];

int score;

};

循环队列就是一个装载结构体数据的“数组”。


2. 空队与满队判断方式

为了简化逻辑、避免队头和队尾重合带来的混淆,循环队列通常会浪费一个空间来区分“队满”和“队空”。因此,如果申请空间为 N,最多只能存储 N - 1 个元素。

判空条件:

head == tail

判满条件:

(tail + 1) % len == head

其中 len 是申请的总空间大小,% 表示取模,确保不会越界。当尾指针追上头指针,意味着队列已满。

 小技巧:申请长度为 10 的数组,最大只能存储 9 个元素。


3. 顺序循环队列的基本操作

这段代码实现了一个顺序循环队列的基本操作流程。

一开始通过 creatSequeue() 函数动态申请了一个队列结构体,它里面包含了:

  • head:指向队头的位置(用于出队)

  • tail:指向队尾的位置(用于入队)

  • pbase:指向一块连续内存空间,用来存储具体的数据,相当于一个数组名

这个队列采用循环的方式进行操作,为了区分队满和队空,默认会浪费一个存储空间。

之后实现了几个常用功能函数:

  • isFullSequeue:判断队列是否满

  • isEmptySequeue:判断队列是否空

  • pushSequeue:入队(尾部插入)

  • popSequeue:出队(从头部取出)

  • getSequeueHead:查看队头元素但不删除

  • printSequeue:遍历并打印所有元素

  • destorySequeue:释放队列所占用的内存资源

#include <stdio.h>
#include "seq_queue.h"
#include <stdlib.h>Sequeue *creatSequeue()
{Sequeue *psq = malloc(sizeof(Sequeue));if(NULL == psq){fprintf(stderr, "malloc error\n");return NULL;}psq->head = 0;psq->tail = 0;psq->pbase = malloc(sizeof(DataType) * SEQQUE_MAX_LEN);if(NULL == psq->pbase){fprintf(stderr, "malloc errer");return NULL;}return psq;
}int isFullSequeue(Sequeue *psq)
{if((psq->tail + 1) % SEQQUE_MAX_LEN == psq->head)\{return 1;}return 0;
}int isEmptySequeue(Sequeue *psq)
{if(psq->head == psq->tail){return 1;}return 0;
}int pushSequeue(Sequeue *psq, DataType data)
{if(isFullSequeue(psq)){fprintf(stderr, "sequeue is full\n");return -1;}psq->pbase[psq->tail] = data;psq->tail = (psq->tail + 1) % SEQQUE_MAX_LEN;return 0;
}int popSequeue(Sequeue *psq, DataType *data)
{if(isEmptySequeue(psq)){return -1;}*data = psq->pbase[psq->head];psq->head = (psq->head + 1) % SEQQUE_MAX_LEN;return 0;
}int getSequeueHead(Sequeue *psq, DataType *data)
{if(isEmptySequeue(psq) || NULL == data){return -1;}*data = psq->pbase[psq->head];return 0;
}void destorySequeue(Sequeue **psq)
{free((*psq)->pbase);free((*psq));*psq = NULL;return ;
}void printSequeue(Sequeue *psq)
{int i;for(i = psq->head; i != psq->tail; i = (i + 1) % SEQQUE_MAX_LEN){printf("%d \n", psq->pbase[i]);}puts("");
}

二、哈希表(Hash Table)

1. 基本定义

哈希表(又称散列表)是一种以查找效率高为特点的数据结构。它通过一个称为“哈希函数”的映射,将关键字(key)转换为一个地址,从而将数据直接存储在对应位置。

这个映射函数 f(key) 就是 哈希函数

 散列技术既是一种存储方式,也是一种查找策略。


2. 哈希表查找过程

  • 插入时:通过 f(key) 计算地址并将数据存入对应位置

  • 查找时:再次用 f(key) 找到目标位置,直接访问数据

如果函数设计得好,理想情况下 时间复杂度为 O(1)


3. 设计哈希函数时考虑的因素

为保证性能,应根据实际情况选择合适的哈希函数,以下是几个考虑维度:

  • 关键字的长度是否过长

  • 关键字在数据集中的分布是否均匀

  • 哈希表的大小(容量)

  • 查找频率是否集中于某些 key

  • 哈希函数的计算复杂度是否合理


4. 构造哈希函数的常用方法

  • 除留余数法(最常用)
    形式为:

    f(key) = key % p // 其中 p <= 哈希表长度

    适用于数值型关键字,常搭配质数 p 来均匀分布。

  • 平方取中法、折叠法
    先进行一些处理(如平方、分段求和),再取模。


5. 哈希冲突与解决方案

由于多个关键字可能映射到同一个地址(哈希冲突),我们需要有策略来处理这种冲突。

常见方法:
  • 链地址法(拉链法):
    每个槽位存储一个链表,所有哈希值相同的元素都放在该链表中。

  • 开放地址法
    冲突发生时,尝试寻找下一个可用槽位。具体策略包括:

    • 线性探测(+1 往后找)

    • 二次探测

    • 再哈希等

 冲突处理方法的选型会影响哈希表的插入与查询效率。


6. 哈希表的基本操作

这段代码实现了一个基于链地址法的哈希表,使用一个指针数组作为哈希表,每个数组元素指向一个链表的头节点,链表中存储哈希值相同的数据;例如多个名字首字母相同的人(如“王五”“王二”)会被存入同一个链表中,每插入一个人就动态申请一个节点,支持插入、查找、打印和销毁等基本操作,实现了按姓名快速分类和查找的功能。

#include <endian.h>
#include <stdio.h>
#include <stdlib.h>
#include "hash.h"
#include <string.h>int hashFunction(char key)
{if(key >= 'a' && key <= 'z'){return key - 'a';}else if(key >= 'A' && key <= 'Z'){return key - 'A';}else{return HASH_TABLE_MAX_SIZE - 1;}
}int insertHashTable(HSNode **hashtable, DataType data)
{HSNode *node = malloc(sizeof(HSNode));if(NULL == node){fprintf(stderr, "malloc error\n");return -1;}node->data = data;node->pnext = NULL;int addr = hashFunction(data.name[0]);if(*(hashtable + addr) == NULL){*(hashtable + addr) = node;}else{if(strcmp(hashtable[addr]->data.name, node->data.name) >= 0){node->pnext = hashtable[addr];hashtable[addr] = node;}else{HSNode *p = hashtable[addr];while(p->pnext != NULL && strcmp(node->data.name, p->pnext->data.name) > 0){p = p->pnext;}node->pnext = p->pnext;p->pnext = node;}}return 0;
}void printhash(HSNode **hashtable)
{HSNode *temp = NULL;for(int i = 0;i < HASH_TABLE_MAX_SIZE;++i){temp = *(hashtable + i);while(temp){printf("%s : %s\n", temp->data.name, temp->data.phone);temp = temp->pnext;}printf("\n");} return ;
}HSNode *findHashNodeByName(HSNode **hashtable, char *s)
{HSNode *temp = hashtable[0];for(int i = 0;i < HASH_TABLE_MAX_SIZE;++i){temp = hashtable[i];while(temp){if(0 == strncmp(temp->data.name, s, strlen(s))){return temp;}temp = temp->pnext;}}return NULL;
}void destroyHash(HSNode **hashtable)
{for(int i = 0;i < HASH_TABLE_MAX_SIZE;++i){HSNode *temp = *(hashtable + i);while(hashtable[i]){hashtable[i] = temp->pnext;free(temp);temp = hashtable[i];}}
}

总结

  • 顺序循环队列:掌握其基本结构、判空/判满逻辑及基本操作

  • 哈希表:理解散列函数设计思路、冲突处理机制以及应用场景

http://www.dtcms.com/a/319752.html

相关文章:

  • 纪念《信号与系统》拉普拉斯变换、Z变换之前内容学完
  • 树莓派Raspberry Pi 烧录
  • TCP/IP 协议中的三次握手和四次挥手-----详解笔记
  • 板块三章节3——NFS 服务器
  • 人工智能2.0时代的人才培养和通识教育
  • 计算机视觉第一课opencv(一)保姆级教学
  • lane_up channel_up
  • mysql8.0.40服务日志时间和linux系统时间不同步问题!
  • 指针——练习
  • 算法 - 贪心算法
  • 计网学习笔记第3章 数据链路层层(灰灰题库)
  • 冷库设备远程监控物联网+省电节能解决方案
  • linux下实现System V消息队列实现任意结构体传输
  • 具身智能,正在翻越三座大山
  • 计算机毕业设计java疫情开放下的新冠信息共享平台 基于Java的社区疫情防控人员流动管理系统 疫情防控期间社区人员动态管理系统
  • 范数的定义、分类与 MATLAB 应用实践
  • 解决React白板应用中的画布内容丢失问题
  • 3363. 最多可收集的水果数目
  • 关键字 - 第二讲
  • Spring AI + Redis:构建高效AI应用缓存方案
  • 【物联网】基于树莓派的物联网开发【25】——树莓派安装Grafana与Influxdb无缝集成
  • 在 Linux 系统上安装 Docker 的步骤如下(以 Ubuntu/Debian为例)
  • 前缀和
  • 简洁明了的讲明什么是哈希(hash)函数
  • [激光原理与应用-170]:测量仪器 - 能量型 - 光功率计的工作原理与内部功能模块组成
  • 【第7话:相机模型3】自动驾驶IPM图像投影拼接技术详解及代码示例
  • 直连微软,下载速度达18M/S
  • Mysql 单行函数 聚合函数
  • MySQL聚簇索引与非聚簇索引详解
  • 北京企业数据防泄漏指南:5款适合北方市场的安全加密工具评测