2024年10月29日 星期二

考慮運算符優先順序

 考慮運算符的優先順序(乘除優先於加減),可以使用「中序轉後序」的技巧,即將中序表達式(如 1 + 2 * 3)轉換為後序表達式(如 1 2 3 * +),然後再依照後序表達式的順序來進行計算。

這裡,我將提供一個完整的程式碼,首先把中序表達式轉換為後序表達式,然後對後序表達式進行計算:

程式碼

# 輸入數學表達式,例如:"1 + 2 * 3"
expression = input("輸入數學表達式,例如 '1 + 2 * 3': ")

# 步驟 1:把數字和符號分開
tokens = []
for i in expression.split():
    if i.isdigit():           # 如果 i 是數字
        tokens.append(int(i))  # 把數字轉為 int 並加入 tokens
    else:
        tokens.append(i)       # 把符號直接加到 tokens

# 儲存後序表達式的結果
output = []
# 儲存運算符的堆疊
operators = []

# 定義運算符的優先順序
precedence = {'+': 1, '-': 1, '*': 2, '/': 2}

# 步驟 2:轉換為後序表達式
for token in tokens:
    if type(token) == int:       # 如果是數字
        output.append(token)     # 直接加入輸出
    else:                        # 如果是運算符
        # 當堆疊中的運算符優先順序比當前運算符高或相等,彈出它們
        while operators and precedence[operators[-1]] >= precedence[token]:
            output.append(operators.pop())
        operators.append(token)  # 把當前運算符推入堆疊

# 把剩下的運算符都彈出
while operators:
    output.append(operators.pop())

# 後序表達式已生成
print("後序表達式:", output)

# 步驟 3:計算後序表達式的結果
stack = []

for token in output:
    if type(token) == int:   # 如果是數字,推入堆疊
        stack.append(token)
    else:                    # 如果是運算符,取出兩個數字計算
        b = stack.pop()      # 堆疊頂部的數字為 b
        a = stack.pop()      # 再取出一個數字為 a
        if token == '+':
            stack.append(a + b)
        elif token == '-':
            stack.append(a - b)
        elif token == '*':
            stack.append(a * b)
        elif token == '/':
            stack.append(a / b)  # 確保除數不為 0

# 最後堆疊中只剩一個結果,即為最終計算結果
result = stack[0]
print("結果:", result)

程式說明

  1. 解析輸入:把數字和運算符分開,並存入 tokens 列表中。數字轉為 int,而運算符則保留為字串。

  2. 轉換為後序表達式

    • 我們使用一個 output 列表來存儲後序表達式,並使用 operators 堆疊來管理運算符的優先順序。
    • 當遇到運算符(例如 +-*/)時,先比較堆疊頂端運算符的優先順序,若優先順序高或相等,就彈出堆疊頂部運算符並加入到 output
    • 當前運算符推入 operators 堆疊。
    • 這樣做的目的是讓乘除優先於加減,並在後序表達式中表現出來。
  3. 計算後序表達式

    • 使用 stack 堆疊來計算後序表達式。
    • 遇到數字時,直接推入 stack
    • 遇到運算符時,彈出兩個數字,依次計算並把結果推回堆疊。
    • 最後,stack 中唯一剩下的數字就是計算結果。

範例執行

輸入:

1 + 2 * 3

執行過程:

  1. tokens 被解析為 [1, '+', 2, '*', 3]
  2. 轉換後的後序表達式:[1, 2, 3, '*', '+']
  3. 後序計算:
    • 2 * 3 = 6
    • 1 + 6 = 7

輸出:

後序表達式: [1, 2, 3, '*', '+']
結果: 7

不考慮運算符優先順序

 不考慮運算符的優先順序,而是簡單地從左到右計算,無論是加、減、乘或除,那麼程式碼可以進一步簡化。我們只需要將數字和運算符按順序排列,然後從左到右依次進行計算。

以下是簡化後的程式碼:

# 輸入數學表達式,例如:"1 + 2 * 3"
expression = input("輸入數學表達式,例如 '1 + 2 * 3': ")

# 步驟 1:把數字和符號分開
tokens = []
for i in expression.split():
    if i.isdigit():           # 如果 i 是數字
        tokens.append(int(i))  # 把數字轉為 int 並加入 tokens
    else:
        tokens.append(i)       # 把符號直接加到 tokens

# 步驟 2:從左到右依次計算
result = tokens[0]  # 初始值為第一個數字
i = 1               # 從第二個項目開始處理

while i < len(tokens):
    operator = tokens[i]       # 取得運算符
    next_number = tokens[i + 1] # 取得下一個數字

    # 根據運算符進行對應的計算
    if operator == '+':
        result += next_number
    elif operator == '-':
        result -= next_number
    elif operator == '*':
        result *= next_number
    elif operator == '/':
        result /= next_number

    i += 2  # 移動到下一組運算符和數字

# 結果
print("結果:", result)

說明

  1. 解析輸入:把表達式中的數字和運算符分開存入 tokens 列表中。數字轉為 int 型別,運算符則保留為字串。

  2. 從左到右依次計算

    • 取 tokens 中的第一個數字作為初始結果 result
    • 從第二個元素開始,依次讀取運算符和數字對。
    • 根據運算符進行相應的計算(加、減、乘、除),並將計算結果更新到 result
    • 重複以上步驟,直到處理完整個 tokens 列表。
  3. 輸出結果:最終的 result 即為計算結果。

範例執行

輸入:

1 + 2 * 3

執行過程:

  • tokens 會被解析為 [1, '+', 2, '*', 3]
  • 從左到右計算:
    1. 1 + 2 = 3
    2. 3 * 3 = 9

輸出:

結果: 9

2024年10月24日 星期四

0/1 背包問題動態規劃解法

  0/1 背包問題 動態規劃解法。下面是對程式的逐步解釋及執行追踪。

def knapsack(weights,values,capacity):

    n = len(weights)

    dp = [[0]*(capacity+1) for _ in range(n+1)]

    

    for i in range(1,n+1):

        for w in range(capacity+1):

            if weights[i-1]<=w:

                dp[i][w]=max(dp[i-1][w],dp[i-1][w-weights[i-1]]+values[i-1])

            else:

                dp[i][w] = dp[i-1][w]

    return dp[n][capacity]


weights = [1,2,3,4,5]

values = [6,10,7,11,14]

capacity = 12


print(knapsack(weights, values, capacity))


# 執行結果

# 41


程式結構與邏輯

  1. 初始化

    • n = len(weights): 計算物品的數量。
    • dp = [[0] * (capacity + 1) for _ in range(n + 1)]: 創建一個二維列表 dp,其中 dp[i][w] 表示當考慮前 i 個物品,背包容量為 w 時的最大價值。每個元素都初始化為 0。
  2. 動態規劃狀態轉移

    • 使用兩層迴圈:
      • 外層迴圈 for i in range(1, n + 1): 用來遍歷每個物品。
      • 內層迴圈 for w in range(capacity + 1): 用來遍歷每個背包容量(從 0 到 capacity)。
    • 狀態轉移方程:
      • 如果第 i 個物品的重量小於等於當前背包容量 w,則有兩種選擇:
        1. 不放入 第 i 個物品:dp[i][w] = dp[i-1][w]
        2. 放入 第 i 個物品:dp[i][w] = dp[i-1][w - weights[i-1]] + values[i-1]
      • 取這兩者的最大值:dp[i][w] = max(dp[i-1][w], dp[i-1][w - weights[i-1]] + values[i-1])
      • 如果第 i 個物品的重量超過當前背包容量 w,只能選擇不放入:dp[i][w] = dp[i-1][w]
  3. 結果

    • 返回 dp[n][capacity],這代表當考慮所有物品,且背包容量為 capacity 時,能獲得的最大價值。

追踪程式執行

以下是使用 weights = [1, 2, 3, 4, 5]values = [6, 10, 7, 11, 14]capacity = 12 時的 dp 表填充過程。

物品編號 (i)重量 (weights)價值 (values)
116
2210
337
4411
5514

初始化 dp 表

dp 為一個 6 x 13 的二維列表,全都初始化為 0。

填表過程

  1. 考慮第 1 個物品 (重量=1, 價值=6)

    • 當 w >= 1 時,可以選擇放入,因此 dp[1][w] = 6
    • 填充結果:dp[1] = [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
  2. 考慮第 2 個物品 (重量=2, 價值=10)

    • 當 w < 2 時,無法放入,dp[2][w] = dp[1][w]
    • 當 w >= 2 時,有兩種選擇:
      • 不放入:dp[2][w] = dp[1][w]
      • 放入:dp[2][w] = dp[1][w-2] + 10
    • 填充結果:dp[2] = [0, 6, 10, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]
  3. 考慮第 3 個物品 (重量=3, 價值=7)

    • 當 w < 3 時,無法放入,dp[3][w] = dp[2][w]
    • 當 w >= 3 時,有兩種選擇:
      • 不放入:dp[3][w] = dp[2][w]
      • 放入:dp[3][w] = dp[2][w-3] + 7
    • 填充結果:dp[3] = [0, 6, 10, 16, 17, 23, 23, 23, 23, 23, 23, 23, 23]
  4. 考慮第 4 個物品 (重量=4, 價值=11)

    • 當 w < 4 時,無法放入,dp[4][w] = dp[3][w]
    • 當 w >= 4 時,有兩種選擇:
      • 不放入:dp[4][w] = dp[3][w]
      • 放入:dp[4][w] = dp[3][w-4] + 11
    • 填充結果:dp[4] = [0, 6, 10, 16, 17, 23, 23, 27, 27, 33, 33, 33, 33]
  5. 考慮第 5 個物品 (重量=5, 價值=14)

    • 當 w < 5 時,無法放入,dp[5][w] = dp[4][w]
    • 當 w >= 5 時,有兩種選擇:
      • 不放入:dp[5][w] = dp[4][w]
      • 放入:dp[5][w] = dp[4][w-5] + 14
    • 填充結果:dp[5] = [0, 6, 10, 16, 17, 23, 24, 27, 30, 33, 34, 37, 41]

最終結果

dp[5][12] = 41,表示在考慮所有 5 個物品,且背包容量為 12 時,能得到的最大價值是 41

總結

這段程式使用了動態規劃的二維表來記錄不同狀況下的最佳解,每一步都基於前一步的計算結果,保證了時間複雜度為 ( O(n \cdot capacity) ),非常適合解決這類 0/1 背包問題

使用遞迴控制輸出次數

 # 使用遞迴控制輸出次數

def f(n):

    print(n,' love python')

    if n==1:return 

    return f(n-1)


n = 3

f(n)


# 執行結果

# 3  love python

# 2  love python

# 1  love python

Longest Increasing Subsequence (LIS) 問題

  Longest Increasing Subsequence (LIS) 問題,並使用二維串列來儲存和計算最長遞增子序列。以下是詳細的程式解釋。

程式邏輯解釋

  1. 函式定義與初始化

    def lis(arr):
        n = len(arr)
        dp = [[i] for i in arr]
    
    • arr 是輸入的數列,例如 [1, 3, 8, 5, 6, 7, 4, 9, 2]
    • n 是數列的長度。
    • dp 是一個二維串列,每個 dp[i] 都初始化為 [arr[i]],表示只包含當前元素的子序列。這意味著在最壞情況下,每個元素本身就是一個長度為 1 的子序列。
  2. 填充 dp 表格

    for i in range(1, n):
        for j in range(0, i):
            if arr[j] < arr[i] and len(dp[j]) + 1 > len(dp[i]):
                dp[i] = dp[j] + [arr[i]]
    
    • 使用雙重迴圈來比較元素:
      • 外層迴圈 i:從 1 到 n-1,遍歷每個元素。
      • 內層迴圈 j:遍歷從 0 到 i-1 的所有元素。
    • 更新邏輯
      • 如果 arr[j] < arr[i],表示可以從 j 延伸出一個遞增序列。
      • 並且如果 dp[j] 的長度加 1 大於 dp[i] 的長度,則更新 dp[i] 為 dp[j] + [arr[i]],表示將 arr[i] 添加到 dp[j] 的序列中,形成更長的 LIS。
      • 這樣做會保證每一個 dp[i] 都儲存從開頭到 i 位置的最長遞增子序列。
  3. 找出最長的 LIS

    longest_lis = max(dp, key=len)
    return longest_lis, len(longest_lis)
    
    • 使用 max(dp, key=len) 來找出 dp 中長度最長的子序列。
    • 返回最長的遞增子序列及其長度。
  4. 測試程式碼

    arr = [1, 3, 8, 5, 6, 7, 4, 9, 2] 
    str1, len1 = lis(arr)
    print(str1, len1)
    
    • 給定數列 arr = [1, 3, 8, 5, 6, 7, 4, 9, 2],調用 lis 函式。
    • 打印最長遞增子序列及其長度。

執行結果:

[1, 3, 5, 6, 7, 9] 6

解釋:

  1. 初始化dp 初始為 [[1], [3], [8], [5], [6], [7], [4], [9], [2]]
  2. 逐步更新 dp
    • 當 i = 2 (arr[2] = 8) 時,j = 1 (arr[1] = 3) 使得 dp[2] 更新為 [1, 3, 8]
    • 當 i = 3 (arr[3] = 5) 時,j = 1 (arr[1] = 3) 使得 dp[3] 更新為 [1, 3, 5]
    • 當 i = 5 (arr[5] = 7) 時,j = 4 (arr[4] = 6) 使得 dp[5] 更新為 [1, 3, 5, 6, 7]
    • 最後,當 i = 7 (arr[7] = 9) 時,j = 5 (arr[5] = 7) 使得 dp[7] 更新為 [1, 3, 5, 6, 7, 9]
  3. 找出最長 LIS:在 dp 中長度最長的子序列是 [1, 3, 5, 6, 7, 9],長度為 6

總結:

  • 使用二維 dp:每個 dp[i] 保存從開頭到 i 位置的最長遞增子序列。
  • 更新邏輯:透過比較前面所有的元素,動態更新 dp[i] 來找到新的 LIS。
  • 時間複雜度:O(n²) 由於雙重迴圈的存在。
  • 優點:可以直接得到實際的 LIS,而不需要另外反向追踪。