【数据结构】栈与队列

一、栈的概念及结构

1. 栈(stack)—— 先进后出

栈(stack)又名堆栈,它是一种特殊的线性表。限定仅在一端进行插入和删除操作的线性表,这一端称为栈顶,另一端称为栈底
在这里插入图片描述
栈中的元素遵循先进后出原则,即插入与删除都只能采用尾插和尾删。故根据这一特色,栈结构的设计我们采用顺序表(顺序表一般采用动态在堆上开辟空间)。

2. 栈、栈帧、栈区 和 堆

  • :一般指数据结构中的栈结构(后进先出的线性结构)
  • 栈区:一块特殊的内存空间,该区域存储的是与函数调用相关的信息(线程)
  • 栈帧:存放在栈区的一种特殊数据结构,编辑器自动分配释放 ,每次函数调用时,都会在内存中的栈区开辟出一个栈帧用来存放函数调用的相关信息
  • 堆区:一块特殊的内存空间,需手动分配释放 ,用来存放进程动态申请的空间

3. 三种开辟堆空间函数 malloc、calloc、realloc

相同点:

  • 三种函数都是用来在堆上开辟空间;
  • 堆上开辟有可能申请空间失败,故申请后需要判空;
  • 堆上开辟的空间使用结束后均需调用 free() 函数释放 ;
  • 三者的返回值都为void* ,所以在使用时需要强转为自己所需的类型。
    在这里插入图片描述
    malloc 函数
    参数:表示在堆上开辟空间的字节数
    功能:直接在堆上开辟空间(最直接);
    在这里插入图片描述
    calloc 函数
    参数:num 表示申请的元素个数、size 表示元素类型大小;
    功能:从堆上开辟num个size大小的元素;开辟后会将每个字节初始化为0
    在这里插入图片描述
    realloc 函数
    参数:ptr为开辟堆空间的起始位置、size为开辟字节大小
    功能:若ptr指针为空,则该函数等价与malloc;若不为空,则会将ptr所指向的空间调整到size字节大小(若ptr指向的原空间满足size字节的空间则直接开辟,若不满足则在堆上重新开辟并拷贝ptr原空间数据后并更新ptr

二、栈的实现

1.栈的开辟

栈本质上就是只能尾插尾删的线性表,故采用顺序表的方式开辟栈结构(因为不涉及头插、头删,且顺序表查找结点优于链表)

因此栈的代码与顺序表基本一致,不过有的代码会将顺序表结构中的size(储存实际大小)表示为top(栈顶),从数值上一样

//采用动态开辟顺序表结构
typedef struct Stack
{
	STDataType* array;
	int capacity;  //空间总大小
	int size;  //栈顶(储存实际大小)
}Stack;

2.栈的函数(与顺序表类似)

  • 初始化顺序表
    一般初始为顺序表开辟三个元素
void StackInit(Stack* ps)
{
	assert(ps);
	ps->array = (STDataType*)malloc(sizeof(STDataType) * 3); //初始化默认开辟三个单位
	if (NULL == ps->array)
	{
		assert(0);
		return;
	}

	ps->capacity = 3;
	ps->size = 0;
}
  • 销毁链表
    堆用完不销毁,error迟早来找你
void StackDestroy(Stack* ps)
{
	assert(ps);
	if (ps->array)
	{
		free(ps->array);
		ps->array = NULL;
		ps->capacity = 0;
		ps->size = 0;
	}
}
  • 检测栈是否为空
    若为空,返回1
int StackEmpty(Stack* ps)
{
	assert(ps);
	return 0 == ps->size;
}
  • 入栈 (尾插)
    这里耦合出检测空间是否已满的static void CheckCapacity(Stack* ps)函数,每次插入新元素需判断堆空间是否足够
//检测空间是否已满
static void CheckCapacity(Stack* ps)
{
	assert(ps);
	int newCapacity = 2 * ps->capacity;
	if (ps->size == ps->capacity)
	{
		//1.申请新空间
		STDataType* newArray = (STDataType*)malloc(sizeof(STDataType) * newCapacity);
		if (NULL == newArray)
		{
			assert(0);
			return;
		}
		//2.拷贝原空间至新空间
		memcpy(newArray, ps->array, ps->size * sizeof(STDataType));
		//3.释放原空间
		free(ps->array);

		ps->array = newArray;
		ps->capacity = newCapacity;
	}
}
// 入栈 (尾插)
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	//1.判断栈空间
	CheckCapacity(ps);
	//2.直接插入
	ps->array[ps->size] = data;
	ps->size++;
}
  • 出栈(尾删)
void StackPop(Stack* ps)
{
	assert(ps);
	//1.检测栈是否为空
	if (StackEmpty(ps))
	{
		printf("删除错误!栈为空\n");
		return;
	}
	//2.直接减值
	ps->size--;
}
  • 获取栈顶元素
STDataType StackTop(Stack* ps)
{
	assert(ps);
	//1.检测栈是否为空
	if (StackEmpty(ps))
	{
		printf("获取错误!栈为空\n");
		return -1;
	}
	//2.返回数组最后一位元素
	return ps->array[ps->size - 1];
}
  • 获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->size;
}

源代码

看源码,还得码云

队列

一、队列的概念及结构

1.队列(queue)—— 先进先出

队列(queue)又名队,它是一种特殊的线性表。限定仅在一端(队尾)进行插入,另一端(队头)删除的线性表。
在这里插入图片描述
队列中的元素遵循先进先出原则,即插入采用尾插、删除采用头删。故根据这一特色,栈结构的设计我们一般采用链表来实现。

2.循环队列

前面我们提到队列由于有头删操作,故采取了链表结构设计队列,但链表寻找结点的复杂度较高,因此我们亦可以尝试通过特殊的顺序表来避免其头删的缺点。

1、所以现在我们采用一种顺序表设计队列(头删尾插),且这个顺序表能够避免头删移动后面元素的缺点,我们首先想到的是让后面的元素不移动就行,即当头元素删除时,头指针直接移向下一位即可。
【问题】假溢出问题,头指针向后移动也意味着开辟堆的总空间虽然不变,但当尾指针走到空时,到达队列上界不能在入队,可这时队列前部仍有空间切不能利用,造成空间浪费。
在这里插入图片描述

2、为了解决假溢出问题,我们采用循环结构即可,循环顺序表头尾相接,头指针虽然向后移,但并未在堆上释放头结点空间,循环结构意味着尾部指针也可指向原来头部空间。
【问题】循环队列空状态与满状态无法区分
在这里插入图片描述

3、循环队列:一个头尾相接的顺序表,且尾指针永远指向空,队列中需要冗余一位空结点,这样的队列即可满足上述多有问题。
在这里插入图片描述

二、队列的实现(链表)

1.队列的开辟

队列结构的构造包含两个指针:front 指向队头(类似头指针)出队使用,rear 指向队尾,进队使用;

typedef int QDataType;


// 定义单链表结点
typedef struct QNode
{
	QDataType data;
	struct QNode* next;
}QNode;

// 定义队列结构体
typedef struct Queue
{
	QNode* front;  //指向队头结点 队头:出队
	QNode* rear;   //指向队尾结点 队尾:入队
	int size;      //队列元素个数
}Queue;

2.队列的函数(与链表类似)

【注意】由于定义了队列的结构,指向链表结点的指针被封装在其结构内部,故队列函数的参数采用一级指针即可

  • 初始化队列
void QueueInit(Queue* q)
{
	assert(q);
	q->front = NULL;
	q->rear = NULL;
	q->size = 0;
}
  • 销毁队列
    销毁不能忘!
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	while (cur)
	{
		q->front = cur->next;
		free(cur);
		cur = q->front;
	}
	q->front = NULL;
	q->rear = NULL;
	q->size = 0;
}
  • 入队列
    耦合出申请新链表结点的函数QNode* buyQNode(QDataType data)
QNode* buyQNode(QDataType data)
{
	QNode* newQNode = (QNode*)malloc(sizeof(QNode));
	if (NULL == newQNode)
	{
		assert(0);
		return NULL;
	}
	newQNode->data = data;
	newQNode->next = NULL;

	return newQNode;
}
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* newQNode = buyQNode(data);
	//1.队列为空
	if (QueueEmpty(q))
	{
		q->front = newQNode;
	}
	else
	{
		//2.队列已有元素
		q->rear->next = newQNode;
	}
	//3.调整队列其余元素
	q->rear = newQNode;
	q->size++;

	return;
}
  • 检测队列是否为空
int QueueEmpty(Queue* q)
{
	assert(q);
	return 0 == q->size;
}
  • 出队列
void QueuePop(Queue* q)
{
	assert(q);
	//1.队列为空
	if (QueueEmpty(q))
	{
		assert(0);
		return;
	}
	else
	{
		//2.队列已有元素
		QNode* delNode = q->front;
		q->front = q->front->next;
		q->size--;
		free(delNode);
		if (NULL == q->front)
		{
			q->rear = NULL;
		}
	}
}
  • 获取队头、尾元素
// 获取队头元素
QDataType QueueFront(Queue* q)
{
	//队列为空情况设为违法
	assert(!QueueEmpty(q));
	return q->front->data;
}

// 获取队尾元素
QDataType QueueBack(Queue* q)
{
	//队列为空情况设为违法
	assert(!QueueEmpty(q));
	return q->rear->data;
}
  • 获取队列中有效元素个数
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}

源代码

来码云

三、循环队列的实现

循环队列结构 初始化

注意循环队列实际申请空间是其实际容量+1

//循环队列以定长数组构成的顺序表为基础
//注意队列特性 前出后进(头删与尾插)

//循环队列结构
typedef struct
{
    int* array; // 指向存储元素空间的起始位置
    int front;  // 队首元素
    int rear;   // 队尾元素
    int size;   // 队列可存放元素
} MyCircularQueue;

//初始化队列
MyCircularQueue* myCircularQueueCreate(int k)
{
    if (k < 0)
    {
        return NULL;
    }
        
    MyCircularQueue* mcq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if (NULL == mcq)
    {
        assert(0);
        return NULL;
    }
    //循环队列实际申请空间是他的容量+1
    //这样可以区分空队列与满队列
    mcq->array = (int*)malloc(sizeof(int) * (k + 1));
    mcq->front = 0;
    mcq->rear = 0;
    mcq->size = k;

    return mcq;
}

源代码

看源码,上码云

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值