Skip to main content

rust_algorithms/searching/
binary.rs

1//! Binary search (ikkilik qidiruv) va uning oilasi.
2//!
3//! Binary search — DSA dagi eng ko'p ishlatiladigan, eng ko'p xato qilinadigan algoritm.
4//! Shu sababli bu yerda uchta muhim variant bor:
5//! aniq qiymat ([`binary_search`]), chegaralar ([`lower_bound`] / [`upper_bound`])
6//! va "javob ustidan qidiruv" ([`binary_search_answer`]).
7
8use std::cmp::Ordering;
9
10/// Tartiblangan slicedan `target` ni topadi va indeksini qaytaradi.
11///
12/// **G'oya:** o'rtaga qaraymiz. Katta bo'lsa — chap yarmiga, kichik bo'lsa — o'ng yarmiga
13/// o'tamiz. Har qadamda qidiruv maydoni **2 baravar** qisqaradi.
14///
15/// - **Shart:** slice o'sish tartibida tartiblangan bo'lishi kerak.
16/// - **Time:** O(log n), **Space:** O(1).
17///
18/// # Xavfsizlik haqida eslatma
19///
20/// Klassik `mid = (left + right) / 2` yozuvi `usize` da **overflow** yoki
21/// `right = mid - 1` da **underflow** (panic) beradi. Shuning uchun bu yerda
22/// yarim ochiq oraliq `[lo, hi)` ishlatilgan — u hech qachon manfiy bo'lmaydi.
23///
24/// # Misol
25/// ```
26/// use rust_algorithms::searching::binary_search;
27///
28/// let v = [1, 3, 5, 7, 9, 11];
29/// assert_eq!(binary_search(&v, &7), Some(3));
30/// assert_eq!(binary_search(&v, &8), None);
31/// ```
32pub fn binary_search<T: Ord>(list: &[T], target: &T) -> Option<usize> {
33    let mut lo = 0usize;
34    let mut hi = list.len(); // yarim ochiq oraliq: [lo, hi)
35
36    while lo < hi {
37        let mid = lo + (hi - lo) / 2; // overflowdan himoya
38        match list[mid].cmp(target) {
39            Ordering::Equal => return Some(mid),
40            Ordering::Less => lo = mid + 1,
41            Ordering::Greater => hi = mid,
42        }
43    }
44    None
45}
46
47/// Binary search ning rekursiv ko'rinishi — o'quv maqsadida.
48///
49/// Amalda iterativ variant afzal: rekursiya stack joy egallaydi (O(log n)).
50///
51/// # Misol
52/// ```
53/// use rust_algorithms::searching::binary_search_recursive;
54///
55/// let v = [2, 4, 6, 8];
56/// assert_eq!(binary_search_recursive(&v, &2), Some(0));
57/// assert_eq!(binary_search_recursive(&v, &5), None);
58/// ```
59pub fn binary_search_recursive<T: Ord>(list: &[T], target: &T) -> Option<usize> {
60    fn go<T: Ord>(list: &[T], target: &T, lo: usize, hi: usize) -> Option<usize> {
61        if lo >= hi {
62            return None;
63        }
64        let mid = lo + (hi - lo) / 2;
65        match list[mid].cmp(target) {
66            Ordering::Equal => Some(mid),
67            Ordering::Less => go(list, target, mid + 1, hi),
68            Ordering::Greater => go(list, target, lo, mid),
69        }
70    }
71    go(list, target, 0, list.len())
72}
73
74/// **Birinchi** `>= target` elementning indeksi (C++ dagi `lower_bound`).
75///
76/// Agar bunday element bo'lmasa, `list.len()` qaytadi — ya'ni "oxiriga qo'shish kerak".
77/// Bu funksiya yordamida takrorlanuvchi qiymatlarning **eng chap** o'rnini topish mumkin.
78///
79/// - **Time:** O(log n), **Space:** O(1).
80///
81/// # Misol
82/// ```
83/// use rust_algorithms::searching::lower_bound;
84///
85/// let v = [1, 2, 2, 2, 5];
86/// assert_eq!(lower_bound(&v, &2), 1); // birinchi 2
87/// assert_eq!(lower_bound(&v, &3), 4); // 3 yo'q → 5 turgan joy
88/// assert_eq!(lower_bound(&v, &9), 5); // hammasidan katta
89/// ```
90pub fn lower_bound<T: Ord>(list: &[T], target: &T) -> usize {
91    let mut lo = 0usize;
92    let mut hi = list.len();
93    while lo < hi {
94        let mid = lo + (hi - lo) / 2;
95        if list[mid] < *target {
96            lo = mid + 1;
97        } else {
98            hi = mid;
99        }
100    }
101    lo
102}
103
104/// **Birinchi** `> target` elementning indeksi (C++ dagi `upper_bound`).
105///
106/// `upper_bound - lower_bound` = `target` necha marta uchraganini beradi (O(log n) da!).
107///
108/// # Misol
109/// ```
110/// use rust_algorithms::searching::{lower_bound, upper_bound};
111///
112/// let v = [1, 2, 2, 2, 5];
113/// assert_eq!(upper_bound(&v, &2), 4);
114/// let nechta = upper_bound(&v, &2) - lower_bound(&v, &2);
115/// assert_eq!(nechta, 3);
116/// ```
117pub fn upper_bound<T: Ord>(list: &[T], target: &T) -> usize {
118    let mut lo = 0usize;
119    let mut hi = list.len();
120    while lo < hi {
121        let mid = lo + (hi - lo) / 2;
122        if list[mid] <= *target {
123            lo = mid + 1;
124        } else {
125            hi = mid;
126        }
127    }
128    lo
129}
130
131/// **Javob ustidan binary search** — olimpiada va real hayotdagi eng kuchli usul.
132///
133/// `lo..=hi` oralig'ida `pred` monoton (`false false ... false true true ... true`)
134/// bo'lsa, `pred(x) == true` bo'lgan **eng kichik** `x` ni qaytaradi.
135/// Agar hech biri `true` bo'lmasa — `None`.
136///
137/// **Qachon kerak:** "eng kichik sig'im", "eng qisqa vaqt", "minimal tezlik" tipidagi
138/// masalalarda. Massiv emas, **javoblar oralig'i** bo'ylab qidiramiz.
139///
140/// - **Time:** O(log(hi-lo) × pred narxi), **Space:** O(1).
141///
142/// # Misol: √x ning butun qismi
143/// ```
144/// use rust_algorithms::searching::binary_search_answer;
145///
146/// let x = 1_000_000i64;
147/// // "s*s >= x" birinchi marta rost bo'ladigan s ni topamiz
148/// let s = binary_search_answer(0, x, |s| s * s >= x).unwrap();
149/// assert_eq!(s, 1000);
150/// ```
151pub fn binary_search_answer<F: Fn(i64) -> bool>(lo: i64, hi: i64, pred: F) -> Option<i64> {
152    let (mut lo, mut hi) = (lo, hi);
153    if lo > hi {
154        return None;
155    }
156    if !pred(hi) {
157        return None; // hech qayerda rost emas
158    }
159    while lo < hi {
160        let mid = lo + (hi - lo) / 2;
161        if pred(mid) {
162            hi = mid;
163        } else {
164            lo = mid + 1;
165        }
166    }
167    Some(lo)
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn hamma_elementni_topadi() {
176        let v: Vec<i32> = (0..100).map(|x| x * 3).collect();
177        for (i, x) in v.iter().enumerate() {
178            assert_eq!(binary_search(&v, x), Some(i));
179            assert_eq!(binary_search_recursive(&v, x), Some(i));
180        }
181    }
182
183    #[test]
184    fn yoq_elementga_none() {
185        let v = [1, 3, 5, 7];
186        for x in [0, 2, 4, 6, 8] {
187            assert_eq!(binary_search(&v, &x), None, "x = {x}");
188        }
189    }
190
191    #[test]
192    fn bosh_va_bitta_elementli() {
193        let bosh: [i32; 0] = [];
194        assert_eq!(binary_search(&bosh, &1), None);
195        assert_eq!(binary_search(&[42], &42), Some(0));
196        assert_eq!(binary_search(&[42], &7), None);
197    }
198
199    #[test]
200    fn chegaralar_takrorlanuvchi_qiymatlarda() {
201        let v = [1, 2, 2, 2, 2, 9];
202        assert_eq!(lower_bound(&v, &2), 1);
203        assert_eq!(upper_bound(&v, &2), 5);
204        assert_eq!(upper_bound(&v, &0), 0);
205        assert_eq!(lower_bound(&v, &100), v.len());
206    }
207
208    #[test]
209    fn javob_ustidan_qidiruv() {
210        assert_eq!(binary_search_answer(0, 100, |x| x >= 37), Some(37));
211        assert_eq!(binary_search_answer(0, 10, |x| x > 100), None);
212        assert_eq!(binary_search_answer(0, 10_000, |s| s * s >= 144), Some(12));
213    }
214
215    #[test]
216    fn std_bilan_solishtirish() {
217        let v: Vec<i32> = (0..1000).step_by(7).collect();
218        for x in 0..1000 {
219            let ours = binary_search(&v, &x);
220            let theirs = v.binary_search(&x).ok();
221            assert_eq!(ours, theirs, "x = {x}");
222        }
223    }
224}