栈和队列 | 数据结构初阶

1.

1.1栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO Last In First Out )的原则。
压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶
出栈:栈的删除操作叫做出栈。 出数据也在栈顶

 

栈的这种结构是后进先出,也可以说是先进后出,有一个形象的比喻,就是给弹夹装子弹,先装的子弹被压到最底下,也最后被发射出去,还有就是浏览器的回退功能,仔细想想看是不是也是用栈实现的,最近浏览的网页弹出来。

1.2 栈的实现 

接下来我们来实现一下栈,栈的基础功能有几个StackPush,StackPop,StackDestroy,StackTop,StackEmpty,还有一些,不是说代码中有这样一个栈的数据结构,而是我们根据概念来创建一种数据结构实现这样的功能,栈的功能不难,可以用顺序表和链表来实现,这里我们选用顺序表来实现,顺序表的实现前面我们讲的差不多,这里需要注意的几个点是,如下:

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;

}ST;

栈里面的Topi相当与顺序表中的size,然后其他的就和顺序表一样了,我们需要的是

 入数据只能从栈顶入,出数据也只能从栈顶出,

//初始化
void StackInit(ST* pst);
//销毁
void StackDestroy(ST* pst);
//插入数据
void StackPush(ST* pst,STDataType x);
//删除数据
void StackPop(ST* pst);
//取栈顶数据
STDataType StackTop(ST* pst);
//判断是否为空
bool StackEmpty(ST* pst);
//元素个数
int StackSize(ST* pst);

 以上是栈的相关接口函数,接口函数的实现难度并不难,接下来是它的接口函数的代码,更重要的是理解它的意义,这里同样也是对顺序表的一个回顾和更深的运用。

1.2.1 初始化和销毁

顺序表的初始话和销毁比较简单,直接free(pst->a)就行,

代码如下:

void StackInit(ST* pst)
{
	assert(pst);

	pst->a = NULL;
	pst->capacity = 0;
	pst->top = 0;
}

void StackDestroy(ST* pst)
{
	free(pst->a);
	pst->a = NULL;

	pst->capacity = 0;
	pst->top = 0;
}

1.2.2 插入和删除

插入和顺序表一样,要检查数组空间是否足够,但是这里不需要把它额外封装成一个函数,因为这里只有插入函数需要使用,删除函数需要断言一下数组中是否有元素,代码如下:

void StackPush(ST* pst, STDataType x)
{
	assert(pst);

	if (pst->capacity == pst->top)
	{
		int newCapacity = (pst->capacity == 0 ? 4 : pst->capacity * 2);
		STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType) * newCapacity);//realloc
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		pst->a = tmp;
		pst->capacity = newCapacity;
	}
	
	//pst->a = tmp;这句话放到这里是会报:未声明的标识符
	
	pst->a[pst->top] = x;
	pst->top++;
}


void StackPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);

	pst->top--;
}

1.2.3  元素个数

因为Top可以表示数组最后一个元素的下一个位置,也指代数组中的元素个数,所以实现起来很简单,代码如下:

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

	return pst->top;
}

1.2.4  判空

因为Top可以表示数据个数,所以也能用来判空,代码如下:

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

	return pst->top == 0;
}

1.2.5 取栈顶数据

因为有Top,所以取栈顶数据也变得简单起来了,代码如下:

STDataType StackTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);

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

以上就是栈的代码实现,所以栈没有很复杂很难以理解的代码,多学习,多敲代码就能学会

下面来看一下测试代码:

先初始化,然后往里面插入数据,打印一下里面的size,然后是循环打印直到链表里面是空的,最后再销毁。

2. 队列

2.1 队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为 队尾
出队列:进行删除操作的一端称为 队头

 队列和栈相反,是先进先出,

2.2 栈的实现

队列分对头和队尾,队列的实现有些难度,这里我们用单链表来实现队列,关键是理解两个结构体之间的关系,以及为什么要把定义两个结构体,先看代码:

typedef int QDataType;

typedef struct QueueNode
{
	QDataType val;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	size_t size;
}Queue;

为什么要定义两个结构体?如果没有Queue这个结构体,取对头的数据容易,但是取队尾的数据就需要遍历一遍,比较麻烦且效率不高。

这两个结构体之间的关系?Queue里面的结构体可以指向QueueNode中的头节点和尾节点,size表示的是节点的个数,也就是数据的个数,通过Queue来控制这个链表,既可以得到队尾的数据,也不用遍历一遍,节省了时间,而且不需要使用二级指针就能够更改头尾节点。

队列的相关函数和栈差不多:

//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//插入数据
void QueuePush(Queue* pq, QDataType x);
//删除数据
void QueuePop(Queue* pq);
//取对头数据
QDataType QueueFront(Queue* pq);
//取对尾数据
QDataType QueueBack(Queue* pq);
//元素个数
size_t QueueSize(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);

2.2.1 初始化和销毁

接下来实现一下队列,和栈一样,队列也有初始化和销毁函数,但会由于对列是使用单链表实现的,所以销毁时需要一个一个节点释放空间,代码如下:

void QueueInit(Queue* pq)
{
	assert(pq);

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


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

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

2.2.2 插入和删除

队列的插入和删除函数需要插入函数和头插差不多,就是插入完后需要改变ptail的位置,需要注意的时,删除函数可能会把头节点的一直删除,删到了ptail的位置还删除,这是ptail就是野指针了,

所以当ptail==NULL的时候,我们需要单独把ptail赋值为NULL,还有需要注意的是,插入完数据需要将size++,删除数据需要将size--,代码如下:

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;

	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}


void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	QNode* del = pq->phead;
	pq->phead = pq->phead->next;
	free(del);
	del = NULL;

	if (pq->phead == NULL)
	{
		pq->ptail = NULL;
	}

	pq->size--;
}

2.2.3 取对头队尾数据

取对头队尾数据就比较简单了,因为有Queue这个结构体,所以可以直接取到,别忘了断言一下,队列是否为空就行。代码如下:

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	return pq->phead->val;
}


QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);

	return pq->ptail->val;

}

2.2.4 元素个数

因为Queue中有一个size的变量用来记录元素个数,所以很简单

size_t QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

2.2.5 判空

判空是用判断phead==NULL来判断的,

bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->phead == NULL;//pq->size == 0;
}

下面是队列的测试结果:

3. 代码

3.1 Stack.h

#pragma once

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

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;

}ST;


//初始化
void StackInit(ST* pst);
//销毁
void StackDestroy(ST* pst);
//插入数据
void StackPush(ST* pst,STDataType x);
//删除数据
void StackPop(ST* pst);
//取栈顶数据
STDataType StackTop(ST* pst);
//判断是否为空
bool StackEmpty(ST* pst);
//元素个数
int StackSize(ST* pst);

3.2 Queue.h

#define _CRT_SECURE_NO_WARNINGS 1

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


typedef int QDataType;

typedef struct QueueNode
{
	QDataType val;
	struct QueueNode* next;
}QNode;


typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	size_t size;
}Queue;

//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//插入数据
void QueuePush(Queue* pq, QDataType x);
//删除数据
void QueuePop(Queue* pq);
//取对头数据
QDataType QueueFront(Queue* pq);
//取对尾数据
QDataType QueueBack(Queue* pq);
//元素个数
size_t QueueSize(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);

3.3 Stack.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Stack.h"

void StackInit(ST* pst)
{
	assert(pst);

	pst->a = NULL;
	pst->capacity = 0;
	pst->top = 0;
}

void StackDestroy(ST* pst)
{
	free(pst->a);
	pst->a = NULL;

	pst->capacity = 0;
	pst->top = 0;
}


void StackPush(ST* pst, STDataType x)
{
	assert(pst);

	if (pst->capacity == pst->top)
	{
		int newCapacity = (pst->capacity == 0 ? 4 : pst->capacity * 2);
		STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType) * newCapacity);//realloc
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		pst->a = tmp;
		pst->capacity = newCapacity;
	}
	
	//pst->a = tmp;这句话放到这里是会报:未声明的标识符
	
	pst->a[pst->top] = x;
	pst->top++;
}


void StackPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);

	pst->top--;
}


STDataType StackTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);

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

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

	return pst->top == 0;
}

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

	return pst->top;
}

3.4 Queue.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Queue.h"

void QueueInit(Queue* pq)
{
	assert(pq);

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


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

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


void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;

	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}


void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	QNode* del = pq->phead;
	pq->phead = pq->phead->next;
	free(del);
	del = NULL;

	if (pq->phead == NULL)
	{
		pq->ptail = NULL;
	}

	pq->size--;
}


QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	return pq->phead->val;
}


QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);

	return pq->ptail->val;

}


size_t QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}


bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->phead == NULL;//pq->size == 0;
}

3.5 Test.c

 

#define _CRT_SECURE_NO_WARNINGS 1

#include "Queue.h"
#include "Stack.h"

//Queue
int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	QueuePush(&q, 6);
	QueuePush(&q, 7);
	
	printf("Size == %zd\n",QueueSize(&q));
	QueuePop(&q);
	printf("Back == %d\n", QueueBack(&q));

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
	QueueDestroy(&q);
	return 0;
}



//Stack
int main()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	StackPush(&st, 6);
	StackPush(&st, 7);

	printf("Size == %d\n", StackSize(&st));

	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}

	StackDestroy(&st);
	return 0;
}

4. 写在最后

最近在学数据结构,时间比上次利用的多了,也少了些许恐惧,不过还是会胡思乱想,路还很长,还有很多东西要学,我们都要加油,要努力,最重要的是坚持下去,路漫漫其修远兮,吾将上下而求索,要无惧,要坚持,别害怕学Linux,别害怕学Linux,加油!加油!加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值