当前位置: 首页 > 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.lryc.cn/news/613672.html

相关文章:

  • SkyWalking-1--SkyWalking是什么?
  • Kubernetes学习
  • 嵌入式开发学习———Linux环境下IO进程线程学习(六)
  • Qwen系列模型
  • 对比学习(Contrastive Learning)面试基础
  • STM32——STM32CubeMX
  • 4G/5G无线电单元系统
  • C语言:单链表学习
  • 北京-4年功能测试2年空窗-报培训班学测开-第七十天-面试第一天
  • rebase 和pull的通俗区别是什么
  • Flink与Kafka核心源码详解-目录
  • 【Unity3D实例-功能-镜头】第三人称视觉-镜头优化
  • 秋招笔记-8.7
  • iSCSI 服务器
  • 《C语言》函数练习题--3
  • 5分钟了解OpenCV
  • 【MATLAB】(十)符号运算
  • XCZU19EG-2FFVB1517I FPGA Xilinx AMD ZynqUltraScale+ MPSoC
  • 《C语言》指针练习题--1
  • Gitee上免费搭建博客
  • 从“炼金术”到“工程学”:深度学习十年范式变迁与未来十年路线图
  • UnivNet论文分析(20210615)
  • 为何毫米波需要采用不同的DPD方法?如何量化其值?
  • 机器学习之随机森林(Random Forest)实战案例
  • OpenAI 开源模型 GPT-OSS深度拆解:从1170亿参数到单卡部署,重构AI开源生态
  • Java面试宝典:类加载
  • 敏捷总结-上
  • 智能制造的中枢神经工控机在自动化产线中的关键角色
  • C++的入门学习
  • TCP粘包问题详解与解决方案