数据结构——栈和队列

基本概念:

栈是一种逻辑结构,是特殊的线性表。特殊在:只能在固定的一端操作

只要满足上述条件,那么这种特殊的线性表就会呈现一种“后进先出”的逻辑,这种逻辑就被称为栈。栈在生活中到处可见,比如堆叠的盘子、电梯中的人们、嵌套函数的参数等等。

由于约定了只能在线性表固定的一端进行操作,于是给栈这种特殊的线性表的“插入”、“删除”,另起了下面这些特定的名称:

栈顶:可以进行插入删除的一端

栈底:栈顶的对端

入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push()

出栈:将节点从栈顶剔除,也称为弹栈,函数名通常为pop()

取栈顶:取得栈顶元素,但不出栈,函数名通常为top()

基于这种固定一端操作的简单约定,栈获得了“后进先出”的基本特性,如下图所示,最后一个放入的元素,最先被拿出来。

存储形式

栈只是一种数据逻辑,如何将数据存储于内存则是另一回事。一般而言,可以采用顺序存储形成顺序栈,或采用链式存储形成链式栈。

顺序栈

顺序存储意味着开辟一块连续的内存来存储数据节点,一般而言,管理栈数据除了需要一块连续的内存之外,还需要记录栈的总容量、当前栈的元素个数、当前栈顶元素位置,如果有多线程还需要配互斥锁和信号量等信息,为了便于管理,通常将这些信息统一于在一个管理结构体之中:

// 顺序栈节点
struct seqStack
{
 datatype *data; // 顺序栈入口
 int size; // 顺序栈总容量
 int top; // 顺序栈栈顶元素下标
};

练习代码:

sstack.h

#ifndef __SSTACK_H
#define __SSTACK_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义数据的类型,可能是复杂的结构体,本案例就使用int
typedef int DATA;

typedef struct
{
    DATA *pData; // 栈中元素的地址
    int size;    // 顺序栈的大小(数组的大小)
    int top;     // 栈顶元素下标
} SeqStack;

// 初始化栈
int SStack_init(SeqStack *s, int size);

// 判断栈是否已满
int SStack_isfull(Seqstack *s);

// 判断栈是否为空
int SStack_isempty(SeqStack *s);
// 入栈|压栈
int SStack_push(seqstack *s, DATA data),

    //  出栈|弹栈(弹出指定元素)
    int SStack_pop(Seqstack *s, DATA *data);

//  出栈|弹栈(弹出所有元素)

// 回收栈
int SStack_destroy(SeqStack *s);

#endif

sstack.c

#include "sstack.h"

// 初始化栈
int SStack_init(SeqStack *s, int size)
{
    // 给栈中的元素开辟存储空间
    s->pData = (DATA *)calloc(sizeof(DATA), size);
    // 校验
    if (s->pData == NULL)
    {
        perror("内存申请失败");
        return -1;
    }
    // 初始化
    s->size = size;
    s->top = -1;

    return 0;
}

// 判断栈是否已满
int SStack_isfull(SeqStack *s)
{
    return s->top + 1 == s->size;
}

// 入栈压栈
int SStack_push(SeqStack *s, DATA data)
{
    if (SStack_isfull(s))
    {
        printf("顺序栈已填满!\n");
        return -1;
    }
    // 操作栈
    s->top++;                // top向后偏移一位,top自增
    s->pData[s->top] = data; // 在更新后的top位置插入数据
    return 0;
}

// 出栈|弹栈(弹出指定元素)
int SStack_pop(SeqStack *s, DATA *data)
{
    // 判断栈是否为空
    if (SStack_isempty(s))
    {
        printf("顺序栈已为空!\n");
        return -1;
    }
    // 将弹出的元素返回
    *data = s->pData[s->top];
    // 改变top
    s->top--;

    printf("弹出数据:%d\n", *data);

    return 0;
}

// 回收栈
int SStack_destroy(SeqStack *s)
{
    if (s->pData)
    {
        // 释放内存
        free(s->pData);
        s->pData = NULL;
    }
    // 重置top
    s->top = -1;
}

sstack_main.c

#include "sstack.h"

int main()
{
    // 声明顺序栈
    SeqStack st;
    // 声明变量,用来接收弹出的数据
    DATA *data;

    // 初始化顺序栈
    SStack_init(&st, 10);

    for (int i = 0; i < 5; i++)
    {
        // 入栈
        SStack_push(&st, i);
    }

    // 弹栈
    while (!SStack_isempty(&st))
    {
        SStack_pop(&st, &data);
        printf("%-4d", data);
    }
    printf("\n");

    printf("====回收栈====");
    SStack_destroy(&st);

    return 0;
}

链式栈

链式栈的组织形式与链表无异,只不过插入删除被约束在固定的一端。为了便于操作,通常也会创建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息:

// 链式栈节点
typedef struct node
{
 datatype data;
 struct node *next;
}node;
// 链式栈管理结构体
struct linkStack
{
 node *top; // 链式栈栈顶指针
 int size; // 链式栈当前元素个数
};

练习代码:

lstack.h

#ifndef __LSTACK_H
#define __LSTACK_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义数据的存储类型
typedef int DATA;

// 定义链式栈节点
typedef struct _node
{
    DATA data;          // 数据域
    struct _node *next; // 指向下一个节点的指针域
} NODE;

// 定义链式栈
typedef struct
{
    NODE *pHead; // 链式栈栈顶指针
    int size;    // 链式栈大小
    int num;     // 记录节点个数
} LinkStack;

// 初始化链式栈
int LStack_init(LinkStack *s, int num);

// 判断栈是否己满
int LStack_isfull(LinkStack *s);

// 判断栈是否为空
int LStack_isempty(LinkStack *s);

// 入栈
int LStack_push(LinkStack *s, DATA data);

//  出栈
int LStack_pop(LinkStack *s, DATA *data);

//  回收栈
int LStack_free(LinkStack *s);

#endif

lstack.c

#include "lstack.h"

// 初始化链式表
int LStack_init(LinkStack *s, int num)
{
    s->pHead = NULL;
    s->size = num;
    s->num = 0; // 用来计数

    return 0;
}

// 判断栈是否己满
int LStack_isfull(LinkStack *s)
{
    return s->pHead = s->size;
}

// 判断栈是否为空
int LStack_isempty(LinkStack *s)
{
    return s->num == 0;
}

// 入栈
int LStack_push(LinkStack *s, DATA data)
{

    // 校验是否已满
    if (LStack_isfull(s))
    {
        return -1;
    }

    // 创建一个节点,申请内存
    NODE *p = (NODE *)malloc(sizeof(NODE));
    if (!p)
    {
        perror("内存申请失败!");
        return -1;
    }

    // 给节点赋初值
    p->data = data;
    // 头插法
    p->next = s->pHead;
    //
    s->pHead = p;

    // 计数
    (s->num)++;

    return 0;
}

// 出栈
int LStack_pop(LinkStack *s, DATA *data)
{
    // 判断栈是否有空
    if (LStack_isempty(s))
    {
        return -1;
    }

    // 创建一个新的变量,用来记录phead的位置
    NODE *p = s->pHead;
    // 如果pHead不存在
    if (!p)
    {
        return -1;
    }

    // 给*data赋值,接收弹出元素的数据
    *data = p->data;
    // 改变pHead的引用关系
    s->pHead = p->next;
    // 释放这块内存-p
    free(p);

    // 修改计数
    (s->num)--;

    return 0;
}

// 回收栈
int LStack_free(LinkStack *s)
{
    NODE *p = s->pHead, *q = NULL;

    while (p)
    {
        // 指针尾随
        q = p;
        p = p->next;
        free(q);
    }
    s->pHead = NULL;
    s->num = 0;

    return 0;
}

lstack_main.c

#include "lstack.h"

int main(void)
{
    LinkStack st;
    LStack_init(&st, 10);

    register int i = 1;

    // 入栈
    for (; i <= 10; i++)
        LStack_push(&st, i);

    // 判断是否满栈
    if (-1 == LStack_push(&st, 1024))
        fprintf(stderr, "满栈,插入失败\n");

    // 出栈
    while (!LStack_isempty(&st))
    {
        DATA data = 0;
        LStack_pop(&st, &data);
        printf("%4d", data);
    }
    printf("\n");

    // 回收栈
    LStack_free(&st);
    return 0;
}

队列

基本概念

        队列是最常见的概念,日常生活经常需要排队,仔细观察队列会发现,队列是一种逻辑结构,是一种特殊的线性表。特殊在:

        只能在固定的两端操作线性表

        只要满足上述条件,那么这种特殊的线性表就会呈现一种“先进先出”的逻辑,这种逻辑就被称为队列。

        由于约定了只能在线性表固定的两端进行操作,于是给队列这种特殊的线性表的插入删除,起个特殊的名称:

        队头:可以删除节点的一端队尾:可以插入节点的一端

        入队:将节点插入到队尾之后,函数名通常为enQueue()

        出队:将队头节点从队列中剔除,函数名通常为outQueue()取队头:取得队头元素,但不出队,函数名通常为front()

        由于这种固定两端操作的简单约定,队列获得了“先进先出”的基本特性,如下图所示:

顺序存储的队列:循环队列

        与其他的逻辑结构类似,队列可以采用顺序存储形成循环队列,也可以采用链式存储形成链式队列。顺序存储的队列之所以被称为循环队列,是因为可以利用更新队头队尾的下标信息,来循环地利用整个数组,出队入队时也不必移动当中的数据。循环队列示意图如下所示:

        从上述动图中可以观察到,需要牺牲至少数组中的一个存储位置,来区分循环队列中的满队和空队。满队和空队的约定如下:

        当front与rear相等时,队列为空

        当rear循环加一与front相等时,队列为满

与其他数据结构一样,管理循环队列除了需要一块连续的内存之外,还需要记录队列的总容量、当前队列的元素个数、当前队头、队尾元素位置,如果有多线程还需要配互斥锁和信号量等信息,为了便于管理,通常将这些信息统一于在一个管理结构体之中:

typedef int DATA;
typedef struct
{
 DATA *pData; // 队列入口
 int size; // 队列总容量
 int head; // 队列队头元素下标
 int tail; // 队列队尾元素下标
}SQueue;
  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值