栈(从数据结构的三要素出发)

逻辑结构

栈是只允许在一端进行插入或删除操作的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。

栈的数学性质:当 n n n个不同的元素进栈时,出栈元素的不同排列个数为 1 n + 1 C 2 n n \frac{1}{n+1}C_{2n}^n n+11C2nn(卡特兰公式)。

在这里插入图片描述


物理结构

顺序栈

利用静态数组实现,并需要记录栈顶指针,且栈的大小不可改变

typedef struct {
	int data[Maxsize];		// 定义栈中元素
	int top;				// 定义栈顶指针
} SqStack;

链栈

利用链表实现,只需要记录头指针,利用链表的头插法实现进栈操作,解决了顺序栈中栈的大小不可扩充的问题。
在这里插入图片描述

typedef struct LinkNode {
	int data;					// 数据域
	struct LinkNode *next;		// 指针域
} LinkNode, *LiStack;

共享栈

利用栈底位置的相对不变性,可以让两个顺序栈共享一个一维数组,将两个栈的栈底分别设置在共享空间的两端,两个栈的栈顶向共享空间的中间延伸,解决了顺序栈空间利用率不足的问题。

在这里插入图片描述

typedef struct {
	int data[Maxsize];		// 数据域
	int top1, top 2;		// 两个栈顶指针
} SStack;

数据的操作

顺序栈的基本操作

初始化

void InitStack(SqStack &s)
{	
	s.top = -1;
}

判断栈为空

bool isEmpty(SqStack s)
{	
	if (s.top == -1) return true;
	return false;
} 

判断栈为满

bool isFull(SqStack s)
{	
	if (s.top == Maxsize - 1) return true;
	return false;
}

入栈

bool Push(SqStack &s, int x)
{	
	if (!isFull(s)) 
	{
		s.data[ ++ s.top] = x;
		return true;
	}
	return false;
}

出栈

bool Pop(SqStack &s, int &x)
{	
	if (!isEmpty(s)) 
	{
		x = s.data[s.top -- ];
		return true;
	}
	return false;
}

读取栈顶元素

bool GetTop(SqStack s, int &x)
{	
	if (!isEmpty(s)) 
	{
		x = s.data[s.top];
		return true;
	}
	return false;
}

链栈的基本操作

初始化

void InitStack(LiStack s)
{	
	s->next = NULL;
}

判断栈为空

bool isEmpty(LiStack s)
{	
	if (s->next == NULL) return true;
	return false;
} 

入栈

bool Push(LiStack s, int x)
{	
	LinkNode *t = (LinkNode *) malloc (sizeof(LinkNode));
	t->next = s->next;
	s->next = t;
	return true;
}

出栈

bool Pop(SqStack s, int &x)
{	
	if (!isEmpty(s)) 
	{
		LinkNode *t = s->next;
		x = t->data;
		s->next = t->next;
		free(t);
		return true;
	}
	return false;
}

读取栈顶元素

bool GetTop(SqStack s, int &x)
{	
	if (!isEmpty(s)) 
	{
		x = s->next->data;
		return true;
	}
	return false;
}

共享栈的基本操作

初始化

void InitStack(SStack &s)
{	
	s.top1 = -1;
	s.top2 = Maxsize;
}

判断栈为空(true:判断栈1,false:判断栈2)

bool isEmpty(SStack s, bool flag)
{	
	if (s.top1 == -1 && flag) return true;
	else if (s.top2 == Maxsize && !flag) return true;
	return false;
} 

判断栈为满

bool isFull(SStack s)
{	
	if (s.top1 + 1 == s.top2) return true;
	return false;
}

入栈(true:入栈1,false:入栈2)

bool Push(SStack &s, int x, bool flag)
{	
	if (!isFull(s)) 
	{
		if (flag) s.data[ ++ top1] = x;
		else s.data[ -- top2] = x;
		return true;
	}
	return false;
}

出栈(true:出栈1,false:出栈2)

bool Pop(SStack &s, int &x, bool flag)
{	
	if (!isEmpty(s, flag)) 
	{
		if (flag) x = s.data[top -- ];
		else x = s.data[top ++ ];
		return true;
	}
	return false;
}

读取栈顶元素(true:读栈1,false:读栈2)

bool GetTop(SStack s, int &x)
{	
	if (!isEmpty(s, flag)) 
	{
		if (flag) x = s.data[top];
		else x = s.data[top];
		return true;
	}
	return false;
}

数据结构的应用

栈在括号匹配中的应用

在这里插入图片描述

bool bracketCheck(char str[], int length)
{
	SqStack s, InitStack(s);
	
	for (int i = 0; i < length; i ++ )
	{
		if (str[i] == '(' || str[i] == '[' || str[i] == '{')		// 遇到左括号进栈
			Push(s, str[i]);
		else 
		{
			if (isEmpty(s)) return false;		// 若遇到右括号,但栈是空的,证明左括号多了一个,则匹配不成功
			
			char topElem;
			Pop(s, topElem);

			/*左右括号不匹配*/
			if (str[i] == ')' && topElem != '(') return false;
			if (str[i] == ']' && topElem != '[') return false;
			if (str[i] == '}' && topElem != '{') return false;
		}
	}
	return isEmpty(s);		// 左右括号全部匹配
}

栈在表达式求值中的应用

在这里插入图片描述
在这里插入图片描述

  • 栈中运算符的优先级高于当前运算符的优先级时:可以得出基于栈顶元素的运算就可以被确定了。
  • 栈中运算符的优先级等于当前运算符的优先级时:基于左优先原则(左侧先做运算)。

其实就是中缀表达式转后缀表达式的过程中,边转换边计算(一旦确定运算就直接计算,再计算完的操作数压回栈中)就可以实现表达式的求值。

计算单元

void eval(Stack<int> &num, Stack<char> &op)
{
	int x = num.top();
	num.pop();
	int y = num.top();
	num.pop();
	char c = op.top();
	op.pop();

	if (c == '+')
        num.push(x + y);
    else if (c == '-')
        num.push(y - x);
    else if (c == '*')
        num.push(x * y);
    else 
        num.push(y / x);
}

计算表达式单元

int exEval(char str[], int length)
{
	unordered_map<char, int> pr = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};			// 定义运算符的优先级
	Stack<int> num;
	Stack<char> op;
	
`	for (int i = 0; i < length; i ++ )
	{
		if (isdigit(str[i]))
		{
			int x = 0;
			while (i < length && isdigit(str[i])) x = x * 10 + (str[i] - '0'), i ++ ;		// 将字符类型转换成数字
			num.push(x);
			i -- ;
		}
		else if (str[i] == '(') op.push(str[i]);
		else if (str[i] == ')')
		{
			while (op.top() != '(') eval(num, op);		// 将括号内的所有表达式已确定,直接计算
			op.pop();
		}
		else			// 操作符的情况 
		{
			while (op.size() && op.top() != '(' && pr[op.top()] >= pr[str[i]]) eval(num, op);			// 将已确定的运算式计算
			op.push(str[i]);		// 将待定运算符入栈
		}
	}
	while (op.size()) eval();		// 若栈内还有元素,直接做计算
	return num.top();				// 栈顶元素就是最终计算的结果
}

栈在递归调用中的应用

在这里插入图片描述
在这里插入图片描述
以斐波那契数列为例,其定义为:

F ( n ) = { F ( n − 1 ) + F ( n − 2 ) , n > 1 1 , n = 1 0 , n = 0 F(n)= \begin{cases}F(n-1)+F(n-2), & n>1 \\ 1, & n=1 \\ 0, & n=0\end{cases} F(n)= F(n1)+F(n2),1,0,n>1n=1n=0

int F(int n)
{	
	if (n == 0) return 0;
	if (n == 1) return 1;
	return F(n - 1) + F(n - 2);
}

递归调用需满足以下两个条件:

  • 递归表达式(递归体)
  • 边界问题(递归出口)

精髓:能否将原始问题转换成属性相同但规模较小的子问题。

以F(5)为例,以下是递归调用的执行过程:
在这里插入图片描述

其中F(3)被调用2次,F(2)被调用3次,F(1)被调用5次,F(0)被调用3次,故递归调用效率低下,但是代码实现简单,容易理解。

  • 30
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nie同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值