3 篇文章 0 订阅

qq群里的一个同学发了一个这样的题目:

给定一个数t,以及n个整数,在这n个数中找到和为t的所有组合,例如t=4,n=6,这6个数为【4,3,2,2,1,1】,这样的不同组合他们的和为4: 4, 3+1 ,3+1,(有两个1,所以有两个3+1) 2+2 , and 2+1+1,2+1+ ,请设计一个高效算法实现。

 

看到这个问题,首先想到的就是递归,思路是这样:实现一个这样的函数:fun(DataList,Sum);即从DataList这一列数据中取出若干个数,使得它们的和为Sum。在函数内部,从DataList中取一个数出来,这个数的值为value,若value==Sum,那么找到了答案(这是一个出口)(如何输出答案呢?需要维护一个栈,每取出一个数,把这个数进栈,每一次回朔,出一次栈,在找到答案时,输出这个栈),若这个数小于t,那么把这个数从DataList中删掉,再调用fun(DataList,Sum-value),然后,记住把DataList复原(把取出来的这个数又加到DataLIst中原来的位置)。

 

如何把取出来的这个节点从DataList中删除,然后又复原?想象一下,感觉整个搜寻答案的过程就是不断的抽取节点和复原节点的过程

 

Donald E.Knuth写的Dancing Links那篇论文巨大的篇幅中,我这里仅仅只用到了前面几句话: “

假设x指向双向链的一个节点;L[x]和R[x]分别表示x的前驱节点和后继节点。每个程序员都知道如下操作:

L[R[x]] ← L[x], R[L[x]] ← R[x]
(1)

是将x从链表删除的操作;但是只有少数程序员意识到如下操作:

L[R[x]] ← x, R[L[x]] ← x
(2)

是把x重新链接到双向链中。

 ”

 所以,节点的出列和入列就可以按照这种链表方法来做,为什么不用一个标志数组呢来表示每一个节点是否在列中呢?因为用链表的话会减少大量无谓的判断,如果总共100个节点,只有10个节点在列中(已经抽取了90个节点了,即递归到底90层了),用标志数组的话仍然得判断100次,而用链表的话就只需10次了。

 

 

这种方法是穷举所有可能的组合,即:c(1,n)+ c(2,n)+ c(3,n)+ 。。。+ c(n,n)。(在这里c(a,b)为 b 中取 a 的组合) (2010-6-2注:这里错了,并没有那么低效,比如,要求sum为4的一群数,取出来两个数后就已经大于或等于4了,就不会再拿这两个数与其他的数进行组合了(即直接打印结果或者return,而不是继续往下递归))

c(1,n)的所有情况在第一层求出,所以第一层有n个判断,

c(2,n)的所有情况在第二层求出,所以第二层有c(2,n)个判断。

。。。 。。。

c(i,n)的所有种情况在递归的第i层求出,第i层判断的次数为c(i,n)

在第n层只有一次判断了(c(n,n) == 1)。

 

就这么做吧,我还想不到更高效的算法,有人知道的话,希望能指点一下。核心代码是这一段:

 

int nodeFilter(int EntryPoint,int sum)
{
   for (int i=EntryPoint; i < NUM; i = R[i]) //参数EntryPoint在此处用到,递归形成的搜索树是所有的组合。

 {
  record[recordEnd++] = data[i];

  //get an answer,print it.
  if (sum == data[i])    
  {
   PrintIntArray(record,recordEnd);
  }
  //try,go forward.
  else if(sum > data[i])   
  {
   //
   //这一段是关键
   //pickup the node (remove node i form the list)
   L[R[i]] = L[i];
   R[L[i]] = R[i]; 
   
   //go forward.
   nodeFilter(R[i],sum - data[i]); //递归。注意第一个参数是传R[i]进去,这样,递归形成的搜索树是所有的组合

                                                  //如果传进去的是R[-1],那么求出来的是所有符合要求的排列,这时,该函数

                                                  //的实现是不需要这个参数的。

   //restore node i to the list.
   L[R[i]] = i;
   R[L[i]] = i;    
   //
  }

  //backtracking
  --recordEnd;     
 }
 return 1;
}

 

 

以下是实现的所有代码:

 

// DataSum.cpp : Defines the entry point for the console application.

#include <iostream>
#include <vector>
// #include <algorithm> //for for_each

#include <memory>
//
// 从文本输出
#include <fstream>
std::ofstream g_ofResult("./record.txt",std::ios::trunc);
//#define  cout ::g_ofResult //开启此行输出到文本
//

using namespace std;

#define  NUM 13
#define  SUM 6

int data[NUM];   //数据,DataList

int HeadAndNext[NUM+1];
//left数组和right数组(L[NUM],R[NUM],即记录前驱节点和后继节点)
int L[NUM];     
#define R (HeadAndNext+1)     //R[-1]为起始节点,R[n]表示节点n的后继节点

int record[NUM];       //记录所求的的答案
int recordEnd;        //栈顶

void InputData(int a[],size_t size);  //输入数据
int nodeFilter(int EntryPoint,int sum);  //递归函数,算法就在这里面
void PrintIntArray(int a[],size_t size); //输出一个整型数组

int main(int argc, char* argv[])
{
 //Initialize
 recordEnd = 0;
 InputData(data,NUM);
 cout<<"Given data:"<<endl;
 PrintIntArray(data,NUM);

 //initialize the doubly linked list.
 R[-1] = 0; //起始节点为节点0
 for (int i=0; i<NUM; ++i)
 {
  L[i] = i-1;
  R[i] = i+1;
 }

 //start searching...
 cout<<"start searching for the answers,to make the sum of their value equal to "<<SUM<<" ..."<<endl;
 nodeFilter(R[-1],SUM);

 system("pause");
 return 0;
}

int nodeFilter(int EntryPoint,int sum)
{
// for (int i=R[-1]; i < NUM; i = R[i])  //开启此行求出所有排列,此时该函数不需要参数EntryPoint
   for (int i=EntryPoint; i < NUM; i = R[i]) //开启此行求出所有组合,参数EntryPoint在此处用到。
 {
  record[recordEnd++] = data[i];

  //get an answer,print it.
  if (sum == data[i])    
  {
   PrintIntArray(record,recordEnd);
  }
  //try,go forward.
  else if(sum > data[i])   
  {
   //
   //这一段是关键
   //pickup the node (remove node i form the list)
   L[R[i]] = L[i];
   R[L[i]] = R[i]; 
   
   //go forward.
   nodeFilter(R[i],sum - data[i]);

   //restore node i to the list.
   L[R[i]] = i;
   R[L[i]] = i;    
   //
  }

  //backtracking
  --recordEnd;     
 }
 return 1;
}

void PrintIntArray(int a[],size_t size)
{
   for (int i = 0; i < size; ++i)
   {
  cout<<a[i]<<" ";
   }
 cout<<endl;
}


void InputData(int a[],size_t size)
{
//  for (int i=0; i<size; ++i)
//  {
//   cin>>a[i];
//  }
// int data[]={1,1,2,2,3,4};
 int data[]={10,9,8,7,6,5,4,3,3,3,2,2,1};
 memcpy(a,data,size*sizeof(int));
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值