這是啥?
先上鏈接
exe附源碼一份
鏈接: https://pan.baidu.com/s/1VQ6zlxCDqsGMtEuMc7bynA 提取碼: fx6z
C版本
Python版,GUI界面
問題來了,標題是什么意思
(以下皆C語言版本,python版本源碼在github,上面已給出)
嚴教:簡單的說就是 拿n張牌,從這n張牌里分出兩組任意數量的牌,加起來總和相等的話就能給玩家
前幾天玩三國殺,朋友抽了個張菖蒲出來,一開始還好,就拿4張牌讓我算,差不多看一眼就出答案了
直到有一次他掏出8張牌,1分鍾的技能持續時間,我硬是一種都沒算出來。一氣之下寫了個程序來算
把這個技能轉換成算法題就是這個意思:

在游戲里追求牌多,所以同樣的點數和就只輸出了一種
算法分析
因為是寫來在游戲里用的,以輕量級為主,我這里使用C語言編寫
- 不多說,上來就是導入stdio
#include<stdio.h>
這題第一眼看過去就能想到是一種排列組合。不過先別急,分析一下先。要在n張牌里分出兩組牌,令第一組牌為A,第二組為B
首先要搞清楚,並不是所有的牌都參與到分組中,也就是說可以有多余的牌,那么A與B的關系就不是簡單的A+B=n了,所以A和B兩組牌是分別進行的排列組合。
解決排列組合最方便的算法應該就是深搜了,如果對深搜不熟悉或者沒聽過的話可以去CSDN搜索(dfs、深度優先搜索)。CSDN上有很多關於深搜的講解
既然清楚了是兩個排列組合,那么就決定使用兩個深搜。先定義幾個全局變量
#define MAXLENTH 16 // 全局變量 int a[MAXLENTH] = { 0 }; // 輸入數組 int b[MAXLENTH] = { 0 }; // 0-未出現 1-第一組 2-第二組 int c[10000] = { 0 }; // 記錄sum int sum1 = 0; int sum2 = 0; int n; int k = 0; // c數組下標 int t = 1; // 深度1 int t2 = 1; // 深度2 // 函數聲明 void dfs(int i, int x); // 深搜-第一組 void dfs2(int i, int x); // 深搜-第二組 void pt(); // 輸出
- MAXLENTH → 自定義的輸入最大長度
- a數組 → 輸入數據
- b數組 → a數組中各個數據的狀態
- c數組 → 已出現過的牌堆和
- sum1 → 牌堆1的點數和
- sum2 → 牌堆2的點數和
- n → 輸入數據個數
- k → 當前c數組長度
- t / t2 → 兩個深搜函數的最大到達深度
這里先不解釋這些變量的作用,能看懂當我沒說,我也覺得我寫的好亂,應該是那時候太急了。
先來看輸入函數pt
void pt() { int x = 1; for (int i = 0; i < 2; i++) { for (int j = 0; j < n; j++) if (b[j] == x) printf("%d ", a[j]); printf("\n"); x++; } printf("\n"); }
為什么
i
是0到1? (*`д´*) 這都看不出來是輸出兩組數嗎那
j
呢?j
不就是遍歷數組嗎x
是什么,x
是狀態,定義變量的時候注釋了b
數組的3中狀態,x
就是特定的一種狀態,當x
等於1的時候, 也就是i
等於0的那次循環,輸出b[j]
等於1的a[j]
,也就是第一組數據然后
x++
,在輸出第二組數應該不難理解,這里提一下就是先知道
b[j]
的3種狀態分別表示什么接下來看看深搜部分。理解題目之后應該清楚,在例子
1 2 3 4 5 6
中,3
與1 2
是一對,4 6
和2 3 5
也是一對,所以兩組數各自的長度是未知的,可以是一個數、兩個數、三個數……那么我們每次排列組合當然需要一個深度咯
int t = 1; // 深度1 int t2 = 1; // 深度2
開頭定義的兩個全局變量
t
t2
分別就是兩個深搜的深度假設有n個數,那么開始
t
t2
都是1,也就是只有一層深度,兩組數都是只有一個數,分別都是遍歷了整個a
數組,而且不能重復還是看圖直觀吧
第一個深搜一個一個慢慢遍歷,深搜1每次指向一個數的時候深搜2都要跑一遍整個數組,每一次都判斷總和是否相等,當然如果某個數已經被選中了就不選了(也就是b數組對應的下標不是0了)。
否則的話就選中(更改b數組中的這個位置的數為1或者2,1就是第一組,2就是第二組)
當然圖里紅線藍線都經過了a[0],不是畫錯了,因為遍歷可不管你這個位置b是多少,只有判斷才管
像這樣藍線走完一次之后,紅色箭頭像下走一格,然后藍線繼續老操作。
是不是很簡單,但是這才第一層
如果我第二組要兩個數呢
還是畫個圖
第一組還是一個數,所以還是老的做法,紅色箭頭每指向一個新的數,綠色箭頭要依次遍歷a
數組,並取出這個數來作為第二組的其中一個數,然后進行第二次深搜,也就是藍線
這么說可能有點難理解,換個形式就是
for(){
for(){
}
}
這樣的嵌套遍歷,第一個for取一個數,第二for也取一個數
這兩個不同的數共同作為第二組數
如果你不懂深搜可能要問了,那為什么不直接用for?深搜是什么?
別急,這里還只是1-2(下面都這樣表示兩組數分別的數量,左邊表示第一組的數,右邊表示第二組)
首先我們要明確一點,for循環是定死的,獲取可以用break來減少循環,但你總不能增加循環吧。
寫了4個for運行能來5個for?
其次,基數小的時候確實寫for方便,但數量一大呢,例如我開頭說的給我了8張牌,4-4甚至1-7的時候呢,寫7個循環?不現實吧。
所以我們要用深搜
直接上代碼
void dfs(int i, int x) {
if(x == t){
return;
}
while (i < n) {
if (b[i] == 0) {
b[i] = 1;
sum1 += a[i];
dfs(i + 1, x + 1);
b[i] = 0;
sum1 -= a[i];
}
i++;
}
};
這是一個最典型的深搜,當然我還沒加一些別的條件
僅僅是對一組數排列組合
第一個if
不用說了,就是深度(也就是遞歸跳出條件)
i
就是當前遍歷的下標
while中如果當前下標沒被選擇那么就選中它並且表明是1(第一組),同時使這一組數的和sum1
加上這個數,然后繼續調用自身進行搜索,出來之后也就是要換目標了,於是讓這個位置的b
重置為0,表示未選中,再讓sum1
減去這個數
這就是個簡單的排列組合,所以第二組的深搜也同樣可以調用這個模塊
void dfs(int i, int x) {
if (x == t) {
for (int j = 1; j <= n - t; j++) {
sum2 = 0;
t2 = j;
dfs2(0, 0);
}
return;
}
// 模塊
while (i < n) {
if (b[i] == 0) {
b[i] = 1;
sum1 += a[i];
dfs(i + 1, x + 1);
b[i] = 0;
sum1 -= a[i];
}
i++;
}
};
void dfs2(int i, int x) {
if (x == t2) {
if (sum1 == sum2) {
int m;
for (m = 0; m <= k; m++)
if (c[m] == sum1) break;
if (m >= k) {
c[k] = sum1;
k++;
printf("第%d種\n", k);
pt();
}
}
return;
}
// 模塊
while (i < n) {
if (b[i] == 0) {
b[i] = 2;
sum2 += a[i];
dfs2(i + 1, x + 1);
b[i] = 0;
sum2 -= a[i];
}
i++;
}
}
上面我已經把相同的模塊標注出來了,要注意的是里面的部分變量要修改成對應的變量
下面來看他們不同的部分
dfs ( 第一組 ) 中:
if (x == t) {
for (int j = 1; j <= n - t; j++) {
sum2 = 0;
t2 = j;
dfs2(0, 0);
}
return;
}
x
等於t
時也就是到達當前最大深度時執行這些代碼
for
循環從1到n-t
,n-t
就是剩下幾個數,所以第二個深搜的深度可以從1到n-t
,每一次循環重置sum2
並且改變t2
,調用第二組的深搜。
應該不難理解,就是以第一組為主,第一組每找到一種排列組合,就要讓第二組找出他的所有排列組合情況
dfs2 ( 第二組 ) 中:
if (x == t2) {
if (sum1 == sum2) {
int m;
for (m = 0; m <= k; m++)
if (c[m] == sum1) break;
if (m >= k) {
c[k] = sum1;
k++;
printf("第%d種\n", k);
pt();
}
}
return;
}
x
等於t2
,第二組的深搜到達當前最大深度,要知道我們前置條件就是第一組已經到達最大深度,所以這時候就可以開始比較了。
如果兩個sum
相等的話,首先當然要遍歷c
數組看看有沒有這個和,如果有就直接跳過了,沒有的話繼續執行下面的代碼。
注意k是全局變量,我們不動他的時候他就是個定值,用來表示c
數組的長度在合適不過了
話題回來,如果還沒有過這個和,那么就向c
數組中添加這個值,並且輸出。
兩個函數部分就到這里結束了,細心的話能發現目前為止我們的第一組始終只有一個數,因為我們的
t
沒變過這個實現方法很多,我是寫在主函數中,所以放到了最后
int main() { while(1){ printf("輸入n ( 0結束 ): "); scanf("%d", &n); if (!n) break; printf("輸入數字序列:"); for (int i = 0; i < n; i++) { scanf("%d", &a[i]); } /************************/ for (int i = 1; i <= n; i++) { sum1 = 0; t = i; dfs(0, 0); } /************************/ // 初始化 for (int i = 0; i <= k; i++) { c[i] = 0; k = 0; } for (int i = 0; i <= n; i++) { a[i] = 0; b[i] = 0; } } }
*號包圍起來的部分就是不斷的改變
t
的值來使第一組數的數量發生變化,不多講了其他部分的代碼應該也不難懂。
講解就到這了,下面附上完整代碼
完整代碼
#include<stdio.h>
int a[16] = { 0 }; // 輸入數組
int b[16] = { 0 }; // 0-未出現 1-第一組 2-第二組
int c[10000] = { 0 }; // 記錄sum
int sum1 = 0;
int sum2 = 0;
int n;
int k = 0; // c數組下標
int t = 1; // 深度1
int t2 = 1; // 深度2
void dfs(int i, int x); // 深搜-第一組
void dfs2(int i, int x); // 深搜-第二組
void pt(); // 輸出
int main() {
while(1){
printf("輸入n ( 0結束 ): ");
scanf("%d", &n);
if (!n) break;
printf("輸入數字序列:");
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; i++) {
sum1 = 0;
t = i;
dfs(0, 0);
}
// 初始化
for (int i = 0; i <= k; i++) {
c[i] = 0;
k = 0;
}
for (int i = 0; i <= n; i++) {
a[i] = 0;
b[i] = 0;
}
}
}
void dfs(int i, int x) {
if (x == t) {
for (int j = 1; j <= n - t; j++) {
sum2 = 0;
t2 = j;
dfs2(0, 0);
}
return;
}
while (i < n) {
if (b[i] == 0) {
b[i] = 1;
sum1 += a[i];
dfs(i + 1, x + 1);
b[i] = 0;
sum1 -= a[i];
}
i++;
}
};
void dfs2(int i, int x) {
if (x == t2) {
if (sum1 == sum2) {
int m;
for (m = 0; m <= k; m++)
if (c[m] == sum1) break;
if (m >= k) {
c[k] = sum1;
k++;
printf("第%d種\n", k);
pt();
}
}
return;
}
while (i < n) {
if (b[i] == 0) {
b[i] = 2;
sum2 += a[i];
dfs2(i + 1, x + 1);
b[i] = 0;
sum2 -= a[i];
}
i++;
}
}
void pt() {
int x = 1;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < n; j++)
if (b[j] == x) printf("%d ", a[j]);
printf("\n");
x++;
}
printf("\n");
}