專案中遇到的一道演算法題
阿新 • • 發佈:2020-11-22
今天想和大家分享一個問題的解決辦法,這個問題是自己在專案開發的過程中遇到的。經過思考和對資料的查詢,最終想出了該問題的解法,趁著週末有點時間就把它整理並分享出來。
在描述問題之前,需要先了解涉及到的名詞概念,便於對後續內容的閱讀。
## 名稱解釋
1. sku(倉儲相關概念):Stock Keeping Unit 的首字母縮寫,指的是一個屬性確定的商品,如衣服其有兩個屬性,顏色和尺寸。那麼一件黃色的 X 碼的衣服與一件紅色的 X 碼的衣服就是不同的 sku
2. 分播槽(倉儲相關概念):一個槽口,用於存放從倉庫中揀取出來的商品
3. 集合的基數(集合相關概念):集合元素的個數稱之為集合的基數,如集合A有6個元素,那麼可以將集合A的基數記為card(A) = 6
4. 二部圖(圖論相關概念):無向圖的一種特殊模型,如果頂點 V 可分割為兩個互不相交的子集( A , B ),並且圖中的每條邊( i ,j )所關聯的兩個頂點 i 和 j 分別屬於這兩個不同的頂點集(i in A , j in B ),則稱圖 G 為一個二分圖。通俗點說,就是如果圖中點可以被分為兩組,並且使得所有邊都跨越組的邊界,則這就是一個二分圖
5. 節點的度:對於無向圖而言,指和該節點相關聯的邊的條數,又稱關聯度
有了以上概念的相關理解之後,我們理解相關問題便會容易很多。下面我們來看相關問題描述。
## 問題描述
在系統執行的過程中,每個分播槽都只能繫結一種 sku 進行分播。上游系統會下發一系列訂單到業務系統中進行執行,其中每個訂單都關聯著一系列的 sku,對於上游系統下發的訂單,業務系統如果因為當前可用分播槽數不足,無法讓每個訂單都執行到,那麼就需要選擇一部分訂單進行執行,同時回退一部分訂單給上游系統。問題就在於,如何從這上游系統下發的訂單集合中得到滿足條件的元最大的訂單子集。
為加深對問題的理解,給出如下兩個示例及其相關解釋:
***示例一:***
**訂單及其關聯的sku:**
order1:sku1,sku3,sku5,sku6
order2:sku1,sku2,sku4,sku8
order3:sku2,sku4,sku8
order4:sku1,sku2,sku3,sku4,sku5,sku6
order5:sku1,sku3
**綁定了sku的分播槽:**
分播槽1:sku1
分播槽2:sku3
**未繫結sku的分播槽口數:** 3
**返回:**{order2,order3,order5}
**解釋:**order1 關聯的 sku 需要新繫結 2 個分播槽位( sku5 , sku6 ),order2 和 order3 需要新繫結 3 個分播槽位( sku2 , sku4 , sku8 ),但是 3 個分播槽綁定了 sku2 , sku4 , sku8 之後,order2 和 order3 都可以滿足,如果綁定了 sku5 , sku6 的話,就只有 order1 才能執行。對於 order4,其關聯的 sku 需要新繫結 4 個分播槽位( sku2 , sku4 , sku5 , sku6 ),而未繫結 sku 的分播槽口數只有 3個,不能滿足其要求,為此,不對訂單 order4 進行考慮,而訂單 order5 其不需要新繫結分播槽位,就可以直接執行
***示例二:***
**訂單及其關聯的sku:**
order1:sku1,sku3
order2:sku1,sku4
order3:sku1,sku6
order4:sku2,sku5
order5:sku2,sku5
**綁定了sku的分播槽:** 無
**未繫結sku的分播槽口數:** 2
**返回:**{order4,order5}
**解釋:**對於每個訂單,其都需要新繫結 2 個分播槽位,選擇了 order1,order2,order3 中的任意一個訂單,則其都不能同時選擇其它訂單。而 order4,order5 所需揀取的sku是相同的,為此,order4 與 order5 可以同時選擇
## 解法
將訂單和 sku 看成一個個的節點,如果訂單與 sku 之間有關聯,那麼就將訂單節點和 sku 節點用一條邊連線起來,這就形成了一個無向圖。由於訂單與訂單,sku 與 sku 之間並沒有關聯,為此,無向圖的節點可以劃分為 2 個子集,一個節點子集是訂單,一個節點子集是 sku。下面我們以示例一為例,來講解該題的相關解法,對示例一中的例子,我們可以得到如下的二部圖:
![示例一的二部圖](https://cdn.jsdelivr.net/gh/MyStringIsNotNull/[email protected]/article/project/algorithm/selectOrder/fullContent.svg)
由於有些訂單所關聯的 sku 已經綁定了分播槽位,對於此類已經綁定了分播槽位的 sku 我們可以不對其進行考慮,為此,我們可以對二部圖進行一些預處理。消除掉那些已經綁定了分播槽的 sku 節點及其所關聯的邊。我們可以得到如下的二部圖:
![示例一消除已繫結sku的二部圖](https://cdn.jsdelivr.net/gh/MyStringIsNotNull/[email protected]/article/project/algorithm/selectOrder/deletedSku.svg)
我們再度觀察二部圖及其示例相關解釋,可以發現對於度超過未繫結 sku 的分播槽口數的訂單節點,其是一定無法被選擇的,為此,我們可以過濾掉那些訂單節點。我們可以得到如下的二部圖:
![示例一過濾掉不可能節點的二部圖](https://cdn.jsdelivr.net/gh/MyStringIsNotNull/[email protected]/article/project/algorithm/selectOrder/unableNode.svg)
在過濾完“不可能”訂單節點之後,我們還需要過濾掉那些“不需要處理”便可直接選擇的訂單(當然,我們需要把這部分訂單作為結果的一部分進行返回)。
![示例一過濾掉不需要處理節點的二部圖](https://cdn.jsdelivr.net/gh/MyStringIsNotNull/[email protected]/article/project/algorithm/selectOrder/undisposeNode.svg)
在經過資料的預處理之後,我們得到的訂單以及 sku 便是需要選取的,且其是符合條件約束的。
對訂單的選取,我們可以**轉化為對 sku 的選取問題**,也就轉化成了選擇“未繫結槽口”數的 sku 組成的集合,使得訂單中關聯的 sku 是所選 sku 組成集合的子集,這麼一個問題。
於是,我們可以遍歷所有“未繫結槽口”數目的 sku 組成的所有集合,並得到對應所選取 sku 集合中滿足條件的訂單數目最多的那個 sku 的集合。
對於生成所有 sku 固定元素的集合列表的方法,我們可以採用遞迴的方式進行實現,定義方法 f(n,k) 為n個元素所組成的集合中選取k個元素的所構成的集合,則 f(n,k) = j + f(s,k-1),k>0 , 其中 j 表示從 n 中選取的任意一個元素,s 表示去除掉元素 j 之後的集合。其程式碼如下:
![子集生成程式碼貼圖](https://cdn.jsdelivr.net/gh/MyStringIsNotNull/[email protected]/article/project/algorithm/selectOrder/subSetGenerater.svg)
其選單過程核心程式碼如下:
![生成所有程式碼貼圖](https://cdn.jsdelivr.net/gh/MyStringIsNotNull/[email protected]/article/project/algorithm/selectOrder/core.svg)
**時間複雜度分析:** 生成所有固定數目的sku集合的時間複雜度為 ${n \choose k}$,其中 n 為 sku 的數量,k 為未繫結槽口數。由於對每個 sku 集合,都需要遍歷一遍各訂單節點及其關聯 sku 。為此,該演算法的時間複雜度為 $mk{n \choose k}$,其中m為訂單節點的數量
## 後話
對於上面所實現的選單演算法,其是基於所有訂單在執行過程中訂單相關的 sku 繫結分播槽後就不再改變這一前提下實現的。當我們拋開這個約束條件並且在訂單執行的過程中,當所有訂單相同的 sku 都揀取之後能夠對分播槽進行解綁時,理論上來說,我們能夠執行的訂單數目就會變成無限,但是這樣就會涉及到倉儲機器人對料箱的搬運效率(因為對料箱搬運順序有要求)以及快取貨架(機器人搬運出來的料箱暫存的地方)限制的問題。當考慮到該層面時,就是整個鏈路的優化問題了,為此暫時不對其做考慮。
---
這個是本人的公眾號,致力於寫出絕大部分人都能讀懂的技術文章。歡迎相互交流,我們博採眾長,共同進步。
![](https://img2020.cnblogs.com/blog/1095776/202004/1095776-20200415171346893-966577587.jpg)