数据结构之栈和队列(超详解)

数据结构之栈和队列(超详解)

概念与结构栈栈:⼀种特殊的线性表,只允许在固定的⼀端进行插入和删除元素操作。数据插入和删除操作

的一端称为栈顶,另⼀端称为栈底。栈中的元素遵守后进先出(或先进后出)的原则。

压栈(进栈/压栈/⼊栈):插入数据在栈顶。

出栈:在栈顶出数据。

栈的实现可由数组或链表实现,那么哪种是优解呢?

底层用数组:在进栈时只需要在栈顶(尾部)插入数据,不用进行遍历;

底层用链表:在进栈时链表遍历到尾结点再进行插入数据,需要O(n)的时间复杂度,会有一定的消耗。

因此数组的结构实现更优⼀些。

队列概念:只允许在⼀端插⼊数据,在另⼀端删除数据的特殊线性表,队列具有先进先出(后进后出)的原则。

入队列:在队尾插入数据。

出队列:在队头删除数据。

同样,队列底层也可由数组或链表实现,哪种是优解呢?

底层用数组:删除数据时需要将后面的数据往前挪,有一定的消耗。

底层用链表:出队列时只需要让指向第一个有效结点的指针指向第二个有效结点,不需要遍历;

因此链表的结构实现更优⼀些。

代码实现栈栈的底层是数组,和前面在实现顺序表的底层结构是一样的。

代码语言:javascript代码运行次数:0运行复制typedef int STDataType;

typedef struct Stack

{

STDataType* arr;

int top;//栈顶

int capacity;

}ST;由于栈的初始化、销毁、入栈(顺序表尾插)、出栈(顺序表尾删)功能与顺序表功能实现是一样的,就不过多赘述。

代码语言:javascript代码运行次数:0运行复制//栈的初始化

void STInit(ST* st)

{

assert(st);

st->arr = NULL;

st->top = st->capacity = 0;

}

//栈的销毁

void STDestroy(ST* st)

{

assert(st);

if (st->arr)

{

free(st->arr);

}

st->arr = NULL;

st->capacity = st->top = 0;

}

//入栈-->栈顶

void STPush(ST* st, STDataType x)

{

assert(st);

//检测空间是否不足

if (st->capacity == st->top)

{

int NewCapacity = st->capacity == 0 ? 4 : 2 * st->capacity;

STDataType* tmp = (STDataType*)realloc(st->arr, NewCapacity * sizeof(STDataType));

if (tmp == NULL)

{

perror("realloc fail!");

exit(1);

}

//malloc成功

st->arr = tmp;

st->capacity = NewCapacity;

}

st->arr[st->top] = x;

st->top++;

}

//出栈帧-->栈顶

void STPop(ST* st)

{

assert(st);

assert(!STEmpty(st));

st->top--;

}栈是否为空,取栈顶数据、栈的有效个数栈是否为空,即栈里面是否存在数据,即top是否为0(为0则返回真)。

代码语言:javascript代码运行次数:0运行复制bool STEmpty(ST* st)

{

assert(st);

return st->top == 0;

}取栈顶数据首先得保证栈不为空,其次取top-1的数据即可,栈的有效个数即top。

代码语言:javascript代码运行次数:0运行复制//取栈顶元素

STDataType STTop(ST* st)

{

assert(!STEmpty(st));

return st->arr[st->top - 1];

}

//获取栈的有效个数

int STSize(ST* st)

{

assert(st);

return st->top;

}队列队列的底层是链表,和前面在实现单链表的底层结构是一样的。

但队列额外定义了指向第一个有效结点的phead指针、指向最后一个有效结点的ptail指针,目的是减少入队列的消耗(不用遍历链表)。

这里多定义的size变量是为了记录队列的有效个数。

代码语言:javascript代码运行次数:0运行复制typedef int QDataType;

//定义队列结点的结构

typedef struct QueueNode

{

QDataType data;

struct QueueNode* next;

}QueueNode;

//定义队列的结构

typedef struct Queue

{

QueueNode* phead;//队头

QueueNode* ptail;//队尾

int size;

}Queue;入队列需要往队尾插入数据,必然涉及申请新节点 --> 在单链表中实现过。

链表为空,phead和ptail指向申请的新结点;链表不为空,ptail指向新结点并成为新结点,并让size++。

代码语言:javascript代码运行次数:0运行复制//往队尾插入数据

void QueuePush(Queue* pq, QDataType x)

{

assert(pq);

//malloc一个新节点

QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));

if (newnode == NULL)

{

perror("malloc: fail");

exit(1);

}

//malloc成功

newnode->data = x;

newnode->next = NULL;

//判断链表是否为空

if (pq->phead == NULL)

{

//链表为空

pq->phead = pq->ptail = newnode;

}

else

{

pq->ptail->next = newnode;

pq->ptail = pq->ptail->next;

}

pq->size++;

}出队列当只存在一个结点时,释放后phead和ptail指向为NULL;释放尾结点,让ptail指向上一个结点。代码语言:javascript代码运行次数:0运行复制//队头删除数据

void QueuePop(Queue* pq)

{

assert(pq);

assert(!QueueEmpty(pq));

//只有一个节点

if (pq->phead == pq->ptail)

{

free(pq->phead);

pq->phead = pq->ptail = NULL;

}

else {

QueueNode* next = pq->phead->next;

free(pq->phead);

pq->phead = next;

}

pq->size--;

}队列判空,取队头、队尾数据,队列的有效个数代码语言:javascript代码运行次数:0运行复制//队列判空

bool QueueEmpty(Queue* pq)

{

assert(pq);

return pq->phead == NULL;

}

//取队头数据

QDataType QueueFront(Queue* pq)

{

assert(pq);

assert(!QueueEmpty(pq));

return pq->phead->data;

}

//取队尾数据

QDataType QueueBack(Queue* pq)

{

assert(pq);

assert(!QueueEmpty(pq));

return pq->ptail->data;

}

//队列有效元素个数

int QueueSize(Queue* pq)

{

assert(pq);

return pq->size;

}算法题解掌握了栈和队列这两种数据结构后,重点在于算法题上的解答,下面通过四种题型领略栈和队列的魅力。

有效的括号题型 --> 有效的括号

根据题目需求判断字符串是否有效,但还存在一些特殊情况如上图所示。那这题思路该从哪里入手呢?用栈的方法实现。

将s从头开始遍历直到结束,若遍历过程遇到 ‘(’ ‘{’ ‘[’ 则入栈st;若遇到 ‘)’ ‘}’ ‘]’ 时则取栈顶比较(前提是栈不为空);遍历结束后,需判断栈是否为空,因为若是有效的字符串则栈应为空(如上图特殊处理1)。

代码语言:javascript代码运行次数:0运行复制bool isValid(char* s) {

ST st;

STInit(&st);

char* pi=s;

while(*pi!='\0')

{

if(*pi=='('||*pi=='{'||*pi=='[')

{

//入栈

STPush(&st,*pi);

}else

{

//取栈顶,判断

if(STEmpty(&st))

{

return false;

}

//不为空

char top=STTop(&st);

if((top=='('&&*pi!=')')||

(top=='{'&&*pi!='}')||

(top=='['&&*pi!=']'))

{

return false;

}

STPop(&st);

}

pi++;

}

bool ret=STEmpty(&st)?true:false;

STDestroy(&st);

return ret;

}用队列实现栈题型 --> 用队列实现栈

算法思路

代码实现

代码语言:javascript代码运行次数:0运行复制typedef struct {

Queue q1;

Queue q2;

} MyStack;

//初始化

MyStack* myStackCreate() {

MyStack* ret=(MyStack*)malloc(sizeof(MyStack));

QueueInit(&ret->q1);

QueueInit(&ret->q2);

return ret;

}

//往不为空的队列插入数据

void myStackPush(MyStack* obj, int x) {

if(!QueueEmpty(&obj->q1))

{

QueuePush(&obj->q1,x);

}else{

QueuePush(&obj->q2,x);

}

}

int myStackPop(MyStack* obj) {

//找空队列和非空队列

Queue* emp=&obj->q1;

Queue* Unemp=&obj->q2;

if(QueueEmpty(&obj->q2))

{

emp=&obj->q2;

Unemp=&obj->q1;

}

while(QueueSize(Unemp)>1)

{

QueuePush(emp,QueueFront(Unemp));

QueuePop(Unemp);

}

int top=QueueFront(Unemp);

QueuePop(Unemp);

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);

obj=NULL;

}用栈实现队列题型 --> 用栈实现队列

算法思路

将STPush栈里的数据全部导入到STPop栈里,并删除STPop栈顶元素。

代码实现

代码语言:javascript代码运行次数:0运行复制typedef struct {

ST pushST;

ST popST;

} MyQueue;

MyQueue* myQueueCreate() {

MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));

STInit(&obj->pushST);

STInit(&obj->popST);

return obj;

}

void myQueuePush(MyQueue* obj, int x) {

STPush(&obj->pushST,x);

}

int myQueuePop(MyQueue* obj) {

if(STEmpty(&obj->popST))

{

//把pushST中的数据导到popST中来

while(!STEmpty(&obj->pushST))

{

STPush(&obj->popST,STTop(&obj->pushST));

STPop(&obj->pushST);

}

}

int top=STTop(&obj->popST);

STPop(&obj->popST);

return top;

}

int myQueuePeek(MyQueue* obj) {

if(STEmpty(&obj->popST))

{

//把pushST中的数据导到popST中来

while(!STEmpty(&obj->pushST))

{

STPush(&obj->popST,STTop(&obj->pushST));

STPop(&obj->pushST);

}

}

int top=STTop(&obj->popST);

return top;

}

bool myQueueEmpty(MyQueue* obj) {

return STEmpty(&obj->pushST)&&STEmpty(&obj->popST);

}

void myQueueFree(MyQueue* obj) {

STDestroy(&obj->pushST);

STDestroy(&obj->popST);

free(obj);

obj=NULL;

}复用由于出队列和取队尾数据的代码相似度极高,因此我们可以用复用的方法使代码更简洁,增强代码可维护性。

设计循环队列题型 --> 设计循环队列

1.若底层用链表的结构来实现,则会出现如下情况1和2出现矛盾,此时又该怎样判断循环队列是否为空呢?

因此,底层用链表的结构来实现很难行得通。

数组结构实现循环队列如果按题目需要多少空间就开多少空间,此时会循环队列空和满的情况下,front和rear的下标都指向同一下标,那该如何判断循环队列是空还是满呢?

解决方案:给数组多开一块空间

构造、销毁循环队列代码语言:javascript代码运行次数:0运行复制typedef struct {

int* arr;

int front;

int rear;

int capacity;

} MyCircularQueue;

MyCircularQueue* myCircularQueueCreate(int k) {

MyCircularQueue* pq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));

if(pq==NULL)

{

exit(1);

}

if(pq->arr==NULL)

{

exit(1);

}

pq->arr=(int*)malloc(sizeof(int)*(k+1));

pq->front=pq->rear=0;

pq->capacity=k;

return pq;

}判断空和满代码语言:javascript代码运行次数:0运行复制bool myCircularQueueIsEmpty(MyCircularQueue* obj) {

return obj->front==obj->rear;

}

bool myCircularQueueIsFull(MyCircularQueue* obj) {

return (obj->rear+1)%(obj->capacity+1)==obj->front;

}入、出队列入队列和出队列的时候会出现特例情况,即front和rear是否会越界。

代码语言:javascript代码运行次数:0运行复制bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {

if(myCircularQueueIsFull(obj))

{

return false;

}

obj->arr[obj->rear++]=value;

obj->rear%=obj->capacity+1;

return true;

}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {

if(myCircularQueueIsEmpty(obj))

{

return false;

}

obj->front++;

obj->front%=obj->capacity+1;

return true;

}取队头、队尾元素取队尾元素时,一般返回rear-1下标位置的元素即可。但此题是一个循环队列,因此存在特殊情况需要处理。

代码语言:javascript代码运行次数:0运行复制int myCircularQueueRear(MyCircularQueue* obj) {

if(myCircularQueueIsEmpty(obj))

{

return -1;

}

return obj->arr[obj->rear-1];

}特例情况:若rear–则为-1,取不到队尾元素。

代码语言:javascript代码运行次数:0运行复制int myCircularQueueFront(MyCircularQueue* obj) {

if(myCircularQueueIsEmpty(obj))

{

return -1;

}

return obj->arr[obj->front];

}

int myCircularQueueRear(MyCircularQueue* obj) {

if(myCircularQueueIsEmpty(obj))

{

return -1;

}

int prev=obj->rear-1;

if(obj->rear==0)

{

prev=obj->capacity;

}

return obj->arr[prev];

}

相关数据

淘宝客都在哪里推广?淘客推广渠道哪里来?
365bet娱乐场游戏

淘宝客都在哪里推广?淘客推广渠道哪里来?

⌛ 07-06 👁️ 8634
原来过生日不能吹蜡烛,原来原因这么简单
365体育欧洲版本

原来过生日不能吹蜡烛,原来原因这么简单

⌛ 07-15 👁️ 7968
淘宝客都在哪里推广?淘客推广渠道哪里来?
365bet娱乐场游戏

淘宝客都在哪里推广?淘客推广渠道哪里来?

⌛ 07-06 👁️ 8634