【数据结构】栈和队列

一、栈

  1. 栈的概念及结构:

栈:

一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端

称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈(入栈):

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

出栈:

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

栈遵循后进先出的规则,即先放入栈的数据后出栈,后放入栈的数据先出栈。

  1. 栈的实现:

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

代价比较小。


typedef int STDataType;
typedef struct Stack
{
    STDataType* a;
    int top;//栈顶,初始为0,表示栈顶下一位置的坐标
    int capacity;
}ST;
//初始化
void StackInit(ST* ps);
//销毁栈
void StackDestroy(ST* ps);
//打印栈
void StackPrint(ST* ps);
//添加数据
void StackPush(ST* ps, STDataType x);
//删除数据
void StackPop(ST* ps);
//返回栈顶数据
STDataType StackTop(ST* ps);
//判断栈为空
bool StackEmpty(ST* ps);
//栈的大小
int StackSize(ST* ps);
//初始化
void StackInit(ST* ps)
{
    assert(ps);
    ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
    if (ps->a == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    ps->top = 0;
    ps->capacity = 4;
}
//销毁栈
void StackDestroy(ST* ps)
{
    assert(ps);
    free(ps->a);
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}
//打印栈
void StackPrint(ST* ps)
{
    assert(ps);
    int size = StackSize(ps);
    for (int i = 0; i < size; i++)
    {
        printf("%d ", ps->a[i]);
    }
    printf("\n");
}
//添加数据
void StackPush(ST* ps,STDataType x)
{
    assert(ps);
    //扩容
    if (ps->top == ps->capacity)
    {
        STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
        if (tmp == NULL)
        {
            perror("realloc fail");
            exit(-1);
        }
        ps->a = tmp;
        ps->capacity *= 2;
    }
    //先存放数据,后++;
    ps->a[ps->top] = x;
    ps->top++;
}
//删除数据
void StackPop(ST* ps)
{
    assert(ps);
    assert(!StackEmpty(ps));//栈不能为空
    ps->top--;
}
//返回栈顶数据
STDataType StackTop(ST* ps)
{
    assert(ps);
    assert(!StackEmpty(ps));
    return ps->a[ps->top - 1];
}
//判断栈为空
bool StackEmpty(ST* ps)
{
    assert(ps);
    /*if (ps->top == 0)
    {
        return true;
    }
    else
    {
        return false;
    }*/
    return ps->top == 0; //一个判断条件 
}
//栈的大小
int StackSize(ST* ps)
{
    assert(ps);
    return ps->top;
}

二、队列

  1. 队列的概念及结构

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

队列从队尾插入数据,从队头删除数据,即先进先出。

  1. 队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

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

typedef struct Queue
{
    QNode* Head;
    QNode* Tail;
    int size;
}Queue;

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
bool QueueEmpty(Queue* pq);
QDataType QueueHead(Queue* pq);
QDataType QueueTail(Queue* pq);
int QueueSize(Queue* pq);

设置头尾指针,方便插入删除数据。

void QueueInit(Queue* pq)
{
    assert(pq);
    pq->Head = NULL;
    pq->Tail = NULL;
    pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
    assert(pq);
    QNode* cur = pq->Head;
    while (cur)
    {
        QNode* next = cur->next;
        free(cur);
        cur = next;
    }
    pq->Tail = pq->Head = NULL;
    pq->size = 0;
}
bool QueueEmpty(Queue* pq)
{
    assert(pq);
    return pq->Tail == NULL && pq->Head == NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
    assert(pq);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    newnode->data = x;
    newnode->next = NULL;

    if (pq->Tail == NULL)
    {
        pq->Tail =pq->Head =  newnode;
    }
    else
    {
        pq->Tail->next = newnode;
        pq->Tail = newnode;
    }
    pq->size++;
}
void QueuePop(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    if (pq->Head->next == NULL)
    {
        free(pq->Head);
        pq->Head = pq->Tail = NULL;
    }
    else
    {
        QNode* del = pq->Head;
        pq->Head = pq->Head->next;
        free(del);
    }
    pq->size--;
}
QDataType QueueHead(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->Head->data;
}
QDataType QueueTail(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->Tail->data;
}
int QueueSize(Queue* pq)
{
    assert(pq);
    return pq->size;
}

三、栈和队列的面试题

  1. 有效的括号:

20. 有效的括号

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

有效字符串需满足:

左括号必须用相同类型的右括号闭合。

左括号必须以正确的顺序闭合。

每个右括号都有一个对应的相同类型的左括号。

解题思路:

观察本题不难看出,要左侧符号与右侧符号保证顺序闭合,运用栈后进先出的特性最为合适,首先,遍历整个字符串,将左侧符号入栈中,当遍历到右侧符号时,将栈顶元素取出并与之比较,比较后删除栈顶元素,然后接着遍历右侧符号并与栈顶元素对比。

此题会有以下三种情况需要考虑:

typedef char STDataType;
typedef struct Stack
{
    STDataType* a;
    int top;
    int capacity;
}ST;
void StackInit(ST* ps)
{
    assert(ps);
    ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
    if (ps->a == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    ps->top = 0;
    ps->capacity = 4;
}
void StackDestroy(ST* ps)
{
    assert(ps);
    free(ps->a);
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}
void StackPush(ST* ps,STDataType x)
{
    assert(ps);
    if (ps->top == ps->capacity)
    {
        STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
        if (tmp == NULL)
        {
            perror("realloc fail");
            exit(-1);
        }
        ps->a = tmp;
        ps->capacity *= 2;
    }
    ps->a[ps->top] = x;
    ps->top++;
}
void StackPop(ST* ps)
{
    assert(ps);
    ps->top--;
}
STDataType StackTop(ST* ps)
{
    assert(ps);
    
    return ps->a[ps->top - 1];
}
bool StackEmpty(ST* ps)
{
    assert(ps);
    return ps->top == 0; 
}

bool isValid(char* s)
{
    ST st;
    StackInit(&st);
    while(*s)
    {
        //将左侧符号入栈
        if(*s =='('||*s=='['||*s=='{')
        {
            StackPush(&st,*s);
            s++;
        }
        //右侧符号
        else
        {
            //当左侧符号都入栈时,栈还为空
            if(StackEmpty(&st))
            {
                StackDestroy(&st);
                return false;
            }
            //遇到右侧符号与栈顶元素相比,并删除参与比较的栈顶元素
            char top = StackTop(&st);
            StackPop(&st);
            //右侧符号与栈顶元素不等
            if(*s==')' && top!='('||
               *s==']' && top!='['||
               *s=='}' && top!='{')
            {
                StackDestroy(&st);
               return false;
            }
            //相等则继续比较
            else
            {
                s++;
            }
        }
    }
    bool ret = StackEmpty(&st);
    StackDestroy(&st);
    return ret;
}
  1. 用队列实现栈

225. 用队列实现栈

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

实现 MyStack 类:

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

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

int top() 返回栈顶元素。

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

解题思路:

可知栈为后进先出的特性,而队列为先进先出的性质,那如何用两个队列来实现栈的功能?

首先,一个队列肯定是无法实现栈,但两个队列,我们可以在两个队列之间倒数据来实现栈的功能,栈与队列插入数据的方法相同,不同的是出数据时删除的数据,如下,将数据1234插入到其中一个队列中,而队列中首先出的数据一定为队头数据即1,而依照栈的结构,出数据时删除的应为4,那么我们只需将有数据的队列中的数据依次删除,并将其插入到另一个空队列中,直到剩下一个数据,该数据即为栈结构应删除的数据,然后再删除改数据,这样又形成了一个非空队列和一个空队列的状态,然后循环往复,两个队列的状态始终为一个有数据一个为空。

typedef int QDatatype;
typedef struct QueueNode
{
    QDatatype data;
    struct QueueNode* next;
}QNode;
typedef struct Queue
{
    QNode* head;
    QNode* tail;
    int size;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDatatype x);
void QueuePop(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueHead(Queue* pq);
QDatatype QueueTail(Queue* pq);
int QueueSize(Queue* pq);

void QueueInit(Queue* pq)
{
    assert(pq);
    pq->head = NULL;
    pq->tail = NULL;
    pq->size = 0;
}
void QueueDestroy(Queue* pq)
{
    assert(pq);
    QNode* cur = pq->head;
    while (cur)
    {
        QNode* del = cur;
        cur = cur->next;
        free(del);
    }
    pq->head = pq->tail = NULL;
    pq->size = 0;
}
void QueuePush(Queue* pq, QDatatype x)
{
    assert(pq);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    newnode->data = x;
    newnode->next = NULL;
    
    if (pq->tail == NULL)
    {
        pq->tail = pq->head = newnode;
    }
    else
    {
        pq->tail->next = newnode;
        pq->tail = newnode;
    }
    pq->size++;
}
void QueuePop(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    if (pq->head->next == NULL)
    {
        free(pq->head);
        pq->head = pq->tail = NULL;
    }
    else
    {
        QNode* del = pq->head;
        pq->head = pq->head->next;
        free(del);
    }
    pq->size--;
}
bool QueueEmpty(Queue* pq)
{
    assert(pq);
    //Ϊȷ
    return pq->head == NULL && pq->tail == NULL;
}
QDatatype QueueHead(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->head->data;
}
QDatatype QueueTail(Queue* pq)
{
    assert(pq);
    assert(!QueueEmpty(pq));
    return pq->tail->data;
}
int QueueSize(Queue* pq)
{
    assert(pq);
    return pq->size;
}

typedef struct {
     Queue q1;
     Queue q2;
} MyStack;//不需要再躲开辟两个队列的空间

bool myStackEmpty(MyStack* obj) {
        assert(obj);
        return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

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

void myStackPush(MyStack* obj, int x) {
     assert(obj);
     //向非空的队列插入数据
     if(!QueueEmpty(&obj->q1))
     {
         QueuePush(&obj->q1,x);
     }
     else
     {
         QueuePush(&obj->q2,x);
     }
}

int myStackPop(MyStack* obj) {
       assert(obj);
       assert(!myStackEmpty(obj));
       //分清非空队列与空队列
       Queue* empty = &obj->q1, *nonempty = &obj->q2;
       if(!QueueEmpty(&obj->q1))
       {
           empty = &obj->q2;
           nonempty = &obj->q1;
       }
       //向空队列中导入非空队列的前n-1项,并将非空队列中的前n-1项删除
       while(QueueSize(nonempty)>1)
       {
           QueuePush(empty,QueueHead(nonempty));
           QueuePop(nonempty);
       }
       //取非空队列的队尾,即"栈顶"
       int top = QueueHead(nonempty);
       QueuePop(nonempty);
       return top;
}

int myStackTop(MyStack* obj) {
       assert(obj);
       assert(!myStackEmpty(obj));
       //取"栈顶"也就是队列尾
       if(!QueueEmpty(&obj->q1))
       {
           return QueueTail(&obj->q1);
       }
       else
       {
           return QueueTail(&obj->q2);
       }
}

void myStackFree(MyStack* obj) {
       assert(obj);
       QueueDestroy(&obj->q1);
       QueueDestroy(&obj->q2);
       free(obj);
}
  1. 用栈实现队列

232. 用栈实现队列

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

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾

int pop() 从队列的开头移除并返回元素

int peek() 返回队列开头的元素

boolean empty() 如果队列为空,返回 true ;否则,返回 false

解题思路:

通过观察,此题与上一题似乎有些相似处,基本原理不变,运用两个相同的数据结构来实现另一个结构,原理还是在两个栈之间倒数据来实现队列功能,但方法上略有不同,通过分析,我们可知两个栈,在其中一个栈导入数据,然后将此栈中的数据导出到另一个栈中,数据的顺序会发生变化,如下,将数据1234插入其中一个栈,然后将其导入到灵一个栈中,数据顺序自然颠倒,然后将其删除时,正好符合队列的特性(先进先出),这样我们就可以建立的两个栈,分别规定其作用,即一个只存入数据,一个倒数据并删除数据,需要注意的是,两个栈的状态始终为一个有数据一个没有数据,只有处于这种状态,我们才可以向入数据的栈中输入数据。

typedef int STDataType;
typedef struct Stack
{
    STDataType* a;
    int top;
    int capacity;
}ST;

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

void StackInit(ST* ps)
{
    assert(ps);
    ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
    if (ps->a == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    ps->top = 0;
    ps->capacity = 4;
}
void StackDestroy(ST* ps)
{
    assert(ps);
    free(ps->a);
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}
void StackPrint(ST* ps)
{
    assert(ps);
    int size = StackSize(ps);
    for (int i = 0; i < size; i++)
    {
        printf("%d ", ps->a[i]);
    }
    printf("\n");
}
void StackPush(ST* ps,STDataType x)
{
    assert(ps);
    
    if (ps->top == ps->capacity)
    {
        STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
        if (tmp == NULL)
        {
            perror("realloc fail");
            exit(-1);
        }
        ps->a = tmp;
        ps->capacity *= 2;
    }
    ps->a[ps->top] = x;
    ps->top++;
}
void StackPop(ST* ps)
{
    assert(ps);
    assert(!StackEmpty(ps));
    ps->top--;
}
STDataType StackTop(ST* ps)
{
    assert(ps);
    assert(!StackEmpty(ps));
    return ps->a[ps->top - 1];
}
bool StackEmpty(ST* ps)
{
    assert(ps);
    return ps->top == 0; 
}
int StackSize(ST* ps)
{
    assert(ps);
    return ps->top;
}

typedef struct {
     ST Pushst;
     ST Popst;
} MyQueue;
//分为入数据栈和出数据栈

MyQueue* myQueueCreate() {
       MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
       StackInit(&obj->Pushst);
       StackInit(&obj->Popst);
       return obj;
}

bool myQueueEmpty(MyQueue* obj) {
       assert(obj);
       return StackEmpty(&obj->Pushst) && StackEmpty(&obj->Popst);
}

int myQueuePeek(MyQueue* obj) {
       assert(obj);
       assert(!myQueueEmpty(obj));
       //将入数据栈的数据导入出数据栈,此过程完成数据的逆置,直接可以在出数据栈删除数据
       if(StackEmpty(&obj->Popst))
       {
           while(!StackEmpty(&obj->Pushst))
           {
               StackPush(&obj->Popst,StackTop(&obj->Pushst));
               StackPop(&obj->Pushst);
           }
       }
       return StackTop(&obj->Popst);
}

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

int myQueuePop(MyQueue* obj) {
        assert(obj);
        assert(!myQueueEmpty(obj));
        int peek = myQueuePeek(obj);
        //逆置后的数据可以直接删除
        StackPop(&obj->Popst);
        return peek;
} 

void myQueueFree(MyQueue* obj) {
        assert(obj);
        StackDestroy(&obj->Pushst);
        StackDestroy(&obj->Popst);
        free(obj);
}
  1. 设计循环队列

622. 设计循环队列

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k): 构造器,设置队列长度为 k 。

Front: 从队首获取元素。如果队列为空,返回 -1 。

Rear: 获取队尾元素。如果队列为空,返回 -1 。

enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。

deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。

isEmpty(): 检查循环队列是否为空。

isFull(): 检查循环队列是否已满。

解题思路:

一般情况下,我们总是用链表来实现队列,但根据此题并画图分析,用链表结构在实现取队尾数据时,由于是单链表,无法上一个节点的地址,只能遍历一遍链表,增加了程序的时间复杂度,并不建议采用。

故采用数组的方式来实现环形队列,从上图可以看出在rear位置空了一个空间没有存储数据,目的是来判断环形队列的状态,即空或满,用数组实现时亦采用这种方法。

当入下图这种情况时队列也是满

在使用malloc函数开辟空间时,我们需要为数组a多开辟一个空间来使用上述方法,剩下需要注意的是在获取尾部数据时,需要考虑以下这种情况:

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


MyCircularQueue* myCircularQueueCreate(int k) {
       MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
       //躲开辟一个空间用来判断环形队列的状态
       obj->a = (int*)malloc(sizeof(int)*(k+1));
       obj->front = obj->rear = 0;
       obj->k = k;
       return obj;
}

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

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

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
       assert(obj);
       if(myCircularQueueIsFull(obj))
       {
           return false;
       }
       obj->a[obj->rear++] = value;
       //循环
       if(obj->rear == obj->k+1)
       {
           obj->rear = 0;
       }
       return true;
}      

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
       assert(obj);
       if(myCircularQueueIsEmpty(obj))
       {
           return false;
       }
       obj->front++;
       //循环
       if(obj->front == obj->k+1)
       {
           obj->front = 0;
       }
       return true;
}

int myCircularQueueFront(MyCircularQueue* obj) {
       assert(obj);
       if(myCircularQueueIsEmpty(obj))
       {
           return -1;
       }
      return obj->a[obj->front];
}

int myCircularQueueRear(MyCircularQueue* obj) {
       assert(obj);
       if(myCircularQueueIsEmpty(obj))
       {
           return -1;
       }
       //特殊情况
       int rear = obj->rear == 0 ? obj->k : obj->rear-1;
       return obj->a[rear];
}

void myCircularQueueFree(MyCircularQueue* obj) {
       assert(obj);
       free(obj->a);
       free(obj);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值