Одномерное случайное блуждание

Представьте человека, который стоит на прямой и на каждом шаге подбрасывает честную монету: орёл — шаг вправо, решка — шаг влево. Куда он придёт через двадцать шагов? А через миллион? Ответ удивительно глубок и лежит в основе физики, биологии и финансов.

Случайное блуждание — последовательность положений, в которой каждое следующее положение получается из текущего прибавлением случайного шага. В простейшем одномерном случае шаг равен +1 или -1 с равной вероятностью.

Почему эта незатейливая игра заслуживает целого раздела? Потому что она оказывается скрытым скелетом огромного числа процессов вокруг нас. Молекула воздуха, которую толкают со всех сторон соседи, ведёт себя как блуждающая точка. Бактерия, плывущая рывками в случайных направлениях, тоже. Цена акции, на которую влияют тысячи непредсказуемых новостей, в первом приближении ведёт себя так же. Если мы поймём поведение блуждания в чистом виде, мы получим ключ ко всем этим явлениям сразу — это и есть сила хорошей модели: одна простая идея объясняет десятки разных вещей.

Шаг, позиция, траектория

Договоримся о словах. Шаг — это одно случайное смещение, +1 или -1. Позиция — текущая координата на прямой; в начале она равна нулю. Траектория — вся последовательность позиций от старта до конца, то есть история блуждания.

Важно понимать: позиция — это не сумма «вправо», а накопленная сумма всех шагов. Если первые пять шагов были +1, -1, +1, +1, -1, то позиции пройдут через 0, 1, 0, 1, 2, 1. Каждая новая позиция зависит от предыдущей — блуждание помнит, где находится сейчас, но совершенно не помнит, как туда попало, и не знает, куда пойдёт дальше. Это свойство «отсутствия памяти» называют марковским, и оно делает анализ блужданий на удивление чистым.

Строим блуждание на Python

Сгенерируем двадцать шагов, фиксируя зерно генератора, чтобы результат был воспроизводимым и совпадал у всех, кто запустит код.

import random
random.seed(7)
pos = 0
positions = [pos]
for _ in range(20):
    step = random.choice([-1, 1])
    pos += step
    positions.append(pos)
print("Позиции:", positions)
print("Финальная:", pos)
print("Макс отход от старта:", max(abs(p) for p in positions))

Вывод:

Позиции: [0, 1, 0, 1, 0, -1, -2, -1, -2, -3, -4, -5, -4, -3, -4, -5, -6, -5, -6, -7, -8]
Финальная: -8
Макс отход от старта: 8

Разберём, что произошло. Список positions начинается с нуля и хранит координату после каждого шага. Финальная позиция -8 означает, что за двадцать шагов наш ходок ушёл на восемь клеток влево. Из двадцати бросков «решек» (шаг -1) выпало на восемь больше, чем «орлов»: ведь чистый сдвиг -8 — это разница между числом шагов влево и вправо.

Как выглядит траектория

Нарисуем словами высоту позиции по горизонтальной оси времени. Каждая строка — момент времени, столбик из звёздочек тянется от нуля до текущей координаты. Видно, как блуждание сначала топчется около нуля, а потом устойчиво сползает вниз.

шаг  поз  траектория (0 слева)
 0:   0  |
 1:   1  |*
 2:   0  |
 5:  -1  |  (ушли влево от нуля)
10:  -4  |  <<<< (четыре клетки влево)
15:  -5  |  <<<<<
20:  -8  |  <<<<<<<< (финал)

Если перезапустить тот же код с другим зерном, картинка будет совсем иной: ходок может уйти вправо, вернуться к нулю или замереть рядом со стартом. Ни одна отдельная траектория не «типична» — типичны лишь усреднённые свойства, к которым мы сейчас перейдём.

Среднее смещение и разброс

Зададим главный вопрос: где в среднем окажется ходок после N шагов? Ответ — ровно там, откуда стартовал, в нуле. Это следствие симметрии: шаги +1 и -1 равновероятны, поэтому матожидание одного шага равно 0.5*(+1) + 0.5*(-1) = 0. Сумма нулевых средних тоже ноль, так что ожидаемая позиция после любого числа шагов равна нулю.

Звучит так, будто блуждание «никуда не движется». Но наш конкретный ходок ушёл на -8! Противоречия нет: ноль — это среднее по миллионам параллельных вселенных. В половине из них ходок уйдёт вправо, в половине влево, и при усреднении плюсы гасят минусы. Но в каждой отдельной вселенной он окажется далеко от нуля.

Чтобы поймать это «далеко», нужно мерить не среднее смещение (оно ноль), а средний модуль или средний квадрат смещения. И здесь начинается самое интересное: типичное расстояние от старта растёт с числом шагов. Не линейно, а медленнее — как квадратный корень из N. После 100 шагов ходок обычно оказывается примерно в 10 клетках от старта, после 10000 шагов — примерно в 100. Этому закону посвящён следующий урок; пока запомним саму идею: среднее положение неподвижно, а облако возможных положений неумолимо расплывается.

Как работает под капотом

Функция random.choice([-1, 1]) внутри обращается к генератору псевдослучайных чисел. Это не «настоящая» случайность: алгоритм (в CPython — Mersenne Twister) детерминированно вычисляет следующее число из текущего внутреннего состояния. Вызов random.seed(7) задаёт это состояние, поэтому вся последовательность шагов предопределена зерном. Уберите seed — и каждый запуск даст новую траекторию, потому что состояние инициализируется от системного времени.

Сама позиция — это аккумулятор: переменная pos хранит текущую сумму, а pos += step добавляет к ней новый шаг. Список positions сохраняет снимок после каждого шага, чтобы потом построить траекторию или измерить максимальный отход. Выражение max(abs(p) for p in positions) проходит по всем позициям, берёт модуль каждой и находит самую далёкую точку — это генераторное выражение, которое не строит промежуточный список, а отдаёт значения по одному.

Частые ошибки

  • Думать, что «раз среднее ноль, ходок останется у старта». Среднее — это центр облака, а сам ходок почти наверняка окажется далеко от центра.
  • Путать сдвиг и расстояние. Финальная позиция -8 — это сдвиг (со знаком), а «8» — это расстояние (модуль). Среднее сдвига равно нулю, среднее расстояния — нет.
  • Ожидать, что блуждание «вернётся, чтобы компенсировать» прошлые шаги. У блуждания нет памяти: после серии шагов влево следующий шаг по-прежнему +1 или -1 с равной вероятностью. Это та же ошибка, что и «вера в отыгрыш» в азартных играх.
  • Забыть random.seed и удивляться, почему результат не совпадает с примером. Без фиксированного зерна воспроизводимости не будет.
  • Прибавлять к positions сам шаг вместо позиции. В список должна попадать накопленная координата pos, а не step.

Итоги

  • Одномерное случайное блуждание — это сумма независимых шагов +1/-1, равновероятных на каждом шаге.
  • Траектория — история позиций; каждая позиция зависит от предыдущей, но блуждание не помнит прошлого и не предсказывает будущего (марковость).
  • Матожидание позиции после любого числа шагов равно нулю из-за симметрии шагов.
  • При этом типичное расстояние от старта растёт с числом шагов (как корень из N) — облако возможных положений расплывается.
  • Эта простая модель лежит в основе диффузии, броуновского движения и моделей цен — поэтому её стоит понять до конца.
Проверьте себя
1. Чему равно матожидание позиции одномерного блуждания после N шагов (старт в нуле)?
AРавно N
BРавно нулю
CРавно корню из N
DРавно N делить на 2
2. В примере финальная позиция равна -8. Что это означает про число шагов влево и вправо?
AШагов влево было ровно 8
BШагов влево на 8 больше, чем вправо
CШагов вправо на 8 больше
DВсе 20 шагов были влево
3. После серии шагов влево чему равна вероятность, что следующий шаг будет вправо?
AБольше 0.5, блуждание «отыграется»
BМеньше 0.5
CРовно 0.5
DЗависит от длины серии