天天看点

数据结构--栈和队列(C语言)

一、栈

1.栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫做出栈。出数据也在栈顶。

数据结构--栈和队列(C语言)

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

数组栈

数据结构--栈和队列(C语言)

链式栈

数据结构--栈和队列(C语言)
数据结构--栈和队列(C语言)

对于第一种头作为栈顶,插入删除数据为头插和头删,这样很容易找到头进行操作,如果用头做栈顶,头插头删,就可以设计成单链表。

第二种尾作为栈顶,插入删除数据为尾插和尾删,这种结构设计成双向链表比较好,否则删除数据效率低。

其次数组栈缓存利用率高,因为数组是一段连续的空间,内存访问数组的第一个数的时候,后面的数字已经预缓存在cache中,而链式栈的空间不是连续的,是用链表将不连续的空间连接起来,在找到链表的第一个数字后,如果找不到第二个空间,假设不命中,一次加载20byte到缓存(具体加载多大取决硬件体系)。链式栈的缓存命中率低。

对于链式栈唯一的好处就是可以随时开辟空间,不浪费空间,数组栈只能在空间不够的时候开辟一定的空间,存在空间的浪费,但是链式栈仅限于这个好处。除非要在一个不能浪费空间的地方使用,比如嵌入式,我们用链式栈。

所以对于栈,两种都可以,非要选一种,我们最好用数组栈。

2.栈的实现

对于各个函数的声明和头文件的包括,我们写在Stack.h中
#pragma once           

#include <stdio.h>

#include <stdlib.h>

#include <assert.h>

#include <stdbool.h>

typedef int STDataType;

typedef struct Stack

{

STDataType* a;//数组指针

int top;//栈顶

int capacity;//最大容量

}ST;

//初始化数组

void StackInit(ST ps);

//销毁数组

void StackDestroy(ST ps);

void StackPush(ST ps, STDataType x);

void STackPop(ST ps);

//找到栈顶元素

STDataType StackTop(ST ps);

//栈的大小

int StackSize(ST ps);

//判断栈是否为空

bool StackEmpty(ST* ps);//引用头文件

#### 栈的初始化
将指针置空,栈中最大个数为0,栈的下标为0或-1
```c
void StackInit(ST* ps)
{
    assert(ps);
    ps->a = NULL;
    ps->top = 0;//指向栈顶的下一个,指向栈顶后,+1指向下一个
    //ps->top=-1;//指向栈顶,先+1再指向栈顶
    ps->capacity = 0;
}           

插入数据

栈的插入数据只有一种,在栈顶插入,将数据插入栈顶,top下标也要增加一个,当插入到栈满的时候,我们需要扩容。

void StackPush(ST* ps, STDataType x)
{
    assert(ps);
    if (ps->top == ps->capacity)
    {
        int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newcapacity);
        if (tmp == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
        ps->a = tmp;
        ps->capacity = newcapacity;
    }
    ps->a[ps->top] = x;
    ps->top++;
}           

销毁数据

void StackDestroy(ST* ps)
{
    assert(ps);
    free(ps->a);//释放置空
    ps->a = NULL;
    ps->capacity = ps->top = 0;//置为0
}           

删除数据

删除非常简单,只需要将top减减,但在test.c中测试,删除数据最后top会小于0,变成-1,-2...,所以我们直接断言一下,在top大于0时,才能删除

void STackPop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);

    ps->top--;
}           

取栈顶数据

因为前面初始化栈顶top为0,最后指向栈顶元素的下一个,所以返回top-1.当栈为空时,top-1为野指针,返回随机值,所以断言一下top>0

STDataType StackTop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);

    return(ps->a[ps->top-1]);
}           

栈的大小

int StackSize(ST* ps)
{
    assert(ps);
    return ps->top;
}           

判断栈是否为空

bool StackEmpty(ST* ps)
{
    /*if (ps->top == 0)//查看下标top是否为0
    {
        return true;
    }
    else
    {
        return false;
    }*/
    return ps->top == 0;//如果为0返回ture,其他情况返回false
}           

栈的实现时非常简单的

那么如何实现栈的遍历打印呢

void TestStack2()
{
    ST st;
    StackInit(&st);
    StackPush(&st, 1);
    StackPush(&st, 2);
    StackPush(&st, 3);
    StackPush(&st, 4);

    while (!StackEmpty(&st))
    {
        printf("%d ", StackTop(&st));
        StackPop(&st);
    }

    StackDestroy(&st);
}           

每次打印栈顶,并删除栈顶即可,最后栈为空跳出循环,打印结束。

3.OJ练习题--有效的括号

我们来做一道OJ题小试牛刀

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。           

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/valid-parentheses

数据结构--栈和队列(C语言)
数据结构--栈和队列(C语言)

这道题我们运用上面写好的接口,因为用的是C语言来写,不像C++有STL库刷起来那么方便,需要自己实现接口,这是C刷题的一个弊端。

思路:当遇到左括号入栈,当遇到右括号左括号出栈跟右括号匹配示例5中,遇到左括号入栈,当遇到有括号的时候栈顶的左括号出栈与之匹配,匹配上了,指针向下走,这时栈顶的第一个左括号已经被删除,当遇到第二个右括号时,栈里的左括号与之匹配,匹配上了,指针向下走,当指针指向空时,返回真,其他情况返回false.

返回数据的时候一定要记得销毁栈,避免内存泄漏,因为这个栈是我们realloc出来的。

当遇到只有一个左括号的时候,左括号入栈,但是指针在向下走的时候,直接跳出循环,得到返回值,无法得到true结果,这时我们要判断是否是空,如果是空返回true,否则为false。

当只有一个右括号的时候,栈中没有元素,栈为空,取不到栈顶,我们在上面断言了栈不能为空,所以要判断一下栈是否为空,然后返回false,这个详情看下面代码。

typedef char STDataType;
typedef struct Stack
{
    STDataType* a;//数组指针
    int top;//栈顶
    int capacity;//最大容量
}ST;

//初始化数组
void StackInit(ST* ps);
//销毁数组
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
//找到栈顶元素
STDataType StackTop(ST* ps);
//栈的大小
int StackSize(ST* ps);
//判断栈是否为空
bool StackEmpty(ST* ps);//引用头文件

void StackInit(ST* ps)
{
    assert(ps);
    ps->a=NULL;
    ps->top=ps->capacity=0;
}

void StackDestroy(ST* ps)
{
    assert(ps);
    free(ps->a);
    ps->a=NULL;
    ps->top=ps->capacity=0;
}

void StackPush(ST* ps, STDataType x)
{
    assert(ps);
    if(ps->top==ps->capacity)
    {
        int newcapacity=ps->capacity==0?4:ps->capacity*2;
        STDataType* tmp=(STDataType*)realloc(ps->a,sizeof(STDataType)*newcapacity);
        if(tmp==NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
        ps->a=tmp;
        ps->capacity=newcapacity;
    }
    ps->a[ps->top]=x;
    ps->top++;
}

void StackPop(ST* ps)
{
    assert(ps);
    assert(ps->top>0);
    ps->top--;
}

STDataType StackTop(ST* ps)
{
    assert(ps);
    assert(ps->top>0);
    return ps->a[ps->top-1];
}

int StackSize(ST* ps)
{
    assert(ps);
    return ps->top;
}

bool StackEmpty(ST* ps)
{
    return ps->top==0;
}

bool isValid(char * s){
    ST st;
    StackInit(&st);
    while(*s)
    {
        if(*s=='(' || *s=='[' || *s=='{')
        {
            //入栈
            StackPush(&st,*s);
            ++s;
        }
        else
        {
            //遇到右括号,但是栈里没有数据,没有左括号了,返回false
            if(StackEmpty(&st))
            {
                StackDestroy(&st);
                return false;
            }
            STDataType top=StackTop(&st);
            StackPop(&st);
            if((*s==')' && top !='(')||
               (*s==']' && top !='[')||
               (*s=='}' && top !='{'))
               {
                   StackDestroy(&st);
                   return false;
               }
            else
            {
                ++s;
            }
        }
    }
    //如果栈不是空,栈中还有左括号未出,没有匹配,返回的时false
   // bool ret=StackEmpty(&st);
   // return ret;

    if(StackEmpty(&st))
    {
        return true;
    }
    else
    {
        return false;
    }
    StackDestroy(&st);
}           

二、队列

1.队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

入队列:进行插入操作的一端称为队尾

出队列:进行删除操作的一端称为队头

2.队列的实现

队列里面我们定义一个指针

typedef struct QueueNode
{
    QDataType data;
    struct QueueNode* next;
}QueueNode;           

因为队列是先进先出,存放数据为尾插,删除数据为头删,所以为了方便,我们定义两个指针,一个指向头一个指向尾,然后放在一个结构体中,这样我们测试的时候可以不用二级指针,直接通过结构体来改变里面的数据。如果不定义这样的一个结构体,通过二级指针传入指针地址,然后通过一级指针找到数据来改变数据。

typedef struct QueueList
{
    QueueNode* head;
    QueueNode* tail;
}QList;           

栈的初始化和销毁

void QueueInit(QList* ps)
{
    assert(ps);
    ps->head = NULL;
    ps->tail = NULL;
}           
void QueueDestroy(QList* ps)
{
    assert(ps);
    QueueNode* cur = ps->head;//定义指针遍历链表
    QueueNode* next = cur->next;//存放当前指针的下一个
    while (cur)
    {
        free(cur);
        cur = next;
    }
    ps->head = ps->tail = NULL;
}           

判断队列是否为空

bool QueueEmpty(QList* ps)
{
    /*if (ps->head == NULL)
    {
        return true;
    }
    else
    {
        return false;
    }*/

    return ps->head == NULL;
}           

尾插和头删

只实现尾插和头删是由于队列的性质写出的

**尾插**
void QueuePush(QList* ps, QDataType x)
{
    assert(ps);
    QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
    newnode->data = x;
    newnode->next = NULL;

    if (ps->tail == NULL)
    {
        ps->tail = ps->head = newnode;
    }
    else
    {
        ps->tail->next = newnode;
        ps->tail = newnode;
    }

}
           

头删

在头删的同时,头删到NULL,tail指针并没有被释放,它还指向最后一个节点,head释放掉之后,tail成为野指针,返回随机值。所以head释放掉变为NULL的同时,也将tail置为空

void QueuePop(QList* ps)
{
    assert(ps);//判断结构体不为空
    assert(!QueueEmpty(ps));//判断队列不为空
    QueueNode* next = ps->head->next;

    free(ps->head);
    ps->head = next;
    if (ps->head == NULL)
    {
        ps->tail = NULL;
    }
}           

返回队尾和队头数据

QDataType QueueFront(QList* ps)
{
    assert(ps);
    assert(!QueueEmpty(ps));
    return ps->head->data;
}

QDataType QueueBack(QList* ps)
{
    assert(ps);
    assert(!QueueEmpty(ps));
    return ps->tail->data;
}           

计算队列大小

int QueueSize(QList* ps)
{
    assert(ps);
    QueueNode* cur = ps->head;
    int count = 0;
    while (cur)
    {
        ++count;
        cur = cur->next;
    }
    return count;
}           

测试一下

test.c

void TestQueue1()
{
    QList q;
    QueueInit(&q);

    QueuePush(&q, 1);
    QueuePush(&q, 2);
    QueuePush(&q, 3);
    QueuePush(&q, 4);

    while (!QueueEmpty(&q))
    {
        QDataType front = QueueFront(&q);
        printf("%d ", front);
        QueuePop(&q);
    }
    printf("\n");

}
int main()
{
    TestQueue1();
    return 0;
}           
数据结构--栈和队列(C语言)

队列OJ题

让我们来做几道OJ题

1.用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。

int pop() 移除并返回栈顶元素。

int top() 返回栈顶元素。

boolean empty() 如果栈是空的,返回 true ;否则,返回false 。

OJ链接

思路:这道题用两个队列实现栈,将数据放进一个队列中,再将数据删除导入到另一个队列,直到删除到最后一个数字,直接将数字删除,实现后进先出

数据结构--栈和队列(C语言)

这道题用C语言,所以需要自己实现一些队列接口。前面我们写好直接用

这道题的函数接口中,将两个队列放在一个结构体中,myStackCreate中调用接口初始化,返回结构体指针,与我们上面写的有所区别,我们上面是直接在test.c中创建一个结构体,把结构体的指针传过去。

typedef struct {           

} MyStack;

MyStack* myStackCreate() {

}

![图片.png](https://s2.51cto.com/images/20211105/1636111112941209.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
两个地方要区分开来。
但是在我们创建结构体返回指针时,我们不能这样创建
```c
MyStack* myStackCreate() {
    Mystack st;
    return &st;
}           

这是我们在栈里面定义的局部变量,除了作用域就会被销毁,我可以定义全局变量,但是会出问题,所以我们用malloc在堆里面定义,这样出了作用域也不会被销毁。

typedef struct {
    QList q1;
    QList q2;
} MyStack;

MyStack* myStackCreate() {
    MyStack* st=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&st->q1);
    QueueInit(&st->q2);
    return st;
}
在释放函数中
```c
void myStackFree(MyStack* obj) {

}           

因为我们的结构是这样的

数据结构--栈和队列(C语言)

需要先释放两个队列,再释放队列的结构体

void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
}           

实现代码

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

typedef int QDataType;

typedef struct QueueNode
{
    QDataType data;
    struct QueueNode* next;
}QueueNode;

typedef struct QueueList
{
    QueueNode* head;
    QueueNode* tail;
}QList;

void QueueInit(QList* ps);
void QueueDestroy(QList* ps);
void QueuePush(QList* ps, QDataType x);
void QueuePop(QList* ps);
QDataType QueueFront(QList* ps);
QDataType QueueBack(QList* ps);
int QueueSize(QList* ps);
bool QueueEmpty(QList* ps);

void QueueInit(QList* ps)
{
    assert(ps);
    ps->head = NULL;
    ps->tail = NULL;
}

void QueueDestroy(QList* ps)
{
    assert(ps);
    QueueNode* cur = ps->head;

    while (cur)
    {
        QueueNode* next = cur->next;
        free(cur);
        cur = next;
    }
    ps->head = ps->tail = NULL;
}
void QueuePush(QList* ps, QDataType x)
{
    assert(ps);
    QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
    newnode->data = x;
    newnode->next = NULL;

    if (ps->tail == NULL)
    {
        ps->tail = ps->head = newnode;

    }
    else
    {
        ps->tail->next = newnode;
        ps->tail = newnode;
    }

}

void QueuePop(QList* ps)
{
    assert(ps);//判断结构体不为空
    assert(!QueueEmpty(ps));//判断队列不为空
    QueueNode* next = ps->head->next;

    free(ps->head);
    ps->head = next;
    if (ps->head == NULL)
    {
        ps->tail = NULL;
    }
}

QDataType QueueFront(QList* ps)
{
    assert(ps);
    assert(!QueueEmpty(ps));
    return ps->head->data;
}

QDataType QueueBack(QList* ps)
{
    assert(ps);
    assert(!QueueEmpty(ps));
    return ps->tail->data;
}

int QueueSize(QList* ps)
{
    assert(ps);

    QueueNode* cur = ps->head;
    int count = 0;
    while (cur)
    {
        ++count;
        cur = cur->next;
    }
    return count;
}
bool QueueEmpty(QList* ps)
{
    assert(ps);
    return ps->head == NULL;
}

typedef struct {
    QList q1;
    QList q2;
} MyStack;

MyStack* myStackCreate() {
    MyStack* st=(MyStack*)malloc(sizeof(MyStack));
    QueueInit(&st->q1);
    QueueInit(&st->q2);
    return st;
}

//如果队列不为空,则放数据
void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
}

//当不为空的队列数据个数大于1,将数据导入空队列,并删除
int myStackPop(MyStack* obj) {
    QList* emptyQ=&obj->q1;
    QList* noneemptyQ=&obj->q2;
    if(!QueueEmpty(&obj->q1))
    {
        emptyQ=&obj->q2;
        noneemptyQ=&obj->q1;
    }

    while(QueueSize(noneemptyQ)>1)
    {
        QueuePush(emptyQ,QueueFront(noneemptyQ));//放入队列头的数据
        QueuePop(noneemptyQ);
    }

    QDataType top=QueueFront(noneemptyQ);
    QueuePop(noneemptyQ);
    return top;//带返回值,返回不为空队列最后一个数据
}

//谁不是空的,返回谁的队尾
int myStackTop(MyStack* obj) {
    if(!QueueEmpty(&obj->q1))
    {
        return QueueBack(&obj->q1);
    }
    else
    {
        return QueueBack(&obj->q2);
    }
}

//两个都是空才是空
bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
}           

3.用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false           
typedef int STDataType;
typedef struct Stack
{
    STDataType* a;
    int top;
    int capacity;
}ST;

void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);

void StackInit(ST* ps)
{
    assert(ps);
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}

void StackDestroy(ST* ps)
{
    assert(ps);
    free(ps->a);
    ps->a = NULL;
    ps->capacity = ps->top = 0;
}

void StackPush(ST* ps, STDataType x)
{
    assert(ps);
    if (ps->top == ps->capacity)
    {
        int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newcapacity);
        if (tmp == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
        ps->a = tmp;
        ps->capacity = newcapacity;
    }
    ps->a[ps->top] = x;
    ps->top++;
}

void StackPop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);

    ps->top--;
}

STDataType StackTop(ST* ps)
{
    assert(ps);
    assert(ps->top > 0);

    return(ps->a[ps->top-1]);
}

int StackSize(ST* ps)
{
    assert(ps);
    return ps->top;
}

bool StackEmpty(ST* ps)
{
    return ps->top == 0;
}

typedef struct {
    ST pushST;
    ST popST;

} MyQueue;

MyQueue* myQueueCreate() {
    MyQueue* q=(MyQueue*)malloc(sizeof(MyQueue));
    StackInit(&q->pushST);
    StackInit(&q->popST);
    return q;
}

void myQueuePush(MyQueue* obj, int x) {
    StackPush(&obj->pushST,x);
}

//当popST不为空,如果pushST不为空,将数据导入popST再进行删除
int myQueuePop(MyQueue* obj) {
    if(StackEmpty(&obj->popST))
    {
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST,StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }

    STDataType top=StackTop(&obj->popST);
    StackPop(&obj->popST);
    return top;
}

int myQueuePeek(MyQueue* obj) {
    if(StackEmpty(&obj->popST))
    {
        while(!StackEmpty(&obj->pushST))
        {
            StackPush(&obj->popST,StackTop(&obj->pushST));
            StackPop(&obj->pushST);
        }
    }

    return StackTop(&obj->popST);
}

bool myQueueEmpty(MyQueue* obj) {
    return StackEmpty(&obj->popST)&&StackEmpty(&obj->pushST);
}

void myQueueFree(MyQueue* obj) {
    StackDestroy(&obj->pushST);
    StackDestroy(&obj->popST);
    free(obj);
}           

3.设计循环队列

typedef struct {
    int* a;
    int front;
    int tail;
    int k;
} MyCircularQueue;

bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* cq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    cq->a=(int*)malloc(sizeof(int)*(k+1));
    cq->tail=cq->front=0;
    cq->k=k;

    return cq;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    //满了不能添加,返回flase
    if(myCircularQueueIsFull(obj))
        return false;

    obj->a[obj->tail]=value;
    ++obj->tail;
    obj->tail%=(obj->k+1);
    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    //空了不能删除,返回false
    if(myCircularQueueIsEmpty(obj))
        return false;

    ++obj->front;
    obj->front%=(obj->k+1);

    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;

    return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
        return -1;

    if(obj->tail%(obj->k+1)==0)
    {
        return obj->a[obj->k];
    }
    else
    {
        return obj->a[obj->tail-1];
    }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front==obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->tail+1)%(obj->k+1)==obj->front;
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    free(obj);
}
           

继续阅读