一、栈

1、栈的概念与结构

栈是一种特殊的线性表,其只允许在固定的一段进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的元素遵循先入后出LIFO(Last In Frist Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫做出战,出数据也在栈顶。

栈与队列_数组实现栈

2、栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构更优一些,因为栈只会在一侧进行数据的插入和删除,我们可以选择在数组的尾上插入和删除,这样的代价比较小,效率也更高一些。

接下来我们开始实现栈。

2.1、定义结构

首先我们需要一个数组,其次需要记录栈顶的位置,以及栈的容量。

typedef int STDataType;
typedef struct Stack
{
	STDataType* data;
	int top;//指向栈顶
	int capacity;//栈的容量
};

2.2、初始化

在初始化时,top有两种初始化形式,第一种是top最开始为0,那么top之中指向的是栈顶元素的后一个位置。

栈为空时,top为0,

栈与队列_链表实现队列_02

然后开始有元素入栈,每次入栈都会++。

栈与队列_链表实现队列_03

top始终指向栈顶元素的后一个位置。

第二种是top最开始为-1,

栈为空时top为-1,

栈与队列_数组实现栈_04

开始有元素入栈,

栈与队列_数组实现栈_05

top始终指向栈顶元素。

代码如下

void STInitial(ST* pst)
{
	assert(pst);
	pst->data = NULL;
	//top有两种初始化形式、
	//1.最开始为0
	pst->top = 0;
	//2.最开始为-1
	//pst->top=-1;
	pst->capacity = 0;
}

2.3、入栈

入栈很简单,top指向的是栈顶元素的下一个位置,我们只需要把入栈的元素放到top指向的位置,再让top++即可。

入栈的一个重点在于当容量不够时,要扩容。

代码如下

void STPush(ST* pst,STDataType x)
{
	assert(pst);
	if (pst->top == pst->capacity)//如果空间不够了,那就扩容
	{
		//如果capacity为0,那就是第一次扩容,开始4个空间
		//如果capacity不为0,那就每次开辟2倍的空间
		int Newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		ST* tmp = (ST*)realloc(pst->data,Newcapacity * sizeof(STDataType));
		assert(tmp);//如果开辟空间失败,程序就报错
		pst->data = tmp;
		pst->capacity = Newcapacity;
	}
	pst->data[pst->top] = x;
	pst->top++;
}

2.4、出栈

在出栈时,栈不能为空,所以我们先写一个检测链表是否为空的函数,如果链表为空返回true,否则返回false。

代码如下

bool STEmpty(ST* pst)
{
	assert(pst);
	return pst->top == 0;//top为0时栈空
}

接下里写出栈函数也是非常的简单,当链表为空时,直接报错,不为空,则让top--即可,没有必要真的把数据删除。

代码如下

void STPop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));//栈不能为空
	pst->top--;
}

2.5、获取栈顶元素

top指向栈顶元素的后一个位置,top的前一个位置就是栈顶元素,注意,获取栈顶元素是栈也不能为空。

代码如下

 STTop(ST* pst)
{
	assert(pst);
	assert(!STEmpty(pst));//栈不能为空
	return pst->data[pst->top - 1];//返回栈顶元素
}

2.6、获取栈中有效元素的个数

有效元素的个数就是top。

代码如下

int STSize(ST* pst)
{
	assert(pst);
	return pst->top;
}

2.7、销毁栈

代码如下 

 STDestroy(ST* pst)
{
	assert(pst);
	free(pst->data);
	pst->data = NULL;
	pst->capacity = 0;
	pst->top = 0;
 }

二、队列

1、队列的概念与结构

队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(Frist In Frist Out)原则。

入队:进行插入操作的一端被称为队尾。

出队:进行删除操作的一端被称为队头。

栈与队列_链表实现队列_06


栈与队列_链表实现队列_07

2、队列的实现

队列的实现需要在一侧进行插入,另一侧进行删除,用数组效率较低,用链表更为合适。

2.1、定义结构

既然要使用链表,那么就要定义节点的结构

typedef int QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueeuNode* next;
}QNode;

同时,为了提高效率,我们还要定义一个结构体,这个结构体包含了指向队头的队头指针、指向队尾的队尾指针,以及记录队列大小的size。

代码如下

typedef struct Queue
{
	QNode* phead;//指向头结点
	QNode* ptail;//指向尾结点
	int size;//记录链表大小
}Queue;

逻辑图如下

栈与队列_数组实现栈_08

2.2、初始化

代码如下

void QueueInitial(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

2.3、判空

如果队列为空,则返回true,不为空返回false,判空的条件很简单,如果size==0,则为空,代码如下

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

2.4、入队

入队都是发生在队尾,入队时,需要考虑队是否为空,

栈与队列_数组实现栈_09

当队列为空时,

栈与队列_数组实现栈_10

直接让队头指针和队尾指针指向新节点即可。

栈与队列_链表实现队列_11

当队列非空时,只需要让最后一个节点的next指向新节点,再让队尾指针指向新节点即可。

栈与队列_链表实现队列_12

代码如下

void QueuePush(Queue* pq,QDataType x)
{
	assert(pq);
	//生成新节点
	QNode* newnode = (QNode*)malloc(sizeof(Queue));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	//开始入队
	if (QueueEmpty(pq))//如果队为空
	{
		assert(pq->phead==NULL);
		pq->phead = pq->ptail = newnode;
	}
	else//如果队不为空
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;//每次入队
}

2.5、出队

出队是在队头发生的,出队时链表不能为空,

当链表有多个节点时,

栈与队列_数组实现栈_13

定义一个指针headNext指向头结点的下一个节点,

栈与队列_链表实现队列_14

把头结点释放掉,队头指针指向headNext指向的节点。

上面的思路对于队尾指针是没有任何处理室,但是当队列只有一个节点时,需要对队尾指针进行操作,

栈与队列_链表实现队列_15

栈与队列_链表实现队列_16

直接释放掉仅有的一个节点,然后把队头指针和队尾指针置空即可。

代码如下

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列不能为空
	if (pq->size==1)//队列只有1个节点
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else//队列有多个节点
	{
		Queue* headNext = pq->phead->next;//记录头结点的下一个位置
		free(pq->phead);
		pq->phead = headNext;
	}
	pq->size--;//每次出队size--
}

2.6、获取队头元素

在获取元素时,队列不能为空。

获取队头元素,只需要使用队头指针即可,

代码如下,

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

2.7、获取队尾元素

获取队尾元素,只需要使用队尾指针即可,

代码如下,

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}

2.8、获取队列有效元素个数

队列有效元素个数,就是size。

代码如下,

QDataType QueueSize(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->size;
}

2.9、销毁

队列的销毁其实就是链表的销毁,遍历链表一个一个销毁就好了,

代码如下

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	QNode* curNext = NULL;
	while (cur)
	{
		curNext = cur->next;
		free(cur);
		cur = curNext;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}




结束。。。。