2010-04-25 34 views
5

this sorusunun devamı olan bir sayımda sorun yaşıyorum. Ben gerçekten matematik öğrencisi değilim, bu yüzden çözünürlük olarak önerilen bu subset sum problem anlamaya gerçekten zor. | Ali taraftarı, alTransaction, alNumber, alPriceBu tabloyu kapaBu tabloyu aç Alıntı sayısı

Tür:

Hangi ı verileri tutan 4 ArrayList yaşıyorum İşlem | Sayı | Fiyat
8 | Satın al | 95.00000000 | 305.00000000
8 | Satın al | 126.00000000 | 305.00000000
8 | Satın al | 93.00000000 | 306.00000000
8 | Aktar 221.00000000 | 305.00000000
8 | Transfer | 221.00000000 | 305.00000000
8 | Sat | 93.00000000 | 360.00000000
8 | Sat | 95.00000000 | 360.00000000
8 | Sat | 126.00000000 | 360.00000000
8 | Satın al | 276.00000000 | Ben müşteri için kalan ve ne 3 dizi listeleri koymak kalanları almaya çalışıyorum Sonunda

380,00000000

:
- alNewHowMuch (alNumber denk gelmektedir)
- alNewPrice (alPrice denk gelmektedir)
- alNewInID (Ali taraftarı için corrseponds)

 ArrayList alNewHowMuch = new ArrayList(); 
     ArrayList alNewPrice = new ArrayList(); 
     ArrayList alNewInID = new ArrayList(); 
     for (int i = 0; i < alTransaction.Count; i++) { 
      string transaction = (string) alTransaction[i]; 
      string id = (string) alID[i]; 
      decimal price = (decimal) alPrice[i]; 
      decimal number = (decimal) alNumber[i]; 
      switch (transaction) { 
       case "Transfer out": 
       case "Sell": 
        int index = alNewHowMuch.IndexOf(number); 
        if (index != -1) { 
         alNewHowMuch.RemoveAt(index); 
         alNewPrice.RemoveAt(index); 
         alNewInID.RemoveAt(index); 
        } else { 
         ArrayList alTemp = new ArrayList(); 
         decimal sum = 0; 
         for (int j = 0; j < alNewHowMuch.Count; j ++) { 
          string tempid = (string) alNewInID[j]; 
          decimal tempPrice = (decimal) alNewPrice[j]; 
          decimal tempNumbers = (decimal) alNewHowMuch[j]; 
          if (id == tempid && tempPrice == price) { 
           alTemp.Add(j); 
           sum = sum + tempNumbers; 
          } 
         } 
         if (sum == number) { 
          for (int j = alTemp.Count - 1; j >= 0; j --) { 
           int tempIndex = (int) alTemp[j]; 
           alNewHowMuch.RemoveAt(tempIndex); 
           alNewPrice.RemoveAt(tempIndex); 
           alNewInID.RemoveAt(tempIndex); 
          } 
         } 
        } 
        break; 
       case "Transfer In": 
       case "Buy": 
        alNewHowMuch.Add(number); 
        alNewPrice.Add(price); 
        alNewInID.Add(id); 
        break; 
      } 
     } 

Temelde ben ekleme ve İşlem Türü, İşlem kimliği ve Sayılar bağlı Array şeyleri çıkarmadan ediyorum. ArrayList'e 156, 340 (TransferIn veya Buyulduğunda) vb. Gibi sayıları ekliyorum ve sonra bunları 156, 340 (TransferOut, Sat) olduğunda yapıyorlar. Benim çözümüm, problemsiz bir şekilde çalışıyor. Sahip olduğum problem, bazı eski veri çalışanlarının toplamı 500 + 400 + 100 + 500 yerine 1500 gibi giriyor olmasıdır. Sell/TransferOut veya Buy/Transfer In olduğunda ve ArrayList içinde eşleşme olmadığında, bu ArrayList'dan birden çok öğe eklemeyi denemeli ve birleştirilecek öğeleri bir araya getirecek şekilde nasıl değiştirirdim?

benim kod içinde orada eşleşme (dizin == 1)

    int index = alNewHowMuch.IndexOf(number); 
        if (index != -1) { 
         alNewHowMuch.RemoveAt(index); 
         alNewPrice.RemoveAt(index); 
         alNewInID.RemoveAt(index); 
        } else { 
         ArrayList alTemp = new ArrayList(); 
         decimal sum = 0; 
         for (int j = 0; j < alNewHowMuch.Count; j ++) { 
          string tempid = (string) alNewInID[j]; 
          decimal tempPrice = (decimal) alNewPrice[j]; 
          decimal tempNumbers = (decimal) alNewHowMuch[j]; 
          if (id == tempid && tempPrice == price) { 
           alTemp.Add(j); 
           sum = sum + tempNumbers; 
          } 
         } 
         if (sum == number) { 
          for (int j = alTemp.Count - 1; j >= 0; j --) { 
           int tempIndex = (int) alTemp[j]; 
           alNewHowMuch.RemoveAt(tempIndex); 
           alNewPrice.RemoveAt(tempIndex); 
           alNewInID.RemoveAt(tempIndex); 
          } 
         } 
        } 

var Ama sadece belli şartlar yerine getirildiği takdirde çalışır ve geri kalanı için başarısız olduğunda basit toplayarak her şey bu sorunu çözmek için çalıştı.

Düzeltme: Bazılarınız benim saygısız değişken isimlerimden öylesine şaşkın (ve kör olmuş) olduklarından, hepsini basitlik ve görünürlük için ingilizceye çevirdim. Umarım bu bana yardım etmeme yardımcı olur :-)

+4

Belirleyicilerin seçiminiz inanılmaz ... – Joren

+0

Anahtarın yanlış kullanımı, iki tane yeterli olurdu .. –

+0

@Joren: Lehçe daha mantıklı olabilir. –

cevap

4

Bunu nasıl yapmanız gerektiği önemli bir şeye bağlıdır: kaç sayıya sahip olacaksınız ve ne kadar büyük olacaklar? Ayrıca, anladığım kadarıyla, verileriniz değişebilir (sayı ekleme/çıkarma vb.), Doğru mu? Bu sorguları ne sıklıkla yapmanız gerekir?

İki çözüm sunacağım. İhtiyacınız için daha iyi olduğundan ve anlaşılması daha kolay olduğundan şüphelendiğim için ikinciyi kullanmanızı öneririm.

Çözüm 1 - dinamik programlama

S[i] = true if we can make sum i and false otherwise.

S[0] = true // we can always make sum 0: just don't choose any number 
S[i] = false for all i != 0 
for each number i in your input 
    for s = MaxSum downto i 
     if (S[s - i] == true) 
      S[s] = true; // if we can make the sum s - i, we can also make the sum s by adding i to the sum s - i. 

başka vektör P[i] = the last number that was used to make sum i tutmalı sizin toplamını oluşturan gerçek sayılar elde etmek için izin verin. Bunu, yukarıdaki if koşulunda buna göre güncelleştirdiniz.

bu zaman karmaşıklığı size her veri değişiklikleri bu algoritmayı yeniden sahip, özellikle de oldukça kötü olan O(numberOfNumbers * maxSumOfAllNumbers) olduğunu. Sayılarınız çok büyük olabildiği ve bunların çoğuna sahip olabileceğiniz sürece bir koşuş için de yavaştır. Aslında, "çok" yanıltıcıdır. 100 rakamınız varsa ve her bir numara 10 000 kadar büyükse, verileriniz her değiştiğinde kabaca 100 * 10 000 = 1 000 000 işlem yaparsınız.

Bilmek güzel bir çözüm, ama pratikte değil gerçekten yararlı veya en azından senin durumunda sanırım.

O yaklaşım için bazı C# var

Önerim:

class Program 
     { 
     static void Main(string[] args) 
     { 
      List<int> testList = new List<int>(); 

      for (int i = 0; i < 1000; ++i) 
      { 
       testList.Add(1); 
      } 

      Console.WriteLine(SubsetSum.Find(testList, 1000)); 

      foreach (int index in SubsetSum.GetLastResult(1000)) 
      { 
       Console.WriteLine(index); 
      } 
     } 
    } 

    static class SubsetSum 
    { 
     private static Dictionary<int, bool> memo; 
     private static Dictionary<int, KeyValuePair<int, int>> prev; 

     static SubsetSum() 
     { 
      memo = new Dictionary<int, bool>(); 
      prev = new Dictionary<int, KeyValuePair<int, int>>(); 
     } 

     public static bool Find(List<int> inputArray, int sum) 
     { 
      memo.Clear(); 
      prev.Clear(); 

      memo[0] = true; 
      prev[0] = new KeyValuePair<int,int>(-1, 0); 

      for (int i = 0; i < inputArray.Count; ++i) 
      { 
       int num = inputArray[i]; 
       for (int s = sum; s >= num; --s) 
       { 
        if (memo.ContainsKey(s - num) && memo[s - num] == true) 
        { 
         memo[s] = true; 

         if (!prev.ContainsKey(s)) 
         { 
          prev[s] = new KeyValuePair<int,int>(i, num); 
         } 
        } 
       } 
      } 

      return memo.ContainsKey(sum) && memo[sum]; 
     } 

     public static IEnumerable<int> GetLastResult(int sum) 
     { 
      while (prev[sum].Key != -1) 
      { 
       yield return prev[sum].Key; 
       sum -= prev[sum].Value; 
      } 
     } 
    } 

Belki de kontrol bazı hata yapmak ve farklı bir ile GetLastResult gidilmesinin söz konusu izin etmemek için belki sınıfta geçen toplamı saklamalısınız Find toplamı ile en son çağrıldı. Her neyse, bu fikir.

Çözüm 2 - randomize algoritma

Şimdi, bu daha kolaydır. İki listeyi saklayın: usedNums ve unusedNums. Ayrıca, usedSum değişkenini, herhangi bir zamanda, usedNums listesindeki tüm sayıların toplamını da içerecek şekilde saklayın.

ayrıca iki listeden birinde (hangisi olduğu önemli, ancak görece eşit dağılımı, yani o rastgele yapmaz) ekleyin, sizin dizi içine bir sayı eklemek için gerektiğinde

. usedSum'u uygun şekilde güncelleştirin.

Setinizden bir sayı çıkarmanız gerektiğinde, iki listeden hangisinin bulunduğunu öğrenin. Çok fazla olmadığınız sürece bunu doğrusal bir ağ bağlantısıyla yapabilirsiniz (bu sefer çok fazla demektir) 10 000, belki de hızlı bir bilgisayarda 100 000 ve bu işlemi sık sık ve hızlı bir şekilde yapmazsanız, her neyse, ihtiyacınız olduğunda doğrusal arama en iyi duruma getirilebilir.). Numarayı bulduktan sonra listeden kaldırın. usedSum'u uygun şekilde güncelleştirin. Liste usedNums size kimin numaralarını verecektir algoritma sonunda

while S != usedSum 
    if S > usedSum // our current usedSum is too small 
     move a random number from unusedNums to usedNums and update usedSum 
    else // our current usedSum is too big 
     move a random number from usedNums to unusedNums and update usedSum 

: Eğer sette sayılar bir dizi S o toplamı varsa bulmalıyız zaman

, bu algoritmayı kullanmak toplam S'dur.

Bu algoritma ihtiyacınız olan şey için iyi olmalı, bence. Veri kümesindeki değişiklikleri çok iyi işler ve yüksek sayı sayımı için iyi çalışır. Ayrıca, sayıların ne kadar büyük olduğuna bağlı değildir, ki büyük sayılarınız varsa çok faydalıdır.

Sorularınız için lütfen gönderin.

+0

Düşünüyordum da: eğer problemdeki tüm sayıları 10 ile çarpıyorsanız, o zaman hala aynı problemdir, bu yüzden algoritmanın çözülmesinin daha uzun sürmesi için bir neden yoktur. Bununla birlikte, DP algoritmanız çok daha uzun sürer. Sanırım bunu bir dizi yerine bir karma tablosu kullanarak düzeltebilir ve diğer şekilde doldurabilirsiniz. – Jules

+0

İşte bu fikir için bir python çözümü: http://pastebin.com/mBirSUeZ Eğer 'subsetsum' ([1,2,3,4], 6) 'yi çağırırsanız,' subsetsum '([100,200,300,400] ile aynı hızdadır, 600) '. – Jules

+0

Kısa bir açıklama eklendi: http://pastebin.com/pyHEXVVK – Jules

5

İşte algoritmam. O(2^(n/2))'da çalışır ve SubsetSum(1000, list-of-1000-ones)'u 20 milisaniyede çözer. IVlad'in gönderisinin sonundaki yorumları görün.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace SubsetSum 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var ns = new List<int>(); 
      for (int i = 0; i < 1000; i++) ns.Add(1); 
      var s1 = Stopwatch.StartNew(); 
      bool result = SubsetSum(ns, 1000); 
      s1.Stop(); 
      Console.WriteLine(result); 
      Console.WriteLine(s1.Elapsed); 
      Console.Read(); 
     } 

     static bool SubsetSum(ist<int> nums, int targetL) 
     { 
      var left = new List<int> { 0 }; 
      var right = new List<int> { 0 }; 
      foreach (var n in nums) 
      { 
       if (left.Count < right.Count) left = Insert(n, left); 
       else right = Insert(n, right); 
      } 
      int lefti = 0, righti = right.Count - 1; 
      while (lefti < left.Count && righti >= 0) 
      { 
       int s = left[lefti] + right[righti]; 
       if (s < target) lefti++; 
       else if (s > target) righti--; 
       else return true; 
      } 
      return false; 
     } 

     static List<int> Insert(int num, List<int> nums) 
     { 
      var result = new List<int>(); 
      int lefti = 0, left = nums[0]+num; 
      for (var righti = 0; righti < nums.Count; righti++) 
      { 

       int right = nums[righti]; 
       while (left < right) 
       { 
        result.Add(left); 
        left = nums[++lefti] + num; 
       } 
       if (right != left) result.Add(right); 
      } 
      while (lefti < nums.Count) result.Add(nums[lefti++] + num); 
      return result; 
     } 
    } 
} 

Ve burada setleri kuru erik geliştirilmiş bir versiyonudur: Bu sonuncusu yaklaşık 5 milisaniye 100000 olanlarla sorunu çözüldü (ama bununla, algoritmanın en iyi durumda

static bool SubsetSum(List<int> nums, int target) 
{ 
    var remainingSum = nums.Sum(); 
    var left = new List<int> { 0 }; 
    var right = new List<int> { 0 }; 
    foreach (var n in nums) 
    { 
     if (left.Count == 0 || right.Count == 0) return false; 
     remainingSum -= n; 
     if (left.Count < right.Count) left = Insert(n, left, target - remainingSum - right.Last(), target); 
     else right = Insert(n, right, target - remainingSum - left.Last(), target); 
    } 
    int lefti = 0, righti = right.Count - 1; 
    while (lefti < left.Count && righti >= 0) 
    { 
     int s = left[lefti] + right[righti]; 
     if (s < target) lefti++; 
     else if (s > target) righti--; 
     else return true; 
    } 
    return false; 
} 

static List<int> Insert(int num, List<int> nums, int min, int max) 
{ 
    var result = new List<int>(); 
    int lefti = 0, left = nums[0]+num; 
    for (var righti = 0; righti < nums.Count; righti++) 
    { 

     int right = nums[righti]; 
     while (left < right) 
     { 
      if (min <= left && left <= max) result.Add(left); 
      left = nums[++lefti] + num; 
     } 
     if (right != left && min <= right && right <= max) result.Add(right); 
    } 
    while (lefti < nums.Count) 
    { 
     left = nums[lefti++] + num; 
     if (min <= left && left <= max) result.Add(left); 
    } 
    return result; 
} 

gerçek dünya verileri daha yavaş olacaktır).

Kullanımınız için bu algoritma muhtemelen yeterince hızlıdır (ve belirgin bir iyileşme göremiyorum). 0 ile 20 arasında rastgele bir fiyata 10,000 ürün girerseniz ve hedefiniz 500 olarak hesaplanırsa, bu, dizüstü bilgisayarımda 0.04 saniyede çözülür.

Düzenleme: Sadece Wikipedia'da en iyi bilinen algoritmanın O(2^(n/2)*n) olduğunu okudum. Bu, O(2^(n/2)). Turing ödülünü alır mıyım?

+0

Teşekkürler, Güzel efor :) – MadBoy

+0

Niçin 'O (2^(n/2)) 'diyorsun? Her bir çağrıda 'num'' listesinin tamamını 'Insert' olarak değiştirirsiniz. Bence bu tür testleri gerçekten yapmak anlamsız, çünkü her algoritmanın (pseudopolynomial veya üstel) başarısız olacağı birisini bulmak kolay. Pseduopolynomial algo'nun başarısızlığa uğradığını buldunuz (uzun bir zaman alır): 100000. İşte algo da uzun bir zaman alır: 10000 sayı: 1 2 3 4 ... 10000. 345600'ü ara. Sadece doğru ya da yanlış yazdırıyorsanız, sayıların da yazdırılmasının bir miktar ek yük katacağını düşünüyorum. Her neyse, bu DP'den çok daha hızlı görünüyor +1, ancak .. – IVlad

+0

Ancak, eğer bu kadar yüksek sayılarla savaşacaksak, üniversiteden döndüğümde rastgele algoritmamı kullanmama izin verin :). Bence çok yüksek sayılarla uğraşırsak daha iyi olur. – IVlad