数据结构笔记三_栈(C++)

一、栈的逻辑结构

1. 栈的概念

后进先出或先进后出的线性结构,最晚到达栈的节点最先被删除。
以取放乒乓球为例:
请添加图片描述

2. 相关概念

请添加图片描述

  • 栈底(bottom): 结构的首部(节点最早到达的部分)
  • 栈顶(top): 结构的尾部(节点最晚到达的地方)
  • 出栈(Pop): 节点从栈顶删除
  • 进栈(Push): 节点在栈顶位置插入
  • 空栈: 栈中节点个数为0的状态

3. 栈的运算

  • create():创建一个空的栈
  • isEmpty():判断栈是否为空。若栈为空,返回ture,否则返回false
  • top():读栈顶元素,返回栈顶元素值
  • push(x):进栈,将x插入使其成为栈顶元素
  • pop():出栈,删除栈顶元素并返回栈顶元素值

由上述待实现的运算,可以构造出栈的抽象类

template<class elemType>
class stack {
public:
	virtual bool isEmpty()const = 0;
	virtual elemType Top()const = 0;
	virtual void push(const elemType& x) = 0;
	virtual elemType pop() = 0;
	virtual ~stack() {};
};

二、 栈的物理结构

1.栈的顺序结构

  • 用连续的空间存储栈中的节点,即数组

存储映像图如下:
请添加图片描述
顺序栈类定义如下:

template<class elemType>
class seqStack :public stack<elemType> {
private:
	elemType* elem;//数组名
	int top;//栈顶
	int maxSize;//最大尺寸
	void doubleSpace();
public:
	seqStack(int initSize = 10);
	~seqStack();

	bool isEmpty()const;
	elemType Top()const;
	void push(const elemType& x);
	elemType pop();
};

2. 栈的链式结构

  • 用单链表存储元素,栈的操作都是在栈顶位置进行的,没有其他位置情况,故不需要头结点。
  • 只需要考虑栈顶元素的插入删除

存储映像图如下:
请添加图片描述
链式栈类定义如下:

template<class elemType>
class linkStack {
private:
	struct node {
		elemType data;
		node* next;
		node(const elemType& x, node* N = NULL) {
			data = x; next = N;
		}
		node():next(NULL){}
	};
	node* Top;
public:
	linkStack();
	~linkStack();

	bool isEmpty()const;
	elemType top()const;//一定要注意top T的大小写问题
	void push(const elemType& x);
	elemType pop();
};

三、栈类的实现

1. 栈的顺序结构实现

实现代码
  • 构造类、析构类
//构造
template<class elemType>
seqStack<elemType>::seqStack(int initSize) {
	elem = new elemType[initSize];
	if (!elem) throw illegalSize();//未能成功生成数组
	maxSize = initSize;
	top = -1;//栈顶的下标,若有一个元素,则top=0
}
template<class elemType>
seqStack<elemType>::~seqStack() {
	delete[]elem;
}
  • 属性类
//判断是否为空
template<class elemType>
bool seqStack<elemType>::isEmpty() const{
	return(top == -1);//若top=-1,则说明数组内无元素
}
//读取栈顶元素
template<class elemType>
elemType seqStack<elemType>::Top()const {
	if (top == -1)throw outOfBound;
	return(elem[top]);
}
  • 操纵类
//使数组空间加倍
template<class elemType>
void seqStack<elemType>::doubleSpace() {
	elemType* tmp = elem;
	elem = new elemType[2 * maxSize];
	for (int i = 0; i < maxSize; ++i) {
		elem[i]=tmp[i];
	}
	maxSize *= 2;
	delete[]tmp;
}
//出栈
template<class elemType>
elemType seqStack<elemType>::pop() {
	if (top == -1) throw outOfBound();
	return elem[top--];
}
//进栈
template<class elemType>
void seqStack<elemType>::push(const elemType& x) {
	if (top == maxSize - 1)doubleSpace();
	elem[++top] = x;
}
性能分析
  • 除了进栈操作外,所有运算的时间复杂度均为 O ( 1 ) O(1) O(1)
  • 进栈最坏情况下时间复杂度为 O ( N ) O(N) O(N)
    注:最坏情况在n次进栈中最多出现一次。如果按均摊分析法来说,插入运算还是常量级别 O ( 1 ) O(1) O(1)的时间复杂度。

2.栈的链式结构实现

实现代码
  • 1.构造、析构类
//构造函数
template<class elemType>
linkStack<elemType>::linkStack() {
	Top = NULL;
}

//析构函数
template<class elemType>
linkStack<elemType>::~linkStack() {
	//释放每个节点
	node* tmp;
	while (Top != NULL) {
		//兄弟协同法
		tmp = Top;
		Top = Top->next;
		delete tmp;
	}
}
  • 属性类
//判断是否为空
template<class elemType>
bool linkStack<elemType>::isEmpty() const{
	return Top == NULL;
}
//返回栈顶的值
template<class elemType>
elemType linkStack<elemType>::top() const {
	if (Top == NULL) throw outOfBound();
	return Top->data;
}
  • 操纵类
template<class elemType>
elemType linkStack<elemType>::pop() {
	if (Top == NULL)throw outOfBound();
	node* tmp = Top;
	elemType x = Top->data;
	Top = Top->next;
	delete tmp;
	return x;
}

template<class elemType>
void linkStack<elemType>::push(const elemType& x) {
	node*tmp =new node(x, Top);
	Top = tmp;
}
性能分析
  • 所有操作都是对栈顶的操作,和栈中元素个数无关
  • 所有运算时间复杂度为 O ( 1 ) O(1) O(1)

四、栈的应用

1. 栈类的测试

代码总结
  • seqstack.h
#include<iostream>
using namespace std;

class illegalSize{};
class outOfBound{};

template<class elemType>
class stack {
public:
	virtual bool isEmpty()const = 0;
	virtual elemType Top()const = 0;
	virtual void push(const elemType& x) = 0;
	virtual elemType pop() = 0;
	virtual ~stack() {};
};

template<class elemType>
class seqStack :public stack<elemType> {
private:
	elemType* elem;//数组名
	int top;//栈顶
	int maxSize;//最大尺寸
	void doubleSpace();
public:
	seqStack(int initSize = 10);
	~seqStack();

	bool isEmpty()const;
	elemType Top()const;//注意T大写,否则会与数据成员top产生重定义
	void push(const elemType& x);
	elemType pop();
};

template<class elemType>
seqStack<elemType>::seqStack(int initSize) {
	elem = new elemType[initSize];
	if (!elem) throw illegalSize();//未能成功生成数组
	maxSize = initSize;
	top = -1;//栈顶的下标,若有一个元素,则top=0
}

template<class elemType>
seqStack<elemType>::~seqStack() {
	delete[]elem;
}

template<class elemType>
bool seqStack<elemType>::isEmpty() const{
	return(top == -1);//若top=-1,则说明数组内无元素
}

template<class elemType>
elemType seqStack<elemType>::Top() const {
	if (top == -1)throw outOfBound();
	return(elem[top]);
}

template<class elemType>
void seqStack<elemType>::doubleSpace() {
	elemType* tmp = elem;
	elem = new elemType[2 * maxSize];
	for (int i = 0; i < maxSize; ++i) {
		elem[i]=tmp[i];
	}
	maxSize *= 2;
	delete[]tmp;
}

template<class elemType>
elemType seqStack<elemType>::pop() {
	if (top == -1) throw outOfBound();
	return elem[top--];
}

template<class elemType>
void seqStack<elemType>::push(const elemType& x) {
	if (top == maxSize - 1)  doubleSpace();
	elem[++top] = x;
}
  • linkstack.h
# include<iostream>

using namespace std;

class outOfBound {};

template<class elemType>
class stack {
public:
	virtual bool isEmpty()const = 0;
	virtual elemType Top()const = 0;
	virtual void push(const elemType& x) = 0;
	virtual elemType pop() = 0;
	virtual ~stack() {};
};

template<class elemType>
class linkStack {
private:
	struct node {
		elemType data;
		node* next;
		node(const elemType& x, node* N = NULL) {
			data = x; next = N;
		}
		node():next(NULL){}
	};
	node* Top;
public:
	linkStack();
	~linkStack();

	bool isEmpty()const;
	elemType top()const;//一定要注意top T的大小写问题
	void push(const elemType& x);
	elemType pop();
};

template<class elemType>
linkStack<elemType>::linkStack() {
	Top = NULL;
}

template<class elemType>
linkStack<elemType>::~linkStack() {
	//释放每个节点
	node* tmp;
	while (Top != NULL) {
		//兄弟协同法
		tmp = Top;
		Top = Top->next;
		delete tmp;
	}
}

template<class elemType>
bool linkStack<elemType>::isEmpty() const{
	return Top == NULL;
}

template<class elemType>
elemType linkStack<elemType>::top() const {
	if (Top == NULL) throw outOfBound();
	return Top->data;
}

template<class elemType>
elemType linkStack<elemType>::pop() {
	if (Top == NULL)throw outOfBound();
	node* tmp = Top;
	elemType x = Top->data;
	Top = Top->next;
	delete tmp;
	return x;
}

template<class elemType>
void linkStack<elemType>::push(const elemType& x) {
	node*tmp =new node(x, Top);
	Top = tmp;
}
  • seqstack 测试main函数
//文件名:seqstack.cpp
#include<iostream>
#include"seqstack.h"

using namespace std;

int main() {
	seqStack<int> s(10);
	for (int i = 14; i >= 0; --i) s.push(i);
	while (!s.isEmpty()) {
		cout << s.Top() << " ";
		s.pop();
	}
	cout << endl;
	return 0;
}
  • linkstack 测试main函数
#include<iostream>
#include"linkstack.h"

using namespace std;

int main() {
	linkStack<int> s;
	for (int i = 14; i >= 0; --i) s.push(i);
	while (!s.isEmpty()) {
		cout << s.top() << " ";
		s.pop();
	}
	cout << endl;
	return 0;
}
代码运行结果

请添加图片描述
在进栈时为倒序,出栈是输出为正序。

2. 递归函数的非递归实现

递归程序的本质是函数调用,而函数调用会花费额外的空间和时间。在系统内部,函数调用是用栈来实现,如果程序员可以自己控制这个栈,就可以消除递归调用。
例如: n ! n! n!的计算

//递归实现
int f1(int n) {
	if (n < 0)return 0;
	if (n == 0 || n == 1)return 1;
	return n * f1(n - 1);
}
//利用栈来实现
int f2(int n)
{
	seqStack<int>s;
	int m, sum;

	sum = 1;
	while (n != 0){
	  s.push(n);
	  n--; 
	}

	while (!s.isEmpty) {
		m = s.pop();
		sum = sum * m;
	}
	return sum;
}

3. 符号平衡检查

见开符号进栈,见闭括号出栈,比较开闭括号是否匹配,结束时判断栈是否为空。
请添加图片描述
代码如下:

#include <iostream>
#include "linkstack.h"
using namespace std;

bool parenMatch(const char* str) {
	linkStack<char> s;
	while (*str != '\0') {
		switch (*str)
		{
		case'(':
			s.push(*str); break;
		case')':
			if (s.isEmpty()) {
				cout << "句中缺少开括号" << endl;
				return false;
			}
			s.pop();
			break;
		}
		str++;
	}
	if (!s.isEmpty()) {//式子读入完毕,发现栈中还有多余的开括号
		cout << "句中缺少闭括号" << endl;
		return false;
		}
		return true;
	}

//测试程序
int main() {
	char* str;
	char str1[20];
	cout << "请输入字符串:" << endl;
	cin.get(str1, 20);
	str = str1;
	bool flag = parenMatch(str);
	if (flag) cout << "没有问题" << endl;
	else cout << "有问题" << endl;
	return 0;
}

运行结果(要注意输入的括号为英文字符):
请添加图片描述

4. 表达式计算

对于一个表达式 a + b a+b a+b:

  • 前缀式(波兰式): + a b +ab +ab
  • 中缀式 : a + b a+b a+b
  • 后缀式(逆波兰式): a b + ab+ ab+

例如计算 3 ∗ ( 5 + 2 ) 3*(5+2) 3(5+2):

  • 前缀式: ∗ 3 + 5 ‾   2 ‾ *3+\underline{5} \ \underline{2} 3+5 2
    当遇到“操作符 数字 数字 ”的组合时则进行运算
    需要进行模式判断多次扫描
  • 中缀式: 3 ∗ ( 5 + 2 ) 3*(5+2) 3(5+2)
  • 后缀式: 3   5 ‾   2 ‾ + ∗ 3\ \underline5 \ \underline2 +* 3 5 2+
    当遇到“数字 数字 操作符”的组合时则进行运算
    不需要模式判断,且只需要一次扫描
    尤其注意在进行计算时,运算符的运算顺序并没有发生改变

计算后缀式的思路:

  • 初始化一个栈
  • 依次读入后缀式的操作数和运算符
  • 若遇到的是操作数,则将其进栈
  • 若遇到的是运算符,则将栈顶的两个元素出栈,并进行运算,得到的结果进栈
  • 回到读入操作,继续进行
  • 当后缀式读完,栈中只剩一个操作数,弹出该操作数即为表达式的值

来举一道OJ上的题:
如:3(5–2)+7对应的后缀表达式为:3.5.2.-7.+@。’@’为表达式的结束符号。‘.’为操作数的结束符号。
注:仅包含-、+、
、/运算,其中/是整除。操作数均是int范围内正整数且计算过程中不会超过int范围 。

样例输入
3.5.2.-*7.+@
样例输出
16

代码清单:

#include <iostream>
#include "linkstack.h"
using namespace std;

int calcPost(char* sufStr) {
	int op1, op2, op;//op储存运算结果
	int tmp=0, i;
	linkStack<int>s;

	i = 0;
	while (sufStr[i] != '@') {

		if (sufStr[i] >= '0' && sufStr[i] <= '9') {
		//考虑位数问题进行对运算数的处理
			tmp = sufStr[i] - '0';
			i = i + 1;
			while (sufStr[i] != '.') {
				tmp = tmp * 10 + sufStr[i] - '0';
				i++;
			}
			s.push(tmp);
		}
		else {
			//弹出运算数
			op1 = s.top();
			s.pop();
			op2 = s.top();
			s.pop();
			switch (sufStr[i])
			{
			case'+':
				op = op1 + op2;
				break;
			case'-':
				op = op2 - op1;
				break;
			case'*':
				op = op2 * op1;
				break;
			case'/':
				op = op2 / op1;
				break;
			}
			s.push(op);
		}
		i++;
	}
	op = s.pop();//最终结果
	return op;
}

int main() {
	char* str;
	char str1[10000];
	cin.getline(str1, 10000);
	str = str1;
	int result = calcPost(str);
	cout << result << endl;
	return 0;
}

5. 中缀式转后缀式

在计算过程中如何实现将中缀式转化为后缀式呢,我们来寻找一下中缀式和后缀式之间的关系:

中缀式: 5 ∗ ( 7 − 2 ∗ 3 ) + 8 / 2 5*(7-2*3)+8/2 5(723)+8/2
后缀式: 5 ‾   7 ‾   2 ‾   3 ‾ ∗ − ∗   8 ‾   2 ‾ / + \underline{5} \ \underline7 \ \underline2 \ \underline3*-*\ \underline8 \ \underline2/+ 5 7 2 3 8 2/+

总结:操作数顺序不变,操作符和优先级、括号有关,先计算的先出来

思路分析:
(呜呜┭┮﹏┭┮,码字已经码不清了)
请添加图片描述
总结:

  • 栈存放操作符,在栈底压入一个‘#“,其运算优先级最低
  • 若读到数字则直接输出
  • 若见到左括号则直接进栈,右括号找左括号
  • 其余运算符看优先级决定是否进栈,若优先级比栈顶元素低,则进栈,反之则压栈。
  • 最终读到’\0’则弹栈直至栈空

题目: 将一个中缀式转化为上面4的后缀式形式
实现代码如下:

#include<iostream>
#include"linkstack.h"
using namespace std;

void inToSufForm(char* inStr, char* sufStr) {
	linkStack<char>s;
	s.push('#');//#压栈
	char topCh;
	int i = 0,j = 0;

	while (inStr[i] != '\0') {
		if (inStr[i] >= '0' && inStr[i] <= '9') {
			while(inStr[i]!='.')
				sufStr[j++] = inStr[i++];
			sufStr[j++] = '.';
		}
		else{
			switch (inStr[i])
			{
			case '(':
				s.push(inStr[i]); break;
			case ')'://弹栈,弹出元素进入后缀式,直到弹出一个左括号
				topCh = s.pop();
				while (topCh != '(') {
					sufStr[j++] = topCh;
					topCh = s.pop();
				}
				break;
			case '*':
			case '/':
				topCh = s.top();
				while (topCh == '*' || topCh == '/') {
					s.pop();
					sufStr[j++] = topCh;
					topCh = s.top();
				}
				s.push(inStr[i]);
				break;
			case '+':
			case '-':
				topCh = s.top();
				while (topCh != '(' && topCh != '#') {
					s.pop();
					sufStr[j++] = topCh;
					topCh = s.top();
				}
				s.push(inStr[i]);
				break;
			}//switch
			i++;
		}//else
	}//while
	//将栈中还没弹出的操作符弹出
	topCh = s.top();
	while (topCh != '#') {
		sufStr[j++] = topCh;
		s.pop();
		topCh = s.top();
	}
	sufStr[j] = '@';
	sufStr[j+1] = '\0';
}

int main() {
	char* str_1,*str_2;
	char str1[100];
	char str2[100];
	cin.getline(str1, 100);

	str_1 = str1;
	str_2 = str2;
	inToSufForm(str_1, str_2);
	int i = 0;
	while (str_2[i]!='\0') {
		cout << char(str_2[i++]);
	}
	return 0;
}

运行结果为:
在这里插入图片描述
将4、5两个part结合,计算一个中缀式的值:
main函数调用如下:

int main() {
	char* str_1,*str_2;
	char str1[100];
	char str2[100];
	cout << "请输入中缀表达式:" << endl;
	cin.getline(str1, 100);

	str_1 = str1;
	str_2 = str2;
	inToSufForm(str_1, str_2);
	int i = 0;
	cout << "后缀表达式形式为:" << endl;
	while (str_2[i]!='\0') {
		cout << char(str_2[i++]);
	}
	cout << endl;
	cout << "计算结果为:" << endl;
	cout << calcPost(str_2) << endl;
	return 0;
}

输出结果为:
在这里插入图片描述

五、总结

终于算是暂时完结了,貌似也是第一次敲这么长的文章。
栈在现实生活中可能并不是应用很广(可能没队列常见),但是在计算机系统中却具有独特的优势和广泛的应用。
在敲代码的过程中也出现了不少的bug,但总体上栈的操作还是比较容易理解的。在应用上,代码的可更改空间也留了不少,改个计算结束标志啊等等,应该还是比较容易滴。
看看接下来的树能不能建起来呢(些许担心…)

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
你好!关于学习数据结构的C语言笔记,我可以给你一些基本的指导和概念。数据结构是计算机科学中非常重要的一门课程,它涉及存储和组织数据的方法。C语言是一种常用的编程语言,很适合用于实现各种数据结构。 下面是一些数据结构的基本概念,你可以在学习笔记中包含它们: 1. 数组(Array):一种线性数据结构,可以存储相同类型的元素。在C语言中,数组是通过索引访问的。 2. 链表(Linked List):也是一种线性数据结构,但不需要连续的内存空间。链表由节点组成,每个节点包含数据和指向下一个节点的指针。 3. (Stack):一种后进先出(LIFO)的数据结构,类似于装满物品的箱子。在C语言中,可以使用数组或链表来实现。 4. 队列(Queue):一种先进先出(FIFO)的数据结构,类似于排队等候的队伍。同样可以使用数组或链表来实现队列。 5. 树(Tree):一种非线性数据结构,由节点和边组成。每个节点可以有多个子节点。二叉树是一种特殊的树结构,每个节点最多有两个子节点。 6. 图(Graph):另一种非线性数据结构,由节点和边组成。图可以用来表示各种实际问题,如社交网络和地图。 这只是数据结构中的一些基本概念,还有其他更高级的数据结构,如堆、哈希表和二叉搜索树等。在学习笔记中,你可以介绍每个数据结构的定义、操作以及适合使用它们的场景。 希望这些信息对你有所帮助!如果你有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值