【从零开始数据结构】栈和队列

94 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

数据结构

第三章 栈

堆栈

具有一定操作约束的线性表

  • 只在一端(栈顶,Top)做 插入、删除
  • 插入数据:入栈(Push)
  • 删除数据:出栈(Pop)
  • 后入先出:Last In First Out(LIFO)

堆栈的顺序存储实现

栈的顺序存储结构通常由一个一维数组和一个记录 栈顶 元素位置的变量组成。

主要操作的实现:

#include <iostream>

using namespace std;

/*
* 暂时没有写扩充容量的功能
* 后期完善时需注意
*/
// 扩充容量的步伐
#define sizestep 10
// 开始的容量大小
#define startsize 100

typedef int ElemType;
struct SeqStack {
	ElemType* data;	// 数据
	int top;		// 用于栈顶指针
	int size;		// 初始容量
};

// 顺序栈初始化,成功返回栈对象指针,失败返回空指针nullptr
void InitSeqStack(SeqStack& s)
{
	s.size = startsize;
	s.data = new ElemType[s.size];
	s.top = -1;

	if (!s.data) {
		cout << "空间不足" << endl;
		exit(-1);
	}
}

// 判断栈是否为空
bool isEmptySeqStack(SeqStack& s)
{
	return (s.top == -1) ? true : false;
}

// 入栈
bool pushSeqStack(SeqStack& s, ElemType e)
{
	if (s.top == s.size - 1) {
		return false;
	}
	s.data[++s.top] = e;
	return true;
}

// 出栈
bool popSeqStack(SeqStack& s, ElemType& e)
{
	if (s.top == -1)	// if (isEmptySeqStack(s))
		return false;
	else {
		e = s.data[s.top--];
		return true;
	}
}

// 取栈顶元素
bool topSeqStack(SeqStack s, ElemType& e)
{
	if (s.top == -1)	
		return false;	// 栈空
	else {
		e = s.data[s.top];
		return true;
	}
}

// 清空栈
void clearSeqStack(SeqStack& s)
{
	s.top = -1;
	delete[] s.data;
	s.data = nullptr;
	// 重新分配堆内存
	s.data = new ElemType[s.size];
}

// 销毁栈
void destroySeqStack(SeqStack& s)
{
	// 归零
	s.size = 0;
	s.top = -1;
	delete[] s.data;
	s.data = nullptr;
}

// 实现两栈共享空间
struct DoubleSeqStack {
	ElemType* data;
	int top1;
	int top2;
};

void initDoubleSeqStack(DoubleSeqStack& s)
{
	s.data = new ElemType[startsize];
	s.top1 = -1;
	s.top2 = startsize;
}

// 入栈,根据flag选择 栈1 还是 栈2
bool pushDoubleSeqStack(DoubleSeqStack& s, ElemType e, int flag)
{
	// 栈满
	if (s.top1 + 1 == s.top2)
		return false;

	if (flag == 1) {
		s.data[++s.top1] = e;
	}
	else if (flag == 2) {
		s.data[--s.top2] = e;
	}
	else {
		cout << "flag有误" << endl;
		return false;
	}
	return true;
}

// 出栈,根据flag选择 栈1 还是 栈2
bool popDoubleSeqStack(DoubleSeqStack& s, ElemType& e, int flag)
{
	if (flag == 1) {
		if (s.top1 == -1)
			return false;
		e = s.data[s.top1--];
	}
	else if (flag == 2) {
		if (s.top2 == startsize)
			return false;
		e = s.data[s.top2++];
	}
	else {
		cout << "flag有误" << endl;
		return false;
	}

	return true;
}

例:用一个数组实现两个堆栈,要求最大地利用数组空间,使数组只要有空间入栈操作就可以成功。

#define startsize 100

typedef int ElemType;
// 实现两栈共享空间
struct DoubleSeqStack {
	ElemType* data;
	int top1;
	int top2;
};

void initDoubleSeqStack(DoubleSeqStack& s)
{
	s.data = new ElemType[startsize];
	s.top1 = -1;
	s.top2 = startsize;
}

// 入栈,根据flag选择 栈1 还是 栈2
bool pushDoubleSeqStack(DoubleSeqStack& s, ElemType e, int flag)
{
	// 栈满
	if (s.top1 + 1 == s.top2)
		return false;

	if (flag == 1) {
		s.data[++s.top1] = e;
	}
	else if (flag == 2) {
		s.data[--s.top2] = e;
	}
	else {
		cout << "flag有误" << endl;
		return false;
	}
	return true;
}

// 出栈,根据flag选择 栈1 还是 栈2
bool popDoubleSeqStack(DoubleSeqStack& s, ElemType& e, int flag)
{
	if (flag == 1) {
		if (s.top1 == -1)
			return false;
		e = s.data[s.top1--];
	}
	else if (flag == 2) {
		if (s.top2 == startsize)
			return false;
		e = s.data[s.top2++];
	}
	else {
		cout << "flag有误" << endl;
		return false;
	}

	return true;
}

堆栈的链式存储实现

栈的链式存储结构实际上就是一个单链表,叫做 链栈。插入和删除操作只能在链栈的栈顶进行。

栈顶指针Top应该在链表的哪一头?

以链表为底层的数据结构时,以链表头为栈顶,便于节点的插入与删除,压栈产生的新节点将一直出现在链表的头部

主要操作的实现:

#include <iostream>

using namespace std;

typedef int ElemType;
// 链栈结点
struct StackNode {
	ElemType value;
	StackNode* next;
};
// 链栈结构
struct LinkStack {
	StackNode* top;
	int length;
};

// 初始化
void initStack(LinkStack& s)
{
	s.top = nullptr;
	s.length = 0;
}

// 判断栈是否为空
bool isEmptyStack(LinkStack s)
{
	return (!s.top) ? true : false;
}

// 入栈
bool pushStack(LinkStack s, ElemType e)
{
	StackNode* temp = new StackNode;
	// 空间申请失败
	if (!temp) {
		return false;
	}

	temp->value = e;
	temp->next = s.top;
	// 将新元素作为栈顶指针
	s.top = temp;
	++s.length;
	return true;
}

// 出栈
bool popStack(LinkStack& s, ElemType& e)
{
	// 空栈
	if (!s.top)
		return false;

	e = s.top->value;
	StackNode* temp = s.top;
	s.top = s.top->next;
	--s.length;
	delete temp;

	return true;
}

// 获取栈顶元素
bool GetTop(LinkStack s, ElemType& e)
{
	if (!s.top)
		return false;
	e = s.top->value;
	return true;
}

例:表达式求值

中缀表达式如何转换为后缀表达式:

从头到尾读取中缀表达式的每个对象,对不同对象按不同的情况处理。

  1. 运算数:直接输出
  2. 左括号:压入堆栈
  3. 右括号:将栈顶的运算符弹出并输出,直到遇到左括号(出栈,不输出)
  4. 运算符:
    • 若优先级大于栈顶运算符时,则把它压栈
    • 若优先级小于栈顶运算符时,将栈顶运算符弹出并输出;再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压栈
  5. 若各对象处理完毕,则把堆栈中存留的运算符一并输出

堆栈的其他引用

  • 函数调用及递归实现
  • 深度优先搜索
  • 回溯算法

第四章 队列

什么是队列?

具有一定操作约束的线性表

  • 插入和删除操作:只能在一端插入,而在另一端删除
  • 数据插入:入队
  • 数据删除:出队
  • 先来先服务
  • 先进先出:FIFO

队列的顺序存储实现

队列的顺序存储结构通常由一个 一维数组 和一个记录队列头元素位置的变量 front以及一个记录队尾元素位置的变量 rear组成。

循环队列

堆栈空和满的判别条件是什么?

解决方案:

  1. 设置一个标志变量flag,当front == rear,且flag = 0时为队列空;当front == rear,且flag = 1时为队列满。
  2. 当队列为空时,条件是front == rear;当队列满时,修改条件,保留一个元素空间front=(rear+1)%MAXSIZE

主要操作的实现:

#include <iostream>

using namespace std;

#define MAXSIZE 100
typedef int ElemType;

struct SeqQueue {
	ElemType* data;
	int front;
	int rear;
};

// 初始化
void initSeqQueue(SeqQueue& Q)
{
	Q.data = new ElemType[MAXSIZE];
	if (!Q.data) {
		exit(-1);
	}
	Q.front = 0;
	Q.rear = 0;
}

// 入队
bool enqueue(SeqQueue& Q, ElemType e)
{
	if ((Q.rear + 1) % MAXSIZE == Q.front) {
		return false;
	}
	Q.data[Q.rear] = e;	// 将e赋值给队尾
	Q.rear = (Q.rear + 1) % MAXSIZE;
	return true;
}

// 出队
bool dequeue(SeqQueue& Q, ElemType& e)
{
	if (Q.front == Q.rear) {
		return false;
	}
	e = Q.data[Q.front];
	Q.front = (Q.front + 1) % MAXSIZE;
	return true;
}

// 求队长
int lengthSeqQueue(SeqQueue Q)
{
	return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

// 取队头元素
bool GetHead(SeqQueue Q, ElemType& e)
{
	if (Q.front == Q.rear) {
		return false;
	}
	else {
		e = Q.data[Q.front];
		return true;
	}
}

队列的链式存储实现

队列的链式存储结构也可以用一个 单链表 实现。插入和删除操作分别在链表的两头进行;队列指针front和rear应该分别指向链表的哪一头?

front 指向链表的头,rear指向链表的尾

注意:下面的队列中有一个头结点

主要操作的实现:

#include <iostream>

using namespace std;

typedef int ElemType;

struct QNode {
	ElemType data;
	QNode* next;
};

struct LinkQueue {
	QNode* front;	// 队头指针
	QNode* rear;	// 队尾指针
};

// 初始化
void initQueue(LinkQueue& Q)
{
	// front和rear指向头结点,头结点不存储元素
	Q.front = Q.rear = new QNode;
	if (!Q.front) {
		exit(-1);
	}
	Q.front->next = nullptr;
}

// 判断队是否为空
bool isEmptyQueue(LinkQueue& Q)
{
	return (!Q.rear->next) ? true : false;
}

// 取队头元素
bool GetHeadQueue(LinkQueue& Q)
{
	if (Q.front == Q.rear) {
		return false;
	}
	else {
		return Q.front->next->data;
	}
}

// 入队
bool enqueue(LinkQueue& Q, ElemType e)
{
	QNode* temp = new QNode;
	if (!temp) {
		return false;
	}
	temp->data = e;
	temp->next = nullptr;
	Q.rear->next = temp;
	Q.rear = temp;
	return true;
}

// 出队
bool dequeue(LinkQueue& Q, ElemType& e)
{
	if (Q.front == Q.rear) {
		return false;
	}
	QNode* temp = Q.front->next;	// 临时指针指向首元结点
	e = temp->data;
	Q.front->next = temp->next;
	if (Q.rear == temp)
		Q.rear = Q.front;
	delete temp;
}

// 销毁队列
void destroyQueue(LinkQueue& Q)
{
	while (Q.front) {
		Q.rear = Q.front->next;
		delete Q.front;
		Q.front = Q.rear;
	}
	Q.rear = Q.front = nullptr;
}

例:多项式的加减法运算实现