数据结构:栈和队列(保姆级教程)

目录

一、栈

1.1 概念

1.2 栈的实现

1.2.1 选用什么形式?数组or链表?

1.2.2 有关操作预览

1.2.3 栈的定义

 1.2.4 初始化操作

1.2.5 完整代码

1.3 栈相关oj题 - 有效的括号

1.3.1 题目描述

1.3.2 思路

1.3.3代码

二、队列

2.1 概念

2.2 队列的实现

2.2.1 选用什么形式?数组or链表?

2.2.2 队列的结构定义

2.2.3 队列的操作列表

2.2.4 出队操作

2.2.5 完整代码

三、循环队列

3.1 概念

3.2 循环队列的实现

3.2.1 数组or链表

3.2.2 操作预览

3.2.3 注意

3.3 循环队列的完整代

四、相关oj题 - 用队列实现栈

4.1 问题描述

4.2 问题分析

4.3 代码

五、相关oj题 - 用栈实现队列

5.1 问题描述

5.2 问题分析

5.3 代码


一、栈

1.1 概念

     栈:是一种特殊的线性表,它只允许在固定的一端进行插入和删除元素操作进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的元素需要满足先入先出的原则。

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

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

我们可以通过下图来理解:

数据结构:栈和队列(保姆级教程)_第1张图片

1.2 栈的实现

     在栈的操作中,我们着重去说明栈的底层结构选择和初始化操作,其余的操作由于与之前的顺序表操作有诸多相似故不再赘述。

1.2.1 选用什么形式?数组or链表?

     对于栈来说,我们可以通过两种方式来实现:数组或者是链表

     用数组来实现栈的底层,数组的结构决定了他在尾插,尾删操作上的优势,相对简单。

数据结构:栈和队列(保姆级教程)_第2张图片

     我们在实现之前的单链表的时候,就发现了单链表的尾插,尾删需要首先去找尾,成本比较高,而栈的特点是先入后出,在栈顶进行插入和删除操作,如果使用单链表让单链表的头结点作为栈底,尾结点作为栈顶,在入栈和出栈操作时,需要先去找尾,相对繁琐。

数据结构:栈和队列(保姆级教程)_第3张图片

     针对单链表的特点,头插和头删操作比较容易,因此我们可以把头看做栈顶,在头节点之前进行插入和删除操作,把尾看做栈底。

数据结构:栈和队列(保姆级教程)_第4张图片

      在本文章中,选择使用数组来实现栈。

1.2.2 有关操作预览


void STInit(ST* ps);  //堆栈初始化 
void STDestroy(ST* ps);   //销毁栈
void STPush(ST* ps, STDataType x);   //入栈
void STPop(ST* ps);   //出栈
int STsize(ST* ps);   //返回栈中元素个数
bool STEmpty(ST* ps);   //判断栈是否为空
STDataType STTop(ST* ps);  //返回栈顶元素

1.2.3 栈的定义

     我们要实现栈这一结构,需要有一个数组来存储数据,栈是要求在栈顶进行插入和删除操作,所以需要一个top来记录栈顶的位置,我们是动态创建的数组,所以还需要一个变量记录容量

     根据以上特点,使用某一种已有的数据类型无法描述出栈这一结构,所以我们要定义一个结构体来描述栈。

typedef int  STDataType;  //方便我们存储其他类型的数据
#define N 4
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

 1.2.4 初始化操作

     对于栈的初始化,我们尤其需要注意top的值,top可以取0或者-1。

     如果top初始化为0,表示的是栈顶元素的下一个位置,即要插入数据的位置。

数据结构:栈和队列(保姆级教程)_第5张图片

     如果top初始化为-1,表示的是栈顶元素的位置

数据结构:栈和队列(保姆级教程)_第6张图片

     在这里我们选取top为0

//初始化函数
void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType)*N);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	
	ps->top = 0;
	ps->capacity = N;
}

1.2.5 完整代码

Stack.h文件

#pragma once
#include
#include
#include
#include
typedef int  STDataType;
#define N 4
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* ps);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
int STsize(ST* ps);
bool STEmpty(ST* ps);
STDataType STTop(ST* ps);

 Stack.c文件

#include "Stack.h"

//初始化函数
void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType)*N);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	
	ps->top = 0;
	ps->capacity = N;
}

//销毁
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

//求栈中的元素
int STsize(ST* ps)
{
	assert(ps);
	return ps->top;
}

//入栈
void STPush(ST* ps, STDataType x)
{
	assert(ps);
	if (STsize(ps) == ps->capacity)
	{
		STDataType* p = (STDataType*)realloc(ps->a, sizeof(STDataType) * ps->capacity * 2);
		if (p == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = p;
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

//判断栈是否为空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	ps->top--;
}

//取出栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
}

Test.c文件

#include "Stack.h"

void Test1()
{
	
	ST ps;
	STInit(&ps);
	
	STPush(&ps, 1);
	int x = STTop(&ps);
	printf("%d\n", x);

	STPush(&ps, 4);
	x = STTop(&ps);
	printf("%d\n", x);

	STPush(&ps, 8);
	x = STTop(&ps);
	printf("%d\n", x);

	STPush(&ps, 5);
	x = STTop(&ps);
	printf("%d\n", x);

	STPush(&ps, 7);
	x = STTop(&ps);
	printf("%d\n", x);
	
	
	STPop(&ps);

	x = STTop(&ps);
	printf("%d\n", x);
	
	

	STDestroy(&ps);

}

int main()
{
	
	Test1();
	return 0;
}

1.3 栈相关oj题 - 有效的括号

1.3.1 题目描述

oj链接:20. 有效的括号 - 力扣(LeetCode)

数据结构:栈和队列(保姆级教程)_第7张图片

1.3.2 思路

     字符串 "()"、"()[]{}" 和 "{[]}" 都是有效的,而 "(]" 和 "([)]" 不是有效的。可以使用栈来解决这个问题,遇到左括号就入栈,遇到右括号就出栈并判断是否匹配。如果栈为空或者左右括号不匹配,则字符串无效。最后,如果栈为空,则字符串有效。

     对于括号匹配问题,每个左括号都与相对应的右括号匹配才能返回true,我们可以想到以下步骤:

  1. 如果是左括号,则入栈。
  2. 如果是右括号,则出栈顶元素,看是否可以与右括号匹配成功。

注意我们需要对以下两种情况进行单独处理:

  • 字符串中的每个字符都是左括号,此时需要在循环结束之后,对栈进行判空操作,如果栈不为空,则返回false。
  • 字符串中的每个字符都是右括号,在循环中直接判断,如果栈为空,直接返回false。

1.3.3代码

typedef char STDataType;


#define N 4
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* ps);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
int STsize(ST* ps);
bool STEmpty(ST* ps);
STDataType STTop(ST* ps);


void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType)* N);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	ps->top = 0;  //栈顶元素的下一个
	//ps->tio=-1;   //栈顶元素

	ps->capacity = N;

}

//销毁函数
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

//求栈中元素的数量
int STsize(ST* ps)
{
	assert(ps);
	return ps->top;
}

//入栈
void STPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		STDataType* p = (STDataType*)realloc(ps->a, sizeof(STDataType) * (ps->capacity) * 2);
		if (p == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = p;
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

//判空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	ps->top--;
}

//出栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
	
}

bool isValid(char * s){
    ST st;
    STInit(&st);
    while(*s)
    {
        if(*s=='('||*s == '['||*s=='{')
        {
            STPush(&st,*s);
        }
        else if(*s==')'||*s == ']'||*s=='}')
        {

            if(STEmpty(&st))
            {
                STDestroy(&st);
                return false;
            }
            else
            {
                char p = STTop(&st);
                STPop(&st);
                if((p=='('&&*s!=')')||
                   (p=='['&&*s!=']')||
                   (p=='{'&&*s!='}'))
                   {
                       STDestroy(&st);
                       return false;
                   }
            }
        }
        s++;
    }
    

    bool ret = STEmpty(&st);
    STDestroy(&st);
    return ret;
}

二、队列

2.1 概念

     队列:只允许在一端进行插入数据操作,在另一端进行删除数据的特殊线性表,队列具有先进先出的特点。

    进行插入操作的一端叫做队尾

    进行删除操作的一端叫做队头

数据结构:栈和队列(保姆级教程)_第8张图片

2.2 队列的实现

     在本文章中主要对出栈操作进行文字说明,其余操作不再赘述具体实现。

2.2.1 选用什么形式?数组or链表?

     队列的特点是在队尾插入数据,在队头删除数据,如果我们采用数组的形式,在队头删除数据时,需要挪动后面的一系列数据,效率较低,所以在本文章中我们采用链表的形式。

数据结构:栈和队列(保姆级教程)_第9张图片

 若采用链式结构:

数据结构:栈和队列(保姆级教程)_第10张图片

 2.2.2 队列的结构定义

     由于队列在队尾插入,在队头删除的特点,其次我们这里的队列底层使用链表,所以我们在这里定义两个结构体类型,一个结构体来表示队列的整体结构,一个用来描述局部结构即队列中的每个元素的结构。

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

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

2.2.3 队列的操作列表

void QueueInit(Queue* pq);  //初始化
void QueueDestroy(Queue* pq);  //销毁
void QueuePush(Queue* pq,QDatatype x);   //入队列
void QueuePop(Queue* pq);	 //出队列
int QueueSize(Queue* pq);    //返回队列中的元素个数
bool QueueEmpty(Queue* pq);   //判空
QDatatype QueueFront(Queue* pq);  //返回队头元素
QDatatype QueueBack(Queue* pq);   ///返回队尾元素

2.2.4 出队操作

     当队列中元素大于一个时,我们只需要改变队头的指针。

数据结构:栈和队列(保姆级教程)_第11张图片

     对于出队操作,我们需要注意的是当队列中只有一个元素的时候,这时我们还需要更改尾指针的值,需要进行单独处理。

数据结构:栈和队列(保姆级教程)_第12张图片

 

//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!(QueueEmpty(pq)));
	if (pq->head == pq->tail)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

2.2.5 完整代码

Queue.h文件 

#pragma once
#include
#include
#include
#include

typedef int QDatatype;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDatatype data;
}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);	
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);

Queue.c文件 

#include "Queue.h"



//初始化函数
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = 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->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");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

//判断是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!(QueueEmpty(pq)));
	if (pq->head == pq->tail)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

//求队列的元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

//返回队首元素
QDatatype QueueFront(Queue* pq)
{
	assert(pq);
	assert(!(QueueEmpty(pq)));
	return pq->head->data;
}

//返回队尾元素
QDatatype QueueBack(Queue* pq)
{
	assert(pq);
	assert(!(QueueEmpty(pq)));
	return pq->tail->data;
}

Test.c文件

#include "Queue.h"

void Test1()
{
	Queue pq;
	QueueInit(&pq);
	QueuePush(&pq,1);
	QDatatype x = QueueFront(&pq);
	printf("%d\n", x);

	QueuePush(&pq, 2);
	x = QueueFront(&pq);
	printf("%d\n", x);

	QueuePush(&pq, 3);
	x = QueueBack(&pq);
	printf("%d\n", x);

	QueuePush(&pq, 4);
	x = QueueBack(&pq);
	printf("%d\n", x);

	QueuePush(&pq, 7);
	x = QueueBack(&pq);
	printf("%d\n", x);

	QueuePop(&pq);
	x = QueueFront(&pq);
	printf("%d\n", x);

	QueuePop(&pq);
	x = QueueFront(&pq);
	printf("%d\n", x);

	QueuePop(&pq);
	x = QueueFront(&pq);
	printf("%d\n", x);
	
	QueueDestroy(&pq);

}


int main()
{
	Test1();
	return 0;
}

三、循环队列

3.1 概念

     循环队列是一种特殊的队列,它可以充分利用数组空间,实现队列的循环利用。其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

数据结构:栈和队列(保姆级教程)_第13张图片

3.2 循环队列的实现

3.2.1 数组or链表

     对于实现循环队列,我们可以有以下两种方式实现,但不论是哪一种方式,都需要一个front值来表示头(队首)的位置,一个rear值表示要插入数据的位置,即最后一个节点的下一个位置,这样就存在一个问题:当队列为空和队列满时他们的的条件都是front==rear

数据结构:栈和队列(保姆级教程)_第14张图片

 对于上述情况有两种解决方式:

  1. 增加一个size变量来记录队列中元素的个数。
  2. 多开一个位置,即有一个位置不存储数据,用来区分,如果多开一个位置,当队列空的时候,front==rear,当队列满时,rear的下一个位置是front。

数据结构:栈和队列(保姆级教程)_第15张图片

      注意上述使用数组来实现循环队列时,画的是他的物理结构,他的逻辑结构如下:

数据结构:栈和队列(保姆级教程)_第16张图片

      在这里如果使用链表来实现在进行取尾操作时,还需要再次进行遍历,或者记录尾前面一个节点的位置,相对繁琐,所以我们在下面的文章中,提供了使用数组来实现循环链表的代码

3.2.2 操作预览

MyCircularQueue(k): 设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。

3.2.3 注意

     用数组去实现循环队列,我们需要注意当front或者rear在数组最后一个下标时的情况,由于是循环链表,空间重复利用,当走到数组的最后一个元素时,他的下一个取值即是数组的一个元素的下标位置,所以我们这里使用%操作达到想要的结果。

数据结构:栈和队列(保姆级教程)_第17张图片

3.3 循环队列的完整代



typedef struct {
	int* a;
	int rear;
	int front;
	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 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;
	obj->rear= (obj->rear + 1) % (obj->k + 1);
	return true;
}

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


//从循环队列中删除一个元素。如果成功删除则返回真。
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
	assert(obj);
	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;
	}
	return obj->a[(obj->rear+obj->k)%(obj->k+1)];
}


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

四、相关oj题 - 用队列实现栈

4.1 问题描述

oj链接:225. 用队列实现栈 - 力扣(LeetCode)

数据结构:栈和队列(保姆级教程)_第18张图片

4.2 问题分析

     用两个队列来实现栈,首先我们需要了解栈和队列这两种结构各自的特点,栈要求先入后出,队列要求先进先出。

     也就是说,用两个队列来模拟实现栈,主要是使用两个队列来完成先入后出的功能,而队列的特点是先入先出,在这里我们主要说明的是入栈和出栈操作:

对于入栈:

数据结构:栈和队列(保姆级教程)_第19张图片

对于出栈:

数据结构:栈和队列(保姆级教程)_第20张图片

总结起来即以下几点:

  1.  定义一个结构体Stack,包含两个队列q1和q2。
  2.  初始化栈时,分别初始化两个队列。
  3.  入栈操作时,将元素插入非空的队列中。
  4.  出栈操作时,将非空队列中的元素依次出队并插入另一个空队列中,直到只剩下一个元素,然后将该元素出队即可。
  5.  取栈顶元素时,将非空队列中的元素依次出队并插入另一个空队列中,直到只剩下一个元素,然后返回该元素即可。
  6.  判断栈是否为空时,判断两个队列是否都为空即可。

4.3 代码

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

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

//初始化函数
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = 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->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");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

//判断是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!(QueueEmpty(pq)));
	if (pq->head == pq->tail)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

//求队列的元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

//返回队首元素
QDatatype QueueFront(Queue* pq)
{
	assert(pq);
	assert(!(QueueEmpty(pq)));
	return pq->head->data;
}

//返回队尾元素
QDatatype QueueBack(Queue* pq)
{
	assert(pq);
	assert(!(QueueEmpty(pq)));
	return pq->tail->data;
}

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq,QDatatype x);
void QueuePop(Queue* pq);	
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);




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


MyStack* myStackCreate() {
    MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
    if(pst==NULL)
    {
        perror("malloc fail");
        return NULL;
    }

    QueueInit(&pst->q1);
    QueueInit(&pst->q2);

    return pst;    
}

void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))
    {
        QueuePush(&obj->q1, x);
    }
    else
    {
        QueuePush(&obj->q2, x);
    }
}

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

int myStackPop(MyStack* obj) {
    assert(!myStackEmpty(obj));
    Queue* nonemptyque = &obj->q1;
    Queue* emptyque = &obj->q2;

    if(QueueEmpty(&obj->q1))
    {
        nonemptyque = &obj->q2;
        emptyque = &obj->q1;
    }

    while(QueueSize(nonemptyque)>1)
    {
        QueuePush(emptyque,QueueFront(nonemptyque));
        QueuePop(nonemptyque);
    }
    int x = QueueFront(nonemptyque);
    QueuePop(nonemptyque);
    return x;
}

int myStackTop(MyStack* obj) {
    assert(!myStackEmpty(obj));
    Queue* nonemptyque = &obj->q1;
    Queue* emptyque = &obj->q2;

    if(QueueEmpty(&obj->q1))
    {
        nonemptyque = &obj->q2;
        emptyque = &obj->q1;
    }
    return QueueBack(nonemptyque);
}



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

五、相关oj题 - 用栈实现队列

5.1 问题描述

oj链接:232. 用栈实现队列 - 力扣(LeetCode)

数据结构:栈和队列(保姆级教程)_第21张图片

5.2 问题分析

     我们要用两个栈来实现一个队列,栈的特点是先入后出,队列的特点是先入先出,我们这里的目的是使用两个栈达到先入先出的目的。

     与之前的用两个队列来实现栈,用两个栈来实现队列相对简单,在之前的用两个队列来实现栈,我们在出栈时需要把两个队列中的数据来回翻转,但在本题中,我们只需要定义一个栈来进行插入操作,一个用来删除。

     我们固定使用栈q1来进行入队操作,使用栈q2来进行出队操作。

入队时:

     直接将数据插入到q1中。

出队时:

数据结构:栈和队列(保姆级教程)_第22张图片

当q2为空时,再次将q1中数据依次出栈,然后依次插入到栈q2中。

具体总结如下:

  1. 定义一个结构体Queue,包含两个栈s1和s2。
  2. 初始化队列时,分别初始化两个栈。
  3. 入队操作时,将元素插入非空的栈s1中。
  4. 出队操作时,如果栈s2为空,则将栈s1中的元素依次出栈并插入栈s2中,然后将栈s2的栈顶元素出栈即可。
  5. 取队首元素时,如果栈s2为空,则将栈s1中的元素依次出栈并插入栈s2中,然后返回栈s2的栈顶元素即可。
  6. 判断队列是否为空时,判断两个栈是否都为空即可。

5.3 代码

typedef int  STDataType;
#define N 4
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* ps);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
int STsize(ST* ps);
bool STEmpty(ST* ps);
STDataType STTop(ST* ps);

//初始化函数
void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType)*N);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;
	}
	
	ps->top = 0;
	ps->capacity = N;
}

//销毁
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

//求栈中的元素
int STsize(ST* ps)
{
	assert(ps);
	return ps->top;
}

//入栈
void STPush(ST* ps, STDataType x)
{
	assert(ps);
	if (STsize(ps) == ps->capacity)
	{
		STDataType* p = (STDataType*)realloc(ps->a, sizeof(STDataType) * ps->capacity * 2);
		if (p == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->a = p;
		ps->capacity *= 2;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

//判断栈是否为空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	ps->top--;
}

//取出栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
}


typedef struct {
    ST q1;
    ST q2;
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue* pst = (MyQueue*)malloc(sizeof(MyQueue));
    if(pst==NULL)
    {
        perror("malloc fail");
        return NULL;
    }
    STInit(&pst->q1);
    STInit(&pst->q2);
    return pst;
}

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

bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->q1)&&STEmpty(&obj->q2);
}

int myQueuePop(MyQueue* obj) {
    assert(!myQueueEmpty(obj));
    if(STEmpty(&obj->q2))
    {
        while(STsize(&obj->q1)>0)
        {
            STPush(&obj->q2, STTop(&obj->q1));
            STPop(&obj->q1);
        }
        int x= STTop(&obj->q2);
        STPop(&obj->q2);
        return x;  
    }
    else
    {
        int x= STTop(&obj->q2);
        STPop(&obj->q2);
        return x;
    }
}

int myQueuePeek(MyQueue* obj) {
    assert(!myQueueEmpty(obj));
    if(STEmpty(&obj->q2))
    {
        while(STsize(&obj->q1)>0)
        {
            STPush(&obj->q2, STTop(&obj->q1));
            STPop(&obj->q1);
        }
        int x= STTop(&obj->q2);
        return x;  
    }
    else
    {
        int x= STTop(&obj->q2);
        return x;
    }
}



void myQueueFree(MyQueue* obj) {
     STDestroy(&obj->q1);
     STDestroy(&obj->q2);
     free(obj);
}

你可能感兴趣的:(算法,数据结构,c语言,leetcode,链表)