【動態規劃】詳解 0-1背包問題

文章目錄

  • 1. 問題引入
  • 2. 從 dfs 到動態規劃
  • 3. 動態規劃過程分析
  • 4. 二維 dp 的遍歷順序
  • 5. 從二維數組到一維數組
  • 6. 一維數組的遍歷次序
  • 7. 背包的遍歷順序
  • 8. 代碼總結
  • 9. 總結


1. 問題引入

0-1 背包是比較經典的動態規劃問題,這里以代碼隨想錄里面的例子來介紹下。總的來說就是:有 n 個物品和一個重量為 w 的背包,這 n 個物品中第 i 個物品的重量為 w[i],價值為 v[i],那么這個背包能裝下的物品最大價值是多少,注意一個物品只能選一次。
在這里插入圖片描述


2. 從 dfs 到動態規劃

我們來把問題具體化,假設現在有一個重量為 4 的背包,有 3 個物品,物品的重量和價值如下:

重量價值
物品 0115
物品 1320
物品 2430

那么現在將物品裝入背包,能裝入的物品的最大價值是多少呢?要想解決動態規劃問題,我們總得從 dfs 入手,那就先從 dfs 講講。

public class Main {public static void main(String[] args) {Main main = new Main();main.findMax(new int[]{1, 3, 4}, new int[]{15, 20, 30}, 4);main.findMax(new int[]{1, 3}, new int[]{15, 20}, 3);}public void findMax(int[] weight, int[] prices, int max){int res = dfs(weight, prices, max, weight.length - 1);System.out.println("最大價值是: " + res);}private int dfs(int[] weight, int[] prices, int max, int i) {if(i < 0){// 遍歷到尾部了return 0;}// 不選當前的價值int noPick = dfs(weight, prices, max, i - 1);// 如果剩余重量大于 weight[i] 才可選return max >= weight[i] ? Math.max(prices[i] + dfs(weight, prices, max - weight[i], i - 1), noPick) : noPick;}
}

dfs 的遍歷邏輯很簡單,就是從后往前遍歷,然后對于當前物品,可以選或者不選,但是有條件,如果選的話剩余的容量必須要大于 weight[i],否則就不能選,因為剩余重量裝不下當前物品。

但是我們知道,這個方法是會超時的,如果數組長度比較大,因為里面有不少重復計算,既然這樣我們就加上記憶化搜索

public class Main {public static void main(String[] args) {Main main = new Main();main.findMax(new int[]{1, 3, 4}, new int[]{15, 20, 30}, 4);main.findMax(new int[]{1, 5}, new int[]{15, 20}, 3);}public void findMax(int[] weight, int[] prices, int max){// memo[i][j] 的意思是從 [0...i] 中將物品放到重量為 j 的背包,最大值是多少int[][] memo = new int[weight.length][max + 1];for (int[] arr : memo) {Arrays.fill(arr, -1);}int res = dfs(weight, prices, max, weight.length - 1, memo);System.out.println("最大價值是: " + res);}private int dfs(int[] weight, int[] prices, int max, int i, int[][] memo) {if(i < 0){// 遍歷到尾部了return 0;}if(memo[i][max] != -1){return memo[i][max];}// 不選當前的價值int noPick = dfs(weight, prices, max, i - 1, memo);return memo[i][max] = (max >= weight[i] ? Math.max(prices[i] + dfs(weight, prices, max - weight[i], i - 1, memo), noPick) : noPick);}
}

好了,在記憶化搜索的基礎上,我們再來改造成動態規劃,首先我們看上面的 dfs 邏輯,當前 i 的結果是基于 i - 1 得來的,也就是說我們可以得到下面的遞推公式:

  • d p [ i ] [ j ] = M a t h . m a x ( d p [ i ? 1 ] [ j ] , p r i c e s [ i ] + d p [ i ? 1 ] [ j ? w e i g h t [ i ] ] ) dp[i][j] = Math.max(dp[i-1][j], prices[i] + dp[i-1][j-weight[i]]) dp[i][j]=Math.max(dp[i?1][j],prices[i]+dp[i?1][j?weight[i]])
  • 上面的意思是如果不選當前下標 i 的物品,那么就繼續往前找
  • 如果選當前下標 i 的物品,那么價值就是 prices[i],接著 j 要減去物品 i 的價值

好了,遞推公式有了,那么如何初始化呢?我們知道 dp[i][j] 的意思是:在下標 [0…i] 中選擇物品裝入重量為 j 的背包,能裝入的最大值是多少!

  • 當 i = 0 的時候,dp[0][j] 表示下標 0 物品裝入重量為 j 的背包,最大值是多少。
  • 當 j = 0 的時候,dp[i][0] 表示下標 [0…i] 的物品裝入重量為 0 的背包,最大值是多少,自然是 0 了。

所以初始化如下:

int[][] dp = new int[weight.length][max + 1];
for(int j = 0; j <= max; j++){if(j > weight[0]){dp[0][j] = prices[i];}
}

下面再結合記憶化搜索的代碼,就能寫出來下面的動態規劃代碼。

public int findMaxD(int[] weight, int[] prices, int max){int[][] dp = new int[weight.length][max + 1];for(int j = 0; j <= max; j++){if(j >= weight[0]){dp[0][j] = prices[0];}}// 遍歷物品for(int i = 1; i < weight.length; i++){// 遍歷背包for(int j = 0; j <= max; j++){if(j < weight[i]){dp[i][j] = dp[i - 1][j];} else {dp[i][j] = Math.max(dp[i - 1][j], prices[i] + dp[i - 1][j - weight[i]]);}}}return dp[weight.length - 1][max];
}

3. 動態規劃過程分析

上面我們寫出了動態規劃的代碼,但是不知道大家有沒有疑問,就是這個物品和背包的遍歷是有順序的嗎?注意我這里指的是二維的 dp 數組,現在我們討論都是在二維 dp 的基礎上去討論,后面會逐步演變成一維 dp。

首先就是遞推公式

  • d p [ i ] [ j ] = M a t h . m a x ( d p [ i ? 1 ] [ j ] , p r i c e s [ i ] + d p [ i ? 1 ] [ j ? w e i g h t [ i ] ] ) dp[i][j] = Math.max(dp[i-1][j], prices[i] + dp[i-1][j-weight[i]]) dp[i][j]=Math.max(dp[i?1][j],prices[i]+dp[i?1][j?weight[i]]),根據這個遞推公式,

通過這個遞推公式,我們能發現 dp[i][j] 其實依賴當前格子的上邊和左上的格子
在這里插入圖片描述
那么從這個角度,我們再來看動態規劃的初始化,你就知道為什么要初始化 i = 0 和 j = 0 的格子值了(j = 0 不需要初始化,因為都是 0)。
在這里插入圖片描述
初始化完第一行之后,再從第二行開始通過遞推公式填充格子,最終填充好的結果如下所示:
在這里插入圖片描述
我用下標 (1,3)舉個例子,當 i = 1,j = 3 的時候,如果不選當前物品,那么就是 dp[0][3],如果選當前物品,那么就是 dp[1 - 1][3 - 3] + 20 = 20,兩者取最大值就是 20,遍歷順序是從左到右,從上到下


4. 二維 dp 的遍歷順序

好了,上面我們解析了 dp 數組的填充過程,那么如果是先遍歷物品,再遍歷背包,填充的過程就是 從左到右,從上到下。那么如果是先遍歷背包,再遍歷物品呢?

還是回到 dp 圖,如果先遍歷背包,再遍歷物品,其實就是從從上到下,從左到右
在這里插入圖片描述
那么我們想一下,如果是這種遍歷順序,在遍歷到 dp[1][3] 的時候,dp[0][3] 和 dp[0][0] 初始化了嗎,換句話說,當遍歷到第 i 行的時候,第 i - 1 行初始化了嗎?

從遍歷過程就能看到,其實是初始化了的,所以我們先遍歷背包,再遍歷物品也是沒問題的。如何遍歷,遍歷順序是什么就取決于當遍歷到(i,j)的時候,需要依賴的格子有沒有初始化。

public int findMaxD(int[] weight, int[] prices, int max) {int[][] dp = new int[weight.length][max + 1];for (int j = 0; j <= max; j++) {if (j >= weight[0]) {dp[0][j] = prices[0];}}// 遍歷背包for (int j = 0; j <= max; j++) {// 遍歷物品for (int i = 1; i < weight.length; i++) {if (j < weight[i]) {dp[i][j] = dp[i - 1][j];} else {dp[i][j] = Math.max(dp[i - 1][j], prices[i] + dp[i - 1][j - weight[i]]);}}}return dp[weight.length - 1][max];
}

那么我再問一句,遍歷背包能倒敘遍歷嗎?其實從 dp 數組的依賴就能看出,可以!!! 因為第 i 行的數據只和第 i - 1 行有關,和本行無關,而我們知道 dp 數組在處理到第 i 行的時候 i - 1就已經處理好了,所以愛怎么遍歷就怎么遍歷。


5. 從二維數組到一維數組

上面我們使用二維數組對 dp 進行填充了,但是大家有沒有注意到,第 i 行的結果只依賴第 i - 1 行,所以我們完全可以只使用一維數組,把 i 省略掉。相當于把 i 的結果粘貼到 i - 1行的位置,所以 dp[i] 就表示重量為 i 的容量能裝入的最大物品價值是多少 ,大體過程如下:

  • 初始化 dp[0]
  • 根據 dp[0] 初始化 dp[1]

初始化 dp[0] 的時候,重量為 0 的背包肯定是放不下任何物品的,所以不需要初始化,下面看遍歷邏輯。

public int findMax(int[] weight, int[] prices, int max){int[] dp = new int[max + 1];// 遍歷物品for(int i = 0; i < weight.length; i++){// 遍歷背包for(int j = max; j >= weight[i]; j--){dp[j] = Math.max(dp[j], dp[j - weight[i]] + prices[i]);}}return dp[max];
}

注意下 dp 公式為:

  • d p [ j ] = M a t h . m a x ( d p [ j ] , d p [ j ? w e i g h t [ i ] ] + p r i c e s [ i ] ) dp[j] = Math.max(dp[j], dp[j - weight[i]] + prices[i]) dp[j]=Math.max(dp[j],dp[j?weight[i]]+prices[i])

大家可能好奇為什么可以直接和 dp[j] 做比較,我把二維數組的 dp 粘貼過來。
在這里插入圖片描述
dp 數組初始化為 0,當 i = 0 的時候,其實就是在初始化第一行 [0,15,15,15,15]當 i = 1 的時候,記住此時 dp 記錄的是 i = 0 的結果,那么 dp[j] = Math.max(dp[j], dp[j - weight[i]] + prices[i]) 其實就是在根據 i = 0 來更新 i = 1 的數據,一直這樣遍歷下去,遍歷到最后就是我們要的結果了


6. 一維數組的遍歷次序

上面一維數組的遍歷次序是先遍歷物品,再遍歷背包,那么可以先遍歷背包,再遍歷物品嗎?也就是下面的寫法。

// 遍歷背包
for (int j = max; j >= 0; j--) {// 遍歷物品for (int i = 0; i < weight.length; i++) {if (j >= weight[i]) {dp[j] = Math.max(dp[j], dp[j - weight[i]] + prices[i]);}}
}

讓我們看下這個過程,當 j = 4 的時候,遍歷所有物品,求 dp[j] 能放下的物品的最大價值,為什么我說求 dp[j] 的最大價值,因為是倒敘遍歷,同時又是 j 在外層一直往前遍歷,所以 dp[j - weight[i]] 你就當成 0 就好了,相當于 dp[j] = Math.max(dp[j], prices[i])

所以最終求出來的結果就是當前這個重量下能放下的物品的最大價值(單個)。

所以這里的遍歷順序就得是:先遍歷物品,再遍歷背包


7. 背包的遍歷順序

public int findMax(int[] weight, int[] prices, int max){int[] dp = new int[max + 1];// 遍歷物品for(int i = 0; i < weight.length; i++){// 遍歷背包for(int j = max; j >= weight[i]; j--){dp[j] = Math.max(dp[j], dp[j - weight[i]] + prices[i]);}}return dp[max];
}

繼續回到遍歷邏輯,注意到背包是從后往前遍歷的,那么為什么不能從前往后遍歷呢?

我們仔細看下 dp 的意義,由于從二維降到一維,我們在遍歷的時候是不斷用新獲取的 dp[j] 覆蓋原來的 dp[j],還是從二維數組的 dp 看下。
在這里插入圖片描述
我上面說的意思相當于說,現在一維 dp 的數組是物品 0 所在的這行數據 [0,15,15,15,15]。當 i = 1 的時候,求出來的 20 會立馬覆蓋回數組,這時候數組是 [0,15,15,20,15],j = 3,繼續往前遍歷。

而如果緩存背包從前往后遍歷,結果會是怎么樣呢?我把物品的重量和價格粘貼過來。

重量價值
物品 0115
物品 1320
物品 2430

這次我們就看 i = 0 的遍歷結果,初始化數組全是 0。
在這里插入圖片描述

  • dp[0] = 0
  • dp[1] = Math.max(dp[1], dp[1-weight[0]] + prices[0]) = 15
  • dp[2] = Math.max(dp[2], dp[2-weight[0]] + prices[0]) = 30
  • dp[3] = Math.max(dp[3], dp[3-weight[0]] + prices[0]) = 45

最終結果就變成了:
在這里插入圖片描述
其實出現這種情況,就是完全背包的做法了,也就是一個物品被選擇了多個。

那么為什么會出現這種情況呢?其實我們還是可以從一維 dp 入手。

  • d p [ j ] = M a t h . m a x ( d p [ j ] , d p [ j ? w e i g h t [ i ] ] + p r i c e s [ i ] ) dp[j] = Math.max(dp[j], dp[j - weight[i]] + prices[i]) dp[j]=Math.max(dp[j],dp[j?weight[i]]+prices[i])

上面是一維的公式,假設現在數字組初始化為 0,那么在初始化 i = 0 的時候假設初始化了 dp[1] = 15,大家記住,這里的 dp 是實時覆蓋的,所以這時候的狀態如下:
在這里插入圖片描述
這時候 dp[0] 和 dp[1] 都計算過了并且覆蓋會原數組下標,而 dp[2]、dp[3]、dp[4] 還保留初始化的狀態,啥意思呢,換成 i - 1 和 i,意思就是這時候 dp[0] 和 dp[1] 是 i = 1 計算出來的,而 dp[2]、dp[3]、dp[4] 則還是 dp[i-1] 的狀態

我們接下來繼續看 dp[2],我們知道 dp[2] = Math.max(dp[2], dp[1] + 15) = 30,意思就是在計算 dp[2] 的時候使用到了 dp[1],而這個 dp[1] 已經被覆蓋過了,意思就是這個 dp[1] 不是 i - 1 的值了,而是 i 的值,所以就造成了多次選擇。

在二維數組中計算 dp[i] 的時候是使用 dp[i-1] 的值,因為不會被覆蓋,所以遍歷順序就無所謂。但是一維數組就不一樣的,因為會實時覆蓋,所以只能從后往前遍歷,否則就會用前面已經計算過的值來計算當前下標的值了。


8. 代碼總結

好了,到這里我們就解析完0-1背包了,分為二維和一維,其實說了這么多,大家只需要記住兩個版本就行了。

public int findMaxD(int[] weight, int[] prices, int max){int[][] dp = new int[weight.length][max + 1];for(int j = 0; j <= max; j++){if(j >= weight[0]){dp[0][j] = prices[0];}}// 遍歷物品for(int i = 1; i < weight.length; i++){// 遍歷背包for(int j = 0; j <= max; j++){if(j < weight[i]){dp[i][j] = dp[i - 1][j];} else {dp[i][j] = Math.max(dp[i - 1][j], prices[i] + dp[i - 1][j - weight[i]]);}}}return dp[weight.length - 1][max];
}

一維的遍歷如下:

public int findMax(int[] weight, int[] prices, int max){int[] dp = new int[max + 1];// 遍歷物品for(int i = 0; i < weight.length; i++){// 遍歷背包for(int j = max; j >= weight[i]; j--){dp[j] = Math.max(dp[j], dp[j - weight[i]] + prices[i]);}}return dp[max];
}

9. 總結

我們總結下二維數組和一維數組的遍歷順序:

  • 二維數組

    • 背包和物品的遍歷順序可以顛倒
    • 遍歷背包的時候可以正序和倒敘遍歷
  • 一維數組

    • 先遍歷物品,再遍歷背包
    • 遍歷背包需要倒敘遍歷





如有錯誤,歡迎指出!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/70171.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/70171.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/70171.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode每日精進:20.有效的括號

題目鏈接&#xff1a;20.有效的括號 題目描述&#xff1a; 給定一個只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判斷字符串是否有效。 有效字符串需滿足&#xff1a; 左括號必須用相同類型的右括號閉合。左括號必須以…

llama.cpp部署 DeepSeek-R1 模型

一、llama.cpp 介紹 使用純 C/C推理 Meta 的LLaMA模型&#xff08;及其他模型&#xff09;。主要目標llama.cpp是在各種硬件&#xff08;本地和云端&#xff09;上以最少的設置和最先進的性能實現 LLM 推理。純 C/C 實現&#xff0c;無任何依賴項Apple 芯片是一流的——通過 A…

Web后端 - Maven管理工具

一 Maven簡單介紹 Maven是apache旗下的一個開源項目&#xff0c;是一款用于管理和構建java項目的工具。 Maven的作用 二 Maven 安裝配置 依賴配置 依賴傳遞 依賴范圍 生命周期 注意事項&#xff1a;在同一套生命周期中&#xff0c;當運行后面的階段時&#xff0c;前面的階段都…

[LeetCode力扣hot100]-C++常用數據結構

0.Vector 1.Set-常用滑動窗口 set<char> ans;//根據類型定義&#xff0c;像vector ans.count()//檢查某個元素是否在set里&#xff0c;1在0不在 ans.insert();//插入元素 ans.erase()//刪除某個指定元素 2.棧 3.樹 樹是一種特殊的數據結構&#xff0c;力扣二叉樹相…

vite+vue3開發uni-app時低版本瀏覽器不支持es6語法的問題排坑筆記

重要提示&#xff1a;請首先完整閱讀完文章內容后再操作&#xff0c;以免不必要的時間浪費&#xff01;切記&#xff01;&#xff01;&#xff01;在使用vitevue3開發uni-app項目時&#xff0c;存在低版本瀏覽器不兼容es6語法的問題&#xff0c;如“?.” “??” 等。為了方便…

《計算機視覺》——角點檢測和特征提取sift

角點檢測 角點的定義&#xff1a; 從直觀上理解&#xff0c;角點是圖像中兩條或多條邊緣的交點&#xff0c;在圖像中表現為局部區域內的灰度變化較為劇烈的點。在數學和計算機視覺中&#xff0c;角點可以被定義為在兩個或多個方向上具有顯著變化的點。比如在一幅建筑物的圖像…

WWW 2025 | 中南、微軟提出端到端雙重動態推薦模型,釋放LLM在序列推薦中的潛力...

©PaperWeekly 原創 作者 | 殷珺 單位 | 中南大學碩士研究生 研究方向 | 大語言模型、推薦系統 論文題目&#xff1a; Unleash LLMs Potential for Sequential Recommendation by Coordinating Dual Dynamic Index Mechanism 論文鏈接&#xff1a; https://openreview.net…

c# 2025/2/17 周一

16. 《表達式&#xff0c;語句詳解4》 20 未完。。 表達式&#xff0c;語句詳解_4_嗶哩嗶哩_bilibili

數據結構與算法面試專題——堆排序

完全二叉樹 完全二叉樹中如果每棵子樹的最大值都在頂部就是大根堆 完全二叉樹中如果每棵子樹的最小值都在頂部就是小根堆 設計目標&#xff1a;完全二叉樹的設計目標是高效地利用存儲空間&#xff0c;同時便于進行層次遍歷和數組存儲。它的結構使得每個節點的子節點都可以通過簡…

iOS開發書籍推薦 - 《高性能 iOS應用開發》(附帶鏈接)

引言 在 iOS 開發的過程中&#xff0c;隨著應用功能的增加和用戶需求的提升&#xff0c;性能優化成為了不可忽視的一環。尤其是面對復雜的界面、龐大的數據處理以及不斷增加的后臺操作&#xff0c;如何確保應用的流暢性和響應速度&#xff0c;成為開發者的一大挑戰。《高性能 …

微信小程序的制作

制作微信小程序的過程大致可以分為幾個步驟&#xff1a;從環境搭建、項目創建&#xff0c;到開發、調試和發布。下面我會為你簡要介紹每個步驟。 1. 準備工作 在開始開發微信小程序之前&#xff0c;你需要確保你已經完成了以下幾個步驟&#xff1a; 注冊微信小程序賬號&…

LabVIEW 中dde.llbDDE 通信功能

在 LabVIEW 功能體系中&#xff0c;位于 C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform\dde.llb 的 dde.llb 庫占據著重要的地位。作為一個與動態數據交換&#xff08;DDE&#xff09;緊密相關的庫文件&#xff0c;它為 LabVIEW 用戶提供了與其他…

gitte遠程倉庫修改后,本地沒有更新,本地與遠程倉庫不一致

問題 &#xff1a;gitte遠程倉庫修改后&#xff0c;本地沒有更新&#xff0c;本地與遠程倉庫不一致 現象&#xff1a; [cxqiZwz9fjj2ssnshikw14avaZ rpc]$ git push Username for https://gitee.com: beihangya Password for https://beihangyagitee.com: To https://gitee.c…

組合模式詳解(Java)

一、組合模式基本概念 1.1 定義與類型 組合模式是一種結構型設計模式,它通過將對象組織成樹形結構,來表示“部分-整體”的層次關系。這種模式使得客戶端可以一致地對待單個對象和組合對象,從而簡化了客戶端代碼的復雜性。組合模式的核心在于定義了一個抽象組件角色,這個角…

LabVIEW危化品倉庫的安全監測系統

本案例展示了基于LabVIEW平臺設計的危化品倉庫安全監測系統&#xff0c;結合ZigBee無線通信技術、485串口通訊技術和傳感器技術&#xff0c;實現了對危化品倉庫的實時無線監測。該系統不僅能提高安全性&#xff0c;還能大幅提升工作效率&#xff0c;確保危化品倉庫的安全運營。…

【私人筆記】Web前端

Vue專題 vue3 vue3 頁面路徑前面添加目錄 - 路由base設置 - vite設置base https://mbd.baidu.com/ma/s/XdDrePju 修改vite.config.js export default defineConfig({base: /your-directory/,// 其他配置... }); vue2 uniapp 【持續更新】uni-app學習筆記_uniapp快速復制一…

數倉搭建:DWB層(基礎數據層)

維度退化: 通過減少表的數量和提高數據的冗余來優化查詢性能。 在維度退化中&#xff0c;相關的維度數據被合并到一個寬表中&#xff0c;減少了查詢時需要進行的表連接操作。例如&#xff0c;在銷售數據倉庫中&#xff0c;客戶信息、產品信息和時間信息等維度可能會被合并到一…

【Linux】進程間通信——進程池

文章目錄 進程池什么進程池進程池的作用 用代碼模擬進程池管道信息任務類InitProcesspool()DisPatchTasks()任務的執行邏輯&#xff08;Work&#xff09;CleanProcessPool() 封裝main.ccChannel.hppProcessPool.hppTask.hppMakefile 總結總結 進程池 什么進程池 進程池&#…

13-跳躍游戲 II

給定一個長度為 n 的 0 索引整數數組 nums。初始位置為 nums[0]。 每個元素 nums[i] 表示從索引 i 向后跳轉的最大長度。換句話說&#xff0c;如果你在 nums[i] 處&#xff0c;你可以跳轉到任意 nums[i j] 處: 0 < j < nums[i] i j < n 返回到達 nums[n - 1] 的最…

Qt的QToolBox的使用

QToolBox 是 Qt 框架中的一個控件&#xff0c;用于創建一個可折疊的“工具箱”界面&#xff08;類似 Windows 資源管理器的側邊欄&#xff09;。每個子項可以展開或折疊&#xff0c;適合用于分組顯示多個功能模塊。以下是其基本用法和示例&#xff1a; 1. 基本用法 創建并添加…