数据结构初阶--栈和队列

news2024/11/18 11:22:35

目录

一.栈

1.栈的定义

2.顺序栈的功能实现

2.1.顺序栈的定义

2.2.顺序栈的初始化

2.3.顺序栈的判空

2.4.顺序栈的入栈

2.5.顺序栈的出栈

2.6.顺序栈的取栈顶元素

2.7.顺序栈的求栈的大小

2.8.顺序栈的销毁

2.9.完整程序

Stack.h

Stack.c

test.c

二.队列

1.队列的定义

2.链式队列的功能实现

2.1.链式队列的定义

2.2.链式队列的初始化

2.3.链式队列的判空

2.4.链式队列的入队

2.5.链式队列的出队

2.6.链式队列的取队头元素

2.7.链式队列的取队尾元素

2.8.链式队列的求队列的大小

2.9.链式队列的销毁

2.10.完整程序

Queue.h

Queue.c

test.c

三.栈与队列刷题

题一:有效的括号

题二:用队列实现栈

题三:用栈实现队列

题四:设计循环队列


一.栈

1.栈的定义

栈是只允许在一端进行插入或删除操作的线性表

逻辑结构:与普通线性表相同

数据的运算:插入,删除操作有区别

特点:后进先出,即Last In First Out(LIFO)

几个重要术语:

  1. 空栈;
  2. 栈顶:允许插入和删除的一端
  3. 栈底:不允许插入和删除的一端
  4. 栈顶元素
  5. 栈底元素

2.顺序栈的功能实现

2.1.顺序栈的定义

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;//动态开辟数组
	int top;//标识栈顶位置
	int capacity;//栈可以容纳的数据个数
}ST;

首先,定义动态数组a,采用动态数组的方式主要是便于后期容量的扩充;然后,定义一个变量top用于标识栈顶位置;最后,定义一个变量capacity用于统计栈可以容纳的数据个数。

2.2.顺序栈的初始化

void StackInit(ST* ps)
{
	//判空
	assert(ps);

	ps->a = NULL;

	ps->top = 0;

	ps->capacity = 0;
}

首先,将指向栈中数据内存空间的指针a初始化为NULL;然后,将指向栈顶元素的变量top初始化为0;最后,再将标识栈当前容量的capacity初始化为0即可。

调试分析:

2.3.顺序栈的判空

bool StackEmpty(ST* ps)
{
	//判空
	assert(ps);

	return ps->top == 0;
}

判断一个栈是否为空,若为空则返回true,否则返回false。这里需要特别说明的一点是,我们将top指向栈顶元素的下一个位置,而非指向栈顶元素本身。

注意:

  1. 当top初始化为:top=0;此时top指向栈顶元素的下一个位置
  2. 当top初始化为:top=-1;此时top指向栈顶元素

调试分析:

2.4.顺序栈的入栈

void StackPush(ST* ps, STDataType x)
{
	//判空
	assert(ps);

	//判断容量是否已满
	//top标识的是最后一个数据的下一个位置,如果想要指向最后一个数据,初始时top=-1
	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("malloc fail\n");
			exit(-1);
		}

		//将新开辟的内存空间的首地址tmp赋值给a
		ps->a = tmp;
		//更新capacity
		ps->capacity = newCapacity;
	}

	//入栈
	ps->a[ps->top] = x;
	//top指向栈顶元素的下一个位置
	ps->top++;
}

在入栈之前,首先需要对顺序栈的空间容量进行检查,这里使用三目运算符进行判断:若当前容量capacity为空,则开辟4个数据的内存空间;若当前容量非空但已满,则将capacity的大小扩容至2*capacity。然后调用realloc函数开辟新的内存空间,并返回新的内存空间的起始地址tmp。接着将新开辟的内存空间的起始地址tmp赋值给a,让a指向这片新开辟的空间。最后,将x插入栈顶位置,并让top继续指向栈顶元素的下一个位置。

调试分析:

2.5.顺序栈的出栈

void StackPop(ST* ps)
{
	//判空
	assert(ps);

	//判断栈是否为空
	assert(!StackEmpty(ps));
	
	//出栈
	ps->top--;
}

在出栈之前,首先需要调用StackEmpty(ps)函数来判断栈是否为空,若不为空,则可以出栈。出栈操作就是将top减一,需要注意的是,出栈的数据还残留在内存中,只是逻辑上被删除了。

调试分析:

2.6.顺序栈的取栈顶元素

STDataType StackTop(ST* ps)
{
	//判空
	assert(ps);

	//判断栈是否为空
	assert(!StackEmpty(ps));

	//取栈顶元素
	return ps->a[ps->top - 1];
}

在取栈顶元素之前,首先需要调用函数StackEmpty(ps)判断栈是否为空,若栈不为空则可以取栈顶元素。因为top指向栈顶元素的下一个位置,所以top需要先进行减一,再取栈顶元素。

调试分析:

2.7.顺序栈的求栈的大小

int StackSize(ST* ps)
{
	//判空
	assert(ps);

	return ps->top;
}

因为top指向栈顶元素的下一个位置,而top的下标又是从0开始的,所以top所在位置的下标就是所求栈的大小,也就是栈中元素个数。

调试分析:

2.8.顺序栈的销毁

void StackDestory(ST* ps)
{
	//判空
	assert(ps);

	//realloc开辟的空间,需要调用free释放
	free(ps->a);
	ps->a = NULL;

	ps->top = ps->capacity = 0;
}

对于栈的销毁,首先需要释放由realloc动态申请开辟的内存空间,这里主要搭配free函数进行释放。然后将top和capacity初始化为0。

调试分析:

2.9.完整程序

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 StackDestory(ST* ps);

//入栈
void StackPush(ST* ps, STDataType x);

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

//取栈顶元素
STDataType StackTop(ST* ps);

//判空
bool StackEmpty(ST* ps);

//栈大小
int StackSize(ST* ps);

Stack.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Stack.h"


//初始化
void StackInit(ST* ps)
{
	//判空
	assert(ps);

	ps->a = NULL;

	ps->top = 0;

	ps->capacity = 0;
}


//销毁
void StackDestory(ST* ps)
{
	//判空
	assert(ps);

	//realloc开辟的空间,需要调用free释放
	free(ps->a);
	ps->a = NULL;

	ps->top = ps->capacity = 0;
}


//入栈
void StackPush(ST* ps, STDataType x)
{
	//判空
	assert(ps);

	//判断容量是否已满
	//top标识的是最后一个数据的下一个位置,如果想要指向最后一个数据,初始时top=-1
	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("malloc fail\n");
			exit(-1);
		}

		//将新开辟的内存空间的首地址tmp赋值给a
		ps->a = tmp;
		//更新capacity
		ps->capacity = newCapacity;
	}

	//入栈
	ps->a[ps->top] = x;
	//top指向栈顶元素的下一个位置
	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;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Stack.h"

void TestStack()
{
	ST st;

	//初始化
	StackInit(&st);

	//入栈
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);

	//出栈
	while (!StackEmpty(&st))
	{
		//读取栈顶元素
		printf("%d ",StackTop(&st));
		
		//出栈
		StackPop(&st);
	}
	printf("\n");

	//销毁
	StackDestory(&st);
}

int main()
{
	TestStack();

	return 0;
}

二.队列

1.队列的定义

队列是只允许在一端进行插入(入队),在另一端删除的线性表(出队)。

队列的特点:先进先出,即First In First Out(FIFO)。

几个重要术语:

  1. 空队列
  2. 队头:允许删除的一端
  3. 队尾:允许插入的一端
  4. 队头元素
  5. 队尾元素

2.链式队列的功能实现

2.1.链式队列的定义

typedef int QDataType;

//链式队列结点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

//链式队列
typedef struct Queue
{
	QNode* head;//队列的头
	QNode* tail;//队列的尾
}Queue;

2.2.链式队列的初始化

void QueueInit(Queue* pq)
{
	//判空
	assert(pq);

	//不带哨兵位
	pq->head = pq->tail = NULL;
}

这里实现的是不带头结点的链式队列,初始时front和rear都指向NULL。

调试分析:

2.3.链式队列的判空

bool QueueEmpty(Queue* pq)
{
	//判空
	assert(pq);

	//看队头元素是否为NULL
	return pq->head == NULL;
}

判断队列是否为空,只需要看队头元素是否为NULL。

调试分析:

2.4.链式队列的入队

void QueuePush(Queue* pq, QDataType x)
{
	//判空
	assert(pq);

	//创建新结点newnode
	QNode* newnode = (QNode*)malloc(sizeof(QNode));

	//判空
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = NULL;

	//链表为空
	if (pq->tail == NULL)
	{
		//在空队列中插入第一个元素
		//修改队头队尾指针
		pq->head = pq->tail = newnode;
	}
	else
	{
		//链表不为空
		pq->tail->next = newnode;//新结点插入到tail结点之后
		pq->tail = newnode;//修改tail指针
	}
}

在入队之前,需要调用malloc函数开辟一个新结点newnode。对于不带头结点的情况,第一个元素入队时要特殊处理。因为一开始这两个指针都是指向NULL的,所以插入第一个元素时对这两个指针都要进行修改。

调试分析:

2.5.链式队列的出队

void QueuePop(Queue* pq)
{
	//判空
	assert(pq);

	//判断队列是否为空
	assert(!QueueEmpty(pq));

	//只含一个结点
	if (pq->head->next == NULL)
	{
		free(pq->head);//释放最后一个结点
		pq->head = pq->tail = NULL;//将队头与队尾指针都置空
	}
	else//含多个结点
	{
		QNode* next = pq->head->next;//next为队头结点的下一个结点
		free(pq->head);//释放队头结点
		pq->head = next;//修改头指针
	}
}

在出队之前,首先需要判断队列是否为空,若队列为空,则无法进行出队操作。当只剩下最后一个元素未出队时需特殊处理。首先需要调用free函数释放首元素,然后将队头指针与队尾指针都置为NULL。当还剩多个元素时,首先找到队头结点的下一个结点,然后调用free函数释放掉队头结点,最后将队头指针head指向next,更新队头元素。

调试分析:

2.6.链式队列的取队头元素

QDataType QueueFront(Queue* pq)
{
	//判空
	assert(pq);

	//判断队列是否为空
	assert(!QueueEmpty(pq));

	//取队头元素
	return pq->head->data;
}

在取队头元素之前,首选需要调用函数QueueEmpty(pq)判断队列是否为空,若为空,则无法取队头元素。若队列不为空,则取队头元素。

调试分析:

2.7.链式队列的取队尾元素

QDataType QueueBack(Queue* pq)
{
	assert(pq);

	//判断队列是否为空
	assert(!QueueEmpty(pq));

	//取队尾元素
	return pq->tail->data;
}

在取队尾元素之前,首选需要调用函数QueueEmpty(pq)判断队列是否为空,若为空,则无法取队尾元素。若队列不为空,则取队尾元素。

调试分析:

2.8.链式队列的求队列的大小

int QueueSize(Queue* pq)
{
	//判空
	assert(pq);

	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;
	int size = 0;

	//遍历队列
	while (cur)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

在求队列的元素个数时,首先设置指针变量cur,并使其指向队头元素,然后设置一个变量size,用于统计元素个数。让指针变量cur进入while循环遍历整个队列,每遍历一个元素,size就自增1,直到cur走到队尾,则跳出循环,并返回size大小。

调试分析:

2.9.链式队列的销毁

void QueueDestory(Queue* pq)
{
	//判空
	assert(pq);

	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;

	//遍历队列
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	//将队头指针,队尾指针均置为空
	pq->head = pq->tail = NULL;
}

首先设置指针变量cur,并使其指向队头元素,然后让指针变量cur进入while循环遍历整个队列,每遍历一个元素,就将其前一个元素释放掉,直到cur走到队尾,则跳出循环。在销毁整个链表之后要将队头指针与队尾指针均置为NULL。

调试分析:

2.10.完整程序

Queue.h

#pragma once

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

typedef int QDataType;

//链式队列结点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

//链式队列
typedef struct Queue
{
	QNode* head;//队列的头
	QNode* tail;//队列的尾
}Queue;


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

//销毁
void QueueDestory(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);

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Queue.h"

//初始化
void QueueInit(Queue* pq)
{
	//判空
	assert(pq);

	//不带哨兵位
	pq->head = pq->tail = NULL;
}


//销毁
void QueueDestory(Queue* pq)
{
	//判空
	assert(pq);

	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;

	//遍历队列
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	//将队头指针,队尾指针均置为空
	pq->head = pq->tail = NULL;
}


//入队
void QueuePush(Queue* pq, QDataType x)
{
	//判空
	assert(pq);

	//创建新结点newnode
	QNode* newnode = (QNode*)malloc(sizeof(QNode));

	//判空
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = NULL;

	//链表为空
	if (pq->tail == NULL)
	{
		//在空队列中插入第一个元素
		//修改队头队尾指针
		pq->head = pq->tail = newnode;
	}
	else
	{
		//链表不为空
		pq->tail->next = newnode;//新结点插入到tail结点之后
		pq->tail = newnode;//修改tail指针
	}
}


//出队
void QueuePop(Queue* pq)
{
	//判空
	assert(pq);

	//判断队列是否为空
	assert(!QueueEmpty(pq));

	//只含一个结点
	if (pq->head->next == NULL)
	{
		free(pq->head);//释放最后一个结点
		pq->head = pq->tail = NULL;//将队头与队尾指针都置空
	}
	else//含多个结点
	{
		QNode* next = pq->head->next;//next为队头结点的下一个结点
		free(pq->head);//释放队头结点
		pq->head = next;//修改头指针
	}
}


//取队头元素
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);

	//看队头元素是否为NULL
	return pq->head == NULL;
}


//总计元素个数
int QueueSize(Queue* pq)
{
	//判空
	assert(pq);

	//设置指针变量cur,用于遍历队列
	QNode* cur = pq->head;
	int size = 0;

	//遍历队列
	while (cur)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Queue.h"

void TestQueue()
{
	Queue q;

	//初始化
	QueueInit(&q);

	//入队
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);

	//出队
	while (!QueueEmpty(&q))
	{
		//取对头元素
		printf("%d ",QueueFront(&q));
		//出队
		QueuePop(&q);
	}
	printf("\n");

	//销毁
	QueueDestory(&q);
}

int main()
{
	test();

	return 0;
}

三.栈与队列刷题

题一:有效的括号

题目描述:

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

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

分析:

我们可以把左括号依次压入栈中,越往后被压入的左括号越先被弹出栈进行匹配。每出现一个右括号,就“消耗”一个左括号进行匹配检查,这个过程对应出栈操作。扫描一连串括号的过程中若发现下列情况都说明括号序列不合法,终止操作。

  1. 弹出栈的左括号与刚刚遇到要检查的右括号不匹配;
  2. 扫描到右括号时发现栈空了(右括号单身);
  3. 处理完所有括号后,栈非空(右括号单身)。

实现:

bool isValid(char* s)
{
	ST st;

	//初始化
	StackInit(&st);

	while (*s)
	{
		if (*s == '(' || *s == '[' || *s == '{')//是左括号
		{
			//入栈
			StackPush(&st, *s);
			++s;
		}
		else//是右括号
		{
			//判断栈是否为空,可能出现字符串只含右括号的情况
			if (StackEmpty(&st))
			{
				//销毁
				StackDestory(&st);

				return false;
			}

			//若栈不为空,则读取栈顶元素并出栈
			STDataType top = StackTop(&st);// 读取栈顶元素
			StackPop(&st);//出栈

			//看栈顶元素与当前字符是否匹配
			if ((top == '{' && *s == '}') || (top == '(' && *s == ')') || (top == '[' && *s == ']'))
			{
				//匹配
				++s;
			}
			else
			{
				//不匹配则销毁
				StackDestory(&st);

				return false;
			}
		}
	}


    //匹配完之后,判断栈是否为空,可能出现字符串只含左括号的情况
	bool ret = StackEmpty(&st);

	//销毁
	StackDestory(&st);

	return ret;
}

题二:用队列实现栈

题目描述:

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push,top,pop和empty)。
实现MyStack类:
void push(int x):将元素x压入栈顶
void pop():移除并返回栈顶元素
int top():返回栈顶元素
boolean empty():如果栈是空的,返回true;否则,返回false

分析:

队列queue1保存原始输入数据,队列queue2作为临时队列缓存数据。当进行stack_pop操作时,先将queue1里除最后一个元素外全部出队,并将出队的数据保存在临时队列queue2里,然后保存queue1的最后一个元素,最后再将queue2里的全部元素出队,且出队的元素重新放进queue1里,返回保存的queue1最后的元素。

实现:

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

typedef int QDataType;

//链式队列结点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

//链式队列
typedef struct Queue
{
	QNode* head;//队列的头
	QNode* tail;//队列的尾
}Queue;


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

//销毁
void QueueDestory(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);


/**********用队列实现栈**********/


//构造一个包含两个队列的栈
typedef struct
{
	Queue q1;
	Queue q2;
}MyStack;

//初始化栈
MyStack* myStackCreate()
{
	//调用malloc为栈开辟内存空间
	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);

	//假设q1队列为空,q2队列不为空
	Queue* emptyQ = &obj->q1;
	Queue* nonEmptyQ = &obj->q2;

	//若q1队列不为空,则将q2队列设为空,q1队列设为非空
	if (!QueueEmpty(&obj->q1))
	{
		emptyQ = &obj->q2;
		nonEmptyQ = &obj->q1;
	}

	//把非空队列的数据导入到空队列,也就是将非空队列的前n-1个数据导入至另一个空队列
	while (QueueSize(nonEmptyQ) > 1)
	{
		QueuePush(emptyQ,QueueFront(nonEmptyQ));//取非空队列对头的数据插入到空队列中去
		QueuePop(nonEmptyQ);//出队
	}

	int top = QueueFront(nonEmptyQ);//取非空队列的队头元素
	QueuePop(nonEmptyQ);//出队

	return top;
}

//取栈顶元素
int myStackTop(MyStack* obj)
{
	//判空
	assert(obj);

	//若q1队列不为空,则取q1队尾元素
	if (!QueueEmpty(&obj->q1))
	{
		return QueueBack(&obj->q1);
	}
	else
	{
		//若q2队列不为空,则取q2队尾元素
		return QueueBack(&obj->q2);
	}
}

//判断栈是否为空
bool myStackEmpty(MyStack* obj)
{
	//判空
	assert(obj);

	//只有当两个队列均为空时,才表示栈为空
	return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

//销毁栈
void myStackFree(MyStack* obj)
{
	//判空
	assert(obj);

	//销毁队列q1和q2
	QueueDestory(&obj->q1);
	QueueDestory(&obj->q2);

	//释放栈
	free(obj);
}

题三:用栈实现队列

题目描述:

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push,pop,peek,empty)
实现MyQueue类:
void push(int x):将元素x推到队列的末尾
int pop():从队列的开头移除并返回元素
int peek():返回队列开头的元素
boolean empty():如果队列为空,返回true;否则,返回false

分析:

用两个栈实现一个队列,设置其中一个栈pushst专门入数据,另一个栈popst专门出数据。若要入队,则进pushst栈;若要出队,首先看popst栈是否为空,如果为空,就先把pushst栈的数据转移过来,然后出队,如果不为空,则直接出popst栈的数据。

实现:

#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 StackDestory(ST* ps);

//入栈
void StackPush(ST* ps, STDataType x);

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

//取栈顶元素
STDataType StackTop(ST* ps);

//判空
bool StackEmpty(ST* ps);

//栈大小
int StackSize(ST* ps);


/**********用栈实现队列**********/


//构造一个包含两个栈的队列
typedef struct
{
	ST pushst;
	ST popst;
}MyQueue;

//初始化队列
MyQueue* myQueueCreate()
{
	//调用malloc为栈开辟内存空间
	MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));

	//初始化两个栈
	StackInit(&obj->pushst);
	StackInit(&obj->popst);

	return obj;
}

//入队
void myQueuePush(MyQueue* obj, int x)
{
	//判空
	assert(obj);

	//入栈
	StackPush(&obj->pushst, x);
}

//出队
int myQueuePop(MyQueue* obj)
{
	//判空
	assert(obj);

	//出队前首先要判断popst栈是否为空
	if (StackEmpty(&obj->popst))
	{
		//如果popst栈为空,则把pushst栈的数据拷贝过来
		while (!StackEmpty(&obj->pushst))
		{
			//将pushst栈的栈顶元素压入popst栈
			StackPush(&obj->popst, StackTop(&obj->pushst));
			
			//pushst栈的栈顶元素出栈
			StackPop(&obj->pushst);
		}
	}

	//读取popst栈的栈顶元素
	int front = StackTop(&obj->popst);

	//pushst栈的栈顶元素出栈
	StackPop(&obj->popst);

	return front;
}

//取队头元素
int myQueuePeek(MyQueue* obj)
{
	//判空
	assert(obj);

	//当popst栈为空
	if (StackEmpty(&obj->popst))
	{
		//如果popst栈为空,则把pushst栈的数据拷贝过来
		while (!StackEmpty(&obj->pushst))
		{
			//将pushst栈的栈顶元素压入popst栈
			StackPush(&obj->popst, StackTop(&obj->pushst));

			//pushst栈的栈顶元素出栈
			StackPop(&obj->pushst);
		}
	}

	//读取popst栈的栈顶元素
	return StackTop(&obj->popst);
}

//判空队列是否为空
bool myQueueEmpty(MyQueue* obj)
{
	//判空
	assert(obj);

	//只有当两个栈均为空时,才表示队列为空
	return StackEmpty(&obj->pushst) && StackEmpty(&obj->popst);
}

//销毁队列
void myQueueFree(MyQueue* obj)
{
	//判空
	assert(obj);

	//销毁pushst栈和popst栈
	StackDestory(&obj->pushst);
	StackDestory(&obj->popst);
	
	//释放队列
	free(obj);
}

题四:设计循环队列

题目描述:

设计你的循环队列实现。循环队列是一种线性数据结构,其操作表现基于FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k):构造器,设置队列长度为k
Front:从队首获取元素。如果队列为空,返回-1
Rear:获取队尾元素。如果队列为空,返回-1
enQueue(value):向循环队列插入一个元素,如果成功插入则返回真
deQueue():从循环队列中删除一个元素。如果成功删除则返回真
isEmpty():检查循环队列是否为空
isFull():检查循环队列是否已满

分析:

1.采用数组或者链表都可以,但是数组缓存利用率更高,所以这里主要采用数组的方式。

2.用模运算将存储空间在逻辑上变成“环状”。当发现rear指针要指向MaxSize时,不应该让它指向MaxSize而是应该让它指向数组下标为0的位置。

3.队列判空:Q.rear==Q.front;队列判满:队尾指针的下一个位置是对头,即(Q.rear+1)%MaxSize==Q.front

实现:

//循环队列的定义
typedef struct
{
	int* a;//动态开辟数组
	int k;//当前有效元素个数
	int head;//队头
	int tail;//队尾
}MyCircularQueue;

//循环队列的初始化
MyCircularQueue* myCircularQueueCreate(int k)
{
	//为队列开辟一块动态内存空间
	MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));

	//为数组开辟一个包含(k+1)个元素的内存空间
	obj->a = (int*)malloc(sizeof(int) * (k + 1));

	//队头,队尾起始都指向数组下标为0的位置
	obj->head = obj->tail = 0;

	//当前有效元素个数设置为k个
	obj->k = k;

	return obj;
}

//判断队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
	//判空
	assert(obj);

	return obj->head == obj->tail;
}

//判断队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
	//判空
	assert(obj);

	int next = obj->tail + 1;

	//当tail指向数组最后一个下标的下一个位置时,则将tail指向数组下标为0的位置
	if (next == obj->k + 1)
	{
		next = 0;
	}

	//队尾指针的下一个位置是对头,则表示队列已满
	return next == obj->head;
}

//入队
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
	//判空
	assert(obj);

	//检查队列是否已满
	if (myCircularQueueIsFull(obj))
	{
		return false;
	}

	//未满则将value插入队尾
	obj->a[obj->tail] = value;
	obj->tail++;//队尾指针后移

	//当tail指向数组最后一个下标的下一个位置时,则将tail指向数组下标为0的位置
	if (obj->tail == obj->k + 1)
	{
		obj->tail = 0;
	}
	//obj->tail%=(k+1);

	return true;
}

//出队
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
	//判空
	assert(obj);

	//检查队列是否为空
	if (MyCircularQueueIsEmpty(obj))
	{
		return false;
	}

	//若不空,则队头指针后移
	++obj->head;

	//当head指向数组最后一个下标的下一个位置时,则将head指向数组下标为0的位置
	if (obj->head == obj->k + 1)
	{
		obj->head = 0;
	}

	return true;
}

//取队头元素
int myCircularQueueFront(MyCircularQueue* obj)
{
	//判空
	assert(obj);

	//检查队列是否为空
	if (myCircularQueueIsEmpty(obj))
	{
		return -1;
	}

	//若不为空,则取队头元素
	return obj->a[obj->head];
}

//取队尾元素
int myCircularQueueRear(MyCircularQueue* obj)
{
	//判空
	assert(obj);

	//检查队列是否为空
	if (myCircularQueueIsEmpty(obj))
	{
		return -1;
	}
	
	//因为tail指向队尾元素的下一个位置,所以要取tail前一位置的下标
	int pre = obj->tail - 1;

	//若tail在数组起始位置,则前一位置的下标为数组的末尾位置
	if (obj->tail == 0)
	{
		pre = obj->k;
	}
	//int pre = obj->tail - 1 + obj->k + 1;
	//pre %= (obj->k+1);

	//取队尾元素
	return obj->a[pre];
}

//销毁
void myCircularQueueFree(MyCircularQueue* obj)
{
	//判空
	assert(obj);

	//释放动态开辟的数组
	free(obj->a);
	
	//释放队列
	free(obj);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/803023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

S475支持 ModbusRTU 转 MQTT协议采集网关

6路模拟量输入和2路RS485串口是一种功能强大的通信接口&#xff0c;可以接入多种设备和系统&#xff0c;支持4-20mA输出的传感器以及开关量输入输出。本文将详细介绍6路模拟量输入和2路RS485串口的应用场景和功能&#xff0c;重点介绍其在SCADA、HMI、远程数据监控以及采集控制…

机器学习——样本不均衡学习

1、样本不均衡定义 一般在分类机器学习中&#xff0c;每种类别的样本是均衡的&#xff0c;也就是不同目标值的样本总量是接近的&#xff0c;但是在很多场景下的样本没有办法做到理想情况&#xff0c;甚至部分情况本身就是不均衡情况&#xff1a; &#xff08;1&#xff09;很多…

[洛谷]P2052 [NOI2011] 道路修建(dfs)

在递归过程中也把子节点的贡献&#xff08;以及左右国家数记录下来了&#xff09;。 void dfs(int u,int fa) {d[u]1;//当前节点也算一个 for(int ih[u]; i; ine[i]) {int toe[i];//子节点if(tofa) continue;//防止重复搜索&#xff0c;即防止从下往上搜dfs(to,u);//子节点 //…

基于SSM+JSP+LayUI的宿舍管理系统

修正初始账号密码 默认账号&#xff1a;admin&#xff0c;默认密码&#xff1a;123456修复后台管理头像消失功能相对简单些&#xff0c;可能需要添加一些功能&#xff0c;需要源码免费提供需要运行服务、添加功能等联系我

springboot开放实验室管理系统【纯干货分享,免费领源码03361】

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是使用动态网页开发技术java作为系统的开发语言&#xff0c;M…

如何挑选滚珠螺杆的润滑油脂?

滚珠螺杆在日常使用中&#xff0c;出现卡顿或者噪音等问题&#xff0c;不用担心&#xff0c;不是你的滚珠螺杆出了问题&#xff0c;而是润滑系统出了问题&#xff0c;提醒你应该更换润滑油了。那么&#xff0c;我们应该怎样挑选滚珠螺杆的润滑油呢&#xff1f; 一般建议用轴承润…

hive整合es,详细过程。

参考官网 Apache Hive integration | Elasticsearch for Apache Hadoop [7.17] | Elastic 官网的介绍很简单&#xff0c;我看了很多博客&#xff0c;写的也很简单&#xff0c;但是我搞了半天才勉强成功&#xff0c;分享下&#xff0c;免得各位多走弯路。 环境准备 官网也很…

K8S暴露pod内多个端口

K8S暴露pod内多个端口 一、背景 公司统一用的某个底包跑jar服务&#xff0c;只暴露了8080端口 二、需求 由于有些服务在启动jar服务后&#xff0c;会启动多个端口&#xff0c;除了8080端口&#xff0c;还有别的端口需要暴露&#xff0c;我这里就还需要暴露9999端口。 注&a…

mysql常用时间相关函数

在我们平常开发的工作中&#xff0c;我们在数据库中经常会用到时间相关的函数&#xff0c;比如格式化当前时间&#xff0c;求当前时间或者计算某个特定间隔后的时间&#xff0c;那么我们主要会用到哪些函数呢&#xff1f; 1&#xff1a;求当前时间&#xff1a; select now();…

Gradle build 失败后提示.lock文件,解决办法

在Gradle build失败之后时&#xff0c;有时候强制关闭AndroidStudio&#xff0c;再次打开build时&#xff0c;会提示各种.lock 文件问题&#xff0c;删除了一个还有下一个&#xff0c;而且路径不一样。 一般情况下是这两个文件夹下的lockfile影响继续build %GRADLE_HOME%/ca…

YoLoV7做图像分类/目标检测过程(附代码+详细操作说明)

一、准备数据 图像在my_1imgs中&#xff0c;一个是原图jpg&#xff0c;一个是用labelimg画的标签xml文件。&#xff08;这个画的是一个矩形框&#xff09; 把自己的数据集&#xff08;原图和标签准备好后&#xff09;&#xff0c;这两个文件复制到VOCdevkit中,ImageSets为空。 …

EtherNet/IP转CAN网关can协议标准

生产管理设备中&#xff0c;会有设备与其他设备的协议不同&#xff0c;数据无法互通&#xff0c;让你的工作陷入困境。这时&#xff0c;一款神奇的产品出现了——远创智控YC-EIP-CAN通讯网关&#xff01; 1, 这款通讯网关采用ETHERNET/IP从站功能&#xff0c;可以将各种CAN总线…

vue-simple-uploader的fileAdded方法不支持异步的解决办法,autoStart 设置

每日鸡汤&#xff1a;悲观者可能正确&#xff0c;但是乐观者往往成功 假设有一个需求&#xff0c;上传的pdf文档不得大于10M 使用 vue-simple-uploader 这个插件&#xff0c;我们需要在 fileAdded 事件里面进行校验&#xff0c;在1.0.0版本以后&#xff0c;如果想停止上传&…

如何快速用Python获取短信验证码

在Python中获取短信验证码需要通过调用短信服务接口或者使用短信网关来实现。具体实现方式取决于你使用的短信服务提供商或者短信网关的API。 一般来说&#xff0c;你需要以下步骤来获取短信验证码&#xff1a; 选择短信服务提供商或者短信网关 你需要选择一个合适的短信服务…

vue利用echarts简单实现具有中心节点的知识图谱

效果展示 边缘节点可拖动&#xff0c;其大小可以根据传入的值而变化&#xff08;比如我更喜欢芒果&#xff0c;所以给了芒果更大的权值&#xff0c;在显示的时候芒果所在的节点显示的比例更大&#xff09; 代码下载 https://download.csdn.net/download/David_house/881151…

汉明距离,两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。

题记&#xff1a; 两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。 给你两个整数 x 和 y&#xff0c;计算并返回它们之间的汉明距离。 示例 1&#xff1a; 输入&#xff1a;x 1, y 4 输出&#xff1a;2 解释&#xff1a; 1 (0 0 0 1) 4 (0 1 0 0…

家用取暖器北美UL 1278测试要求内容

取暖器是指用于取暖的设备&#xff0c;取暖设备根据加热介质不同、加热原不同&#xff0c;大体可以分为&#xff1a;燃气取暖设备、电加热取暖设备、锅炉取暖设备、电壁挂炉取暖。但一般这类产品要上架美国亚马逊平台都必须要办理UL1278测试报告。 适用产品范围&#xff1a; …

苍穹外卖-day02

苍穹外卖-day02 本项目学自黑马程序员的《苍穹外卖》项目&#xff0c;是瑞吉外卖的Plus版本 功能更多&#xff0c;更加丰富。 结合资料&#xff0c;和自己对学习过程中的一些看法和问题解决情况上传课件笔记 视频&#xff1a;https://www.bilibili.com/video/BV1TP411v7v6/?sp…

机器学习|分类入门

顾名思义&#xff0c;分类就是把事物“分类”成子类别的任务。但是&#xff0c;被一台机器&#xff01;如果这听起来并不多&#xff0c;想象一下你的电脑能够区分你和陌生人。在土豆和西红柿之间。在A和F之间。现在听起来很有趣。分类是监督机器学习的一部分&#xff0c;我们将…