数据结构基础详解(C语言): 栈与队列的详解附完整代码

简介: 栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。

数据结构 栈

栈的核心重点:

栈是只能从表尾插入和删除的数据结构。
栈的顺序存储结构由两部分组成,top指针和数组。
链栈其实本质就是单链表头插法

@[toc]

1.栈的基本概念

栈( Stack)是只允许在一端进行插入或删除操作的线性表
image.png

1.1 栈的常用操作

  • InitStack(&s):初始化栈,构造一个空栈S,分配内存空间.
  • DestroyStack(&L):销毁栈.销毁并释放S所占用的内存空间
  • Push(&S,x):进栈,若栈S未满,则将x加入使之成为新栈顶
  • Pop(&S,&x):出栈,若栈S非空,则弹出栈顶元素,并用x放回
  • GetTop(S,&x):读栈顶元素.若栈S非空,则用x返回栈顶元素
  • StackEmpty(S):判断一个栈S是否为空.若S为空,则返回true,否则返回false

2.栈的存储结构

2.1 栈的顺序存储结构

2.1.1 栈的定义

#define MaxSize 10
typedef struct{
   
   
    int data[MaxSize]; //静态数组存放栈中元素 
    int top;           // 栈顶元素 
}SqStack;

2.1.2 栈的初始化

void InitStack(SqStack &S){
   
   
    S.top=-1;   //初始化栈顶指针 
}

2.1.3 入栈

bool Push(SqStack &S,int x)
{
   
   
    if(S.top==MaxSize-1)
        return false;
    S.top=S.top+1;
    S.data[S.top]=x;
    return true;
}

2.1.4 出栈

bool Pop(SqStack &S,int &x)
{
   
   
    if(S.top==-1)   //栈空,报错 
        return false;
    x=S.data[S.top];  //栈顶元素先出栈 
    S.top=S.top-1; //指针再减1 
    return true;
}

2.1.5 读取栈顶元素

bool GetTop(SqStack &S,int &x){
   
   
    if(S.top==-1)
        return false;
    x=S.data[S.top];
    return true;
}
int main()
{
   
       
    SqStack S;
    InitStack(S);
    int x=0;
    Push(S,2);
    Push(S,3);
    Push(S,4);
    GetTop(S,x);
    printf("%d",x);
}

上述完整代码如下:

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

#define MaxSize 10
typedef struct{
   
   
    int data[MaxSize]; //静态数组存放栈中元素 
    int top;           // 栈顶元素 
}SqStack;

void InitStack(SqStack &S){
   
   
    S.top=-1;   //初始化栈顶指针 
}

bool Push(SqStack &S,int x)
{
   
   
    if(S.top==MaxSize-1)
        return false;
    S.top=S.top+1;
    S.data[S.top]=x;
    return true;
}

bool Pop(SqStack &S,int &x)
{
   
   
    if(S.top==-1)   //栈空,报错 
        return false;
    x=S.data[S.top];  //栈顶元素先出栈 
    S.top=S.top-1; //指针再减1 
    return true;
}
bool GetTop(SqStack &S,int &x){
   
   
    if(S.top==-1)
        return false;
    x=S.data[S.top];
    return true;
} 


int main()
{
   
       
    SqStack S;
    InitStack(S);
    int x=0;
    Push(S,2);
    Push(S,3);
    Push(S,4);
    GetTop(S,x);
    printf("%d",x);
}

2.1.6 共享栈

两个栈共享同一片空间

#define MaxSize 10
typedef struct{
   
   
    int data[MaxSize]; //静态数组存放栈中元素 
    int top0;           // 0号栈顶元素
    int top1;           // 1号栈顶元素
}ShStack;

void InitStack(ShStack &S){
   
   
s.top0=-1;  //初始化栈顶指针
s.top1=MaxSize;

2.2 栈的链式存储结构

使用不带头结点的链表

2.2.1 链栈的定义

typedef struct LinkNode{
   
   
    int data;//数据域
    struct LinkNode *next;//指针域 
}stackNode,*LinkStack;

2.2.2 链栈的初始化

void InitStack(LinkStack &s)
{
   
   
    s=NULL;//不需要头节点 
}

2.2.3 入栈

bool Push(LinkStack &S,int e)       
{
   
   
    stackNode *p=(stackNode *)malloc(sizeof(stackNode));
    p->data=e;
    p->next=NULL;
    if(S==NULL)
    {
   
   
        S=p;
    }
    else
    {
   
   
        p->next=S;
        S=p;
    }
    return true;
}

2.2.4 出栈

bool Pop(LinkStack &S,int &e)
{
   
   
    stackNode *p;
    if(S==NULL)
        return false;
    else
    {
   
   
        p=S;
        e=p->data;
        S=S->next;
        delete p;
        return true;
    }
}

2.2.5 取栈顶元素

int top(LinkStack s)
{
   
   
    if(s==NULL)
        return -1;
    return s->data;
}

2.2.6 销毁栈

bool DestoryStack(LinkStack &S)
{
   
   
    stackNode *p;
    while(S)
    {
   
   
        p=S;
        S=S->next;
        delete p;
    }
    S=NULL;
    return true;
}

队列

队列的核心:队列是一种先进先出的数据结构。
顺序存储是一个front指针和一个rear指针,和数组
front指针指向队头,rear指针指向队尾。
队空条件:Q.front==Q.rear
进队:Q.data[Q.rear++]
出队:Q.data[Q.front++]

快速图解复习队列:

1.队列的定义

队列( Queue)是只允许在一端进行插入,在另一端删除的线性表
在这里插入图片描述

1.1 队列的基本操作

  • InitQueue(&Q):初始化队列,构造一个空队列Q。
  • DestroyQueue(&Q):销毁队列。销毁并释放队列Q所占用的内存空间
  • EnQueue(&Q):入队,若队列Q未满,将x加入,使之成为新的队尾
  • DeQueue(&Q,&x):出队,若队列Q非空,删除队头元素,并用x返回
  • GetHead(Q,&x):读队头元素,若队列Q非空,则将队头元素赋值给x。
  • QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。

2.队列的实现

2.1 顺序存储的实现

在接下来的代码实现中,rear和front指针最初指向相同的位置,在实际的题目中,我们也可以让rear指向data[1],front指向data[0],不必拘泥于某种固定的格式.

image.png

2.1.1 队列的定义

  • 用静态数组存放数据元素
  • 声明一个队头和队尾指针
  • 有typedef,所以定义typedef struct后面没加SqQueue,这个就是一个可写可不写的方式,因为我们取好了别名,也不用struct SqQueue sq1; 这种形式去定义队列
    typedef struct{
         
         
      int data[MaxSize];
      int front,rear;
    }SqQueue;
    

    2.1.2 队列的初始化

void InitQueue(SqQueue &Q)
{
   
   
    Q.front=Q.rear=0;
}

2.1.3 队列判空

bool QueueEmpty(SqQueue &Q)
{
   
   
    if(Q.front==Q.rear)
         return true;
    return false;
}

2.1.4 入队和出队

入队和出队要注意队列是循环的,出到最后之后,重新找头部从头部开始出队,以此类推
当然了,简单的问题中可能不使用这种循环队列的形式.

image.png

值得思考的是,在入队和出队操作过程中,我们需要进行判断队列是否为空或者队列是否满的,下面先采取放弃一个存储空间的方式,进行判满操作,通过放弃一个存储空间,就不会和判空操作相同,即rear指向最后一个空间没存的情况下就判断它已经满了

:star:入队

bool EnQueue(SqQueue &Q,int x)
{
   
   
    if((Q.rear+1)%MaxSize==Q.front)
         return false;
    Q.data[Q.rear]=x;
    Q.rear=(Q.rear+1)%MaxSize;
    return true;
}

:star:出队

bool DeQueue(SqQueue &Q,int &x)
{
   
   
    if(Q.rear==Q.front)
         return false;
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;
    return true;
}

当前实现队列元素的个数=(rear+MaxSize-front)%MaxSize

2.1.5 取队头元素

bool GetHead(SqQueue Q,int &x)
{
   
   
    if(Q.rear==Q.front)
         return false;
    x=Q.data[Q.front];
    return true;
}

上述完整代码:

#include <stdio.h>
#include <stdlib.h>
#define  MaxSize 10
typedef struct{
   
   
    int data[MaxSize];
    int front,rear;
}SqQueue;

void InitQueue(SqQueue &Q)
{
   
   
    Q.front=Q.rear=0;
}

bool QueueEmpty(SqQueue &Q)
{
   
   
    if(Q.front==Q.rear)
         return true;
    return false;
}

bool EnQueue(SqQueue &Q,int x)
{
   
   
    if((Q.rear+1)%MaxSize==Q.front)
         return false;
    Q.data[Q.rear]=x;
    Q.rear=(Q.rear+1)%MaxSize;
    return true;
}

bool DeQueue(SqQueue &Q,int &x)
{
   
   
    if(Q.rear==Q.front)
         return false;
    x=Q.data[Q.front];
    Q.front=(Q.front+1)%MaxSize;
    return true;
}

bool GetHead(SqQueue Q,int &x)
{
   
   
    if(Q.rear==Q.front)
         return false;
    x=Q.data[Q.front];
    return true;
}

int main()
{
   
   
    SqQueue q;
}

2.1.6 判断队列已满/为空的其他两种方案

之前我们的方案是牺牲一个存储空间
方案一:增加一个size数据,记录队列的当前长度

typedef struct{
   
   
    int data[MaxSize];
    int front,rear;
    int size;
}SqQueue;

size==MaxSize //队列已满
size==0       //队列为空

方案二:增加一个tag数据,指明最近的依次操作是删除还是插入

typedef struct{
   
   
    int data[MaxSize];
    int front,rear;
    int tag;  //tag=1 代表上一次是插入 tag=0代表上一次是删除
}SqQueue;

rear==front&&tag=1   //队列已满
rear==front&&tag=0   //队列为空

2.2 链式存储的实现

2.2.1 定义

  • 定义一个结构体是链式队列结点
  • 再定义一个结构体,里面有2个结点指针指向结点,这样便于代码的简洁
typedef struct LinkNode{
   
   
    int data;
    LinkNode *next;
}LinkNode;

typedef struct {
   
   
    LinkNode *front,*rear;
}LinkQueue;

2.2.2 初始化

void InitQueue(LinkQueue &Q)
{
   
   
    Q.front=Q.rear=(LinkNode*)malloc(sizeof(LinkNode));
    Q.front->next=NULL;
}

2.2.3 判空与判满

判空

bool QueueEmpty(LinkQueue Q)
{
   
   
    if(Q.front==Q.rear)
         return true;
    return false;
}

链式存储一般情况不会满,所以不判满

2.2.4 入队

:star:不带头结点的入队

bool EnQueue(LinkQueue &Q,int x)
{
   
   
    LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));
    s->data=x;
    s->next=NULL;
    Q.rear->next=s;
    Q.rear=s;
    return true;
}

:star:带头结点的入队第一个元素要特殊处理

bool EnQueue(LinkQueue &Q,int x)
{
   
   
    LinkNode *s=(LinkNode*)malloc(sizeof(LinkNode));
    s->data=x;
    s->next=NULL;
    if(Q.front==NULL)
    {
   
   
        Q.front=s;
        Q.rear=s;
    }
    else
    {
   
   
        Q.rear->next=s;
        Q.rear=s;
    }
    return true;
}

2.2.5 出队

出队的操作,值得注意的是,如果front指向的是最后一个元素,将他出队后 front指针就指向空了,但是此时的rear指针被蒙在鼓里,所以如果此时p指针和rear指针相同,则说明rear要更新和front指针一样,如果不更新,rear还指向最后一个元素

为什么只有指向最后一个元素的情况下才更新呢?
因为其实,不到最后一个出队,rear和front之间没什么关系,没什么联系

:star:不带头结点的出队

bool DeQueue(LinkQueue &Q,int &x)  //不带头结点 
{
   
   
    if(Q.front==NULL)
         return false;
    LinkNode *p=Q.front; 
    x=p->data;
    Q.front=p->next;
    if(Q.rear==p)
    {
   
   
        Q.rear=Q.front; //王道给出的是Q.front==NULL 和 Q.rear==NULL 我觉得没必要,这么写就行 
    }
    free(p);
    return true;
}

:star:带头结点的出队

bool DeQueue(LinkQueue &Q,int &x)  //带头结点 
{
   
   
    if(Q.rear==Q.front)
         return false;
    LinkNode *p=Q.front->next; 
    x=p->data;
    Q.front->next=p->next;
    if(Q.rear==p)
    {
   
   
        Q.rear=Q.front;
    }
    free(p);
    return true;
}
相关文章
|
4天前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
22天前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
142 77
|
1月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
38 7
|
1月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
44 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
1月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
46 9
|
C语言
C语言队列实现
一,简介 开发环境是VC6.0,实现了一个基于C语言的队列。 主要功能,入队、出队、显示当前队列元素。
141 0
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
37 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
20 2
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
66 16

热门文章

最新文章