数据结构-队列,循环队列,哈希表

一. 队列(queue)

1.1 概念

**队列:**允许从一端进行数据的插入,另一端进行数据删除的线性存储结构

**特点:**先进先出(FIFO)

**用处:**一般用于缓存数据

1.2 队列的实现

1.2.1 队列的声明

cpp 复制代码
typedef int DataType_t;

//队列节点
typedef struct queuenode{
    DataType_t data;
    struct queuenode *pnext;
}QNode_t;

//队列对象
typedef struct queue{
    QNode_t *phead;
    QNode_t *ptail;
    int clen;
}Queue_t;

1.2.2 创建队列对象与节点、队列判空

cpp 复制代码
//创建队列对象
Queue_t *creat_queue()
{
    Queue_t *pqueue = malloc(sizeof(Queue_t));
    if(pqueue == NULL){
        printf("malloc error\n");
        return NULL;
    }
    pqueue->phead = NULL;
    pqueue->ptail = NULL;
    pqueue->clen = 0;
    return pqueue;
}

//创建队列节点
QNode_t *creat_queuenode(DataType_t data)
{
    QNode_t *newnode = malloc(sizeof(QNode_t));
    if(newnode == NULL){
        printf("malloc error\n");
        return NULL;
    }
    newnode->pnext = NULL;
    newnode->data = data;
    return newnode;
}
//判断队列为空
bool is_empty_queue(Queue_t *pqueue)
{
    return pqueue->clen == 0;
}

1.2.3 队尾入队列

cpp 复制代码
//队尾入队列
int queue_push(Queue_t *pqueue, DataType_t data)
{
    QNode_t *newnode = creat_queuenode(data);
    if(newnode == NULL){
        return -1;
    }
    if(is_empty_queue(pqueue)){
        pqueue->phead = newnode;
        pqueue->ptail = newnode;
    }
    else{
        pqueue->ptail->pnext = newnode;
        pqueue->ptail = newnode;
    }
    pqueue->clen++;
    return 0;
}

1.2.4 队头出队列

cpp 复制代码
//队头出队列
int queue_pop(Queue_t *pqueue, DataType_t *pdata)
{
    if(is_empty_queue(pqueue)){
        return -1;
    }
    QNode_t *pfree = pqueue->phead;
    pqueue->phead = pfree->pnext;
    if(pfree->pnext == NULL){
        pqueue->ptail = NULL;
    }
    if(pdata){
        *pdata = pfree->data;
    }
    free(pfree);
    pfree = NULL;
    pqueue->clen--;
    return 0;
}

1.2.5 获取队头元素

cpp 复制代码
//获取队头元素
int get_queue_top_element(Queue_t *pqueue, DataType_t *pdata)
{
    if(is_empty_queue(pqueue)){
        return -1;
    }
    *pdata = pqueue->phead->data;
    return 0;
}

1.2.6 打印队列

cpp 复制代码
//打印队列
void print_queue(Queue_t *pqueue)
{
    if(is_empty_queue(pqueue)){
        return;
    }
    QNode_t *cur = pqueue->phead;
    while(cur){
        printf("%d->",cur->data);
        cur = cur->pnext;
    }
    printf("NULL\n");
}

1.2.7 销毁队列

cpp 复制代码
//销毁队列
int destroy_queue(Queue_t **ppqueue)
{
    while(!is_empty_queue(*ppqueue)){
        queue_pop(*ppqueue, NULL);
    }
    free(*ppqueue);
    *ppqueue = NULL;
    return 0;
}

二. 循环队列

2.1 普通顺序队列

普通顺序队列存在的问题:假溢出

**假溢出:**队列数组没满(队头出过队列),但是此时继续入队列,会导致rear越界,导致不能入队

列。

**原因:**普通数组队列是直线结构,不能复用前面的空间

2.2 循环队列

队头与队尾相连

注意:循环队列中有一个存储单元不存储数据,用于队列判满

2.2.1 循环队列的实现

1、循环队列的声明
cpp 复制代码
#define MAX_QUEUE_LEN 6

typedef int DataType_t;

//循环队列对象
typedef struct sysqueue{
    DataType_t *pbase;
    int head;
    int tail;
}SQueue_t;
2、循环队列的创建
cpp 复制代码
//创建循环队列
SQueue_t *creat_sysqueue()
{
    SQueue_t *psqueue = malloc(sizeof(SQueue_t));
    if(psqueue == NULL){
        printf("malloc error\n");
        return NULL;
    }
    psqueue->pbase = malloc(MAX_QUEUE_LEN * sizeof(DataType_t));
    if(psqueue->pbase == NULL){
        printf("malloc error\n");
        return NULL;
    }
    psqueue->head = psqueue->tail = 0;
    return psqueue;
}
3、循环队列的判空与判满

**判空:**当队头下标等于队尾下标时,则队列为空

**判满:**当队尾下标加一等于队头下标时,则队列为满

cpp 复制代码
//循环队列判空
int is_empty_sysqueue(SQueue_t *psqueue)
{
    return psqueue->tail == psqueue->head;
}

//循环队列满
int is_full_sysqueue(SQueue_t *psqueue)
{
    return (((psqueue->tail + 1) % MAX_QUEUE_LEN) == psqueue->head);
}
4、循环队列的遍历
cpp 复制代码
//打印循环队列
void print_sysqueue(SQueue_t *psqueue)
{
    if(is_empty_sysqueue(psqueue)){
        return;
    }
    int cur = psqueue->head;
    printf("head->");
    //while(cur < psqueue->tail){
    while(cur != psqueue->tail){
        printf("%d->",psqueue->pbase[cur]);
        cur = (cur + 1) % MAX_QUEUE_LEN; 
    }
    printf("tail\n");
}
5、循环队列的队尾入队列
cpp 复制代码
//循环队列队尾入队
int sysqueue_push(SQueue_t *psqueue, DataType_t data)
{
    if(is_full_sysqueue(psqueue)){
        return -1;
    }
    psqueue->pbase[psqueue->tail] = data;
    psqueue->tail = (psqueue->tail + 1) % MAX_QUEUE_LEN;
    return 0;
}
6、循环队列的队头出队列
cpp 复制代码
//循环队列队头出队
int sysqueue_pop(SQueue_t *psqueue, DataType_t *pdata)
{
    if(is_empty_sysqueue(psqueue)){
        return -1;
    }
    if(pdata != NULL){
        *pdata = psqueue->pbase[psqueue->head];
    }
    psqueue->head = (psqueue->head + 1) % MAX_QUEUE_LEN;
    return 0;
}
7、获取队头元素
cpp 复制代码
//获取循环队列队头元素
int get_top_sysqueue(SQueue_t *psqueue, DataType_t *pdata)
{
    if(is_empty_sysqueue(psqueue)){
        return -1;
    }
    *pdata = psqueue->pbase[psqueue->head];
    return 0;
}
8、销毁队列
cpp 复制代码
//销毁循环队列
void destroy_sysqueue(SQueue_t **ppsqueue)
{
    free((*ppsqueue)->pbase);
    (*ppsqueue)->pbase = NULL;
    free(*ppsqueue);
    *ppsqueue = NULL;
}

三. 哈希表与哈希存储

3.1 哈希存储

哈希存储(散列存储) :将要存储数据的关键字 和数据存储位置值之间建立起对应的函数关系(哈希函数),当数据存储时,根据该关系映射数据的存储位置;查找数据时,利用该函数关系运算出数据的存储位置。

**目的:**为了快速检索数据

3.2 哈希冲突

**哈希冲突/哈希矛盾:**哈希冲突是指两个不同的关键字通过哈希函数映射到同一个哈希地址

解决方法:

1、开放定址法

**开放定址法:**当发生冲突时,通过探测找到下一个空闲槽位存储数据

2、链地址法

链地址法:将所有哈希地址相同 的元素存储在一个链表中。哈希表的每个槽位存储链表的头指针,

冲突元素追加到链表末尾。

3.3 哈希的实现

1、哈希的声明

cpp 复制代码
typedef struct contact{
    char name[32];
    char tel[15];
}DataType_t;

//哈希节点
typedef struct hashnode{
    DataType_t data;
    struct hashnode *pnext;
}HSNode_t;

#define HASH_TABLE_SIZE 27

2、哈希函数

cpp 复制代码
//哈希函数
int hash(char key)
{
    if(key >= 'a' && key <= 'z'){
        return key - 'a';
    }
    else if(key >= 'A' && key <= 'Z'){
        return key - 'A';
    }
    else{
        return HASH_TABLE_SIZE - 1;
    }
}

3、哈希表的遍历

cpp 复制代码
//哈希表遍历
void print_hash(HSNode_t **hash_table)
{
    int i = 0;
    for(i = 0; i < HASH_TABLE_SIZE; i++){
        HSNode_t *cur = hash_table[i];
        while(cur){
            print(cur);
            cur = cur->pnext;
        }
    }
}

4、哈希表销毁

cpp 复制代码
//哈希表销毁
void destroy_hash(HSNode_t **hash_table)
{
    int i = 0;
    for(i = 0; i < HASH_TABLE_SIZE; i++){
        HSNode_t *cur = hash_table[i];
        HSNode_t *next = NULL;
        while(cur){
            next = cur->pnext;
            free(cur);
            cur = next;
        }
        hash_table[i] = NULL;
    }
}

5、哈希表查找

cpp 复制代码
//哈希表查找
HSNode_t *find_hash(HSNode_t **hash_table, char *target)
{
    int addr = hash(target[0]);
    HSNode_t *cur = hash_table[addr];
    while(cur){
        if(strcmp(target, cur->data.name) == 0){
            return cur;
        }
        cur = cur->pnext;
    }
    return NULL;
}

6、哈希表的插入(有序插入)

cpp 复制代码
//哈希表插入排序
void sort_insert_hash(HSNode_t **hash_table, HSNode_t *pnewnode, int addr)
{
    HSNode_t *cur = hash_table[addr];
    HSNode_t *prev = NULL;
    //找一个合适的位置,插入的位置的下一个的name比newnode的name大
    while(cur && strcmp(cur->data.name, pnewnode->data.name) < 0){
        prev = cur;
        cur = cur->pnext;
    }
    //跳出情况一:链表为空
    //情况二:第一个节点就比newnode的name大,这里使用字符串比较
    if(prev == NULL){
        pnewnode->pnext = cur;
        hash_table[addr] = pnewnode;
    }
    //跳出情况:找到了合适的位置
    else{
        pnewnode->pnext = cur;
        prev->pnext = pnewnode;
    }
}

//哈希表插入
int insert_hash_table(HSNode_t **hash_table, DataType_t data)
{
    HSNode_t *pnewnode = malloc(sizeof(HSNode_t));
    if(pnewnode == NULL){
        printf("malloc error\n");
        return -1;
    }
    pnewnode->data = data;
    pnewnode->pnext = NULL;
    int addr = hash(data.name[0]);
    //有序插入
    sort_insert_hash(hash_table,pnewnode,addr);
    return 0;
}
相关推荐
CSharp精选营1 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假4 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠5 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦12 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠13 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾13 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82113 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q13 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒13 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
WL学习笔记14 天前
单项不带头不循环链表
数据结构·链表