找出最小匹配对

时间:2018-01-23 05:43:30

标签: dynamic-programming knapsack-problem greedy running-total

我有2个列表具有相同的对象,有3个属性(accNo,accType& balance)。

List<> CSList
'CS1', 'CS', 3000
'CS2', 'CS', 2000   
'CS3', 'CS', 1000   

List<> CLList
'CL1', 'CL', 4000   
'CL2', 'CL', 500    
'CL3', 'CL', 1000

在我的示例中,所有acctype = CS(3000+2000+1000=6000)的余额总和将始终大于或等于acctype = CL(4000+500+1000=5500)

我正在尝试按CL余额清算CS余额。例如,余额为4000的帐户CL1可由CS1 & CS3(3000+1000)CS1 & CS2(3000+1000(CS2 remaining balance 1000 can be used next))清算。

我想找出最少的交易数量,使CL个帐户余额为CS个帐户(首选输出2)。

Possible output 1:

Debit       Credit      Amount
CS1         CL1         3000
CS2         CL1         1000
CS2         CL2         500
CS2         CL3         500
CS3         CL3         500


Possible output 2:

Debit       Credit      Amount
CS1         CL1         3000
CS3         CL1         1000
CS2         CL2         500
CS2         CL3         1000

3 个答案:

答案 0 :(得分:2)

贪婪的方法不会产生这个问题的最佳结果。 找到最佳交易次数的解决方案:

  1. 生成CL帐户的电力设定
    例如((CL1,CL2,CL3),(CL1,CL2),(CL1,CL3),(CL2,CL3),(CL1),(CL2),(CL3))
  2. 对于每个子集,选择所有CS帐户,该帐户大于子集的CL帐户金额的总和。
  3. 对于每个已挑选的CS帐户,从已挑选的CS帐户中扣除子集的CL帐户金额的总和
  4. 对于剩余的CL帐户,重复步骤1到3,直到所有CL帐户扣除完成
  5. 如果您拥有M个CS账户和N个账户,那么复杂性可能是O(M N * 2 N 2

答案 1 :(得分:1)

获得最佳结果的一种方法是首先将信用与相同的借方匹配 然后匹配其余部分并在必要时减去直至完全消耗。

根据借方和贷方的排序方式,您会得到不同的结果 但是当两者都按升序值排序时,它似乎工作得很好。

下面是一个用于演示该策略的javascript示例代码:

&#13;
&#13;
var debits  = [ [ 'CS1', 3000 ], [ 'CS2', 2000 ], [ 'CS3', 1000 ], ['CS4',800] ];
var credits = [ [ 'CL1', 4000 ], [ 'CL2', 500 ], [ 'CL3', 1000 ], ['CL4',800] ];

console.log("\n# Debits and Credits sorted by ascending value\n\n");
sort_arrays(debits, 1, 1); //sort debits by ascending value
sort_arrays(credits, 1, 1); //sort credits by ascending value
console.log("Debits : "+JSON.stringify(debits));
console.log("Credits: "+JSON.stringify(credits));
var result = match_debits_to_credits (debits, credits);
sort_arrays(result, 1, 1);
console.log("Result : "+JSON.stringify(result));

console.log("\n# Debits and Credits sorted by descending value\n\n");
sort_arrays(debits, 1, -1); //sort debits by descending value
sort_arrays(credits, 1, -1); //sort credits by descending value
console.log("Debits : "+JSON.stringify(debits));
console.log("Credits: "+JSON.stringify(credits));
var result = match_debits_to_credits (debits, credits);
sort_arrays(result, 1, 1);
console.log("Result : "+JSON.stringify(result));

console.log("\n# Credits and Debits sorted by ascending name.\n# And without matching the same values first.\n\n");
sort_arrays(debits, 0, 1); //sort debits by ascending name
sort_arrays(credits, 0, 1); //sort credits by ascending name
console.log("Debits : "+JSON.stringify(debits));
console.log("Credits: "+JSON.stringify(credits));
var result = match_debits_to_credits (debits, credits, false);
sort_arrays(result, 1, 1);
console.log("Result : "+JSON.stringify(result));

function sort_arrays (p_array, index = 0, order_direction = 1){
  p_array.sort(
    function(a, b){
      return a[index] === b[index] ? 0 : a[index]<b[index] ? -order_direction : order_direction
    });
}

function match_debits_to_credits (p_debits, p_credits, p_match_equal_first = true){

  let output = [];
  let v_debits = JSON.parse(JSON.stringify(p_debits));
  let v_credits = JSON.parse(JSON.stringify(p_credits));
  
  if(p_match_equal_first){
    // First match the credit with an equal debit
    for (let i=0; i < v_credits.length; i++){
  
      for (let j=0; j < v_debits.length; j++){
  
        if (v_credits[i][1] == v_debits[j][1]){
  
          console.log('credit ' + v_credits[i] + ' = debit ' + v_debits[j]);
          output.push([ v_debits[j][0], v_credits[i][0], v_credits[i][1] ]);
  
          v_credits.splice(i,1); i--; //consumed by the matching debit
          v_debits.splice(j,1); //consumed by the matching credit
          break;
        }
      }
    }
  }
  
  for (let i=0; i < v_credits.length; i++){

    for (let j=0; j < v_debits.length && v_credits[i][1] > 0; j++){

      if (v_debits[j][1] > 0 && v_credits[i][1] > v_debits[j][1]){

        console.log('credit ' + v_credits[i] + ' > debit ' + v_debits[j]);
        v_credits[i][1] = v_credits[i][1] - v_debits[j][1];

        output.push([ v_debits[j][0], v_credits[i][0], v_debits[j][1] ]);

        v_debits.splice(j,1); j--; //consumed by the credit
      }
      else if (v_credits[i][1] <= v_debits[j][1]){

        console.log('credit ' + v_credits[i] + ' <= debit ' + v_debits[j]);
        v_debits[j][1] = v_debits[j][1] - v_credits[i][1];

        output.push([ v_debits[j][0], v_credits[i][0], v_credits[i][1] ]);

        if(v_debits[j][1] == 0) {v_debits.splice(j,1);}
        v_credits.splice(i,1); i--; //consumed by the debit
        break; //no need for further matching for this credit
      }
    }
  }
  
  return output;
  
}
&#13;
&#13;
&#13;

虽然不知道如何计算复杂性。
由于在信用完全消耗时退出循环的中断 如果M是信用数,N是债务数 然后我假设复杂性可能类似于O(M*N+M*2)

答案 2 :(得分:0)

想想我会尝试在SQL中执行此操作 在我的另一个答案中使用相同的策略。

下面的T-SQL适用于SQL Server。

declare @Transactions table (id int identity(1,1) primary key, name varchar(30), type char(2), value int, accountid int);
insert into @Transactions (name, type, value, accountid) values
('CS1','CS',3000,1),('CS2','CS',2000,1),('CS3','CS',1000,1),
('CL1','CL',4000,1),('CL2','CL', 500,1),('CL3','CL',1000,1);

declare @Credits table (creditid int identity(1,1) primary key, name varchar(30), value int);
declare @Debits table (debitid int identity(1,1) primary key, name varchar(30), value int);
declare @Result table (resultid int identity(1,1) primary key, accountid int, 
                       credit_name varchar(30), credit_value int, debit_name varchar(30), debit_value int, debited int, credit_remaining int, 
                       loopcnt int, comparison varchar(2));

declare @AccountId int;
set @AccountId = 1;

insert into @Credits (name, value) 
select name, value
from @Transactions
where type = 'CL' and accountid = @AccountId
order by value asc, id;

insert into @Debits (name, value) 
select name, value
from @Transactions
where type = 'CS' and accountid = @AccountId
order by value asc, id;

declare @TotalCredits INT;
declare @TotalDebits INT;
declare @CounterCredits INT;
declare @CounterDebits INT;

declare @CurrentCreditName varchar(30);
declare @CurrentCreditValue int;
declare @RemainingCreditValue int;
declare @Debited int;
declare @LoopCnt int = 0;

declare @CurrentDebitName varchar(30);
declare @CurrentDebitValue int;

set @TotalCredits = (select count(*) from @Credits);
set @TotalDebits = (select count(*) from @Debits);

set @CounterCredits = 1;
WHILE @CounterCredits <= @TotalCredits
BEGIN
   select @CurrentCreditName = name, @CurrentCreditValue = value, @RemainingCreditValue = value
   from @Credits where creditid = @CounterCredits;

   set @CounterDebits = 1;
   WHILE @RemainingCreditValue > 0 AND @CounterDebits <= @TotalDebits
   BEGIN
       set @LoopCnt = @LoopCnt + 1;

       select @CurrentDebitName = name, @CurrentDebitValue = value 
       from @Debits where debitid = @CounterDebits;

       if(@RemainingCreditValue = @CurrentDebitValue)
       begin
           set @Debited = @CurrentDebitValue;
           set @RemainingCreditValue = 0;

           update @Credits set value = 0 where creditid = @CounterCredits;
           update @Debits  set value = 0 where debitid = @CounterDebits;

           insert into @Result (accountid, credit_name, credit_value, debit_name, debit_value, debited, credit_remaining, loopcnt, comparison) 
           values (@AccountId, @CurrentCreditName, @CurrentCreditValue, @CurrentDebitName, @CurrentDebitValue, @Debited, @RemainingCreditValue, @LoopCnt, '=');
       end;

       set @CounterDebits = @CounterDebits + 1;
   END;
   set @CounterCredits = @CounterCredits + 1;
END;

set @CounterCredits = 1;
WHILE @CounterCredits <= @TotalCredits
BEGIN
   select @CurrentCreditName = name, @CurrentCreditValue = value, @RemainingCreditValue = value 
   from @Credits where creditid = @CounterCredits;

   set @CounterDebits = 1;
   set @TotalDebits = (select count(*) from @Debits);

   WHILE @CounterDebits <= @TotalDebits AND @RemainingCreditValue > 0
   BEGIN
       set @LoopCnt = @LoopCnt + 1;

       select @CurrentDebitName = name, @CurrentDebitValue = value 
       from @Debits where debitid = @CounterDebits;

       IF(@CurrentDebitValue > 0 AND @RemainingCreditValue >= @CurrentDebitValue)
       BEGIN
           set @Debited = @CurrentDebitValue;
           set @RemainingCreditValue = @RemainingCreditValue - @CurrentDebitValue;

           update @Credits set value = @RemainingCreditValue where creditid = @CounterCredits;
           update @Debits set value = 0 where debitid = @CounterDebits;

           insert into @Result (accountid, credit_name, credit_value, debit_name, debit_value, debited, credit_remaining, loopcnt, comparison) 
           values (@AccountId, @CurrentCreditName, @CurrentCreditValue, @CurrentDebitName, @CurrentDebitValue, @Debited, @RemainingCreditValue, @LoopCnt, '>=');

           set @CurrentCreditValue = @RemainingCreditValue;
       END;
       ELSE IF(@RemainingCreditValue > 0 AND @RemainingCreditValue < @CurrentDebitValue)
       BEGIN
           set @Debited = @RemainingCreditValue;
           set @RemainingCreditValue = 0;

           update @Credits set value = 0 where creditid = @CounterCredits;
           update @Debits set value = value - @Debited where debitid = @CounterDebits;

           insert into @Result (accountid, credit_name, credit_value, debit_name, debit_value, debited, credit_remaining, loopcnt, comparison) 
           values (@AccountId, @CurrentCreditName, @CurrentCreditValue, @CurrentDebitName, @CurrentDebitValue, @Debited, @RemainingCreditValue, @LoopCnt, '<');

       END;

       set @CounterDebits = @CounterDebits + 1;
   END;

   set @CounterCredits = @CounterCredits + 1;
END;

select 
debit_name as Debit, 
credit_name as Credit,
debited as Amount
from @Result
order by debit_name, credit_name;

<强>结果:

Debit Credit Amount
----- ------ ------
CS1   CL1    2500
CS2   CL1    1500
CS2   CL2    500
CS3   CL3    1000