Skip to main content

rust_algorithms/greedy/
classic.rs

1//! Klassik ochko'z masalalar.
2
3/// Tadbir: boshlanish va tugash vaqti.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Activity {
6    /// Boshlanish vaqti.
7    pub start: i64,
8    /// Tugash vaqti.
9    pub end: i64,
10}
11
12impl Activity {
13    /// Yangi tadbir.
14    pub fn new(start: i64, end: i64) -> Self {
15        Self { start, end }
16    }
17}
18
19/// Tadbirlarni tanlash: bitta zalda maksimal nechta tadbir o'tkazish mumkin?
20///
21/// **Ochko'z qoida:** **eng erta tugaydigan** tadbirni tanlang. Nima uchun bu ishlaydi?
22/// Chunki erta tugagan tadbir keyingilar uchun eng ko'p joy qoldiradi.
23///
24/// - **Time:** O(n log n) (tartiblash), **Space:** O(n).
25///
26/// Qaytadi: tanlangan tadbirlarning **asl** indekslari.
27///
28/// # Misol
29/// ```
30/// use rust_algorithms::greedy::{activity_selection, Activity};
31///
32/// let tadbirlar = vec![
33///     Activity::new(1, 4),
34///     Activity::new(3, 5),
35///     Activity::new(0, 6),
36///     Activity::new(5, 7),
37///     Activity::new(8, 9),
38///     Activity::new(5, 9),
39/// ];
40/// let tanlangan = activity_selection(&tadbirlar);
41/// assert_eq!(tanlangan, vec![0, 3, 4]); // (1,4), (5,7), (8,9)
42/// ```
43pub fn activity_selection(activities: &[Activity]) -> Vec<usize> {
44    let mut tartib: Vec<usize> = (0..activities.len()).collect();
45    tartib.sort_by_key(|&i| activities[i].end);
46
47    let mut natija = Vec::new();
48    let mut oxirgi = i64::MIN;
49    for i in tartib {
50        if activities[i].start >= oxirgi {
51            natija.push(i);
52            oxirgi = activities[i].end;
53        }
54    }
55    natija
56}
57
58/// Xaltaga solinadigan buyum: qiymati va og'irligi.
59#[derive(Debug, Clone, Copy)]
60pub struct Item {
61    /// Buyum qiymati.
62    pub value: f64,
63    /// Buyum og'irligi.
64    pub weight: f64,
65}
66
67impl Item {
68    /// Yangi buyum.
69    pub fn new(value: f64, weight: f64) -> Self {
70        Self { value, weight }
71    }
72}
73
74/// **Bo'linadigan xalta masalasi** (fractional knapsack).
75///
76/// Buyumlarni bo'lish mumkin (masalan, un, shakar, oltin kukuni).
77/// Sig'imi `capacity` bo'lgan xaltaga maksimal **qiymat** joylang.
78///
79/// **Ochko'z qoida:** `qiymat / og'irlik` nisbati eng katta buyumdan boshlang.
80/// Bu yerda ochko'zlik **isbotlangan optimal** — chunki buyumni bo'lish mumkin.
81///
82/// - **Time:** O(n log n), **Space:** O(n).
83///
84/// # Misol
85/// ```
86/// use rust_algorithms::greedy::{fractional_knapsack, Item};
87///
88/// let buyumlar = vec![
89///     Item::new(60.0, 10.0),  // nisbat 6.0
90///     Item::new(100.0, 20.0), // nisbat 5.0
91///     Item::new(120.0, 30.0), // nisbat 4.0
92/// ];
93/// let qiymat = fractional_knapsack(&buyumlar, 50.0);
94/// assert!((qiymat - 240.0).abs() < 1e-9); // 60 + 100 + 120*(20/30)
95/// ```
96pub fn fractional_knapsack(items: &[Item], capacity: f64) -> f64 {
97    let mut tartib: Vec<&Item> = items.iter().collect();
98    tartib.sort_by(|a, b| {
99        let na = a.value / a.weight;
100        let nb = b.value / b.weight;
101        nb.partial_cmp(&na).unwrap_or(std::cmp::Ordering::Equal)
102    });
103
104    let mut qolgan = capacity;
105    let mut jami = 0.0;
106    for it in tartib {
107        if qolgan <= 0.0 {
108            break;
109        }
110        if it.weight <= qolgan {
111            jami += it.value; // butunligicha olamiz
112            qolgan -= it.weight;
113        } else {
114            jami += it.value * (qolgan / it.weight); // qismini olamiz
115            qolgan = 0.0;
116        }
117    }
118    jami
119}
120
121/// Qaytim berish (coin change) — **ochko'z** yondashuv.
122///
123/// Har safar eng katta sig'adigan tangani olamiz.
124///
125/// > ⚠️ **Diqqat:** bu **har doim ham optimal emas!** U faqat "kanonik" tanga
126/// > tizimlarida to'g'ri ishlaydi (masalan, 1, 5, 10, 25, 50, 100).
127/// >
128/// > Qarshi misol: tangalar `[1, 3, 4]`, summa `6`.
129/// > Ochko'z: 4 + 1 + 1 = **3 ta** tanga. Optimal: 3 + 3 = **2 ta**.
130/// > To'g'ri javob uchun [`crate::dp::coin_change_min`] (DP) ni ishlating.
131///
132/// - **Time:** O(n log n + summa/eng_kichik_tanga).
133///
134/// # Misol
135/// ```
136/// use rust_algorithms::greedy::coin_change_greedy;
137///
138/// // kanonik tizim — to'g'ri ishlaydi
139/// assert_eq!(coin_change_greedy(&[1, 5, 10, 25], 63), Some(vec![25, 25, 10, 1, 1, 1]));
140///
141/// // kanonik bo'lmagan tizim — optimal emas!
142/// assert_eq!(coin_change_greedy(&[1, 3, 4], 6), Some(vec![4, 1, 1])); // 3 ta, optimal 2 ta
143///
144/// assert_eq!(coin_change_greedy(&[5, 10], 3), None); // yechim yo'q
145/// ```
146pub fn coin_change_greedy(coins: &[u64], amount: u64) -> Option<Vec<u64>> {
147    let mut tangalar: Vec<u64> = coins.iter().copied().filter(|&c| c > 0).collect();
148    tangalar.sort_unstable_by(|a, b| b.cmp(a)); // kattadan kichikka
149
150    let mut qolgan = amount;
151    let mut natija = Vec::new();
152    for c in tangalar {
153        while qolgan >= c {
154            qolgan -= c;
155            natija.push(c);
156        }
157        if qolgan == 0 {
158            break;
159        }
160    }
161    if qolgan == 0 {
162        Some(natija)
163    } else {
164        None
165    }
166}
167
168/// Minimal perron soni: bir vaqtning o'zida stansiyada eng ko'pi bilan nechta
169/// poyezd turadi?
170///
171/// **Ochko'z qoida:** kelish va ketish vaqtlarini alohida tartiblab, ikkita
172/// ko'rsatkich bilan yuramiz. Kelish ketishdan oldin bo'lsa — yangi perron kerak.
173///
174/// - **Time:** O(n log n).
175///
176/// # Misol
177/// ```
178/// use rust_algorithms::greedy::min_platforms;
179///
180/// let kelish = [900, 940, 950, 1100, 1500, 1800];
181/// let ketish = [910, 1200, 1120, 1130, 1900, 2000];
182/// assert_eq!(min_platforms(&kelish, &ketish), 3);
183/// ```
184pub fn min_platforms(arrivals: &[i64], departures: &[i64]) -> usize {
185    assert_eq!(arrivals.len(), departures.len());
186    let mut kelish = arrivals.to_vec();
187    let mut ketish = departures.to_vec();
188    kelish.sort_unstable();
189    ketish.sort_unstable();
190
191    let (mut i, mut j) = (0usize, 0usize);
192    let (mut hozir, mut maksimum) = (0usize, 0usize);
193
194    while i < kelish.len() {
195        if kelish[i] <= ketish[j] {
196            hozir += 1;
197            maksimum = maksimum.max(hozir);
198            i += 1;
199        } else {
200            hozir -= 1;
201            j += 1;
202        }
203    }
204    maksimum
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn tadbirlar_kesishmaydi() {
213        let a = vec![
214            Activity::new(1, 3),
215            Activity::new(2, 5),
216            Activity::new(4, 7),
217            Activity::new(6, 8),
218        ];
219        let tanlangan = activity_selection(&a);
220        assert_eq!(tanlangan, vec![0, 2]);
221        // tanlanganlar kesishmasligini tekshiramiz
222        for w in tanlangan.windows(2) {
223            assert!(a[w[0]].end <= a[w[1]].start);
224        }
225    }
226
227    #[test]
228    fn bosh_royxat() {
229        assert!(activity_selection(&[]).is_empty());
230        assert_eq!(fractional_knapsack(&[], 10.0), 0.0);
231    }
232
233    #[test]
234    fn xalta_sigimi_yetarli_bolsa_hammasi() {
235        let items = vec![Item::new(10.0, 1.0), Item::new(20.0, 2.0)];
236        assert!((fractional_knapsack(&items, 100.0) - 30.0).abs() < 1e-9);
237    }
238
239    #[test]
240    fn qaytim_summasi_togri() {
241        for summa in 1u64..200 {
242            let t = coin_change_greedy(&[1, 5, 10, 25], summa).unwrap();
243            assert_eq!(t.iter().sum::<u64>(), summa);
244        }
245        assert_eq!(coin_change_greedy(&[2], 5), None);
246        assert_eq!(coin_change_greedy(&[1], 0), Some(vec![]));
247    }
248
249    #[test]
250    fn perronlar() {
251        assert_eq!(min_platforms(&[900, 1100, 1235], &[1000, 1200, 1240]), 1);
252        assert_eq!(min_platforms(&[900, 910], &[1000, 1010]), 2);
253    }
254}