Математика · Криві
📅 Липень 2026 ⏱ ≈ 12 хв читання 🎯 Середній рівень

Крива Гільберта та криві, що заповнюють простір — локальність, індексація, стиснення

Крива, що відвідує кожну точку квадрата, жодного разу не перетинаючи себе — і чому бази даних, GPU й відеокодеки тихо покладаються на неї, щоб перетворити розсіяні 2D- і 3D-дані на кеш-дружні 1D-послідовності.

1. Парадокс Пеано: лінія, що заповнює квадрат

У 1890 р. італійський математик Джузеппе Пеано побудував дещо, що, здавалося, суперечило здоровому глузду: неперервну криву — єдиний, нерозривний одновимірний шлях — яка проходить через кожну без винятку точку двовимірного квадрата. До Пеано математики вважали, що крива, будучи по суті одновимірною, просто не може мати «достатньо точок», щоб покрити цілу 2D-площу. Георг Кантор вже показав, що множина точок на прямій і множина точок у квадраті мають однакову потужність (обидві незліченно нескінченні, у бієкції одна з одною) — але конструкція Пеано пішла далі, показавши, що цю відповідність можна зробити неперервною: можна прокреслити відображення пером, не відриваючи його, і зрештою зафарбувати кожну точку квадрата.

Цей клас кривих називається кривими, що заповнюють простір. Вони неперервні (без стрибків), але, як відомо, ніде не диференційовні — не мають чітко визначеного напрямку дотичної в жодній точці, бо вони нескінченно часто змінюють напрямок на кожному масштабі, подібно до фрактальної кривої розмірності рівно 2.

Парадокс розмірності: Криві, що заповнюють простір, мають топологічну розмірність 1 (це криві, параметризовані одним дійсним числом t), але розмірність Гаусдорфа рівно 2 (вони заповнюють площу). Це була одна з перших чітких демонстрацій того, що «розмірність» — не одне просте поняття: різні строгі означення розмірності можуть суперечити одне одному навіть для чітко визначеного, явно побудовного об'єкта.

Девід Гільберт уточнив конструкцію Пеано у 1891 р. у чистіше, візуально інтуїтивніше рекурсивне правило, і саме версія Гільберта — а не оригінальний зигзаг Пеано — використовується майже повсюдно в обчисленнях сьогодні, завдяки одній вирішальній додатковій властивості конструкції Гільберта: локальності.

2. Рекурсивна конструкція Гільберта

Крива Гільберта будується рекурсивно. Почніть з простого U-подібного шляху, що відвідує чотири квадранти квадрата по порядку. Щоб отримати наступний рівень деталізації, замініть кожен з чотирьох квадрантів меншою, відповідно повернутою й/або віддзеркаленою копією тієї самої U-подібної форми, обраною так, щоб точка виходу одної підкривої точно збігалася з точкою входу наступної. Повторюйте цю підстановку нескінченно, і в межі крива відвідує кожну точку квадрата.

Конструкція кривої Гільберта (порядок n): Порядок 1: 4 квадранти, відвідані U-подібним шляхом Порядок n: кожен квадрант кривої порядку (n−1), повернутий/віддзеркалений так, щоб кінці з'єднувалися безшовно На порядку n крива відвідує 4ⁿ клітинок сітки (сітка 2ⁿ × 2ⁿ, повністю обходжена одиничними кроками) Ключова гарантія: послідовні цілі числа i, i+1 на кривій → ЗАВЖДИ відображаються у просторово суміжні клітинки сітки

Повороти на кожному рекурсивному рівні — саме те, що робить криву Гільберта особливою: вони гарантують, що там, де завершується шлях однієї підквадрата, шлях наступного підквадрата підхоплюється прямо поруч у фізичному просторі, а не лише в абстрактному 1D-впорядкуванні. Застосована рекурсивно, ця гарантія стає глобальною: послідовні індекси вздовж усієї кривої завжди фізично суміжні клітинки, на кожному масштабі, усюди в сітці — властивість, якої жодна простіша рекурсивна схема не досягає так добре.

// Перетворення 1D-відстані Гільберта d у координати сітки (x, y), порядок n
// (Класичний бітовий алгоритм; n = кількість бітів на вісь)
function hilbertD2XY(n, d) {
  let [x, y] = [0, 0];
  let t = d;
  for (let s = 1; s < n; s *= 2) {
    const rx = 1 & (t / 2 | 0);
    const ry = 1 & (t ^ rx);
    // Повертаємо квадрант так, щоб вхід/вихід вирівнявся з батьківською кривою
    if (ry === 0) {
      if (rx === 1) { x = s - 1 - x; y = s - 1 - y; }
      [x, y] = [y, x];  // обмін (віддзеркалення по діагоналі)
    }
    x += s * rx;
    y += s * ry;
    t = Math.floor(t / 4);
  }
  return [x, y];
}
3D і вищі вимірності: Та сама рекурсивна підстановка узагальнюється на 3D-криву Гільберта (що відвідує 8 октантів куба) і на довільну вимірність d ціною складніших правил повороту. 3D-криві Гільберта використовуються для індексації об'ємних медичних сканів і 3D просторових баз даних.

3. Гільберт проти Мортона (Z-порядок)

Простіша, швидша у обчисленні альтернатива — порядок Мортона, також званий Z-кривою: перемежувати біти координат x і y безпосередньо, щоб отримати єдиний індекс. Вона поділяє з кривою Гільберта ідею рекурсивних квадрантів, але пропускає крок повороту — що робить її драматично простішою й швидшою для обчислення, ціною реальних втрат якості локальності.

// Кодування Мортона (Z-порядок): перемежування бітів x і y
function mortonEncode(x, y) {
  function spread(v) {
    v &= 0x0000ffff;
    v = (v | (v << 8)) & 0x00ff00ff;
    v = (v | (v << 4)) & 0x0f0f0f0f;
    v = (v | (v << 2)) & 0x33333333;
    v = (v | (v << 1)) & 0x55555555;
    return v;
  }
  return spread(x) | (spread(y) << 1);
}

Крива Гільберта

O(n) рекурсивний поворот

Найкраща можлива локальність: послідовні індекси завжди просторово суміжні. Трохи повільніше кодування/декодування.

Мортон (Z-порядок)

Перемежування бітів

Надзвичайно швидко (кілька бітових операцій). Локальність у середньому хороша, але з часовими великими «стрибками» на межах квадрантів.

Слабкість Z-кривої видна вже в її назві: прокреслюючи послідовні індекси Мортона, малюється Z-подібна форма, і щоразу, коли крива переходить з одного квадранта в наступний, вона може стрибнути через усю сітку — бо старші біти коду Мортона можуть перемкнутися, тоді як молодші скидаються, породжуючи великий стрибок у фізичному просторі при зміні індексу лише на 1. Повороти кривої Гільберта існують саме для того, щоб усунути ці стрибки, роблячи її локальність доказово оптимальною, ціною приблизно у 2–4 рази більшої обчислювальної вартості порівняно з кодуванням/декодуванням Мортона.

4. Чому це важливо: бази даних, кеші, стиснення

Індексація просторових баз даних. PostGIS, географічні інформаційні системи й бази даних часових рядів із локацією відображають 2D- чи 3D-координати в єдиний індекс Гільберта і зберігають рядки, відсортовані за цим індексом, у звичайному B-дереві. Оскільки крива Гільберта настільки добре зберігає локальність, суцільний діапазон індексів Гільберта відповідає компактному географічному регіону, перетворюючи дорогі багатовимірні діапазонні запити на швидкі одновимірні сканування діапазону B-дерева.

Локальність кешу CPU й GPU. Обхід 2D-текстури, висотного поля чи сітки в растровому (порядковому) порядку спричиняє некеш-дружні патерни доступу щоразу, коли ви переходите на новий рядок. Зберігання тих самих даних у порядку кривої Гільберта натомість означає, що будь-яка невелика локальна околиця пікселів чи клітинок сітки — це також невеликий суцільний блок адрес пам'яті, що драматично покращує показники влучень у кеш для алгоритмів на кшталт генерації mip-мап, стрімінгу текстур плитками й ядер розмиття/згортки зображень.

Розподілені системи й балансування навантаження. Системи, що шардують дані на багатьох машинах за географічним чи просторовим ключем (маршрутизація доставки, географічне шардування, розподілені хеш-таблиці з вимогами локальності), використовують індекси Гільберта, щоб близькі реальні локації потрапляли на той самий чи сусідній шард, мінімізуючи дорогі міжшардові запити.

Стиснення зображень і відео. Деякі алгоритми стиснення зображень і дизерингу обходять пікселі в порядку кривої Гільберта, а не в растровому порядку саме тому, що це тримає візуально схожі (просторово близькі) пікселі поруч у результуючому 1D-потоці даних, що допомагає ентропійним кодерам і алгоритмам квантування кольору ефективніше використовувати локальну кореляцію, ніж це зробив би растровий обхід.

Процедурна генерація й потокові світи. Ігри з відкритим світом, що стрімлять фрагменти рельєфу в пам'ять і назад під час руху гравця, часто використовують впорядкування за Гільбертом чи Мортоном для індексів фрагментів, щоб просторово близькі фрагменти були близькими й у черзі пріоритету стрімінгу, і в розкладці на диску — зменшуючи витрати на позиціонування й покращуючи точність попереднього завантаження.

Досліджуй математичні симуляції

Візуалізуй криві, фрактали й геометричні структури інтерактивно у браузері.

Дослідити симуляції →

Пов'язані статті

Часті запитання

Що таке крива, яка заповнює простір?

Крива, що заповнює простір — це неперервна крива, зображення якої, коли її одновимірний параметр пробігає від 0 до 1, проходить через кожну точку 2D (або вищовимірної) області — наприклад, усього одиничного квадрата. Джузеппе Пеано побудував перший приклад у 1890 р., здивувавши математиків, бо це показало, що одновимірна крива може мати ту саму потужність точок, що й двовимірна площа, суперечачи наївним інтуїціям про розмірність. Криві, що заповнюють простір, неперервні, але ніде не диференційовні, і сучасні приклади, як крива Гільберта, широко використовуються в обчисленнях для локальність-зберігаючого відображення 1D в 2D.

Чому крива Гільберта краща за криву Z-порядку (Мортона) для локальності?

Обидві криві відображають 1D-індекс у 2D/3D-координати, намагаючись тримати сусідні індекси просторово близькими, але рекурсивні квадрантні стрибки Z-кривої породжують часом довгі «стрибки» через сітку на межах квадрантів, бо сусідні коди Мортона можуть відрізнятися у старшому біті, телепортуючись через усю сітку. Крива Гільберта, побудована з поворотами, що вирівнюють точки входу й виходу кожного підквадранта, гарантує, що сусідні точки на кривій завжди просторово суміжні — довгих стрибків ніколи не відбувається. Це робить локальність кривої Гільберта доказово оптимальною серед кривих, що заповнюють простір, ціною трохи складнішої конструкції й алгоритму декодування, ніж просте перемежування бітів у порядку Мортона.

Як крива Гільберта використовується у базах даних і просторовій індексації?

Просторові бази даних (PostGIS, географічні інформаційні системи, альтернативи R-дереву) відображають 2D- або 3D-координати в єдиний індекс кривої Гільберта (відстань Гільберта), а потім використовують це єдине число як звичайний ключ B-дерева. Оскільки крива Гільберта настільки добре зберігає локальність, діапазони сусідніх індексів Гільберта відповідають близьким географічним регіонам, тож одновимірний діапазонний запит по індексу наближає 2D просторовий запит по регіону — перетворюючи дорогі багатовимірні пошуки в діапазоні на швидкі, добре вивчені одновимірні пошуки в B-дереві.

Хто і коли винайшов криву Гільберта?

Девід Гільберт опублікував свою конструкцію в 1891 р., через рік після оригінальної кривої Джузеппе Пеано 1890 р., що заповнює простір. Версія Гільберта описується як простіше, візуальніше рекурсивне правило, засноване на повторюваному поділі квадрата на чотири квадранти й з'єднанні їх повернутими копіями тієї самої U-подібної форми — і саме конструкція Гільберта, а не оригінал Пеано, домінує в сучасних обчислювальних застосуваннях завдяки своїй кращій властивості збереження локальності.

Що означає «ніде не диференційовна» для кривої, що заповнює простір?

Крива, що заповнює простір, змінює напрямок нескінченно часто на кожному масштабі збільшення, тож у неї немає чітко визначеної дотичної (похідної) у жодній точці вздовж її довжини — подібно до того, як крива Коха чи функція Вейєрштрасса неперервна всюди, але гладка ніде. Це необхідний наслідок того, що одновимірна крива покриває повну двовимірну площу: гладка (диференційовна) крива просто не може мати достатньо «місця», щоб заповнити простір так щільно.

Як обчислити координати кривої Гільберта в коді?

Стандартний алгоритм перетворює 1D-відстань Гільберта d на координати (x, y) сітки (або навпаки) через ітеративний цикл обробки бітів: на кожному рекурсивному масштабі витягуються два біти з d, щоб визначити поточний квадрант, потім умовно віддзеркалюються й обмінюються накопичені координати x/y, щоб застосувати правильний поворот для цього квадранта перед додаванням зсуву квадранта. Це виконується за O(log n) для сітки n×n, де n — степінь 2.

Що таке порядок Мортона / крива Z-порядку?

Порядок Мортона (крива Z-порядку) кодує 2D- чи 3D-координати в єдиний індекс, перемежовуючи двійкові розряди кожної координати — для 2D чергуючи біти x і y. Він драматично швидший для обчислення, ніж крива Гільберта (лише зсуви й маскування бітів), і використовується там, де швидкість кодування/декодування важливіша за ідеальну локальність, наприклад у схемах плиткування текстур GPU й деяких реалізаціях просторового хешування.

Чи використовуються криві, що заповнюють простір, у стисненні зображень?

Так. Деякі алгоритми стиснення зображень, дизерингу й квантування кольору обходять пікселі в порядку кривої Гільберта замість стандартного растрового (порядкового) порядку, бо порядок Гільберта тримає просторово суміжні (а отже, ймовірно, візуально схожі) пікселі поруч у результуючому 1D-потоці даних. Це покращує ефективність ентропійних кодерів і зменшує видимі артефакти патернів порівняно з растровим обходом.

Чи узагальнюється крива Гільберта на 3D і вищі вимірності?

Так — та сама рекурсивна підстановка узагальнюється на 3D-криву Гільберта, що відвідує 8 октантів куба, і на довільну вимірність d з відповідно складнішими правилами повороту для кожного рекурсивного кроку. 3D і вищовимірні криві Гільберта використовуються для індексації об'ємних даних, таких як медичні КТ/МРТ-скани, 3D просторові бази даних і структури пошуку найближчих сусідів у високих вимірностях.