数据结构栈和队列OJ练习(栈和队列相互实现+循环队列实现)

Posted 风继续吹TT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构栈和队列OJ练习(栈和队列相互实现+循环队列实现)相关的知识,希望对你有一定的参考价值。

目录

前言

1.用队列实现栈

2.用栈实现队列

3.循环队列


前言

前面在学习了栈和队列的实现之后,相信大家对栈和队列的结构和使用方式都有了一些理解。

下面我们就来进行一些练习,这这章的练习相对于原来在难度上有了一些提升。

原来的题只需要实现一个接口,而今天的练习题需要实现多个接口。

1.用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode) (leetcode-cn.com)

栈:后进先出队列:先进先出 

方法:

创建两个队列,用来来回导数据。

入栈:将数据放入不为空的队列。

出栈:将不为空的队列中的数据以出队入栈的方式放入空队列,直到该队列中只剩一个数据。

           这个数据就是要出栈的数据,直接将他pop掉即可。

栈顶:返回队尾。

检测栈是否为空:两个队列都为空,则栈为空。

口说无凭,请看图:

如图:

队列1中有4个数,以1,2,3,4顺序入队,所以1为队头,4为队尾。

根据队列先进先出规则,1应该先出队。而这里我们要实现栈,即后进先出,即应该先出队尾数据。

所以我们将除队尾数据全部先导入队列2中,这样队列1中就只剩下队尾数据。

注意:队列1中的数据导入队列2中后,数据的相对位置是不变的。

即:

最后出栈,就可得到栈顶数据。

代码如下:

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

typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

//初始化
void QueueInit(Queue* pq);

//销毁
void QueueDestroy(Queue* pq);

//入队
void QueuePush(Queue* pq, QDataType x);

//出队
void QueuePop(Queue* pq);


QDataType QueueFront(Queue* pq);

QDataType QueueBack(Queue* pq);

bool QueueEmpty(Queue* pq);

int QueueSize(Queue* pq);


//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

//销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
}

//入队
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

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


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

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

int QueueSize(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	int size = 0;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}
//因为C语言中没有直接可以调用的栈和队列,所以需要将自己实现的队列拷贝到前面


typedef struct {//在栈中创建两个队列
    Queue q1;
    Queue q2;
} MyStack;

bool myStackEmpty(MyStack* obj);
MyStack* myStackCreate() {
    MyStack*obj = (MyStack*)malloc(sizeof(MyStack));//动态开辟存放两个队列结构体
    QueueInit(&obj->q1);//初始化两个队列
    QueueInit(&obj->q2);
    return obj;//需返回该结构体地址
}

void myStackPush(MyStack* obj, int x) {
    if(!QueueEmpty(&obj->q1))//入栈时向不为空的队列插入数据,都为空这里默认向队列2中插入
    {
        QueuePush(&obj->q1,x);
    }
    else
    {
        QueuePush(&obj->q2,x);
    }
    
}

int myStackPop(MyStack* obj) {
    Queue*empty = &obj->q1;//出栈时需在不为空的队列里出,所以先找不为空的队列
    Queue*noempty = &obj->q2;//默认队列1不为空
    if(!QueueEmpty(&obj->q1))//如果队列1不为空,则交换两队列
    {
        empty = &obj->q2;
        noempty = &obj->q1;
    }
    while(QueueSize(noempty)>1)//将不为空队列中除队尾数据全部放入空队列中
    {
        QueuePush(empty,QueueFront(noempty));
        QueuePop(noempty);
    }
    int pop = QueueFront(noempty);//剩下的最后一个数据
    QueuePop(noempty);//将数据Pop掉
    return pop;
}

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

2.用栈实现队列

232. 用栈实现队列 - 力扣(LeetCode) (leetcode-cn.com)

方法:

与上题类似,创建两个栈,将数据入栈与入队相同。栈1用来入数据,栈2用来出数据。

不过出队时,队头的数据位于栈底,所以还是要将除栈底的数据全部导入另一个栈中。

但不同的是,队列实现栈时,导过去后数据的顺序不变,所以需要来回导;而这里倒过去后数据顺序会颠倒,所以不需要再导回去,可以直接出队,直到该栈中没有数据。

口说无凭,请看图:

这样下一次再需要出队时,只需将栈2的栈顶Pop即可,直到栈2中没有数据,然后再从栈1中导数据。

代码如下:

typedef int STDataType;

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

//初始化
void StackInit(Stack*ps);

//销毁
void StackDestroy(Stack* ps);

//压栈
void StackPush(Stack* ps, STDataType x);

//出栈
void StackPop(Stack* ps);

//栈顶
STDataType StackTop(Stack* ps);

//判断栈是否为空
bool StackEmpty(Stack* ps);

//栈中元素个数
int StackSize(Stack* ps);


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

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

void StackPush(Stack* 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)
		{
			perror("realloc");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}


void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top - 1];
}

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


int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}
//同样需拷贝自己实现的栈



typedef struct {//创建两个栈,s1用来入数据,s2用来出数据
    Stack s1;
    Stack s2;
} MyQueue;


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

void myQueuePush(MyQueue* obj, int x) {
    StackPush(&obj->s1,x);//直接将数据Push到s1中
}

int myQueuePop(MyQueue* obj) {
    if(StackEmpty(&obj->s2))//pop数据之前先检查s2中是否有数据,没有数据需从s1中导入
    {
        while(!StackEmpty(&obj->s1))
        {
            StackPush(&obj->s2,StackTop(&obj->s1));
            StackPop(&obj->s1);
        }
    }
    int pop = StackTop(&obj->s2);//直接pop栈顶数据即可
    StackPop(&obj->s2);
    return pop;
}

int myQueuePeek(MyQueue* obj) {//返回队尾数据(即s2栈顶数据)
    if(StackEmpty(&obj->s2))//返回数据之前先检查s2中是否有数据,没有数据需从s1中导入
    {
        while(!StackEmpty(&obj->s1))
        {
            StackPush(&obj->s2,StackTop(&obj->s1));
            StackPop(&obj->s1);
        }
    }
    return StackTop(&obj->s2);
}

bool myQueueEmpty(MyQueue* obj) {//两个栈都为空,队列才为空
    return StackEmpty(&obj->s1)&&StackEmpty(&obj->s2);
}

void myQueueFree(MyQueue* obj) {
    StackDestroy(&obj->s1);
    StackDestroy(&obj->s2);
    free(obj);
}

3.循环队列

622. 设计循环队列 - 力扣(LeetCode) (leetcode-cn.com)

这道题的意思是:

设计一个队列,这个队列的大小是固定的,且队列头尾相连, 然后该队列能够实现题目中的操作。

那么是使用数组实现,还是用链表实现呢?我们接着往下看。

例如:用k表示数组大小

图中我们用数组实现。用head和tail表示队头队尾下标,每插入一个数据,在数组tail位置插入,然后再将tail++。

但是有个问题如果将head==tail认为是队列为空的条件,那么怎么判断队列满了呢?

面对这个问题我们这样解决:将数组的空间大小增加1,即数组比队列大1个空间。

这样:当 head = tail 时队列为空,(tail+1)%(k+1) = head 时队列满了。

且以链表实现时也会多申请一个空间。

代码如下:数组实现

typedef struct {
    int*arr;
    int head;//队头
    int tail;//队尾
    int capacity;//队列容量
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue*obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->arr = (int*)malloc(sizeof(int)*(k+1));//申请数组空间
    obj->head = 0;
    obj->tail = 0;
    obj->capacity = k;//队列大小
    return obj;
}

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

//插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj))//检查容量
    {
        return false;
    }
    else
    {
        obj->arr[obj->tail] = value;//在队尾位置插入
        obj->tail++;//队尾向后移动
        obj->tail %= obj->capacity+1;//当队尾移动到数组最后一个元素后面的下标时,队尾回到数组下标为0的位置 
        return true;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))//检测队列是否为空
    {
        return false;
    }
    else
    {
        obj->head++;
        obj->head %= obj->capacity+1; 当队头移动到数组最后一个元素后面的下标时,队尾回到数组下标为0的位置 

        return true;

    }

}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;//队列为空返回-1
    }
    else
    {
        return obj->arr[obj->head];//返回队头数据
    }
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;//队列为空返回-1
    }
    else
    {
        return obj->arr[(obj->tail+obj->capacity)%(obj->capacity+1)];//返回下标为tail位置前面一个位置数据
    }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->head == obj->tail;//head==tail队列为空
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->tail+1)%(obj->capacity+1) == obj->head;//为防止数组越界,不能直接将tail+1与head比较
}

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

链表实现与数组实现类似,只是判断队列满的条件的不一样。

链表判满的条件时tail->next = head;

代码如下:

typedef struct ListNode1 ListNode1;
struct ListNode1{
    int data;
    ListNode1* next;
};


typedef struct {
    ListNode1* front;//创建头尾指针指向队列头和尾
    ListNode1* tail;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    obj->front = NULL;
    ListNode1* cur = NULL;
    while (k > -1)//创建k+1个结点
    {
        if (obj->front == NULL)
        {
            obj->front = (ListNode1*)malloc(sizeof(ListNode1));
            obj->front->next = NULL;
            cur = obj->front;
        }
        else
        {
            ListNode1* next = (ListNode1*)malloc(sizeof(ListNode1));
            cur->next = next;
            cur = next;
        }
        k--;
    }
    cur->next = obj->front;//将链表成环
    obj->tail = obj->front;//头尾指针复位
    return obj;
}


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

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if (myCircularQueueIsFull(obj))
    {
        return false;
    }
    else
    {
        obj->tail->data = value;//插入数据将数据放入tail结点
        obj->tail = obj->tail->next;//tail结点向后移动
        return true;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return false;
    }
    else
    {
        obj->front = obj->front->next;//删除数据时直接将头指针向后移动,不用将该位置的数据删除
        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    {
        return obj->front->data;//返回头指针结点处的数据
    }
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if (myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    {
        ListNode1* cur = obj->front;//因为不知到队尾位置,只知道队尾的下一个位置,所以需要遍历链表寻找队尾。
        while (cur->next != obj->tail)
        {
            cur = cur->next;
        }
        return cur->data;
    }
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->tail;//head==tail队列为空
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return obj->tail->next == obj->front;//tail->next == head 队列满了
}

void myCircularQueueFree(MyCircularQueue* obj) {
    ListNode1* cur = obj->front->next;
    while (cur != obj->front)//释放链表结点
    {
        ListNode1* next = cur->next;
        free(cur);
        cur = next;
    }
    free(cur);
    free(obj);
}

如有错误,或更好的方法,可以在评论区一起交流哟~

你的点赞支持就是我创作的动力!

以上是关于数据结构栈和队列OJ练习(栈和队列相互实现+循环队列实现)的主要内容,如果未能解决你的问题,请参考以下文章

栈和队列数据结构的相互实现[LeetCode]

栈和队列-第五节:JavaC++Python实现栈和队列

数据结构初阶:栈和队列

数据结构之队列的基本操作以及栈和队列的OJ题画图详解

栈和队列C语言实现附加力扣题

(王道408考研数据结构)第三章栈和队列-第二节:队列基本概念顺序栈和链栈基本操作