26考研——栈、队列和数组_栈(3)
408答疑
文章目录
- 一、栈
- 1、栈(Stack)的概念和特点
- 定义
- 术语
- 操作特性
- 示例
- 直观理解
- 栈的基本操作
- 初始化栈
- 判断栈是否为空
- 入栈操作
- 出栈操作
- 读取栈顶元素
- 销毁栈
- 栈的数学性质
- 2、栈的顺序存储结构
- 顺序栈的定义
- 栈顶指针初始化
- 注意事项
- 共享栈
- 共享栈的操作
- 共享栈的优势
- 代码实操
- 静态顺序栈
- 结构定义
- 初始化
- 判空
- 入栈
- 出栈
- 取栈顶元素
- 打印栈
- 动态顺序栈(小概率出现)
- 结构定义
- 初始化
- 入栈
- 3、栈的链式存储结构
- 链栈概念
- 链栈的优点
- 链栈的实现
- 链栈的操作
- 注意事项
- 代码实操
- 结点定义
- 初始化
- 判空
- 入栈操作
- 出栈操作
- 取栈顶元素
- 打印链栈
- 4、栈的应用
- 栈在表达式转换中的应用
- 表达式分类
- 表达式划分规则
- 表达式手动转换(最基础最重要)
- 中缀表达式转化为后缀表达式
- 步骤
- 示例
- 表达式借助栈转换
- 运算符的优先级关系
- 计算机利用栈将中缀表达式转化为后缀表达式的过程
- 步骤
- 示例
- 计算机利用栈将中缀表达式转化为前缀表达式的过程
- 步骤
- 示例
- 栈在表达式求值中的应用
- 算术表达式
- 中缀表达式
- 括号的必要性
- 后缀表达式的特点
- 示例
- 表达式求值
- 中缀表达式求值
- 步骤
- 示例
- 后缀表达式求值
- 定义
- 步骤
- 例题
- 前缀表达式求值
- 栈的深度分析
- 栈在括号匹配中的应用
- 概述
- 括号序列示例
- 分析过程
- 算法思想
- 代码实操
- 栈在递归中的应用
- 递归的定义
- 递归的特点
- 斐波那契数列的递归定义
- 递归模型的条件
- 递归的精髓
- 栈在函数调用中的作用和工作原理
- 递归调用执行过程
- 递归算法转换为非递归算法
- 进制转换
- 代码实操
- 出栈的顺序判断
- 代码实操
- 四、参考资料
- 鲍鱼科技课件
- 26王道考研书
一、栈
1、栈(Stack)的概念和特点
定义
栈是一种特殊的线性表,其特点是只允许在一端进行插入或删除操作。
术语
- 栈顶(Top):允许进行插入和删除操作的一端。
- 栈底(Bottom):固定的,不允许进行插入和删除操作的另一端。
- 空栈:不含任何元素的空表。
操作特性
栈的操作特性可以明显地概括为后进先出(Last In First Out,LIFO)。这意味着最后进入栈的元素会最先被移除。
示例
假设某个栈 S = ( a 1 , a 2 , a 3 , a 4 , a 5 ) S = (a_1, a_2, a_3, a_4, a_5) S=(a1,a2,a3,a4,a5),则:
- a 1 a_1 a1 为栈底元素。
- a 5 a_5 a5 为栈顶元素。
- 入栈次序依次为 a 1 , a 2 , a 3 , a 4 , a 5 a_1, a_2, a_3, a_4, a_5 a1,a2,a3,a4,a5。
- 出栈次序为 a 5 , a 4 , a 3 , a 2 , a 1 a_5, a_4, a_3, a_2, a_1 a5,a4,a3,a2,a1。
入栈次序 : a 1 → a 2 → a 3 → a 4 → a 5 出栈次序 : a 5 → a 4 → a 3 → a 2 → a 1 \begin{align*} \text{入栈次序} & : a_1 \rightarrow a_2 \rightarrow a_3 \rightarrow a_4 \rightarrow a_5 \\ \text{出栈次序} & : a_5 \rightarrow a_4 \rightarrow a_3 \rightarrow a_2 \rightarrow a_1 \end{align*} 入栈次序出栈次序:a1→a2→a3→a4→a5:a5→a4→a3→a2→a1

直观理解
栈的先进后出结构可以用“喝多了吐”来形象地理解:最后喝的酒最先吐出来,即最后进入的元素最先被移除。
栈的基本操作
各种辅导书中给出的基本操作的名称不尽相同,但所表达的意思大致是一样的。这里我们以严蔚敏编写的教材为准给出栈的基本操作,希望读者能熟记下面的基本操作。
初始化栈
InitStack(&S):初始化一个空栈S。
判断栈是否为空
StackEmpty(S):判断一个栈是否为空,若栈S为空则返回true,否则返回false。
入栈操作
Push(&S, x):入栈,若栈S未满,则将x加入使之成为新栈顶。
出栈操作
Pop(&S, &x):出栈,若栈S非空,则弹出栈顶元素,并用x返回。
读取栈顶元素
GetTop(S, &x):读栈顶元素,但不出栈,若栈S非空,则用x返回栈顶元素。
销毁栈
DestroyStack(&S):销毁栈,并释放栈S占用的存储空间(“&”表示引用调用)。
在解答算法题时,若题干未做出限制,则也可直接使用这些基本的操作函数。
栈的数学性质
当 n n n 个不同元素入栈时,出栈元素不同排列的个数为 1 n + 1 C 2 n n \frac{1}{n+1} C_{2n}^n n+11C2nn。这个公式称为卡特兰数(Catalan)公式,可采用数学归纳法证明,有兴趣的读者可以参考组合数学教材。
2、栈的顺序存储结构
顺序栈的定义
顺序栈是栈的顺序实现,利用顺序存储结构(数组)实现的栈。栈顶指针 top 的初始化有两种形式:-1 和 0,这会影响入栈 push 和取栈顶元素 top 的操作实现。
栈顶指针初始化
- 初始设置
S.top = -1:栈顶元素:S.data[S.top]。- 入栈操作:栈不满时,栈顶指针先加 1,再送值到栈顶。
- 出栈操作:栈非空时,先取栈顶元素,再将栈顶指针减 1。
- 栈空条件:
S.top == -1;栈满条件:S.top == MaxSize - 1;栈长:S.top + 1。
- 初始设置
S.top = 0:入栈时先将值送到栈顶,栈顶指针再加 1;出栈时,栈顶指针先减 1,再取栈顶元素;栈空条件是S.top == 0;栈满条件是S.top == MaxSize。
注意事项
顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能发生栈上溢,此时应及时向用户报告消息,以便及时处理,避免出错。
共享栈
利用栈底位置相对不变的特性,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。

共享栈的操作
- 两个栈的栈顶指针都指向栈顶元素,
top0 = -1时 0 号栈为空,top1 = MaxSize - 1时 1 号栈为空;仅当两个栈顶指针相邻(top1 - top0 = 1)时,判断为栈满。 - 当 0 号栈入栈时
top0先加 1 再赋值,1 号栈入栈时top1先减 1 再赋值;出栈时则刚好相反。
共享栈的优势
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。其存取数据的时间复杂度均为 O ( 1 ) O(1) O(1),所以对存取效率没有什么影响。
代码实操
静态顺序栈
结构定义
- 静态顺序栈使用固定大小的数组存储栈元素,栈顶指针 top 用于记录栈的当前状态
typedef struct SeqStack
{ElemType data[MAX_SIZE]; // 栈空间,固定大小int top; // 栈顶指针
} SeqStack;
初始化
- 将栈顶指针初始化为0,表示栈为空。
void initStack(SeqStack &pst)
{pst.top = 0;
}
判空
- 通过判断栈顶指针是否为0来判断栈是否为空。
bool empty(SeqStack &pst)
{return pst.top == 0;
}
入栈
- 将元素压入栈中,同时检查是否栈满。
void pushStack(SeqStack &pst, ElemType x)
{if (pst.top >= MAX_SIZE) // 栈满时无法入栈{printf("空间已满, %d 不能入栈.\n", x);return;}pst.data[pst.top] = x;pst.top++;
}
出栈
- 移除栈顶元素,同时检查栈是否为空。
void popStack(SeqStack &pst)
{if (empty(pst)){printf("栈已空,不能出栈.\n");return;}pst.top--;
}
取栈顶元素
- 返回栈顶元素,但不移除。
int topStack(SeqStack &pst)
{return pst.data[pst.top - 1]; // 返回栈顶元素
}
打印栈
- 从栈顶到栈底依次打印栈内元素。
void printStack(SeqStack &pst)
{for (int i = pst.top - 1; i >= 0; --i)printf("%d\n", pst.data[i]);
}
静态顺序栈使用固定大小的数组存储数据,操作简单,但无法动态扩展。适合栈大小已知且固定的应用场景。
动态顺序栈(小概率出现)
结构定义
- 动态顺序栈使用动态分配的数组存储栈元素,支持动态扩展。
typedef struct SeqStack
{int *data; // 动态分配的栈空间int top; // 栈顶指针int maxsize; // 当前栈的最大容量
} SeqStack;
初始化
- 动态分配栈空间,并初始化栈顶指针和容量。
void initStack(SeqStack &pst, int size)
{pst.data = (int *)malloc(sizeof(int) * size);pst.top = 0;pst.maxsize = size;
}
入栈
- 将元素压入栈中,同时检查是否栈满。
void push(struct seqstack *s, int value)
{if (s->top < s->maxsize){s->data[s->top] = value;s->top++;printf("Pushed %d onto the stack.\n", value);}else{printf("Stack is full. Cannot push %d.\n", value);}
}
动态顺序栈通过动态分配内存,可以在运行时调整栈的大小,但需要手动管理内存分配和释放。
3、栈的链式存储结构
链栈概念
链栈是栈的链式实现,利用链式存储结构(链表)进行实现。使用链表实现栈结构,只允许在链表的一头(一般为表头)插入和删除。
链栈的优点
- 便于多个栈共享存储空间和提高其效率。
- 不存在栈满上溢的情况。
链栈的实现
通常采用单链表实现链栈,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头结点,Lhead 指向栈顶元素。

链栈的操作
- 入栈和出栈的操作都在链表的表头进行。
- 对于带头结点和不带头结点的链栈,具体的实现会有所不同。
注意事项
采用链式存储,便于结点的插入与删除。链栈的操作与链表类似,但需要注意的是,对于带头结点和不带头结点的链栈,具体的实现会有所不同。
代码实操
结点定义
- 链栈使用链表存储栈元素,每个结点存储一个元素和指向下一个结点的指针。
typedef struct LinkStackNode
{int data; // 栈元素struct LinkStackNode *next; // 指向下一个结点
} LinkStackNode, *LinkStack;
初始化
- 创建一个头结点,初始化链栈。
LinkStack initStack()
{LinkStackNode *s = (LinkStackNode *)malloc(sizeof(LinkStackNode));s->next = NULL;return s;
}
判空
- 通过判断头结点的 next 指针是否为空来判断栈是否为空。
bool empty(LinkStack pst)
{return pst->next == NULL;
}
入栈操作
- 将新元素插入到头结点的下一个位置,实现头插法。
void pushStack(LinkStack pst, int x)
{LinkStackNode *s = (LinkStackNode *)malloc(sizeof(LinkStackNode));s->data = x;s->next = pst->next;pst->next = s;
}
出栈操作
- 移除头结点的下一个结点,并释放内存。
void popStack(LinkStack pst)
{LinkStackNode *p = pst->next;pst->next = p->next;free(p);
}
取栈顶元素
- 返回头结点的下一个结点的数据。
int topStack(LinkStack pst)
{return pst->next->data;
}
打印链栈
- 从头结点的下一个结点开始,依次打印链栈中的元素。
void printStack(LinkStack pst)
{LinkStackNode *p = pst->next;while (p != NULL){printf("%d\n", p->data);p = p->next;}
}
链栈使用链表实现,支持动态扩展,无需提前分配固定大小的内存。适合栈大小不确定或频繁变化的场景。
4、栈的应用
栈在表达式转换中的应用
表达式分类
- 前缀表达式
- 中缀表达式
- 后缀表达式
表达式划分规则
表达式的分类是按照运算符跟两个运算数的位置进行划分的:
- 前缀表达式: + a b +ab +ab(运算符在两个运算数的前面)
- 中缀表达式: a + b a+b a+b(运算符在两个运算数的中间)
- 后缀表达式: a b + ab+ ab+(运算符在两个运算数的后面)
表达式手动转换(最基础最重要)
中缀表达式转化为后缀表达式
步骤
- 按照运算符的运算顺序对所有运算单位加括号。
- 将运算符移至对应括号的后面,相当于按“左操作数右操作数运算符”重新组合。
- 去除所有括号。
示例
例如,中缀表达式 A + B ∗ ( C − D ) − E / F A+B*(C-D)-E/F A+B∗(C−D)−E/F 转后缀表达的过程如下(下标表示运算符的运算顺序):
- 加括号: ( ( A + ③ ( B ∗ ② ( C − ① D ) ) ) − ⑤ ( E / ④ F ) ) ((A+③(B*②(C-①D)))-⑤(E/④F)) ((A+③(B∗②(C−①D)))−⑤(E/④F))。
- 运算符后移: ( ( A ( B ( C D ) − ① ) ∗ ② ) + ③ ( E F ) / ④ ) − ⑤ ((A(B(CD)-①)*②)+③(EF)/④)-⑤ ((A(B(CD)−①)∗②)+③(EF)/④)−⑤。
- 去除括号后,得到后缀表达式: A B C D − ① ∗ ② + ③ E F / ④ − ⑤ ABCD-①*②+③EF/④-⑤ ABCD−①∗②+③EF/④−⑤。
表达式借助栈转换
运算符的优先级关系
| θ 1 \theta _{1} θ1 \ θ 2 \theta _{2} θ2 | + | - | * | / | ( | ) | # |
|---|---|---|---|---|---|---|---|
| + | > | > | < | < | < | > | > |
| - | > | > | < | < | < | > | > |
| * | > | > | > | > | < | > | > |
| / | > | > | > | > | < | > | > |
| ( | < | < | < | < | < | = | |
| ) | > | > | > | > | > | > | |
| # | < | < | < | < | < | = |
表格隐含了左结合思想,所以是同级别符号中栈顶运算符大于当前运算符
- θ 1 \theta _{1} θ1为栈顶运算符, θ 2 \theta _{2} θ2为当前运算符
计算机利用栈将中缀表达式转化为后缀表达式的过程
步骤
- 手算(检验借助栈的答案正确否)。
- 借助一个栈和一个队列:操作符栈、结果栈。
- 表达式扫描顺序:
从左往右扫描。 - 遇到操作数,直接输出到结果栈。
- 遇到运算符,则比较优先级:
- 若其优先级高于栈顶运算符或遇到栈顶为“(”,则直接入栈;
- 若其优先级低于或等于栈顶运算符,则依次弹出栈中的运算符并输出到结果栈,直到遇到一个优先级低于它的运算符或遇到“(”或栈空为止,之后将当前运算符入栈。
- 如果遇到括号,则根据括号的方向进行处理:
- 若为“(”,则直接入栈;
- 若为“)”,则不入栈,且依次弹出栈中的运算符并输出到结果栈,直到遇到“(”为止,并直接删除“(”。
- 重复上述的3、4、5步骤,直到表达式扫描完毕。
- 扫描完成中缀表达式后,结果栈中所保留的数据则为后缀表达式。
示例
表达式: ( a + b ) ∗ c + d − ( e + g ) ∗ h (a+b)*c+d-(e+g)*h (a+b)∗c+d−(e+g)∗h
手算结果: a b + c ∗ d + e g + h ∗ − ab+c*d+ eg+h*- ab+c∗d+eg+h∗−





计算机利用栈将中缀表达式转化为前缀表达式的过程
步骤
- 手算(检验借助栈的答案正确否)。
- 借助一个栈和一个队列:操作符栈、结果栈。
- 表达式扫描顺序:
从右往左扫描。 - 遇到操作数,直接输出到结果栈。
- 遇到运算符,则比较优先级:
- 若其优先级高于或等于栈顶运算符或遇到栈顶为“)”,则直接入栈;
- 若其优先级低于栈顶运算符,则依次弹出栈中的运算符并输出到结果栈,直到遇到一个优先级低于或等于它的运算符或遇到“)”或栈空为止,之后将当前运算符入栈。
- 如果遇到括号,则根据括号的方向进行处理:
- 若为“)”,则直接入栈;
- 若为“(”,则不入栈,且依次弹出栈中的运算符并输出到结果栈,直到遇到“)”为止,并直接删除“)”。
- 重复上述的3、4、5步骤,直到表达式扫描完毕。
- 扫描完成中缀表达式后,结果栈中所保留的数据则为前缀表达式。
示例
表达式: ( a + b ) ∗ c + d − ( e + g ) ∗ h (a+b)*c+d-(e+g)*h (a+b)∗c+d−(e+g)∗h
手算结果: − + ∗ + a b c d ∗ + e g h -+*+abcd *+egh −+∗+abcd∗+egh
栈在表达式求值中的应用
算术表达式
中缀表达式
中缀表达式(如 3 + 4 3+4 3+4)是人们常用的算术表达式,操作符以中缀形式处于操作数的中间。与前缀表达式(如 + 34 +34 +34)或后缀表达式(如 34 + 34+ 34+)相比,中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它更符合人们的思维习惯。
括号的必要性
与前缀表达式或后缀表达式不同的是,中缀表达式中的括号是必需的。计算过程中必须用括号将操作符和对应的操作数括起来,用于指示运算的次序。
后缀表达式的特点
后缀表达式的运算符在操作数后面,后缀表达式中考虑了运算符的优先级,没有括号,只有操作数和运算符。
示例
中缀表达式 A + B ∗ ( C − D ) − E / F A+B*(C-D)-E/F A+B∗(C−D)−E/F 对应的后缀表达式为 A B C D − ∗ + E F / − ABCD-*+EF/- ABCD−∗+EF/−,将后缀表达式与原表达式对应的表达式树的后序遍历序列进行比较,可发现它们有异曲同工之妙。

表达式求值
中缀表达式求值
步骤
- 手算求值用于检验借助栈的答案是否正确。
- 需要借助两个栈结构
- 操作符栈
- 数据栈
- 表达式扫描顺序:从左往右扫描。
- 遇到操作数,将操作数压入数据栈。
- 遇到运算符,比较优先级:
- 如果当前运算符的优先级 > > > 栈顶运算符的优先级(当栈顶是括号时,直接入栈),则将运算符直接入栈。
- 如果当前运算符的优先级 < < < 栈顶运算符的优先级,则将栈顶运算符出栈,并将数据栈出栈,先出的为右值,后出的为左值,将运算之后的结果重新入到数据栈。
- 遇到括号,根据括号的方向进行处理:
- 如果是左括号,则直接入栈。
- 如果是右括号,则遇到左括号前将所有的运算符全部出栈,并将数据栈两个数出栈,将运算之后的结果重新入到数据栈,直到遇到左括号为止。
- 重复上述的3、4、5步骤,直至整个表达式扫描完成。
示例
中缀表达式求值,例如 ( 3 + 4 ) − 7 × 5 − 6 (3+4)-7\times 5 - 6 (3+4)−7×5−6。



后缀表达式求值
定义
后缀表达式又称逆波兰表达式,运算符位于操作数之后。
步骤
- 手算求值用于检验借助栈的答案是否正确。
- 只需借助一个栈:数据栈。
- 表达式扫描顺序:从左往右扫描。
- 如果遇到操作数,将操作数压入数据栈。
- 如果遇到运算符:
- 弹出栈顶的两个数,先出栈的为右数,后出栈的为左数。
- 做运算后将结果重新入栈。
- 重复步骤 3 和 4,直到表达式扫描完毕,则数据栈中保存的数据则为表达式的结果。
例题
例如, ( 3 + 4 ) − 7 × 5 − 6 (3+4) -7\times 5 - 6 (3+4)−7×5−6 对应的后缀表达式就是 3 4 + 75 × − 6 − 3\ 4\ +\ 75\ \times-\ 6\ - 3 4 + 75 ×− 6 −。




前缀表达式求值
与后缀表达式求值差不多,扫描方向相反即可
栈的深度分析
所谓栈的深度,是指栈中的元素个数,通常是给出入栈和出栈序列,求最大深度(栈的容量应大于或等于最大深度)。有时会间接给出入栈和出栈序列,例如以中缀表达式和后缀表达式的形式给出入栈和出栈序列。掌握栈的先进后出的特点进行手工模拟是解决这类问题的有效方法。
栈在括号匹配中的应用
概述
假设表达式中允许包含两种括号:圆括号和方括号,其嵌套的顺序任意,即 []() 或 [([][])] 等均为正确的格式,而 [(]) 或 ([()]) 或 (()) 均为不正确的格式。
括号序列示例
考虑下列括号序列:

分析过程
- 计算机接收第 1 个括号
[后,期待与之匹配的第 8 个括号]出现。 - 获得了第 2 个括号
(,此时第 1 个括号[暂时放在一边,而急迫期待与之匹配的第 7 个括号)出现。 - 获得了第 3 个括号
[,此时第 2 个括号(暂时放在一边,而急迫期待与之匹配的第 4 个括号]出现。第 3 个括号的期待得到满足,消解之后,第 2 个括号的期待匹配又成为当前最急迫的任务。 - 以此类推,可见该处理过程与栈的思想吻合。
算法思想
- 初始设置一个空栈,顺序读入括号。
- 若是左括号,则作为一个新的更急迫的期待压入栈中,自然使原有的栈中所有未消解的期待的急迫性降了一级。
- 若是右括号,则或使置于栈顶的最急迫期待得以消解,或是不合法的情况(括号序列不匹配,退出程序)。算法结束时,栈为空,否则括号序列不匹配。
代码实操
- 给定一个只包括’(‘,’)‘,’{‘,’}‘,’[‘,’]'的字符串 s,判断括号字符串是否有效。
有效字符串需满足:- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
- 示例:
- 输入:
"()"
输出:true - 输入:
"()[]{}"
输出:true - 输入:
"(]"
输出:false - 输入:
"([)]"
输出:false - 输入:
"{[]}"
输出:true
- 输入:
//判断字符串中的括号是否匹配,利用链栈存储左括号。
bool isValid(char *s)
{LinkStack st = initStack();while (*s != '\0'){if (*s == '{' || *s == '[' || *s == '(') // 左括号入栈pushStack(st, *s);else{if (empty(st)) // 右括号但栈为空,不匹配return false;char topval = topStack(st); // 取栈顶元素if ((*s == '}' && topval != '{') || (*s == ']' && topval != '[') || (*s == ')' && topval != '('))return false;popStack(st); // 匹配成功,出栈}s++;}return empty(st); // 栈为空则匹配
}
利用栈存储左括号,遇到右括号时检查栈顶元素是否匹配。最终栈为空则表示括号匹配。
栈在递归中的应用
递归的定义
递归是一种重要的程序设计方法。简单来说,若在一个函数、过程或数据结构的定义中又应用了它自身,则这个函数、过程或数据结构称为是递归定义的,简称递归。
递归的特点
递归通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。但在通常情况下,它的效率并不是太高。
斐波那契数列的递归定义
以斐波那契数列为例,其定义为:
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(n−1)+F(n−2),1,0,n>1n=1n=0
递归模型的条件
必须注意递归模型不能是循环定义的,其必须满足下面的两个条件:
- 递归表达式(递归体)。
- 边界条件(递归出口)。
递归的精髓
递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题。
栈在函数调用中的作用和工作原理
在递归调用的过程中,系统为每一层的返回点、局部变量、传入实参等开辟了递归工作栈来进行数据存储,递归次数过多容易造成栈溢出等。而其效率不高的原因是递归调用过程中包含很多重复的计算。
递归调用执行过程
下面以 n = 5 n=5 n=5 为例,列出递归调用执行过程:

显然,在递归调用的过程中, F ( 3 ) F(3) F(3) 被计算 2 次, F ( 2 ) F(2) F(2) 被计算 3 次。 F ( 1 ) F(1) F(1) 被调用 5 次, F ( 0 ) F(0) F(0) 被调用 3 次。所以,递归的效率低下,但优点是代码简单,容易理解。
递归算法转换为非递归算法
可以将递归算法转换为非递归算法,通常需要借助栈来实现这种转换。
进制转换
代码实操
- 编写函数实现,将一个十进制整数value,转换为对应的二进制。
//将十进制数转换为二进制数,利用栈存储中间结果。
void Dec2Bin(int value)
{int stack[200] = {0};int top = 0;while (value){stack[top++] = value % 2; // 计算余数并入栈value /= 2; // 更新值}while (top)printf("%d", stack[--top]); // 逆序输出栈内容
}
利用栈的后进先出特性,存储每次除法的余数,最后逆序输出即为二进制结果。
出栈的顺序判断
代码实操
- 给出入栈序列 In = [6,7,8,9,10,11],出栈序列 Out = [9,11,10,8,7,6],判断出栈序列是否是入栈序列的一种出栈可能性。
//判断给定的出栈序列是否合法,利用链栈模拟入栈和出栈过程。
bool isValidStackSeq(ElemType pushed[], ElemType popped[], int n)
{LinkStack st = initStack();int i = 0, j = 0;while (i < n){if (pushed[i] != popped[j]){pushStack(st, pushed[i]); // 不匹配时入栈i++;}else{i++;j++;while (!empty(st) && topStack(st) == popped[j]) // 匹配时出栈{popStack(st);j++;}}}return empty(st); // 栈为空则表示出栈序列合法
}
通过模拟入栈和出栈操作,判断给定的出栈序列是否与入栈序列匹配。栈为空时说明出栈序列合法。
四、参考资料
鲍鱼科技课件
b站免费王道课后题讲解:

网课全程班:

26王道考研书
相关文章:
26考研——栈、队列和数组_栈(3)
408答疑 文章目录 一、栈1、栈(Stack)的概念和特点定义术语操作特性示例直观理解栈的基本操作初始化栈判断栈是否为空入栈操作出栈操作读取栈顶元素销毁栈 栈的数学性质 2、栈的顺序存储结构顺序栈的定义栈顶指针初始化注意事项 共享栈共享栈的操作共享栈…...
“十五五”时期航空弹药发展环境分析
1.“十五五”时期航空弹药发展环境分析 (标题:小二号宋体居中) 一、建言背景介绍 (一级标题:黑体三号,首行空两格) 航空弹药作为现代战争的核心装备,其发展水平直接关乎…...
桥接模式的优点和典型实现
桥接模式的优点 桥接模式通过将抽象部分与实现部分分离,使得它们可以独立变化,从而提高系统的灵活性和可扩展性。以下是桥接模式的主要优点: 分离抽象和实现: 桥接模式将抽象部分和实现部分分离,使得两者可以独立变化…...
Kotlin 协程官方文档知识汇总(一)
1、协程基础 Kotlin 是一门仅在标准库中提供最基本底层 API 以便其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async 与 await 在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的挂起函数概念为异步操作提…...
删除字符串邻近的重复项
1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode) 对于字符串中字符的匹配或者删除等问题,通常会用到栈这个数据结构,要保持这样一个思路。 对于这道题,可以遍历字符串,用栈存储,一旦遇到…...
基于 mxgraph 实现流程图
mxgraph 可以实现复杂的流程图绘制。mxGraph里的Graph指的是图论(Graph Theory)里的图而不是柱状图、饼图和甘特图等图(chart),因此想找这些图的读者可以结束阅读了。 作为图论的图,它包含点和边,如下图所示。 交通图 横道图 架构图 mxGrap…...
动态路由机制MoE专家库架构在多医疗AI专家协同会诊中的应用探析
随着医疗人工智能技术的飞速进步,AI在医学领域的应用日益增多,尤其是在复杂疾病的诊断和治疗中,AI技术的应用带来了巨大的潜力。特别是动态路由机制混合专家(Mixture of Experts,MoE)架构,因其灵活、高效的特点,正逐渐成为实现多AI专家协同会诊的关键技术。通过将多个不…...
双工通信:WebSocket服务
(一)WebSocket概述 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输 注意;Websocket也只能由客户端先握…...
洪水灌溉算法 + 总结
文章目录 floodfill算法图像渲染题解代码 岛屿数量题解代码 岛屿的最大面积题解代码 被围绕的区域题解代码 太平洋大西洋水流问题题解代码 扫雷游戏题解代码 衣橱整理题解代码 总结 floodfill算法 1. 寻找相同性质的联通块,可以使用dfs或者bfs解决,比如…...
docker中间件部署
1.docker安装 # 1.卸载旧版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine# 2.需要的安装包 yum install -y yum-utils# 3.设置镜像的仓库 # 3.1.默认是国外的&#x…...
LangChain4j(1):初识LangChain4j
1 什么是LangChain和LangChain4j LangChain是一个大模型的开发框架,使用LangChain框架,程序员可以更好的利用大模型的能力,大大提高编程效率。如果你是一个lava程序员,那么对LangChain最简单直观的理解就是,LangChain…...
基于 Swoole 的高性能 RPC 解决方案
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
Photoshop 2025安装包下载及Photoshop 2025详细图文安装教程
文章目录 前言一、Photoshop 2025安装包下载二、Photoshop 2025安装教程1.解压安装包2.运行程序3.修改安装路径4.设安装目录5.开始安装6.等安装完成7.关闭安装向导8.启动软件9.安装完成 前言 无论你是专业设计师,还是初涉图像处理的小白,Photoshop 2025…...
CPU跑大模型怎么加速?
一、概念 近几年,大模型的规模越做越大。普通码农没几张显卡几乎都跑不动动辄几百B的模型了。当然,随着SLM进一步发展,移动端、PC端部署SLM变得轻松了起来。即便只有CPU也能带得起3B以内的SLM,只不过推理速度比较感人。因此&#…...
PostgreSQL详解
第一章:环境部署与基础操作 1.1 多平台安装详解 Windows环境 图形化安装 下载EnterpriseDB安装包(含pgAdmin) 关键配置项说明: # postgresql.conf优化项 max_connections 200 shared_buffers 4GB work_mem 32MB 服务管理命…...
SQL Server安装程序无法启动:系统兼容性检查失败
问题现象: 运行 SQL Server 2022 安装程序时,提示 “硬件或软件不满足最低要求”,安装向导直接退出或无法继续。 快速诊断 操作系统版本检查: # 查看 Windows 版本(需 20H2 或更高) winver 支持的系统&…...
期权合约作废的话,权利金和保证金会退还么?
在期权交易中,权利金是否可以退回,主要取决于期权的交易情况和合约条款。 期权作废的三种情形 一般来说期权作废一共有三种情况,分别是到期没有行权、主动放弃或者是标的退市了。 第一种是到期未行权,一般来说值得都是虚值期权&…...
MIPI计算ECC和CRC工具介绍
一、MIPI简介 MIPI联盟,即移动产业处理器接口(Mobile Industry Processor Interface 简称MIPI)联盟。MIPI(移动产业处理器接口)是MIPI联盟发起的为移动应用处理器制定的开放标准和一个规范。MIPI官网https://mipi.org/…...
医院管理系统(源码)分享
「医院管理系统(源码) 源码: https://pan.quark.cn/s/b6e21488fce3 第1章 绪论 1.1 项目背景 随着计算机科学的迅猛发展和互联网技术的不断推进,人们的生活方式发生了巨大的变化,同时也推动了整个软件产业的发展。把…...
使用Geotools从DEM数据中读取指定位置的高程实战
目录 前言 一、GridCoverage2D对象介绍 1、GridCoverage2D的属性 2、GridCoverage2D核心方法 3、GridCoverage2D中的高级操作 二、指定位置的高程获取 1、存储原理 2、相关属性的获取 3、获取高程的方法 三、总结 前言 在地理信息科学领域,高程数据是至关重…...
uniapp 在app上 字体如何不跟着系统字体大小变
在UniApp开发中,默认情况下App的字体可能会跟随系统字体设置而变化。如果你希望保持固定的字体样式,不随系统字体设置改变,可以采用以下几种方法: 方法一:全局CSS设置 在App.vue的样式中添加以下CSS: /*…...
RAG优化:python从零实现GraphRag 一场文档与知识的“恋爱”之旅
嘿,亲爱的算法工程师们,准备好迎接一场文档与知识的“恋爱”之旅了吗?今天我们要介绍的 Graph RAG,就像是一位“红娘”,帮助文档和知识在图的世界里找到彼此,擦出智慧的火花! 文章目录 为什么需要 Graph RAG?Graph RAG 的“恋爱秘籍”准备好了吗?让我们开始吧!环境设…...
STM32F103_LL库+寄存器学习笔记05 - GPIO输入模式,捕获上升沿进入中断回调
导言 GPIO设置输入模式后,一般会用轮询的方式去查看GPIO的电平状态。比如,最常用的案例是用于检测按钮的当前状态(是按下还是没按下)。中断的使用一般用于计算脉冲的频率与计算脉冲的数量。 项目地址:https://github.…...
如何为你的github开源项目选择合适的开源协议?
如何为你的github开源项目选择合适的开源协议? 导言 在github开源世界中,选择一个合适的开源协议是至关重要的。它不仅定义了他人如何使用你的代码,还决定了你的项目能否被广泛接受和传播,还能避免侵权问题。 然而,面…...
【深度破解】爬虫反反爬核心技术实践:验证码识别与指纹伪装
一、反爬技术体系全景图 现代Web应用的常见反爬手段: mermaid: graph TDA[反爬体系] --> B[行为特征检测]A --> C[验证码体系]A --> D[指纹追踪]B --> B1[请求频率]B --> B2[鼠标轨迹]B --> B3[页面停留时间]C --> C1[图形验证码…...
dynamic_cast的理解
dynamic_cast:(具体使用就不详细说明了) C 中用于 安全的类层次结构转换 的类型转换运算符,主要用于 多态类型(即包含虚函数的类)的指针或引用之间的转换 前提条件: 必须要有虚函数才能使用 dy…...
MATLAB 编写的函数或算法生成可供 C++ 调用的库或组件
MATLAB 编写的函数或算法生成可供 C 调用的库或组件 使用 MATLAB Coder 生成 C/C 代码: MATLAB Coder 允许您将 MATLAB 函数转换为可移植的 C 或 C 代码。生成的代码可以作为静态库、动态库或源代码,供 C 项目直接调用。具体步骤包括: 准备…...
Java基础知识-反射
一、什么是反射? Java反射(Reflection)是Java语言的核心特性之一,它允许程序在运行时(Runtime)动态地操作类和对象。通过反射API,我们可以在程序运行期间: 获取任意类的Class对象构…...
【大模型学习】什么是具身智能
目录 一、技术背景与历史发展 二、什么是具身智能? 三、技术要点及具体实现细节 1. 感知技术: 2. 运动控制: 3. 学习机制: 4. 人机交互: 四、架构 五、应用 六、实际应用案例 一、技术背景与历史发展 人工智能的…...
直播预告 | TDgpt 智能体发布 时序数据库 TDengine 3.3.6 发布会即将开启
从海量监控数据,到工业、能源、交通等场景中实时更新的各类传感器数据,时序数据正在以指数级速度增长。而面对如此庞杂的数据,如何快速分析、自动发现问题、精准预测未来,成为企业数字化转型过程中的关键挑战。 TDengine 的答案是…...
