数据结构之栈

1. 什么是栈?

栈是一种常见的数据结构,它遵循先进后出(LIFO)的原则。栈可以用来解决很多实际问题,比如函数调用、表达式求值、括号匹配等。

一般使用数组、链表实现栈

2. 特点

  • 栈是一种线性数据结构,由一系列元素组成。
  • 栈的插入和删除操作只能在栈的顶部进行。
  • 栈的顶部元素是最后一个插入的元素,也是唯一可以访问的元素。

3. 操作

栈支持以下几种操作:

  • Push:将元素插入到栈的顶部。
  • Pop:将栈顶部的元素删除,并返回该元素的值。
  • Peek:返回栈顶部元素的值,但不删除该元素。
  • isEmpty:检查栈是否为空。
  • size:返回栈中元素的个数。

 

4. 最佳实践

在使用栈的时候,有一些最佳实践可以帮助我们更好地利用它:

  • 在使用栈之前,先确定栈的大小,以避免栈溢出的问题。
  • 使用异常处理机制来处理栈溢出的情况。
  • 在进行复杂的操作时,可以使用多个栈来简化问题。

 

5. 坑

在使用栈的过程中,有一些常见的坑需要注意:

  • 忘记检查栈是否为空就进行 Pop 操作,可能会导致栈溢出异常。
  • 没有正确处理运算符的优先级,可能会导致表达式求值结果错误。
  • 在使用栈求解问题时,需要确保栈中元素的顺序和操作的顺序一致,否则可能会得到错误的结果。

 

6. 示例

6.1 计算表达式的值

复制代码
 1 def evaluate_expression(expression):
 2     stack = []  # 使用数组/列表实现栈
 3     operators = {'+': lambda x, y: x + y, '-': lambda x, y: x - y, '*': lambda x, y: x * y, '/': lambda x, y: x / y}
 4     for char in expression.split():
 5         if char.isdigit():
 6             stack.append(int(char))  # 遇到数字就入栈
 7         elif char in operators:  # 遇到字符,请出栈,出2次即操作的2个数
 8             operand2 = stack.pop()
 9             operand1 = stack.pop()
10             result = operators[char](operand1, operand2)  # 做运算操作
11             stack.append(result)  # 将结果入栈
12     print(f"表达式{expression},栈:{stack}")  # 表达式3 4 + 2 *,栈:[14]
13     return stack.pop()  # 将结果出栈,最终列表只有1个元素即表达式结果
14 
15 
16 '''
17 规律是:
18     1. 顺序从左向右;
19     2. 开头2个必须是数字,第3个是操作符,然后做运算,其结果与第4个数字(必须是数字)、第5个字符进行操作,依次类推
20 因此构造此表达是解题的关键
21 '''
22 expression = "3 4 + 2 *"  # 3和4做+操作,然后其结构7和2做乘法操作。
23 print(evaluate_expression(expression))  # 输出结果为 14
复制代码

输出:

1
2
表达式3 4 + 2 *,栈:[14]
14

  

 

6.2 括号匹配

复制代码
 1 '''
 2 1. 题目: 栈是一个非常常用的数据结构,可以用来实现括号匹配。括号匹配是指检查一个字符串中的括号是否正确配对。
 3 
 4 2. 实现思路
 5     1. 遍历字符串中的每个字符。
 6     2. 如果遇到左括号(如'('、'['、'{'),则将其压入栈中。
 7     3. 如果遇到右括号(如')'、']'、'}'),则检查栈顶部的元素。
 8         1. 如果栈为空或栈顶部的元素与当前右括号不匹配,则括号不匹配,返回 False。
 9         2. 如果栈顶部的元素与当前右括号匹配,则将栈顶部的元素弹出。
10     4. 遍历完字符串后,检查栈是否为空。
11         1. 如果栈为空,则括号匹配,返回 True。
12         2. 如果栈不为空,则括号不匹配,返回 False。
13 
14 '''
15 
16 
17 def is_valid_parentheses(s):
18     if not (len(s) % 2 == 0 and len(s) > 0):  # 判断是否可以数量上是否满足
19         return False
20 
21     stack = []  # 列表实现栈
22     # 特点,匹配后半部分为key,前半部分为value(这样入栈只能是前半部分,总之栈里的元素与字典的key匹配,与字典的value相等)。
23     # 与栈顶元素比较是否匹配,匹配继续遍历,不匹配直接返回False
24     mapping = {')': '(', ']': '[', '}': '{'}
25     for char in s:
26         if char in mapping:
27             if not stack or stack.pop() != mapping[char]:
28                 return False
29         else:
30             stack.append(char)
31     return not stack
32 
33 
34 s = "([{}])"
35 print(is_valid_parentheses(s))  # 输出结果为 True
36 
37 s = "([{}]))"
38 print(is_valid_parentheses(s))  # 输出结果为 False
复制代码

 

6.3 汉诺塔(Tower of Hanoi)

汉诺塔(Hanoi Tower)是一个经典的递归问题,它可以通过递归算法来解决。

在这个问题中,有三根柱子(通常称为A、B和C),其中一根柱子上有一组不同大小的圆盘,按照从大到小的顺序堆叠在柱子上。

任务是将所有的圆盘从一根柱子移动到另一根柱子,每次只能移动一个圆盘,并且大的圆盘不能放在小的圆盘上面。

规则:

  1. 一次只能移动一个盘子。
  2. 永远不能将一个较大的盘子放在一个较小的盘子之上。

python语言:注意声明hanoi函数、调用hanoi函数参数的顺序

复制代码
def hanoi(n, source, auxiliary, target):
    if n == 1:
        print(f"移动盘子 1 从 {source} 到 {target}")
        return
    hanoi(n - 1, source, target, auxiliary)
    print(f"移动盘子 {n} 从 {source} 到 {target}")
    hanoi(n - 1, auxiliary, source, target)

# 在这里,n 表示盘子的数量,source 是初始柱子,auxiliary 是辅助柱子,target 是目标柱子
n = 3  # 你可以根据需要修改盘子的数量
hanoi(n, 'A', 'B', 'C')
复制代码

输出:

1
2
3
4
5
6
7
移动盘子 1 从 A 到 C
移动盘子 2 从 A 到 B
移动盘子 1 从 C 到 B
移动盘子 3 从 A 到 C
移动盘子 1 从 B 到 A
移动盘子 2 从 B 到 C
移动盘子 1 从 A 到 C

  

Java语言

复制代码
public class HanoiTower {
    public static void main(String[] args) {
        int numDiscs = 3; // 圆盘的数量
        char source = 'A'; // 起始柱子
        char auxiliary = 'B'; // 辅助柱子
        char target = 'C'; // 目标柱子

        solveHanoi(numDiscs, source, auxiliary, target);
    }

    public static void solveHanoi(int n, char source, char auxiliary, char target) {
        if (n == 1) {
            // 如果只有一个圆盘,直接将其从起始柱子移动到目标柱子
            System.out.println("Move disk 1 from " + source + " to " + target);
        } else {
            // 递归地将 n-1 个圆盘从起始柱子移动到辅助柱子
            solveHanoi(n - 1, source, target, auxiliary);
            // 移动剩下的一个圆盘到目标柱子
            System.out.println("Move disk " + n + " from " + source + " to " + target);
            // 递归地将 n-1 个圆盘从辅助柱子移动到目标柱子
            solveHanoi(n - 1, auxiliary, source, target);
        }
    }
}
复制代码

输出:

1
2
3
4
5
6
7
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C

  

最佳实践和详解:

  1. 使用递归:递归是解决汉诺塔问题的最佳方法,因为它自然地反映了问题的分治性质。通过递归,你可以将复杂的问题分解成更小的子问题,然后逐步解决它们。

  2. 参数说明:在递归函数中,参数n表示要移动的圆盘数量,source表示起始柱子,auxiliary表示辅助柱子,target表示目标柱子。

  3. 终止条件:递归函数中的终止条件是n等于1,即只有一个圆盘时直接移动到目标柱子。

  4. 递归调用:在递归步骤中,先将 n-1 个圆盘从起始柱子移动到辅助柱子,然后将一个圆盘从起始柱子移动到目标柱子,最后将 n-1 个圆盘从辅助柱子移动到目标柱子。这个过程保证了大圆盘永远不会放在小圆盘上面。

  5. 输出移动步骤:在每次移动圆盘时,输出移动的详细步骤,以便了解整个过程。

 

javascirpt语言

复制代码
 1 function hanoiTower(discs, source, auxiliary, target) {  // 顺序是 源柱子、辅助柱子、目标柱子
 2     if (discs === 1) {
 3         console.log(`Move disc 1 from ${source} to ${target}`);
 4         return;
 5     }
 6 
 7     hanoiTower(discs - 1, source, target, auxiliary);  // 顺序是:源柱子、目标柱子、辅助柱子
 8     console.log(`Move disc ${discs} from ${source} to ${target}`);
 9     hanoiTower(discs - 1, auxiliary, source, target);   // 顺序是:辅助柱子、源柱子、目标柱子
10 }
11 
12 const numDiscs = 3;         // 设置圆盘的数量
13 const sourcePeg = 'A';      // 源柱子
14 const auxiliaryPeg = 'B';   // 辅助柱子
15 const targetPeg = 'C';      // 目标柱子
16 
17 hanoiTower(numDiscs, sourcePeg, auxiliaryPeg, targetPeg);
复制代码

输出:

1
2
3
4
5
6
7
Move disc 1 from A to C
Move disc 2 from A to B
Move disc 1 from C to B
Move disc 3 from A to C
Move disc 1 from B to A
Move disc 2 from B to C
Move disc 1 from A to C

  

6.4 老鼠走迷宫

java语言:链表实现的栈

1. 链表实现栈

复制代码
 1 class Node
 2 {
 3     int x;  // 坐标x
 4     int y;  // 坐标y
 5     Node next;
 6     public Node(int x,int y)
 7     {
 8         this.x=x;
 9         this.y=y;
10         this.next=null;
11     }
12 }
13 
14 /**
15  * 使用链表实现栈的功能
16  */
17 public class TraceRecord
18 {
19     public Node first;
20     public Node last;
21     public boolean isEmpty()
22     {
23         return first==null;
24     }
25     public void insert(int x,int y)
26     {
27         Node newNode=new Node(x,y);
28         if(this.isEmpty())
29         {
30             first=newNode;
31             last=newNode;
32         }
33         else
34         {
35             last.next=newNode;
36             last=newNode;
37         }
38     }
39 
40     public void delete()
41     {
42         Node newNode;
43         if(this.isEmpty())
44         {
45             System.out.print("[�����Ѿ�����]\n");
46             return;
47         }
48         newNode=first;
49         while(newNode.next!=last) 
50             newNode=newNode.next;
51         newNode.next=last.next;
52         last=newNode;
53 
54     }
55 }
复制代码

2. 老鼠走迷宫

复制代码
 1 // 老鼠走迷宫
 2 
 3 import java.io.IOException;
 4 
 5 public class MouseGo {
 6     public static int ExitX = 8;        //定义出口的X坐标在第8行
 7     public static int ExitY = 10;    //定义出口的Y坐标在第10列
 8     //声明迷宫数组10 X 12
 9     public static int[][] MAZE = {{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
10             {1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1},
11             {1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1},
12             {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1},
13             {1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1},
14             {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1},
15             {1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1},
16             {1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1},
17             {1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1},
18             {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
19 
20     public static void main(String args[]) throws IOException {
21         int i, j, x, y;
22         TraceRecord path = new TraceRecord();
23         x = 1;    // 1,1 坐标是入口
24         y = 1;
25         //     0. 打印迷宫
26         System.out.print("[迷宫的路径(0标记的部分)]\n");
27         for (i = 0; i < 10; i++) {
28             for (j = 0; j < 12; j++)
29                 System.out.print(MAZE[i][j]);
30             System.out.print("\n");
31         }
32         // 从1,1到出口(8,10)尝试路径
33         while (x <= ExitX && y <= ExitY) {
34             MAZE[x][y] = 2;  // 设置2表示可通行
35             if (MAZE[x - 1][y] == 0)  // 向上,0表示可行
36             {
37                 x -= 1;             // 更新当前坐标
38                 path.insert(x, y);// 当前位置入栈
39             } else if (MAZE[x + 1][y] == 0)  // 向下,0表示可行
40             {
41                 x += 1;
42                 path.insert(x, y);
43             } else if (MAZE[x][y - 1] == 0)  // 向左,0表示可行
44             {
45                 y -= 1;
46                 path.insert(x, y);
47             } else if (MAZE[x][y + 1] == 0) // 向右,0表示可行
48             {
49                 y += 1;
50                 path.insert(x, y);
51             } else if (chkExit(x, y, ExitX, ExitY) == 1) // 检查是否到底出口,到底直接退出
52                 break;
53             else {
54                 MAZE[x][y] = 2;  // 走过的路径
55                 path.delete(); // 删除节点
56                 x = path.last.x; // 返回重试
57                 y = path.last.y;
58             }
59         }
60         System.out.print("[老鼠走过的路径(2标记的部分)]\n");
61         for (i = 0; i < 10; i++) {
62             for (j = 0; j < 12; j++)
63                 System.out.print(MAZE[i][j]);
64             System.out.print("\n");
65         }
66     }
67 
68     // 检查是否到底出口
69     public static int chkExit(int x, int y, int ex, int ey) {
70         if (x == ex && y == ey) {
71             if (MAZE[x - 1][y] == 1 || MAZE[x + 1][y] == 1 || MAZE[x][y - 1] == 1 || MAZE[x][y + 1] == 2)
72                 return 1;
73             if (MAZE[x - 1][y] == 1 || MAZE[x + 1][y] == 1 || MAZE[x][y - 1] == 2 || MAZE[x][y + 1] == 1)
74                 return 1;
75             if (MAZE[x - 1][y] == 1 || MAZE[x + 1][y] == 2 || MAZE[x][y - 1] == 1 || MAZE[x][y + 1] == 1)
76                 return 1;
77             if (MAZE[x - 1][y] == 2 || MAZE[x + 1][y] == 1 || MAZE[x][y - 1] == 1 || MAZE[x][y + 1] == 1)
78                 return 1;
79         }
80         return 0;
81     }
82 }
复制代码

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[迷宫的路径(0标记的部分)]
111111111111
100011111111
111011000011
111011011011
111000011011
111011011011
111011011011
111111011011
110000001001
111111111111
[老鼠走过的路径(2标记的部分)]
111111111111
122211111111
111211222211
111211211211
111222211211
111211011211
111211011211
111111011211
110000001221
111111111111

  

 

python语言:非栈

复制代码
 1 '''
 2 老鼠走迷宫是一个经典的计算机科学和算法问题,通常使用深度优先搜索(DFS)或广度优先搜索(BFS)等算法来解决。
 3 下面我将使用Python来实现一个简单的老鼠走迷宫的解决方案,采用深度优先搜索算法。
 4     首先,我们需要一个迷宫的表示方法。通常,我们可以使用二维数组来表示迷宫,其中0表示可以通过的路径,1表示墙,2表示老鼠的路径,3表示已经探索过但无法通行的路径。
 5 '''
 6 # 模拟迷宫
 7 maze = [
 8     [0, 1, 0, 0, 0],
 9     [0, 1, 0, 1, 0],
10     [0, 0, 0, 1, 0],
11     [1, 1, 1, 1, 0],
12     [0, 0, 0, 0, 0]
13 ]
14 
15 
16 def solve_maze(maze, start, end):
17     rows = len(maze)
18     cols = len(maze[0])
19 
20     def is_valid(x, y):  # 验证是否可行
21         # return 0 <= x < rows and 0 <= y < cols and maze[x][y] == 0
22         return rows > x >= 0 == maze[x][y] and 0 <= y < cols
23 
24     def dfs(x, y):
25         if x == end[0] and y == end[1]:
26             return True  # 已经到达终点
27         if is_valid(x, y):
28             maze[x][y] = 2  # 将当前位置标记为老鼠的路径
29 
30             # 尝试向四个方向移动
31             if (dfs(x + 1, y) or
32                     dfs(x - 1, y) or
33                     dfs(x, y + 1) or
34                     dfs(x, y - 1)):
35                 return True
36 
37             # 如果四个方向都无法通行,将当前位置标记为已探索过但无法通行
38             maze[x][y] = 3
39             return False
40 
41         return False
42 
43     if dfs(start[0], start[1]):
44         return maze  # 返回包含老鼠路径的迷宫
45     else:
46         return None  # 如果没有找到路径,返回None
47 
48 
49 # 测试
50 start = (0, 0)
51 end = (4, 4)
52 result = solve_maze(maze, start, end)
53 
54 if result:
55     for row in result:
56         print(row)
57 else:
58     print("没有找到路径")
复制代码

输出:

[2, 1, 2, 2, 2]
[2, 1, 2, 1, 2]
[2, 2, 2, 1, 2]
[1, 1, 1, 1, 2]
[0, 0, 0, 0, 0]

 

javascript语言:用深度优先搜索(DFS)算法来解决迷宫问题

函数接受一个迷宫矩阵作为输入,矩阵中包含了障碍物(1)、可通行路径(0)和已访问路径(2)。函数的目标是找到从迷宫的起点到终点的路径,并返回包含路径的迷宫矩阵,或者如果没有可行路径,则返回 "No solution found."。

复制代码
 1 function solveMaze(maze) {
 2     const rows = maze.length;
 3     const cols = maze[0].length;
 4     const directions = [
 5         [-1, 0], //
 6         [1, 0],  //
 7         [0, -1], //
 8         [0, 1]   //
 9     ];
10 
11     function isSafe(x, y) {
12         return x >= 0 && x < rows && y >= 0 && y < cols && maze[x][y] === 0;
13     }
14 
15     function dfs(x, y) {
16         maze[x][y] = 2; // 标记当前位置为已访问
17         if (x === rows - 1 && y === cols - 1) {
18             // console.log(x,rows-1)
19             // console.log(y,cols-1)
20             return true; // 找到了出口
21         }
22 
23 
24         for (const [dx, dy] of directions) {
25             const newX = x + dx;
26             const newY = y + dy;
27 
28             if (isSafe(newX, newY)) {
29                 if (dfs(newX, newY)) {
30                     return true; // 如果从新位置找到出口,则返回true
31                 }
32             }
33         }
34 
35         maze[x][y] = 0; // 如果当前路径不通,则回溯
36         return false;
37     }
38 
39     if (dfs(0, 0)) {   // 从0,0开始
40         return maze; // 返回包含路径的迷宫
41     } else {
42         return "No solution found.";
43     }
44 }
45 
46 const maze = [
47     [0, 1, 0, 0, 0],
48     [0, 1, 0, 1, 0],
49     [0, 0, 0, 0, 0],
50     [0, 1, 1, 1, 0],
51     [0, 0, 0, 1, 0]
52 ];
53 
54 const solvedMaze = solveMaze(maze);
55 if (typeof solvedMaze === "string") {
56     console.log(solvedMaze); // 输出 "No solution found."
57 } else {
58     for (const row of solvedMaze) {
59         console.log(row.join(" "));
60     }
61 }
复制代码

输出结果:

1
2
3
4
5
2 1 2 2 2
2 1 2 1 2
2 2 2 0 2
0 1 1 1 2
0 0 0 1 2

  

解释:

  1. solveMaze(maze) 函数接受迷宫矩阵 maze 作为输入参数。

  2. 定义了 rowscols 变量来获取迷宫的行数和列数。

  3. directions 数组定义了四个方向,用于在迷宫中移动,分别是上、下、左、右。每个方向都表示为一个二维数组 [dx, dy],其中 dx 表示在行上的移动,dy 表示在列上的移动。

  4. isSafe(x, y) 函数用于检查给定的坐标 (x, y) 是否是一个安全的位置,即不超出迷宫边界并且没有障碍物(值为0表示可通行)。

  5. dfs(x, y) 是深度优先搜索函数,它从坐标 (x, y) 开始搜索路径。首先,它检查是否已经到达了终点 (rows - 1, cols - 1),如果是,则返回 true 表示找到了出口。然后,它将当前位置标记为已访问(值为2)。接下来,它遍历四个方向,检查每个方向上的新坐标 (newX, newY) 是否是一个安全的位置,如果是,则递归调用 dfs 函数。如果在任何一个方向上找到了出口,函数会返回 true,表示已经找到通往出口的路径。如果四个方向都没有找到通往出口的路径,函数会将当前位置标记为可通行(值为0),然后返回 false,表示当前路径不通,需要回溯。

  6. 在函数的最后,如果 dfs(0, 0) 返回 true,则说明找到了通往出口的路径,函数返回包含路径的迷宫矩阵;否则,返回 "No solution found." 表示没有可行路径。

  7. 最后,代码检查返回值的类型,如果是字符串,则输出 "No solution found.",否则,遍历迷宫矩阵并将每一行以空格分隔后输出,显示找到的路径。

在给定的示例迷宫中,如果有可行路径,那么函数将返回包含路径的迷宫矩阵,否则会输出 "No solution found."。

posted @   Allen_Hao  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示