求算法:滿足條件的排列組合


假設有一個隨機數列{1,2,3,4,5....}
求其中相加為100的所數有組合,比方{{1,99},{1,2,97},{4,96}......}


自己想了一天,找不到解決辦法。求師兄們指點

10 个解决方案

#1


類似於背包問題,遞歸來實現吧.
每個數字都可以選擇2中狀態,在組合里和不在組合里.

#2


這可定是要遞歸搜索樹,問題是如何具體實現。

#3


#define ARRAY_SIZE (10)
int pArray[ARRAY_SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int pPrint[ARRAY_SIZE];
int nPrintPos;
int nTempSum;
int nSum;


void CheckSum(int nPos)
{
int i;
if(nSum == nTempSum)
{
printf("%d = ", nSum);
for(i=0; i<nPrintPos-1; i++)
{
printf("%d + ", pPrint[i]);
}
printf(" %d\n", pPrint[i]);
return;
}

if(nPos < ARRAY_SIZE)
{
if(nTempSum+pArray[nPos] <= nSum)
{
nTempSum += pArray[nPos];
pPrint[nPrintPos++] = pArray[nPos];
CheckSum(nPos+1);
nPrintPos--;
nTempSum -= pArray[nPos];
}
CheckSum(nPos+1);
}
}

int main()
{
nTempSum = 0;
memset(pPrint, 0, sizeof(int)*ARRAY_SIZE);
nPrintPos = 0;
scanf("%d", &nSum);

CheckSum(0);

return 0;
}

垃圾代碼,僅供參考.

#4


這樣的問題用回溯法解決比較合適。

回溯法的效率瓶頸在於剪枝函數與限界函數的效率。

為了使回溯法的效率高,應該注意:
1:要有一個好的搜索次序。因此,將隨機數列{1,2,3,4,5....}排序是有用的,因為這樣剪枝函數就不盲目,更有效率。
2:在判斷一個組合最后一個元素是否存在時,可用哈希表提高效率。比如:對一個三元組合,若已經確定前兩個為1,2,現在要確定第三個元97是否在數列中,可用哈希查找。


另外,樓主所謂的“隨機數列”,讓人很不解。如果“隨機數列”中有兩個是一樣的數,並且這個數包含在某個組合A中,那么A是算一個,還是兩個?

#5


那復雜度是2^n了,有沒有更好的算法那?

#6


帖子暫時不結貼,再加50分,希望更多的人來一起討論。

#7



回溯法:

#include <iostream>

using   namespace std;

int     list[20]={1,8,71,98,2,45,3,9,11,85,73,
  5,61,54,38,79,28,18,21,31};
int     Stack[10];
int     Stacklen=0;
int     s=0;

int     GetMaxLen( )
{
        int  i=0, s=0;
        while(s+list[i]<100)
s+=list[i++];
        return  i;
}

void    Sort( )
{
int  i, j, temp;
for( i=0; i<20; ++i )
{
for( j=i+1; j<20; ++j )
{
if( list[i]>list[j] )
{
temp=list[i];
list[i]=list[j];
list[j]=temp;
}
}
}
}

void  Found( int f, int len, int sum )
{
int i;
if( len==0 )
{
if( s==100 )
{
cout<<"{";
for( i=0; i<Stacklen-1; ++i )
cout<<Stack[i]<<",";
cout<<Stack[i]<<"}"<<endl;
}
return ;
}
i=f;
while( list[i]+s<=100 && i<20 )
{
Stack[Stacklen]=list[i];
s+=list[i];
++Stacklen;
if( Stacklen==1 )
Found(f+1, len-1, sum-list[i]);
else
if(Stack[Stacklen-2]<Stack[Stacklen-1])
Found(f+1, len-1, sum-list[i]);

--Stacklen;
s-=list[i];
++i;
}
}

int   main()

Sort();

int  i, Maxlen=GetMaxLen();

for( i=2; i<=Maxlen; ++i )
Found( 0, i, 100 );

system("PAUSE");
return 1;
}

#8



如果要找比回溯法更好的算法,那就可能要消耗空間來換取時間了。

記f(start,len,sum)為以在排完序的數列中序號為start首元,長度為len,和為sum的所有組合的集合。

注意到:回溯法重復解了許多子問題,為了避免這個問題,可以將子問題的解保存在一個表中,這個思路就是動態規划了。

比如:一個組合長度為6,已經確定頭3個數,頭3個中最大者在排完序的數列中序號是j,頭3個數的和為20.如果在以前的搜索中,f(j+k,3,80)(0<k<100-j)已經知道,那么將頭三個數和f(j+k,3,80)連接起來輸出就可以了。

但是,由於樓主要找的是所有組合,保存的解是相當多的,空間消耗太大。
有一個減少空間的辦法:用一位表示一個元在一個組合中的狀態:0表示不在這個組合中;1表示在這個組合中。設數列長度為L,那么任何一個組合都只需要L/8個字節。

#9


==========================================
那復雜度是2^n了,有沒有更好的算法那?
==========================================

其實我上面說的回溯法,就已經能在8秒鍾之內給出全部解了。雖然復雜度是O(2^n),但是,通過剪枝,其實數據量並不大,我用從1到100的長度為100的自然數數列進行測試,用文件輸出,結果只用了7秒鍾,就得出全部的組合,總個數為444793個。

附上我的測試用的程序:

#include <iostream>
#include <fstream>
#include <windows.h>

using   namespace   std;

ofstream file("data.txt");

const  int  Max=100;
const  int  Sum=100;
int    list[Max];
int    Stack[Max];
int    Stacklen=0;
int    s=0;
int    c=0;
int GetMinLen( )
{
    int  i=Max-1, s=0, len=0;
    while( s+list[i]<Sum && i>=0 )
           s+=list[i--],++len;
    return  len;
}

int GetMaxLen( )
{
int i=0, s=0;
while(s+list[i]<Sum && i<Max)
s+=list[i++];
return i;
}

void Found( int f, int len, int sum )
{
int i;
if( len==0 )
{
if( s==Sum )
{
file<<"{";
for( i=0; i<Stacklen-1; ++i )
file<<Stack[i]<<",";
file<<Stack[i]<<"}"<<endl;
++c;
}
return ;
}
i=f;
while( list[i]+s<=Sum && i<Max )
{
Stack[Stacklen]=list[i];
s+=list[i];
++Stacklen;
if( Stacklen==1 )
              Found(f+1, len-1, sum-list[i]);
        else
              if(Stack[Stacklen-2]<Stack[Stacklen-1])
                      Found(f+1, len-1, sum-list[i]);
--Stacklen;
s-=list[i];
++i;
}
}

int main()
{
int  i;
for( i=0; i<Max; ++i )
list[i]=i+1;
int  time=GetTickCount();
int  Maxlen=GetMaxLen();
int  Minlen=GetMinLen();

for( i=Minlen; i<=Maxlen; ++i )
Found( 0, i, Sum );

    cout<<"Total :"<<c<<endl;
    cout<<"Use time :"<<GetTickCount()-time<<" ms\n";
    cout<<"請打開文件'data.txt'查看結果!\n";
system("PAUSE");

return 1;
}


#10


分數不多,謝謝回復。
需要時間消化,稍后再來請教

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com