chuyenhalong.ucode.vn cung cấp các khóa học lập trình tương tác với Python, Scratch, C/C++, Pascal, Game, Thuật toán, Web, Ứng dụng di động… cho tất cả mọi lứa tuổi, từ học sinh tiểu học đến người đi làm. Các khóa học được biên soạn bởi các chuyên gia giàu kinh nghiệm với những video, bài giảng tương tác, bài tập trực quan và những dự án thực tế. Tất cả đều được thực hiện ngay trên trình duyệt web của bạn.
- title Tìm kiếm nhị phân (Binary search) - Tìm kiếm nhị phân (Binary search) === - ###### ✍️ Author: 2School Guideline ###### 📋 Content: [TOC] - # Giới thiệu chung Tìm kiếm là một kĩ thuật vô cùng quan trọng để giải quyết các bài toán trong Tin học. Có nhiều phương pháp tìm kiếm được nghiên cứu, phát triển và ứng dụng; mỗi thuật toán đều có độ phức tạp và ưu/nhược điểm nhất định. Trong bài viết này, chúng mình sẽ tập trung đề cập đến một giải thuật tìm kiếm căn bản trong Khoa học máy tính - Binary Search (còn được biết đến là Tìm kiếm nhị phân hay Chặt nhị phân). # Tìm kiếm phần tử trong mảng ## Bài toán 1. Cho mảng $a$ có $n$ phần tử và 1 số $x$ ($n ≤ 10^6, |a_i| ≤ 10^9, |x| ≤ 10^9$), xác định xem có phần tử $a_i = x$ trong mảng $a$ hay không, nếu tìm thấy in ra $“YES”$, không tìm thấy thì in ra $“NO”$ (giới hạn bài toán chỉ mang tính chất minh họa). 2. Cho mảng $a$ có $n$ phần tử **đã được sắp xếp không giảm** (với mọi $i$ sao cho $1 ≤ i < n$, $a_i ≤ a_{i + 1}$) và $q$ truy vấn, mỗi truy vấn là một số $x$ ($2 ≤ n ≤ 10^6, q ≤ 10^5, |a_i| ≤ 10^9, |x| ≤ 10^9$), với mỗi truy vấn, xác định xem có phần tử $a_i = x$ trong mảng $a$ hay không, nếu tìm thấy in ra $“YES”$, không tìm thấy thì in ra $“NO”$ (giới hạn bài toán chỉ mang tính chất minh họa). ## Tìm kiếm tuần tự kết quả (Linear Search) Một giải pháp cơ bản để giải các bài toán trên chính là duyệt qua tất cả các phần tử của mảng $a$ để tìm ra phần tử thỏa yêu cầu đề. ```cpp= void linearSearch(long long x) { //Quét qua tất cả phần tử trong mảng for (int i = 1; i <= n; i) { //Giả sử chỉ số phần tử bắt đầu từ 1 //Tìm thấy phần tử thỏa đề => Xuất ra "YES" và thoát ra khỏi hàm if (a[i] == x) { cout << "YES\n"; return; } } //Không tìm thấy phần tử thỏa đề => Xuât ra "NO" cout << "NO\n"; } ``` Ưu điểm của phương pháp này là dễ dàng suy ra từ đề bài, code ngắn gọn, cài đặt đơn giản. Ở bài toán $(1)$, các phần tử trong mảng được sắp xếp theo thứ tự ngẫu nhiên, do đó, Linear Search là phương pháp tốt nhất để tìm kiếm phần tử theo yêu cầu đề, do không có thêm dữ kiện nào về vị trí của $x$ trong mảng $a$. Trong tình huống xấu nhất, thuật toán buộc phải duyệt qua tất cả các phần tử trong mảng $a$, do đó, độ phức tạp của Linear Search là $O(n)$. Tuy nhiên, đối với bài toán $(2)$, ta phải lặp lại thao tác tìm kiếm này trong $q$ lần, dẫn đến số thao tác cần thực hiện tối đa là $n \cdot\ q$. Khi số bước lặp càng lớn ($n \cdot\ q \ge 10^7$), quá trình thực thi trở nên chậm hơn và có thể vượt quá giới hạn thời gian đề yêu cầu (thường là $1$ giây). Vì vậy, ta cần tìm một giải thuật tối ưu hơn để giải quyết bài toán $(2)$ trong thời gian hạn định. ## Tìm kiếm nhị phân (Binary Search) ### Cách 1 Để cải tiến thuật toán tìm kiếm, ta cần lưu ý một dữ kiện hết sức quan trọng trong đề bài $(2)$: **mảng $a$ đã được sắp xếp không giảm**. Dựa vào thông tin trên, ta thiết lập được một thuật toán thu hẹp phạm vi tìm kiếm dựa trên giá trị của phần tử. Để hiểu hơn về cách giải thuật này hoạt động, ta xét ví dụ sau với $n = 9$, $a =\{3,5,8,13,18,19,21,27,32\}$, $q = 1$ và phần tử cần tìm trong truy vấn có giá trị $x = 13$. - Đặt $left$ là vị trí đầu bên trái, $right$ là vị trí đầu bên phải của vùng cần xét. Ví dụ: nếu bạn muốn tìm kiếm ở khu vực $[1, 9]$ thì $left = 1, right = 9$. ![](//hackmd.io/_uploads/rkd3OEchh.png) - Xét vị trí chính giữa của $a$ bằng công thức: $mid = \lfloor\frac{left + right}{2}\rfloor$, lúc này ta được giá trị của phần tử chính giữa là $a[mid] = 18$ - Do $a[mid] > x$ $(18 > 13)$ nên $a[mid...right] > x$ $\Rightarrow$ ta có thể loại bỏ đoạn $[mid...right]$ (trong trường hợp này là đoạn $[5, 9]$) và tiếp tục xét đoạn còn lại là $[left...mid - 1]$ (đoạn $[1, 4]$). - Khi này, ta có $mid = \lfloor\frac{left + right}{2}\rfloor = 2$ $\Rightarrow$ xét $a[2] = 5$. ![](//hackmd.io/_uploads/S1K4YNcn3.png) - Vì $a[mid] < x$ nên $a[left...mid] < x \Rightarrow$ ta có thể lược đi đoạn $[left...mid]$ (đoạn $[1, 2]$) và tiếp tục xét đoạn $[mid + 1...right]$ (đoạn $[3, 4]$). - Lúc này, ta có $mid = \lfloor\frac{left + right}{2}\rfloor = 3 \Rightarrow$ xét $a[3] = 8$. ![](//hackmd.io/_uploads/r1j5tE9nh.png) - Do $a[mid] < x$ nên ta có thể lược đi đoạn $[3, 3]$ và tiếp tục xét đoạn $[4, 4]$. - Lúc này, ta có $mid = \lfloor\frac{left + right}{2}\rfloor = 4$ $\Rightarrow$ xét $a[4] = 13$. Ta thấy được $a[4] = x$ $\Rightarrow$ có tồn tại phần tử $x$ ở vị trí $i = 4$ trong mảng $a$. Tổng quát lại các bước hoạt động: ![](//hackmd.io/_uploads/HJNVNOi2h.png) Minh hoạ quá trình tìm kiếm phần tử $x$: ![](//hackmd.io/_uploads/HJGS14cn3.gif) Bây giờ ta sẽ tiến hành cài đặt thuật toán dựa trên ý tưởng nêu trên. **Vòng lặp** ```cpp= void binarySearch(long long x) { //Đặt biến trái và biến phải int left = 1, right = n; while (left <= right) { //Tìm vị trí ở giữa int mid = (left + right) / 2; //Tìm thấy phần tử ở vị trí mid if (a[mid] == x) { cout << "YES\n"; return; } //Phần tử ở vị trí mid > x else if(a[mid] > x) right = mid - 1; //Phần tử ở vị trí mid < x else left = mid + 1; } //Không tìm thấy phần tử thỏa đề cout << "NO\n"; return; } ``` **Đệ quy** ```cpp= int binarySearch(int left, int right, long long x) { //Không tìm thấy phần tử thỏa đề if(left > right) return -1; //Tìm vị trí ở giữa int mid = (left + right) / 2; //Tìm thấy phần tử thỏa đề if(a[mid] == x) return 1; //Phần tử ở vị trí mid > x else if(a[mid] > x) return binarySearch(left, mid - 1, x); //Phần tử ở vị trí mid < x return binarySearch(mid + 1, right, x); } int main() { //Nhập dữ liệu //Duyệt qua q truy vấn while (q--) { long long x; cin >> x; if (binarySearch(1, n, x) != -1) cout << "YES\n"; else cout << "NO\n"; } } ``` - Phạm vi tìm kiếm khi khởi tạo là $[1...n]$, và sau mỗi bước, kích thước của vùng tìm kiếm này lại giảm đi một nửa, vì vậy, đối với mảng đã được sắp xếp theo thứ tự nhất định thì độ phức tạp của giải thuật là $O(\log{n})$. Cách cài đặt có hằng số tương đối nhỏ, tuy nhiên, nhược điểm của phương pháp cài này là đoạn code sẽ dài hơn các cách khác được giới thiệu bên dưới. - Ngoài ra, chúng ta cũng có thể tìm kiếm nhị phân trên mảng $a$ không tăng, hay nói chung là một mảng **đã được sắp xếp**. Cách cài giống với tư tưởng trên thế nhưng chúng ta chỉ cần đổi điều kiện không tăng và phạm vi tìm kiếm. - Đối với mảng không tăng, ta chỉ cần thay điều kiện thành $a[mid] < x$ thì $right = mid - 1$ bởi chúng ta có thông tin rằng $a[mid...right] < x$ và trong trường hợp ngược lại ta bỏ đoạn $[left...mid]$ hay $left = mid + 1$ ### Cách 2 (mở rộng tham khảo) Khi giải các bài toán $(1)$ và $(2)$ bằng phương pháp Tìm kiếm tuần tự (Linear Search), ta cần "nhảy" lần lượt qua các phần tử trong mảng cho đến khi tìm được phần tử thỏa yêu cầu đề bài. Vì trong mỗi lượt, thao tác "nhảy" từ một phần tử đến phần tử kế cận được thực hiện $\Rightarrow$ độ dài "bước nhảy" là $1$. Để tối ưu giải thuật tìm kiếm nhằm giải quyết bài toán $(2)$, câu hỏi đặt ra là: làm thế nào để tăng độ dài "bước nhảy", giảm số thao tác "nhảy"? Dựa vào thông tin về tính có thứ tự của phần tử mảng $a$, ta sẽ thực hiện những bước nhảy có độ dài $\frac{n}{2}$, $\frac{n}{4}$, $\frac{n}{8}$,... và chậm dần tốc độ khi tiến càng gần đến mục tiêu tìm kiếm (giảm độ dài bước nhảy đến $\frac{n}{n} = 1$). Ý tưởng cài đặt Tìm kiếm nhị phân (Binary Search) này được giới thiệu trong cuốn sách [Competitive Programmer’s Handbook](//cses.fi/book/book.pdf) của Antti Laaksonen. Xét một ví dụ: Cho $n = 9$, $a =\{3,5,8,13,18,19,21,27,32\}$, $q = 1$ và phần tử cần tìm trong truy vấn có giá trị $x = 27$. ![](//hackmd.io/_uploads/HJ_04Scn2.png) Quan sát hình ta thấy, từ vị trí $i = 1$, để đến được vị trí của phần tử có giá trị bằng $x$ cần tìm, ta thực hiện "nhảy" lần lượt 1 bước có độ dài $\frac{n}{2}$, 1 bước có độ dài $\frac{n}{4}$ và 1 bước có độ dài là $\frac{n}{8} = 1$. Thuật toán được cài đặt như sau: ```cpp void binarySearch(long long x) { //Vị trí bắt đầu nhảy int k = 1; //Thực hiện các bước nhảy for (int b = n / 2; b >= 1; b /= 2) { while ((k + b <= n) && (a[k + b] <= x)) k += b; } //Kiểm tra phần tử có thỏa đề if (a[k] == x) cout << "YES\n"; else cout << "NO\n"; } ``` Chúng ta cùng xem cách giải thuật hoạt động với ví dụ trên. - Đoạn code thực hiện $2$ lần "nhảy thử" với độ dài "bước nhảy" là $\frac{n}{2}$. Ở lần "nhảy thử" thứ $2$, $a[k + b] > x$ (tương ứng $32 > 27$) $\Rightarrow$ thoát khỏi vòng lặp while, chuyển đến lần lặp tiếp theo của vòng for. ![](//hackmd.io/_uploads/r1Nd4Icnn.png) - Đoạn code tiếp tục thực hiện $2$ lần "nhảy thử" với độ dài "bước nhảy" là $\frac{n}{4}$. Ở lần "nhảy thử" thứ $2$, $a[k + b] > x$ (tương ứng $32 > 27$) $\Rightarrow$ thoát khỏi vòng lặp while, chuyển đến lần lặp tiếp theo của vòng for. ![](//hackmd.io/_uploads/BJatTI5hh.png) - Đoạn code tiếp tục thực hiện $2$ lần "nhảy thử" với độ dài "bước nhảy" là $\frac{n}{8} = 1$. Ở lần "nhảy thử" thứ $2$, $a[k + b] > x$ (tương ứng $32 > 27$) => thoát khỏi vòng lặp while. Trong lần lặp tiếp theo của vòng lặp for, $\lfloor\frac{b}{2}\rfloor$ $=$ $\lfloor\frac{1}{2}\rfloor$ $= 0$ $\Rightarrow$ thoát khỏi vòng lặp for. ![](//hackmd.io/_uploads/ByjgyDcnh.png) - Lúc này, $k = \frac{n}{2} + \frac{n}{4} + \frac{n}{8} = 1 + 4 + 2 + 1 = 8$. Nhận thấy, $a[8] = 27 = x$ $\Rightarrow$ Tồn tại phần tử thỏa đề $\Rightarrow$ Xuất ra $"YES"$. Tổng quát lại các bước hoạt động: ![](//hackmd.io/_uploads/Sk7TfVoh3.png) Ta có thể chứng minh được, ở mỗi bước, đoạn code trong vòng lặp for sẽ được thực hiện tối đa $2$ lần (trung bình là $1$ lần), và thao tác "nhảy thử" được thực hiện từ $1 - 3$ lần. :::spoiler **Chứng minh tham khảo** Ta có: $\lfloor\frac{x}{2}\rfloor * 3 > x \ge \lfloor\frac{x}{2}\rfloor * 2$. Do đó, với $b = \lfloor\frac{x}{2}\rfloor * 3$, điều kiện $a[k + b] \le x$, không còn đúng, dẫn đến việc thoát ra khỏi vòng lặp $while$. ::: Với vòng lặp for, độ dài "bước nhảy" $b$ sẽ được giảm đi một nửa sau mỗi lần lặp. Vì vậy, độ phức tạp thuật toán là $O(\log{n})$. Ưu điểm của cách này là code vô cùng ngắn gọn và thuận theo dòng suy nghĩ khi cài đặt. Tuy vậy, việc "nhảy thử" từ $1 - 3$ lần ở mỗi bước sẽ là một vấn đề khi bạn dùng cách cài đặt này cho các bài toán có giới hạn chặt, các bài **interactive** (bài toán mà chương trình tương tác trực tiếp với trình chấm) dùng đến Binary Search. Để giải được các bài toán sử dụng Tìm kiếm nhị phân (Binary Search) trong các kì thi, bạn chỉ cần cài đặt thành thạo $1$ trong $2$ cách nêu trên. Tuy nhiên, cách bạn mô phỏng một thuật toán sẽ định hướng tư duy của bạn về thuật toán đó. Việc tìm hiểu đa dạng các cách xây dựng chương trình cho một giải thuật giúp việc nhận biết thuật toán đó trong một bài toán trở nên dễ dàng và thuận lợi hơn. Do đó, chúng mình khuyến khích bạn đọc và hiểu cả hai cách cài Binary Search được giới thiệu ở trên. ### Các hàm tìm kiếm có sẵn trong ngôn ngữ C Thư viện chuẩn C++ cũng cung cấp cho lập trình viên các hàm có sẵn để thực hiện tìm kiếm bằng Binary Search. Trong nhiều trường hợp, việc sử dụng chương trình con và thủ tục được cài đặt sẵn trong ngôn ngữ lập trình làm cho code của bạn được ngắn gọn hơn, tốc độ thực thi cũng nhanh hơn (do các hàm sẵn có thường có hằng số tương đối nhỏ). Sau đây, chúng mình sẽ giới thiệu đến bạn một số hàm Tìm kiếm nhị phân phổ biến trong ngôn ngữ C++ có thể sử dụng để giải quyết bài toán $(2)$. #### Hàm binary_search() - **Công dụng:** Hàm ```binary_search()``` trong C++ được sử dụng trên mảng đã sắp xếp để trả về **true** nếu tồn tại $x$ trong mảng chứa, và trả về **false** nếu không tìm thấy. - **Cách khai báo:** ```binary_search({first}, {last}, {x});``` Trong đó: - **first, last** lần lượt là 2 con trỏ trỏ đến vị trí xuất phát và kết thúc của dãy cần tìm kiếm. Khoảng được tìm kiếm là **[first, last)**, tức là tất cả các phần tử giữa first và last và thêm phần tử được trỏ bởi first nhưng không có phần tử trỏ bởi last, nếu tất cả phần tử trong khoảng tìm kiếm đều bé hơn **x** thì hàm sẽ trả về **last**. - **x** là giá trị cần được tìm kiếm. **Code minh họa bài toán (2)** ```cpp= int main() { //Nhập dữ liệu //Duyệt qua q truy vấn while (q--) { long long x; cin >> x; //Tìm thấy phần tử thỏa đề if (binary_search(a + 1, a + n + 1, x)) cout << "YES\n"; //Không tìm thấy phần tử thỏa đề else cout << "NO\n"; } } ``` #### Hàm equal_range() - **Công dụng:** Hàm ```equal_range()``` trong C++ được sử dụng trên mảng đã sắp xếp để trả về một **pair** gồm 2 con trỏ: con trỏ đầu tiên trỏ tới vị trí đầu tiên có giá trị **lớn hơn hoặc bằng** $x$, con trỏ thứ hai trỏ tới vị trí đầu tiên có giá trị **lớn hơn** $x$. Tận dụng điều này để giải bài toán $(2)$, ta kiểm tra xem đoạn con gồm các phần tử có giá trị bằng $x$ có độ dài lớn hơn $0$ hay không. Nếu độ dài đoạn con tìm được lớn hơn $0$ $\Rightarrow$ trong mảng tồn tại phần tử có giá trị bằng $x$ $\Rightarrow$ xuất ra $"YES"$, ngược lại thì in ra $"NO"$. - **Cách khai báo:** ```equal_range({first}, {last}, {x});``` Trong đó: - **first, last** lần lượt là $2$ con trỏ trỏ đến vị trí xuất phát và kết thúc của dãy cần tìm kiếm. Khoảng được tìm kiếm là **[first, last)**, tức là tất cả các phần tử giữa first và last và thêm phần tử được trỏ bởi first nhưng không có phần tử trỏ bởi last, nếu tất cả phần tử trong khoảng tìm kiếm đều bé hơn $x$ thì hàm sẽ trả về **last**. - $x$ là giá trị cần được tìm kiếm. **Code minh họa bài toán $(2)$** **Mảng thường** ```cpp= int main() { //Nhập dữ liệu //Duyệt qua q truy vấn while (q--) { long long x; cin >> x; //Khai báo pair gồm hai con trỏ auto r = equal_range(a + 1, a + n + 1, x); //Độ dài đoạn con có phần tử bằng x int ln = r.second - r.first; //Tồn tại phần tử bằng x if (ln > 0) cout << "YES\n"; //Không tồn tại phần tử bằng x else cout << "NO\n"; } } ``` **Vector** ```cpp= vector<long long> a; int main() { //Nhập dữ liệu //duyệt qua q truy vấn while (q--) { long long x; cin >> x; //Khai báo pair gồm hai con trỏ auto r = equal_range(a.begin(), a.end(), x); //Độ dài đoạn con có phần tử bằng x int ln = r.second - r.first; //Tồn tại phần tử bằng x if(ln > 0) cout << "YES\n"; //Không tồn tại phần tử bằng x else cout << "NO\n"; } } ``` # Tìm phần tử trong mảng có giá trị gần nhất với một số x Chúng ta đã thấy sự tiện lợi lớn của thuật toán Tìm kiếm nhị phân khi đã giúp cải thiện rất nhiều tốc độ của bài toán **tìm kiếm phần tử trong mảng đã được sắp xếp**. Không dừng lại ở đó, Binary Search vẫn còn nhiều những phương hướng áp dụng thú vị khác trong giải quyết các vấn đề Tin học. Chúng ta hãy cùng xem qua một ứng dụng phổ biến của Binary Search trong lập trình - tìm phần tử trong mảng có giá trị gần nhất với số $x$. ## Bài toán tìm phần tử gần nhất bé hơn hoặc bằng - Cho mảng $a$ có $n$ phần tử được đánh số từ $1 \rightarrow n$ **đã được sắp xếp tăng dần** và $1$ số nguyên $x$, xác định vị trí lớn nhất của phần tử bé hơn hoặc bằng $x$. Nếu không có phần tử nào bé hơn $x$, xuất $-1$ $(n ≤ 10^6, |a_i| ≤ 10^9, |x| ≤ 10^9)$. - Ví dụ: $a = [3,5,10,11,13, 18,18,25,27,31], x = 20$ có kết quả là $7$ $(a[7] = 18)$. ### Lời giải - Đầu tiên chúng ta sẽ cài đặt lại thuật toán nhị phân, với hai biên $left$ và $right$, cùng phần tử trung vị $mid = \lfloor\frac{left + right}{2}\rfloor$ - Đặt biến $ans$ để lưu kết quả mỗi lần $a[mid] \le x$ thì $mid$ đó sẽ là $1$ đáp án hợp lệ, hay $ans = mid$. Sau đó bỏ qua toàn bộ đoạn $l \rightarrow mid$ bởi vì ta biết rằng trong đoạn này đáp án $mid$ là tối ưu nhất, ta sẽ đi kiểm tra đoạn $[mid + 1, r]$, hay ta đặt $l = mid + 1$. Còn ngược lại nếu $a[mid] > x$, thì ta biết rằng tất cả các giá trị $a[mid \rightarrow r] > x$ (không hợp lệ), thế nên ta đặt $r = mid - 1$ để bỏ qua đoạn $mid \rightarrow r$. - Nếu không tìm được giá trị nào bé hơn hoặc bằng $x$ sau khi $2$ biên $left$ và $right$ chạm nhau thì ta xuất ra $-1$. **Code:** ```cpp int binarySearch (int x) { int left = 1, right = n; // Đặt vùng biên trái và biên phải cần xét int ans = -1; // khai báo biến ans để lưu trữ kết quả, nếu không tìm thấy thì mặc định là -1 while (left <= right) { int mid = (left + right) / 2; // Tìm vị trí ở giữa if (a[mid] <= x) { ans = mid; //lưu trữ kết quả left = mid + 1; // Nếu a[mid] <= x thì ta bỏ qua đoạn l -> mid và ghi nhận mid là 1 kết quả hợp lệ } else right = mid - 1; // Nếu a[mid] > x thì bỏ qua đoạn mid -> r } // Trả về vị trí lớn nhất có giá trị <= x return ans; } ``` ## Bài toán tìm phần tử gần nhất lớn hơn hoặc bằng - Qua cách xử lí của bài toán trên, chúng ta cũng có thể biến đổi bài toán thành tìm kiếm phần tử lớn hơn gần nhất với phần tử $x$ cho trước bằng cách thay đổi điều kiện. **Code:** ```cpp int binarySearch(int x) { int left = 1, right = n; // Đặt vùng biên trái và biên phải cần xét int ans = -1; // khai báo biến ans để lưu trữ kết quả, nếu không tìm thấy thì mặc định là -1 while (left <= right) { int mid = (left + right) / 2; // Tìm vị trí ở giữa if (a[mid] >= x) { ans = mid; //lưu trữ kết quả right = mid - 1; // Nếu a[mid] >= x thì ta bỏ qua đoạn mid -> r và ghi nhận mid là 1 kết quả hợp lệ } else left = mid + 1; // Nếu a[mid] < x thì bỏ qua đoạn l -> mid } // Trả về vị trí lớn nhất có giá trị >= x return ans; } ``` - Độ phức tạp của phương pháp giải cho $2$ bài toán trên là: $O(\log{n})$ (với mảng $a$ đã được sắp xếp) - Minh hoạ quá trình tìm vị trí lớn nhất của phần tử $\le x$ trong mảng: ![](//hackmd.io/_uploads/HJuDhS5h2.gif) ## Sử dụng hàm có sẵn - Bên cạnh đó, để giải quyết những bài toán trên, thư viện chuẩn C++ cũng cung cấp cho lập trình viên các hàm có sẵn như `lower_bound()` và `upper_bound()`. Việc sử dụng các hàm này sẽ giúp code của bạn ngắn gọn hơn, tiết kiệm thời gian cài đặt. - Trong bài viết có đề cập tới khái niệm con trỏ và iterator, để tránh vượt quá giới hạn bài viết, bạn đọc có thể đọc về 2 khái niệm này tại đây: [con trỏ trong mảng 1 chiều](//cpp.daynhauhoc.com/8/2-con-tr-va-mang-mot-chieu/), [iterator](//cpp.daynhauhoc.com/11/2-stl-iterators/) ### Hàm lower_bound() - **Công dụng:** Hàm `lower_bound()` trong C++ được sử dụng trên mảng đã sắp xếp để trả về một iterator trỏ đến phần tử đầu tiên có giá trị không nhỏ hơn $x$ trong phạm vi vùng biên trái đến trước vùng biên phải mà ta khai báo cho hàm. - **Cách khai báo:** - ```lower_bound({first}, {last}, {x});``` đối với mảng thường và vector - **first, last** lần lượt là 2 con trỏ trỏ đến vị trí xuất phát và kết thúc của dãy cần tìm kiếm. Khoảng được tìm kiếm là **[first, last)**, tức là tất cả các phần tử giữa first và last và thêm phần tử được trỏ bởi first nhưng không có phần tử trỏ bởi last, nếu tất cả phần tử trong khoảng tìm kiếm đều bé hơn $x$ thì hàm sẽ trả về **last**. - $x$ là giá trị cần được tìm kiếm. **Code minh họa:** ```cpp=