【数据结构】栈和队列

一、栈的定义

        堆栈又名栈(stack),它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

        栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构 。

        栈分为顺序栈(通过顺序表实现)与链式栈(通过链表实现),接下来我们来就顺序栈进行说明。


二、顺序栈的实现

2.1 顺序栈的结构定义

#define StackDataType int

typedef struct Stack
{
	StackDataType* data;

	//用于记录栈顶
	//当top初始化为零时,还具有顺序表中size的功能
	int top;

	int capacity;
}Stack;

        我们这里采用动态顺序表的实现方式,使栈的大小更加灵活。

        top变量用于记录栈顶位置,top有两种初始化的方式,初始化为0或初始化为-1。当初始化为0时,top指向的是栈顶的下一个位置,此时它能够充当size来记录栈中的数据个数。而当初始化为-1时,top就真真切切的指向栈顶。

        我们接下来以初始化为0的情况来实现栈。

2.2 栈的初始化

void StackInit(Stack* pST)
{
	assert(pST);

	pST->data = NULL;

	pST->top = pST->capacity = 0;

2.3 压栈

static void CapacityCheck(Stack* pST)
{
	assert(pST);

	if (pST->top == pST->capacity)
	{
		int newcapacity = ((pST->capacity == 0) ? (5) : (pST->capacity * 2));
		StackDataType* tmp = (StackDataType*)realloc(pST->data, sizeof(StackDataType) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc error\n");
			exit(-1);
		}

		pST->data = tmp;
		tmp = NULL;
		pST->capacity = newcapacity;
	}
}
void StackPush(Stack* pST, const StackDataType val)
{
	assert(pST);

	CapacityCheck(pST);

	pST->data[pST->top] = val;
	pST->top++;
}

         同样的,我们将栈容量的检查扩容单独封装,让各个接口的独立性更高、内聚性更强。

        因为栈的特性,栈的压栈与出栈(数据的插入与删除)都是在栈顶进行,我们可以将顺序表的尾部看作栈顶,便于压栈与出栈的操作。

2.4 出栈

void StackPop(Stack* pST)
{
	assert(pST);

	//检查栈内有数据可以出栈
	assert(pST->top > 0);

	pST->top--;
}

        只要将top--,将原本栈顶的数据视为无效数据,就可以达到出栈的效果。

2.5 获取栈顶数据

StackDataType StackTop(Stack* pST)
{
	assert(pST);
	//assert(pST->top > 0);
	assert(!StackEmpty(pST));

	return pST->data[pST->top - 1];
}

        因为我们将top初始化为0,所以top指向的是栈顶的下一个位置,所以top-1才是我们想要的栈顶位置。

2.6 判断栈是否为空

bool StackEmpty(Stack* pST)
{
	assert(pST);

    //if(pST->top == 0)
    //{ 
    //    return true; //栈空 
    //}

    //else
    //{ 
    //    return false; //不空 
    //} 

	//如果top == 0返回真,反之则返回假
	return pST->top == 0;
}

        两种方法都能达到判断栈是否为空的效果,可以根据个人喜好选择。

2.7 检查栈内有多少个数据

int StackSize(Stack* pST)
{
	assert(pST);

	return pST->top;
}

2.8 销毁栈

void StackDestroy(Stack* pST)
{
	assert(pST);

	free(pST->data);
	pST->data = NULL;
	pST->top = pST->capacity = 0;
}

队列

一、队列的定义

        队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

        队列是一种先进先出(First In First Out)的线性表,简称FIFO。

        队列与栈相似,有着顺序队列与链式队列,我们接下来以不带哨兵位的链式队列来学习。

二、链式队列的实现

2.1 链式队列的结构定义

typedef int QueueDataType;

//定义队列的节点
typedef struct QueueNode
{
	QueueDataType data;
	struct QueueNode* next;
}QueueNode;

//定义队列
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

        链式队列的实现需要定义两个结构,一个是队列的节点,另一个是队列本身。链式队列结构需要存贮队列的首节点和尾节点,这样会方便数据的进出。

2.2 队列的初始化

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

	pQue->head = pQue->tail = NULL;
}

2.3 进队列

static QueueNode* QueueNodeCreat(const QueueDataType val)
{
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		printf("malloc err\n");
		exit(-1);
	}

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

	return newnode;
}
void QueuePush(Queue* pQue, const QueueDataType val)
{
	assert(pQue);

	QueueNode* newnode = QueueNodeCreat(val);
	if (pQue->head == NULL)
	{
		pQue->head = pQue->tail = newnode;
	}
	else
	{
		pQue->tail->next = newnode;
		pQue->tail = newnode;
	}
}

         我们以链表的头为队列的头,列表的尾为队尾,在进行数据入队时不要忘记对当队列为空时头插的特殊情况进行特殊处理。

2.4 出队列

void QueuePop(Queue* pQue)
{
	assert(pQue);
	assert(!QueueEmpty(pQue));

	QueueNode* HeadNext = pQue->head->next;
	free(pQue->head);
	pQue->head = HeadNext;

	if (pQue->head == NULL)
	{
		pQue->tail = NULL;
	}
}

        在出队列之前我们一定要判断是否有数据可以出,而且要记住当队列的数据被出完后,一定要将尾指针tail设置为NULL,不然会有野指针的隐患。

2.5 判断队列是否为空

bool QueueEmpty(const Queue* pQue)
{
	assert(pQue);

	return pQue->head == NULL;
}

2.6 获取队首或队尾的数据

QueueDataType QueueFront(const Queue* pQue)
{
	assert(pQue);
	assert(!QueueEmpty(pQue));

	return pQue->head->data;
}
QueueDataType QueueBack(const Queue* pQue)
{
	assert(pQue);
	assert(!QueueEmpty(pQue));

	return pQue->tail->data;
}

2.7 检查队列内的数据个数

size_t QueueSize(const Queue* pQue)
{
	assert(pQue);

	size_t count = 0;
	QueueNode* cur = pQue->head;

	while (cur)
	{
		++count;
		cur = cur->next;
	}

	return count;
}

2.8 销毁队列

void Queuedestroy(Queue* pQue)
{
	assert(pQue);

	QueueNode* cur = pQue->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pQue->head = pQue->tail = NULL;
}

小结及推荐练习题目

        我们可以看到栈和队列的实现相当简单,有了顺序表和链表的基础,完成这两个结构就是小菜一碟。我们在文中完成的是顺序栈和链式队列,你可以试试完成另外两种形式。

        这里还有两道题目来巩固相关知识:

        LeetCode232.用栈实现队列

        LeetCode225.用队列实现栈

        希望我的文章能够对你有所帮助,如有错误还请指正!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值