[Структура данных] Реализация стека и очереди (исходный код прилагается в конце)

Оглавление

1 стопка

1.1 Понятие и структура стека

1.2 Реализация стека

1.2.1 Инициализация и уничтожение динамического стека

1.2.2 Толкать и хлопать

1.2.3 Получить количество эффективных элементов в стеке

2 очереди

2.1 Понятие и структура очереди

2.2 Реализация очереди

2.2.1 Инициализация и уничтожение очереди

2.2.2 Вставка и удаление очередей

2.2.3 Получить начальные и конечные элементы, а также количество эффективных элементов в очереди

3 исходный код

3.1 стек

3.1.1 Стек.ч

3.1.2 Стек.c 

3.2 Очередь

3.2.1 Quene.h

3.2.2 Кене.с


1 стопка

1.1 Понятие и структура стека

Стек — это специальная структура данных. Это специальная линейная таблица, которая позволяет вставлять и удалять элементы только с одного фиксированного конца.

В стеке один конец, где выполняются операции вставки и удаления данных, называется вершиной стека, а другой конец называется нижней частью стека. Элементы данных в стеке следуют принципу LIFO «последним поступил — первым вышел» (также называемому «первым пришел — последним вышел») .

Кроме того, есть два профессиональных термина о стеке: push и pop.

Стек push: операция вставки в стек называется push/push/push, а вставляемые данные находятся в верхней части стека.

Извлечение: операция удаления стека называется извлечением, и удаленные данные находятся на вершине стека.

1.2 Реализация стека

Для реализации стека можно использовать массив или связанный список . Условно говоря, лучше использовать массив, потому что в массиве можно добиться произвольного доступа к индексам, а вставлять данные удобнее с конца.

Логическая структура, которую мы изначально представляли, такова: создайте массив, первый элемент массива — это низ стека, а последний элемент — это вершина стека, а стеком косвенно управляют, манипулируя этим массивом.

 

 В конкретной реализации стек также делится на два способа: статический и динамический.Следующая структура статического стека

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

В реальной работе нам, как правило, сложно оценить объем данных, поэтому статические стеки используются не очень часто. Обычно используется динамический стек.

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

1.2.1 Инициализация и уничтожение динамического стека

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

Инициализация и уничтожение являются относительно рутинными операциями, но вот момент, который требует внимания.Во время инициализации, если мы присвоим top значение 0, так как top нужно увеличить один раз после вставки данных, это приведет к тому, что top будет на 1 больше чем нижний индекс, где находится верхний элемент стека. Это повлияет на последующие операции, поэтому это должно быть ясно, прежде чем вносить какие-либо изменения позже.

Если вы хотите, чтобы значение top было прямым индексом верхнего элемента стека, вы можете установить top равным -1.

Когда значение top отличается, многие из следующих операций также будут другими. Чтобы избежать этих различий, мы единообразно устанавливаем значение top равным 0.

1.2.2 Толкать и хлопать

Поскольку для реализации стека мы используем массив, операция push логически относительно проста, то есть вставляется в конец массива. Но есть проблема, на которую здесь нужно обратить внимание, потому что пространство массива динамически открывается. Поэтому перед каждой вставкой необходимо судить, достаточно ли места. Если этого недостаточно, его необходимо расширить.

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

Если на предыдущем шаге top было присвоено значение -1, то первое условие оценки здесь должно быть изменено на pst->top + 1 == pst->capacity.

Операция извлечения из стека более беззаботна.Во-первых, оцените, пуст ли стек.Если он не пуст, сразу позвольте top--, сделайте верхнюю точку на элемент перед удаленным элементом и сделайте этот элемент новым вершина стека.

Следует отметить, что в действительности это не приводит к удалению исходного верхнего элемента стека, но если его не удалить здесь, это не повлияет на него.

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

	pst->top--;
}

Мы обнаружили, что при выполнении операции извлечения стек может быть пуст. Следовательно, можно использовать логическое значение, чтобы определить, пуст ли стек.

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

	return pst->top == 0; 
}

Если мы хотим узнать значение верхнего элемента стека в определенный момент, мы также можем написать функцию для получения верхнего элемента стека.

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

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

В реальном применении стека получение верхнего элемента стека и извлечение стека обычно используются вместе.

1.2.3 Получить количество эффективных элементов в стеке

Иногда нам нужно знать, сколько действительных данных хранится в стеке, тогда мы можем написать функцию.

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

	return  pst->top;
}

Конечно, если начальное значение top раньше было -1, то здесь нужно вернуть top+1.

2 очереди

2.1 Понятие и структура очереди

Очередь — это структура данных, а не стек. Специальная линейная таблица, которая позволяет вставлять операции с данными только на одном конце и удалять операции с данными на другом конце.

Конец операции вставки называется хвостом очереди, а конец операции удаления — началом очереди.

2.2  Реализация очереди

Очереди также могут быть реализованы в виде массивов или связанных списков. Однако из-за того, что очередь должна удалить голову, если вы используете массив, вам нужно перемещать данные один за другим, а временные затраты слишком высоки. Поэтому при реализации очереди целесообразнее использовать связанный список.

Структура каждого узла в очереди выглядит следующим образом:

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

Здесь, поскольку нам всегда нужно знать голову и хвост очереди при работе с очередью, нам нужны два указателя для записи указателя головы и указателя хвоста, а также для записи количества данных в очереди. Таким образом, мы можем создать структуру для хранения этих данных.

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

В этой структуре головной узел связанного списка является главой команды, а хвостовой узел — хвостом команды. 

2.2.1 Инициализация и уничтожение очереди

Инициализация и уничтожение очень похожи на односвязный список, вот непосредственно исходный код. Если вы не уверены, вы можете проверить соответствующую часть этой статьи: [Структура данных] Добавление, удаление, проверка и изменение безголового + одностороннего + некругового связанного списка (исходный код прилагается в конце)

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 Вставка и удаление очередей

Операция вставки и удаления также аналогична вставке и удалению односвязного списка. Разница здесь в том, что в очереди есть две структуры: одна — это структура узлов в очереди, а другая — это структура самой очереди. Поэтому при выполнении операций вставки и удаления необходимо учитывать, не повлияет ли это на структуру самой очереди. Поэтому необходимо добавить некоторые условия оценки.

//插入(相当于尾插)
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 Получить начальные и конечные элементы, а также количество эффективных элементов в очереди

Эти три функции более интуитивно понятны, прямо в коде

//获取队头元素
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 исходный код

3.1 стек

3.1.1 Стек.ч

#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 Стек.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 Очередь

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 Кене.с

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

рекомендация

отblog.csdn.net/fbzhl/article/details/131351376