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

一、顺序循环队列

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:释放队列所占用的内存资源

cs 复制代码
#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. 哈希表的基本操作

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

cs 复制代码
#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];
        }
    }
}

总结

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

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

相关推荐
快去睡觉~3 小时前
力扣238:除自身之外数组的乘积
数据结构·算法·leetcode
yics.4 小时前
数据结构——栈和队列
java·数据结构
秋难降5 小时前
【数据结构与算法】———回溯之美
数据结构·算法
ikkkkkkkl5 小时前
LeetCode:347.前K个高频元素
数据结构·c++·算法·leetcode
_祝你今天愉快6 小时前
SparseArray & ArrayMap
android·数据结构
筱砚.6 小时前
【数据结构——并查集】
数据结构·算法
发发发发8887 小时前
leetcode 674.最长连续递增序列
java·数据结构·算法·leetcode·动态规划·最长连续递增序列
先做个垃圾出来………7 小时前
[链表]两两交换链表中的节点
数据结构·链表
效效超爱笑8 小时前
单链表应用实践
数据结构·c++·链表