Pengenalan Algoritma Greedy dengan Java

1. Pengenalan

Dalam tutorial ini, kita akan memperkenalkan algoritma tamak dalam ekosistem Java.

2. Masalah Kerakusan

Semasa menghadapi masalah matematik, mungkin ada beberapa cara untuk merancang penyelesaian. Kita dapat melaksanakan penyelesaian berulang, atau beberapa teknik canggih, seperti prinsip divide and winer (misalnya algoritma Quicksort) atau pendekatan dengan pengaturcaraan dinamik (misalnya masalah Knapsack) dan banyak lagi.

Selalunya, kami mencari penyelesaian yang optimum, tetapi sayangnya, kami tidak selalu mendapat hasil seperti itu. Walau bagaimanapun, terdapat kes di mana hasil suboptimal walaupun bernilai. Dengan bantuan beberapa strategi tertentu, atau heuristik , kita mungkin memperoleh ganjaran yang sangat berharga.

Dalam konteks ini, mengingat masalah yang dapat dibagi, strategi yang pada setiap tahap proses mengambil pilihan optimal lokal atau "pilihan tamak" disebut algoritma tamak.

Kami menyatakan bahawa kami harus mengatasi masalah "terpecah belah": Suatu keadaan yang dapat digambarkan sebagai sekumpulan sub-masalah dengan, hampir, ciri-ciri yang sama. Akibatnya, sebahagian besar masa, algoritma tamak akan dilaksanakan sebagai algoritma rekursif .

Algoritma tamak boleh menjadi cara untuk membawa kita ke jalan penyelesaian yang munasabah walaupun persekitarannya keras; kekurangan sumber daya komputasi, kekangan masa pelaksanaan, batasan API, atau sekatan lain.

2.1. Senario

Dalam tutorial ringkas ini, kita akan menerapkan strategi tamak untuk mengekstrak data dari rangkaian sosial menggunakan API-nya.

Katakanlah kami ingin menjangkau lebih banyak pengguna di sosial "burung kecil-biru". Cara terbaik untuk mencapai tujuan kita adalah dengan menghantar kandungan asli atau tweet semula sesuatu yang akan menimbulkan minat kepada khalayak luas.

Bagaimana kita menemui penonton seperti itu? Kita mesti mencari akaun dengan banyak pengikut dan tweet beberapa kandungan untuk mereka.

2.2. Klasik vs tamak

Kami mengambil kira situasi berikut: Akaun kami mempunyai empat pengikut, masing-masing mempunyai, seperti yang digambarkan dalam gambar di bawah, masing-masing 2, 2, 1 dan 3 pengikut, dan seterusnya:

Dengan tujuan ini dalam fikiran kita, kita akan mengambil satu dengan lebih banyak pengikut di antara pengikut akaun kita. Kemudian kita akan mengulangi prosesnya dua kali lagi sehingga kita mencapai tahap ke-3 sambungan (keseluruhan empat langkah).

Dengan cara ini, kami menentukan jalan yang dibuat oleh pengguna, yang membawa kami ke pangkalan pengikut paling banyak dari akaun kami. Sekiranya kita dapat menyampaikan beberapa kandungan kepada mereka, mereka pasti akan sampai ke halaman kami.

Kita boleh mulakan dengan pendekatan "tradisional". Pada setiap langkah, kami akan melakukan pertanyaan untuk mendapatkan pengikut akaun. Hasil daripada proses pemilihan kami, jumlah akaun akan bertambah setiap langkah.

Secara mengejutkan, secara keseluruhan, kami akhirnya akan melakukan 25 pertanyaan:

Di sini timbul masalah: Contohnya, API Twitter menghadkan jenis pertanyaan ini kepada 15 setiap 15 minit. Sekiranya kita berusaha untuk melakukan lebih banyak panggilan daripada yang diizinkan, kita akan mendapat " Kod yang melebihi had tarif - 88 ", atau " Dikembalikan dalam API v1.1 ketika permintaan tidak dapat dilayani karena had tarif aplikasi telah habis untuk sumber daya ". Bagaimana kita dapat mengatasi had tersebut?

Baiklah, jawapannya betul-betul di hadapan kita: Algoritma tamak. Sekiranya kita menggunakan pendekatan ini, pada setiap langkah, kita dapat menganggap bahawa pengguna dengan pengikut terbanyak adalah satu-satunya yang perlu dipertimbangkan: Pada akhirnya, kita hanya memerlukan empat pertanyaan. Cukup peningkatan!

Hasil kedua pendekatan tersebut akan berbeza. Dalam kes pertama, kita mendapat 16, penyelesaian yang optimum, sementara yang terakhir, jumlah maksimum pengikut yang dapat dijangkau hanyalah 12 orang.

Adakah perbezaan ini akan sangat berharga? Kami akan memutuskan kemudian.

3. Pelaksanaan

Untuk menerapkan logika di atas, kami menginisialisasi program Java kecil, di mana kami akan meniru API Twitter. Kami juga akan menggunakan perpustakaan Lombok.

Sekarang, mari tentukan komponen SocialConnector kami di mana kami akan melaksanakan logik kami. Perhatikan bahawa kami akan meletakkan penghitung untuk mensimulasikan sekatan panggilan, tetapi kami akan menurunkannya menjadi empat:

public class SocialConnector { private boolean isCounterEnabled = true; private int counter = 4; @Getter @Setter List users; public SocialConnector() { users = new ArrayList(); } public boolean switchCounter() { this.isCounterEnabled = !this.isCounterEnabled; return this.isCounterEnabled; } } 

Kemudian kita akan menambahkan kaedah untuk mendapatkan senarai pengikut akaun tertentu:

public List getFollowers(String account) { if (counter < 0) { throw new IllegalStateException ("API limit reached"); } else { if (this.isCounterEnabled) { counter--; } for (SocialUser user : users) { if (user.getUsername().equals(account)) { return user.getFollowers(); } } } return new ArrayList(); } 

Untuk menyokong proses kami, kami memerlukan beberapa kelas untuk memodelkan entiti pengguna kami:

public class SocialUser { @Getter private String username; @Getter private List followers; @Override public boolean equals(Object obj) { return ((SocialUser) obj).getUsername().equals(username); } public SocialUser(String username) { this.username = username; this.followers = new ArrayList(); } public SocialUser(String username, List followers) { this.username = username; this.followers = followers; } public void addFollowers(List followers) { this.followers.addAll(followers); } }

3.1. Algoritma tamak

Akhirnya, sudah tiba masanya untuk menerapkan strategi tamak kita, jadi mari kita tambahkan komponen baru - GreedyAlgorithm - di mana kita akan melakukan pengulangan:

public class GreedyAlgorithm { int currentLevel = 0; final int maxLevel = 3; SocialConnector sc; public GreedyAlgorithm(SocialConnector sc) { this.sc = sc; } }

Kemudian kita perlu memasukkan kaedah findMostFollowersPath di mana kita akan menemui pengguna dengan kebanyakan pengikut, mengira mereka, dan kemudian meneruskan ke langkah seterusnya:

public long findMostFollowersPath(String account) { long max = 0; SocialUser toFollow = null; List followers = sc.getFollowers(account); for (SocialUser el : followers) { long followersCount = el.getFollowersCount(); if (followersCount > max) { toFollow = el; max = followersCount; } } if (currentLevel < maxLevel - 1) { currentLevel++; max += findMostFollowersPath(toFollow.getUsername()); } return max; }

Ingat: Di sinilah kita melakukan pilihan tamak. Oleh itu, setiap kali kita menggunakan kaedah ini, kita akan memilih satu dan satu elemen dari senarai dan meneruskannya: Kita tidak akan pernah membuat keputusan!

Sempurna! Kami sudah bersedia untuk pergi, dan kami boleh menguji permohonan kami. Sebelum itu, kita perlu ingat untuk mengisi rangkaian kecil kita dan akhirnya, laksanakan ujian unit berikut:

public void greedyAlgorithmTest() { GreedyAlgorithm ga = new GreedyAlgorithm(prepareNetwork()); assertEquals(ga.findMostFollowersPath("root"), 5); }

3.2. Algoritma Tidak Ber tamak

Mari buat kaedah yang tidak tamak, hanya untuk memeriksa dengan mata kita apa yang berlaku. Oleh itu, kita perlu memulakan dengan membina kelas NonGreedyAlgorithm :

public class NonGreedyAlgorithm { int currentLevel = 0; final int maxLevel = 3; SocialConnector tc; public NonGreedyAlgorithm(SocialConnector tc, int level) { this.tc = tc; this.currentLevel = level; } }

Let's create an equivalent method to retrieve followers:

public long findMostFollowersPath(String account) { List followers = tc.getFollowers(account); long total = currentLevel > 0 ? followers.size() : 0; if (currentLevel 
    
      0; i--) { if (count[i-1] > max) { max = count[i-1]; } } return total + max; } return total; }
    

As our class is ready, we can prepare some unit tests: One to verify the call limit exceeds and another one to check the value returned with a non-greedy strategy:

public void nongreedyAlgorithmTest() { NonGreedyAlgorithm nga = new NonGreedyAlgorithm(prepareNetwork(), 0); Assertions.assertThrows(IllegalStateException.class, () -> { nga.findMostFollowersPath("root"); }); } public void nongreedyAlgorithmUnboundedTest() { SocialConnector sc = prepareNetwork(); sc.switchCounter(); NonGreedyAlgorithm nga = new NonGreedyAlgorithm(sc, 0); assertEquals(nga.findMostFollowersPath("root"), 6); }

4. Results

It's time to review our work!

First, we tried out our greedy strategy, checking its effectiveness. Then we verified the situation with an exhaustive search, with and without the API limit.

Our quick greedy procedure, which makes locally optimal choices each time, returns a numeric value. On the other hand, we don't get anything from the non-greedy algorithm, due to an environment restriction.

Comparing the two methods' output, we can understand how our greedy strategy saved us, even if the retrieved value that is not optimal. We can call it a local optimum.

5. Conclusion

Dalam konteks yang boleh berubah dan cepat berubah seperti media sosial, masalah yang memerlukan mencari penyelesaian yang optimum dapat menjadi chimera mengerikan: Sulit dijangkau dan, pada masa yang sama, tidak realistik.

Mengatasi batasan dan mengoptimumkan panggilan API adalah tema, tetapi, seperti yang telah kita bincangkan, strategi tamak berkesan. Memilih pendekatan seperti ini menjimatkan banyak penderitaan, menghasilkan hasil yang berharga sebagai pertukaran.

Perlu diingat bahawa tidak setiap situasi sesuai: Kita perlu menilai keadaan kita setiap masa.

Seperti biasa, contoh kod dari tutorial ini terdapat di GitHub.