Я работаю над изучением структур данных и алгоритмов. Один алгоритм, который очень хорошо работает для улучшения временной сложности Big O, - это двоичный поиск, который улучшает базовый линейный поиск. Однако, чтобы использовать двоичный поиск, элементы, которые вы ищете, должны быть отсортированы.

Я начну с демонстрации линейного поиска и того, как он работает и как двоичный поиск улучшает его. Принцип работы линейного поиска заключается в том, что он просматривает каждый элемент и проверяет, соответствует ли он искомому элементу. Я дам код для линейного поиска (в JavaScript), и для этого примера код получит массив и значение элемента, который мы ищем:

function linearSearch(arr, val){
    for(let i = 0; i < arr.length; i++){
        if(arr[i] === val){
            return i
        }
    }
    return -1
}

Если мы находим искомое значение, мы возвращаем индекс этого значения в массиве, в противном случае мы возвращаем -1, что означает, что мы не смогли найти значение в массиве. Это может быть эффективным в лучшем случае: мы находим наше значение в первом элементе массива и возвращаем 0 (для индекса 0), но мы можем рассчитывать на то, что сразу найдем то, что ищем, или найдем значение вообще. . Итак, для нашего худшего случая, а также для среднего линейного поиска, мы получаем Big O из O (n), когда он проходит через каждое значение массива.

Двоичный поиск

Как я уже упоминал ранее, для использования двоичного поиска элементы должны быть отсортированы. Но учитывая, что элементы отсортированы, мы можем гораздо эффективнее использовать функцию поиска. Бинарный поиск работает с использованием указателя для начальной, конечной и средней точки (также может быть левой, правой и средней). Поиск начинается с просмотра среднего значения, если среднее значение - это то, что мы ищем, тогда все готово. В противном случае мы увидим, больше или меньше искомое значение среднего значения. Если значение, которое мы ищем, больше среднего значения, мы игнорируем среднее значение и все, что ниже него. Тогда новая начальная точка становится значением после среднего значения, а новое среднее значение - серединой между новой начальной точкой и конечной точкой. Точно так же, если значение, которое мы ищем, меньше среднего значения, мы поступим наоборот. Что означало бы игнорировать среднее значение и все, что выше него. Затем новая конечная точка становится значением перед средним значением, а новое среднее значение становится серединой между начальной точкой и новой конечной точкой. Этот процесс повторяется до тех пор, пока либо элемент не будет найден, либо мы не определим, что то, что мы ищем, не существует в массиве.

function binarySearch(arr, val){
    let left = 0
    let right = arr.length - 1
    let middle = Math.floor((right + left)/2)
    
    while(arr[middle] !== val && left <= right){
        if(val < arr[middle]){
            right = middle - 1
        } else {
            left = middle + 1
        }
        middle = Math.floor((right + left)/2)
    }
    if(arr[middle] === val){
        return middle
    }
    return -1
}

Мы видим, что эта функция намного более эффективна, чем обычный линейный поиск, потому что, просматривая ее, мы делим массив, который ищем, пополам, поэтому вдвое уменьшаем количество значений, которые мы должны искать в каждом цикле. Эта функция дает нам намного лучшую временную эффективность Big O, равную O (log n).

Курсы Udemy Мастер-класс по алгоритмам JavaScript и структурам данных и Освойте собеседование по кодированию: структуры данных + алгоритмы помогли мне изучить алгоритмы поиска и проработать их.