此文主要栈与队列的介绍以及使用c的具体实现,同时介绍了循环队列的具体实现,以及其实现的难点,并对其进行了绘图示意,希望可以帮助大家,如有错误,还望大家不吝赐教。

一、栈(Stack)

1、什么是栈?

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。<br>举例来说,就像是一串糖葫芦,只能从尖端插入山楂球(数据),也只能从尖端咬出完整的山楂球。这也就是栈的后入先出(LIFO),last in first out。

2、栈的实现

结构体

借用数组实现栈。

  • 使用typedef,方便后续修改栈中内容的数据类型;
  • top指向栈顶,其初始值即可设为0,也可设为-1,看具体的实现方式,本文中采取前者,top指向的也是栈顶的下一位;
  • capacity表示此时栈的容量,增加栈的存在便于当容量不够时,增加容量。
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;		// 栈顶
	int capacity;  // 容量 
}Stack;

void StackInit(Stack* ps)
{
	assert(ps);
	ps->capacity = ps->top = 0;
	ps->a = NULL; // 指针需置空,否则在检查容量时使用,会出现访问野指针的情况
	Checkcapacity(ps);
}

插入 Push

  • 设置capacity就是为了扩容,所以在插入数据时,首先就要检查栈容量;
  • 数据插入后,top再++,保持指向栈顶下一位的状态。
void Checkcapacity(Stack* ps)
{
	assert(ps);
	// 容量不够时,翻倍增加
	if (ps->capacity == ps->top)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("malloc failed");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
}

void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	Checkcapacity(ps);

	ps->a[ps->top++] = data;
}

删除Pop

数组实现,只需将top下移即可。

void StackPop(Stack* ps)
{
	assert(ps);
	// 有数据才可删
	assert(ps->top);

	ps->top--;
}

访问栈顶数据

由于top指向的是下一个插入数据时的位置,因此访问栈顶时,需要 -1。

STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top);

	return ps->a[ps->top - 1];
}

其余基本操作

// 数组现有数据数量,即为top值
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}
// 检查栈是否为空
int StackEmpty(Stack* ps)
{
	assert(ps);

	return ps->top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
	free(ps->a);
	ps->capacity = ps->top = 0;
}

二、队列(Queue)

1、什么是队列?

队列亦是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。 这个数据结构的名称其实很好的表示的它的性质,就像一条队列,在食堂排队,先排的人先打饭。也就是先进先出(FIFO),First in First out。

2、队列的实现

结构体

考虑到栈采用的数组实现,对于有异曲同工之妙的队列,此文采取链表实现。

  • 分别定义结点的数据结构与队列的数据结构;
  • 链表实现相比数组实现的优势:如果使用数组的结构,出队列在数组头上出数据,会出现大量的数据挪动,效率会比较低。
typedef int QDataType;

// 链式结构:表示队列 
typedef struct QListNode
{
	QDataType data;
	struct QListNode* next;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
}Queue;

void QueueInit(Queue* q)
{
	assert(q);
	q->front = q->rear = NULL;
}

插入Push

新申请节点空点,随后将其链接到队列中即可。

void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	if (!tmp)
	{
		perror("malloc failed");
		exit(-1);
	}
	tmp->data = data;
	tmp->next = NULL;
	// 没有元素
	if (q->front == NULL)
	{
		q->front = q->rear = tmp;
	}
	else
	{
		q->rear->next = tmp;
		q->rear = tmp;
	}
}

删除Pop

将表尾结点取消链接,随后释放。

void QueuePop(Queue* q)
{
	assert(q);
	// 只有一个元素
	if (q->front == q->rear)
	{
		free(q->front);
		q->front = q->rear = NULL;
	}
	else
	{
		QNode* tmp = q->front;
		q->front = tmp->next;
		free(tmp);
	}
}

其余基本操作

// 访问队头数据
QDataType QueueFront(Queue* q)
{
	assert(q);
	return q->front->data;
}
// 访问队尾数据
QDataType QueueBack(Queue* q)
{
	assert(q);
	return q->rear->data;
}
// 队列数据数量
int QueueSize(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	int size = 0;
	while (cur != q->rear)
	{
		cur = cur->next;
		size++;
	}
	return size;
}
// 队列判空
int QueueEmpty(Queue* q)
{
	assert(q);
	return q->front == NULL;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
	assert(q);
	while (q->front != NULL)
	{
		QueuePop(q);
	}
}

三、循环队列

此题在力扣有相关OJ题————>设计循环队列

实现难点

  • 若按寻常队列,front指向队头,rear指向队尾,则会出现一个问题:队列空和满的状态时,都有front == rear,(图画的很烂,大家凑合看看,哈哈哈哈哈)如下: front指向队头,rear指向队尾的下一处,则队列满时与队列空时都指向同一位置; image.png
  • 为了解决此问题,多申请一个结点,则判空条件为front == rear,判满条件为rear + 1 == front。如下: image.png

循环队列的实现

结构体

// 利用数组实现循环队列
typedef struct {
    int* data;
    int head;
    int rear;
    int sz;
} MyCircularQueue;

创建循环队列

MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    // 多申请一个空间,便于判断队列是空还是满
    obj->data = (int*)malloc(sizeof(int) * (k + 1));
    obj->sz = k + 1;
    obj->head = obj->rear = 0;
    return obj;
}

入队Push

此处,判断队列是否已满,即用rearfront比较,但由于rear++时有可能会大于队列的容量,故此时需要模运算(%),得到不大于容量的rear值,同时也就达到了循环效果;如下: image.png

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    int tmp = (obj->rear + 1) % obj->sz;
    // 队列已满
    if(tmp == obj->head)
    {
        return false;
    }
    obj->data[obj->rear] = value;
    obj->rear = tmp;
    return true;
}

删除Pop

同理,当front到达循环队列末尾时,需要注意采用模运算

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    // 队列为空
    if(obj->head == obj->rear)
    {
        return false;
    }
    obj->head = (obj->head + 1) % obj->sz;
    return true;
}

其余基本操作,访问队头队尾等;

int myCircularQueueFront(MyCircularQueue* obj) {
    if(obj->head == obj->rear)
    {
        return -1;
    }
    return obj->data[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(obj->head == obj->rear)
    {
        return -1;
    }
    int tmp = (obj->rear - 1 + obj->sz) % obj->sz;
    return obj->data[tmp];
}

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

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    int tmp = (obj->rear + 1) % obj->sz;
    return tmp == obj->head;
}

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