[Data structure] Implementation of stack and queue (source code attached at the end)

Table of contents

1 stack

1.1 The concept and structure of the stack

1.2 Implementation of the stack

1.2.1 Initialization and destruction of dynamic stack

1.2.2 Push and pop

1.2.3 Get the number of effective elements in the stack

2 queues

2.1 The concept and structure of the queue

2.2 Implementation of the queue

2.2.1 Initialization and destruction of the queue

2.2.2 Insertion and deletion of queues

2.2.3 Get the head and tail elements and the number of effective elements in the queue

3 source code

3.1 stack

3.1.1 Stack.h

3.1.2 Stack.c 

3.2 Queue

3.2.1 Quene.h

3.2.2 Quene.c


1 stack

1.1 The concept and structure of the stack

A stack is a special data structure. It is a special linear table that only allows insertion and deletion of elements at one fixed end.

In the stack, one end where data insertion and deletion operations are performed is called the top of the stack, and the other end is called the bottom of the stack. The data elements in the stack follow the principle of last-in-first-out LIFO (also called first-in-last-out) .

In addition, there are two professional terms about the stack, push and pop.

Push stack: The insertion operation of the stack is called push/push/push, and the inserted data is at the top of the stack.

Popping: The deletion operation of the stack is called popping, and the deleted data is at the top of the stack.

1.2 Implementation of the stack

To implement a stack, you can use an array or a linked list . Relatively speaking, it is better to use an array, because random access to subscripts can be achieved in the array, and it is more convenient to insert data from the end.

The logical structure we initially imagined is: create an array, the first element of the array is the bottom of the stack, and the last element is the top of the stack, and the stack is indirectly manipulated by manipulating this array.

 

 In the specific implementation, the stack is also divided into two ways: static and dynamic. The following is the structure of the static stack

typedef int STDataType;
#define N 10
typedef struct Stack
{
 STDataType _a[N];
 int _top; // 栈顶
}Stack;

In actual operation, it is generally difficult for us to estimate the amount of data, so static stacks are not very commonly used. Generally, a dynamic stack is used.

typedef int STDataType;
typedef struct Stack
{
 STDataType* _a;
 int _top; // 栈顶
 int _capacity; // 容量
}Stack;

1.2.1 Initialization and destruction of dynamic stack

void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = 0;//top将会指向栈顶元素的下一个位置
	//pst->top=-1   //top将会指向栈顶元素的位置
	pst->capacity = 0;
}

void STDestory(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

Initialization and destruction are relatively routine operations, but here is a point that needs attention. During initialization, if we assign top a value of 0, since top needs to be incremented once after inserting data, this will lead to top The value is 1 greater than the subscript where the top element of the stack is located. This will have an impact on subsequent operations, so this must be clear before any changes can be made later.

If you want to directly let the value of top be the subscript of the top element of the stack, you can set top to -1.

When the value of top is different, many of the following operations will also be different. In order to avoid these differences, we uniformly set the value of top to 0.

1.2.2 Push and pop

Since we use an array to implement the stack, the push operation is logically relatively simple, that is, to insert at the end of the array. But there is a problem that needs to be paid attention to here, because the space of the array is dynamically opened up. Therefore, before each insertion, it is necessary to judge whether there is enough space. If it is not enough, it needs to be expanded.

void STPush(ST* pst, STDatatype x)
{
	assert(pst);

	if (pst->top == pst->capacity)  //如果容量已满
	{
		int newCapacity = (pst->capacity == 0 )? 4 : 2 * pst->capacity;
		STDatatype* tmp = (STDatatype*)realloc(pst->a, sizeof(STDatatype) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = newCapacity;
	}
	pst->a[pst->top] = x;
	pst->top++;
}

If top is assigned a value of -1 in the previous step, then the first judgment condition here should be changed to pst->top + 1 == pst->capacity.

The operation of popping the stack is more worry-free. First, judge whether the stack is empty. If it is not empty, directly let top--, make top point to the element before the deleted element, and make this element the new top of the stack.

It should be noted that doing this does not really delete the original top element of the stack, but not deleting it here will not affect it.

void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));

	pst->top--;
}

We found that when the pop operation is performed, the stack may be empty. Therefore, a Boolean value can be used to determine whether the stack is empty.

bool STEmpty(ST* pst)
{
	assert(pst);

	return pst->top == 0; 
}

If we want to know the value of the top element of the stack at a certain moment, we can also write a function to get the top element of the stack.

STDatatype STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));

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

In the actual application of the stack, getting the top element of the stack and popping the stack are usually used together.

1.2.3 Get the number of effective elements in the stack

Sometimes we want to know how many valid data are stored in the stack, then we can write a function.

int STSize(ST* pst)
{
	assert(pst);

	return  pst->top;
}

Of course, if the initial value of top was -1 before, then top+1 needs to be returned here.

2 queues

2.1 The concept and structure of the queue

A queue is a data structure as opposed to a stack. A special linear table that only allows inserting data operations at one end and deleting data operations at the other end.

The end of the insertion operation is called the tail of the queue, and the end of the deletion operation is called the head of the queue.

2.2  Implementation of the queue

Queues can also be implemented as arrays or linked lists. However, because the queue needs to delete the head, if you use an array, you need to move the data one by one, and the time cost is too high. Therefore, when implementing a queue, it is more appropriate to use a linked list.

The structure of each node in the queue is as follows:

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

Here, since we always need to know the head and tail of the queue when operating the queue, we need two pointers to record the head pointer and tail pointer, as well as record the number of data in the queue. So we can create a structure to store these data.

typedef struct Quene
{
	QNode* phead;
	QNode* ptail;
	int size;
}Quene;

In this structure, the head node of the linked list is the head of the team, and the tail node is the tail of the team. 

2.2.1 Initialization and destruction of the queue

The initialization and destruction are very similar to those of the singly linked list, here is the source code directly. If you are not sure, you can check the relevant part of this article: [Data structure] Addition, deletion, checking and modification of headless + one-way + non-circular linked list (source code attached at the end)

void QueneInit(Quene* pq)
{
	assert(pq);

	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

void QueneDestroy(Quene* pq)
{
	assert(pq);

	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

2.2.2 Insertion and deletion of queues

The insertion and deletion operation is also similar to the insertion and deletion of the singly linked list. The difference here is because there are two structures in the queue, one is the structure of the nodes in the queue, and the other is the structure of the queue itself. Therefore, when performing insert and delete operations, it is necessary to consider whether it will affect the structure of the queue itself. Therefore, some judgment conditions need to be added.

//插入(相当于尾插)
void QuenePush(Quene* 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->ptail == NULL)
	{
		assert(pq->phead == NULL);

		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}

//删除(相当于头删)
void QuenePop(Quene* pq)
{
	assert(pq);
	assert(!QueneEmpty(pq));

	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = NULL;
	}
	else
	{
		QNode* cur = pq->phead->next;
		free(pq->phead);
		pq->phead = cur;
	}
	
	pq->size--;
}

2.2.3 Get the head and tail elements and the number of effective elements in the queue

These three functions are more intuitive, directly on the code

//获取队头元素
QDatatype QueneFront(Quene* pq)
{
	assert(pq);
	assert(!QueneEmpty(pq));

	return pq->phead->data;
}

//获取队尾元素
QDatatype QueneBack(Quene* pq)
{
	assert(pq);
	assert(!QueneEmpty(pq));

	return pq->ptail->data;
}

//队列中有效元素个数
int QueneSize(Quene* pq)
{
	assert(pq);

	return pq->size;
}

3 source code

3.1 stack

3.1.1 Stack.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <stdbool.h>

typedef int STDatatype;
typedef struct Stack
{
	STDatatype* a;
	int top;
	int capacity;
}ST;

void STInit(ST* pst);

void STDestory(ST* pst);

void STPush(ST* pst, STDatatype x);

void STPop(ST* pst);

STDatatype STTop(ST* pst);

bool STEmpty(ST* pst);

int STSize(ST* pst);

3.1.2 Stack.c 

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = 0;//top将会指向栈顶元素的下一个位置
	//pst->top=-1   //top将会指向栈顶元素的位置
	pst->capacity = 0;
}

void STDestory(ST* pst)
{
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}

void STPush(ST* pst, STDatatype x)
{
	assert(pst);

	if (pst->top == pst->capacity)
	{
		int newCapacity = (pst->capacity == 0 )? 4 : 2 * pst->capacity;
		STDatatype* tmp = (STDatatype*)realloc(pst->a, sizeof(STDatatype) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = newCapacity;
	}
	pst->a[pst->top] = x;
	pst->top++;
}

void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));

	pst->top--;
}

STDatatype STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));

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

bool STEmpty(ST* pst)
{
	assert(pst);

	return pst->top == 0;
}

int STSize(ST* pst)
{
	assert(pst);

	return  pst->top;
}

3.2 Queue

3.2.1 Quene.h

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

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

typedef struct Quene
{
	QNode* phead;
	QNode* ptail;
	int size;
}Quene;

void QueneInit(Quene* pq);
void QueneDestroy(Quene* pq);
void QuenePush(Quene* pq,QDatatype x);
void QuenePop(Quene* pq);
QDatatype QueneFront(Quene* pq);
QDatatype QueneBack(Quene* pq);
int QueneSize(Quene* pq);
bool QueneEmpty(Quene* pq);

3.2.2 Quene.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Quene.h"

bool QueneEmpty(Quene* pq)
{
	assert(pq);
	return pq->size == 0;
}

void QueneInit(Quene* pq)
{
	assert(pq);

	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

void QueneDestroy(Quene* pq)
{
	assert(pq);

	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

void QuenePush(Quene* 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->ptail == NULL)
	{
		assert(pq->phead == NULL);

		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}

void QuenePop(Quene* pq)
{
	assert(pq);
	assert(!QueneEmpty(pq));

	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = NULL;
	}
	else
	{
		QNode* cur = pq->phead->next;
		free(pq->phead);
		pq->phead = cur;
	}
	
	pq->size--;
}

QDatatype QueneFront(Quene* pq)
{
	assert(pq);
	assert(!QueneEmpty(pq));

	return pq->phead->data;
}

QDatatype QueneBack(Quene* pq)
{
	assert(pq);
	assert(!QueneEmpty(pq));

	return pq->ptail->data;
}

int QueneSize(Quene* pq)
{
	assert(pq);

	return pq->size;
}

Guess you like

Origin blog.csdn.net/fbzhl/article/details/131351376