Tìm hiểu thuật toán Tìm kiếm Tuyến tính (Linear Search)

Trong khoa học máy tính, tìm kiếm tuyến tính là thuật toán đơn giản và cơ bản nhất trong giải quyết các vấn đề tìm kiếm dữ liệu. Dựa trên một cấu trúc dữ liệu tuyến tính, phương pháp này cho phép chúng ta tìm kiếm một phần tử cụ thể trong tập dữ liệu một cách hiệu quả. Trong bài viết này, chúng ta sẽ tìm hiểu và thảo luận về thuật toán tìm kiếm tuyến tính, cách nó hoạt động và các ứng dụng thực tế của nó.


Khái niệm thuật toán tìm kiếm tuyến tính

Tìm kiếm tuyến tính (tiếng Anh: Linear Search), hay còn được gọi là tìm kiếm tuần tự (tiếng Anh: Sequential search) là một phương pháp tìm kiếm một phần tử cụ thể trong một tập dữ liệu theo thứ tự tuyến tính. Thuật toán này hoạt động bằng cách lần lượt so sánh từng phần tử một trong tập dữ liệu với giá trị cần tìm cho đến khi tìm được hoặc đã duyệt hết toàn bộ tập dữ liệu. Nếu phần tử được tìm thấy, ta nhận được kết quả là vị trí của nó trong tập dữ liệu, ngược lại ta sẽ kết luận phần tử không có trong tập dữ liệu.

Cách thức hoạt động của thuật toán tìm kiếm tuyến tính

Thuật toán tìm kiếm tuyến tính là thuật toán rất đơn giản. Các bước thực hiện như sau:

  • Bước 1: Nhận giá trị cần tìm kiếm.
  • Bước 2: Duyệt qua từng phần tử trong tập dữ liệu.
  • Bước 3: So sánh phần tử hiện tại với giá trị cần tìm.
  • Bước 4: Nếu phần tử được tìm thấy, trả về vị trí của nó.
  • Bước 5: Nếu duyệt qua toàn bộ tập dữ liệu mà không tìm thấy, thông báo rằng phần tử không tồn tại.
Ví dụ, giả sử chúng ta có mảng arr[] = {94, 97, 80, 28, 77, 50, 69, 57, 32}, và giá trị cần tìm là 80, thì thuật toán sẽ hoạt động như sau:

  • Bắt đầu với arr[0] = 94, thuật toán so sánh phần tử này với giá trị cần tìm. Vì 94 ≠ 80 nên chuyển qua phần tử tiếp theo là arr[1] = 97
  • Tiếp tục so sánh arr[1] với phần tử cần tìm. Vì 97 ≠ 80 nên qua phần tử tiếp theo
  • So sánh arr[2] với phần tử cần tìm. Vì 80 = 80 nên thuật toán sẽ thông báo giá trị được tìm thấy và trả về vị trí này

Triển khai thuật toán

Sau đây là code triển khai thuật toán tìm kiếm tuyến tính bằng những ngôn ngữ khác nhau mà bạn có thể tham khảo.

C

#include <stdio.h>

// Hàm linearSearch() sẽ lấy 3 tham số
int linearSearch(int arr[], int arrLength, int target)
{
    // Lặp từng phần tử của mảng
    for (int i = 0; i < arrLength; i++)
    {
        // Kiểm tra phần tử arr[i] với giá trị cần tìm target, nếu đúng thì trả về vị trí i
        if (arr[i] == target)
        {
            return i;
        }
    }
    // Trả về -1 nếu không tìm thấy
    return -1;
}

int main()
{
    int arr[] = {94, 97, 80, 28, 77, 50, 69, 57, 32};
    // Tính kích thước của mảng arr
    int arrLength = sizeof(arr) / sizeof(arr[0]);
    int target = 80;

    int result = linearSearch(arr, arrLength, target);

    if (result == -1)
    {
        printf("Phan tu khong tim thay trong mang.");
    }
    else
    {
        printf("Phan tu tim thay tai vi tri %d.", result);
    }
   
    // Output: 2

    return 0;
}

C++

#include <iostream>

using namespace std;

// Hàm linearSearch() sẽ lấy 3 tham số
int linearSearch(int arr[], int arrLength, int target)
{
    // Lặp từng phần tử của mảng
    for (int i = 0; i < arrLength; i++)
    {
        // Kiểm tra phần tử arr[i] với giá trị cần tìm target, nếu đúng thì trả về vị trí i
        if (arr[i] == target)
        {
            return i;
        }
    }
    // Trả về -1 nếu không tìm thấy
    return -1;
}

int main()
{
    int arr[] = {94, 97, 80, 28, 77, 50, 69, 57, 32};
    // Tính kích thước của mảng arr
    int arrLength = sizeof(arr) / sizeof(arr[0]);
    int target = 80;

    int result = linearSearch(arr, arrLength, target);

    if (result == -1)
    {
        cout << "Phan tu khong tim thay trong mang.";
    }
    else
    {
        cout << "Phan tu tim thay tai vi tri " << result;
    }
   
    // Output: 2

    return 0;
}

C#

using System;

namespace LinearSearch
{
    class Program
    {
        // Phương thức linearSearch() lấy 2 tham số
        static int linearSearch(int[] arr, int target)
        {
            // Lặp từng phần tử của mảng
            for (int i = 0; i < arr.Length; i++)
            {
                // Kiểm tra phần tử arr[i] với giá trị cần tìm target, nếu đúng thì trả về vị trí i
                if (arr[i] == target)
                {
                    return i;
                }
            }
            // Trả về -1 nếu không tìm thấy
            return -1;
        }

        static void Main(string[] args)
        {
            int[] arr = { 94, 97, 80, 28, 77, 50, 69, 57, 32 };
            int target = 80;

            int result = linearSearch(arr, target);

            if (result == -1)
            {
                Console.WriteLine("Phan tu khong tim thay trong mang.");
            }
            else
            {
                Console.WriteLine($"Phan tu tim thay tai vi tri {result}.");
            }
            // Output: 2
        }
    }
}

Java

public class Main {
    // Phương thức linearSearch() lấy hai tham số
    public static int linearSearch(int[] arr, int target) {
        // Lặp từng phần tử của mảng
        for (int i = 0; i < arr.length; i++) {
            // Kiểm tra phần tử arr[i] với giá trị cần tìm target, nếu đúng thì trả về vị trí i
            if (arr[i] == target) {
                return i;
            }
        }
        // Trả về -1 nếu không tìm thấy
        return -1;
    }

    public static void main(String[] args) {
        int[] arr = {94, 97, 80, 28, 77, 50, 69, 57, 32};
        int target = 80;

        int result = linearSearch(arr, target);

        System.out.println(result == -1? "Phan tu can tim khong co trong mang." : "Tim thay phan tu tai vi tri " + result);
        // Output: 2
    }
}

Python

# Hàm linearSearch() sẽ lấy 2 tham số
def linearSearch(lst, target):
    # Lặp từng phần tử của mảng
    for i in range(len(lst)):
        # Kiểm tra phần tử arr[i] với giá trị cần tìm target, nếu đúng thì trả về vị trí i
        if lst[i] == target:
            return i
    # Trả về -1 nếu không tìm thấy
    return -1

lst = [94, 97, 80, 28, 77, 50, 69, 57, 32]
target = 80

result = linearSearch(lst, target)

if result == -1:
    print("Phần tử không tìm thấy trong danh sách")
else:
    print(f"Phần tử tìm thấy tại vị trí {result}")

# Output: 2

JavaScript

// Hàm linearSearch() lấy hai tham số
function linearSearch(arr, target) {
    // Lặp từng phần tử của mảng
    for (let i = 0; i < arr.length; i++) {
        // Kiểm tra phần tử arr[i] với giá trị cần tìm target, nếu đúng thì trả về vị trí i
        if (arr[i] === target) {
            return i;
        }
    }
    // Trả về -1 nếu không tìm thấy
    return -1;
}

let arr = [94, 97, 80, 28, 77, 50, 69, 57, 32];
let target = 80;

let result = linearSearch(arr, target);

if (result === -1) {
    window.alert("Phần tử không tìm thấy trong mảng.");
} else {
    window.alert("Phần tử tìm thấy tại vị trí " + result);
}
// Output: 2

Phân tích độ phức tạp về thời gian và bộ nhớ của thuật toán

Độ phức tạp về thời gian

Trong trường hợp tốt nhất, khi phần tử cần tìm nằm ở vị trí đầu tiên trong tập dữ liệu, thuật toán tìm kiếm tuyến tính chỉ mất thời gian O(1), tức là thời gian tìm kiếm là hằng số.

Trong trường hợp xấu nhất, khi phần tử cần tìm nằm ở vị trí cuối cùng hoặc không có trong tập dữ liệu, thuật toán tìm kiếm tuyến tính cần duyệt qua toàn bộ tập dữ liệu. Vì vậy, thời gian tìm kiếm là O(n), trong đó n là số lượng phần tử trong tập dữ liệu. Điều này có nghĩa là thời gian tìm kiếm tăng theo độ lớn của tập dữ liệu.

Độ phức tạp về bộ nhớ

Thuật toán tìm kiếm tuyến tính không yêu cầu bộ nhớ bổ sung ngoài tập dữ liệu ban đầu. Nó chỉ sử dụng một số biến và không cần tạo ra cấu trúc dữ liệu phức tạp. Do đó, độ phức tạp về bộ nhớ của thuật toán tìm kiếm tuyến tính là O(1), tức là không phụ thuộc vào kích thước của tập dữ liệu.

Tóm lại, độ phức tạp về thời gian của thuật toán tìm kiếm tuyến tính là tuyến tính O(n), trong khi độ phức tạp về bộ nhớ là hằng số O(1). Điều này có nghĩa là thời gian tìm kiếm tăng theo số lượng phần tử trong tập dữ liệu, trong khi bộ nhớ sử dụng không thay đổi. 

Ưu điểm và hạn chế của thuật toán tìm kiếm tuyến tính

Ưu điểm

  • Đơn giản, dễ hiểu và ngắn gọn: Như các bạn thấy trong code ở trên, một hàm/phương thức để thực hiện thuật toán tìm kiếm tuyến tính tốn ít dòng và dễ nhớ, dễ viết và dễ thực hiện. Với thuật toán này, ai cũng có thể nghĩ ra được cả.
  • Hiệu quả với tập dữ liệu nhỏ: Khi số lượng phần tử trong tập dữ liệu nhỏ, thuật toán hoạt động khá hiệu quả.
  • Không yêu cầu sắp xếp trước: Vì thuật toán duyệt từng phần tử một, không bỏ sót cái nào nên cho dù tập dữ liệu không theo thứ tự nhất định, thuật toán vẫn cho ra kết quả chính xác. Hãy tưởng tượng bạn gặp một danh sách sắp xếp lộn ở ngoài đời đi, các bạn có thể tìm từng phần một như thuật toán tìm kiếm tuyến tính sẽ tìm được kết quả mong muốn.

Hạn chế

Như đã phân tích độ phức tạp về thời gian ở trên, thời gian tìm kiếm tăng lên rất đáng kể khi kích thước của tập dữ liệu trở nên rất lớn, lý do là vì thuật toán phải duyệt hết từng phần tử một. Khi trường hợp xấu nhất xảy ra, là khi phần tử cần tìm nằm ở cuối hoặc không tồn tại, thì thuật toán sẽ duyệt hết toàn bộ tập dữ liệu, khiến thời gian tốn rất nhiều, và việc thực hiện thuật toán trở nên không hiệu quả với tập dữ liệu có kích thước lớn. Chính vì hạn chế này, thuật toán tìm kiếm tuyến tính không được sử dụng rộng rãi như các phương pháp hiệu quả hơn như tìm kiếm nhị phân, ...

Ứng dụng

Thuật toán tìm kiếm tuyến tính có thể được ứng dụng trong một số trường hợp sau:

  • Tìm kiếm trong danh sách: Khi chúng ta cần tìm một phầntử cụ thể trong một danh sách, tìm kiếm tuyến tính có thể được sử dụng để xác định vị trí của phần tử đó.
  • Tìm kiếm trong các cấu trúc dữ liệu tuyến tính: Tìm kiếm tuyến tính cũng có thể được áp dụng trong các cấu trúc dữ liệu tuyến tính như mảng hoặc danh sách liên kết để tìm kiếm phần tử cụ thể.
  • Tìm kiếm trong tệp tin: Khi chúng ta cần tìm kiếm thông tin trong tệp tin, tìm kiếm tuyến tính có thể được sử dụng để duyệt qua từng dòng trong tệp tin và tìm kiếm chuỗi hoặc giá trị cần tìm.

Kết

Trong bài viết này, chúng ta đã đi qua khái niệm, nguyên tắc hoạt động, cách áp dụng, độ phức tạp, ưu nhược, và ứng dụng của thuật toán tìm kiếm tuyến tính. Tìm kiếm tuyến tính là một phương pháp đơn giản và dễ hiểu để giải quyết các vấn đề tìm kiếm. Tuy nhiên, hiệu quả của phương pháp này phụ thuộc vào kích thước của tập dữ liệu. Đối với các tập dữ liệu nhỏ, tìm kiếm tuyến tính có thể hoạt động tốt, nhưng đối với các tập dữ liệu lớn, nó có thể không hiệu quả. Khi làm việc với các vấn đề tìm kiếm đơn giản hoặc các cấu trúc dữ liệu tuyến tính, tìm kiếm tuyến tính có thể là một lựa chọn hợp lý. Tuy nhiên, trong các trường hợp phức tạp hơn, các bạn nên sử dụng các phương pháp tìm kiếm khác như tìm kiếm nhị phân.


Đăng nhận xét

Mới hơn Cũ hơn