Обмен значениями переменных (swap) – элементарная, но принципиально важная операция, лежащая в основе сортировок, перестановок, алгоритмов выбора и структурных преобразований массивов/графов. При внешней простоте обмен критически зависит от семантики присваивания, типов, ограничений памяти, порядка вычисления выражений и инвариантов корректности. В учебной и экзаменационной практике (ЕГЭ по информатике) умение формально описывать и доказывать корректность обмена – фундамент для задач на массивы, сортировки, комбинаторику и работы с битами.
Состояние памяти и операция обмена
Пусть M – состояние памяти, отображение имён переменных в значения:
M : Vars → Values
Для двух различных адресуемых переменных x, y ∈ Vars, операция swap – это биекция τ_{x,y} на множестве состояний, меняющая местами M(x) и M(y) и оставляющая прочие значения неизменными:
(τ_{x,y}(M))(x) = M(y)
(τ_{x,y}(M))(y) = M(x)
(τ_{x,y}(M))(z) = M(z) для всех z ≠ x, y
Требования корректности: (i) после выполнения x = old(y) ∧ y = old(x); (ii) отсутствуют побочные изменения других переменных; (iii) операция завершается за конечное число шагов.
Типы и домены значений
Обмен корректен при совместимости типов (одно и то же множество значений или приводимые типы) и при соблюдении семантики: для арифметических приёмов – кольцевая алгебра без переполнений (или с контролируемым модулем), для XOR – битовые строки равной длины.
Инвариант корректности обмена (для любой реализации):
Inv: x ⊕ y = const
где ⊕ – подходящая бинарная операция, сохраняющаяся на всех промежуточных шагах:
С использованием временной переменной (надёжный стандарт)
Псевдокод:
процедура swap_temp(ссылка x, ссылка y)
t ← x
x ← y
y ← t
конец
Сложность: время Θ(1), память Θ(1) дополнительная (одна временная ячейка).
Достоинства: типобезопасность, отсутствие переполнений, читабельность.
Инвариант: мультисет значений {x, y, t} на каждом шаге равен {old(x), old(y), t}; по завершении {x, y} = {old(y), old(x)}.
Множественное присваивание (кортежный swap)
В языках с параллельной семантикой присваивания:
(x, y) ← (y, x)
Семантика: сначала вычислить правую кортежную часть (y, x) в старом состоянии, затем одновременно записать в (x, y).
Эквивалентность: функционально тождественно swap_temp, часто транслируется компилятором в него.
Арифметические обмены (без временной переменной)
Сложение/вычитание (для числовых типов без переполнения):
x ← x + y
y ← x − y // стало old(x)
x ← x − y // стало old(y)
или сумма/разность с минимизацией переприсваиваний:
s ← x + y
y ← s − y // old(x)
x ← s − y // old(y)
Ограничения: риск переполнений, требования к замкнутости операции, уязвимость к нецелым типам/NaN. В учебных задачах не рекомендуется без оговорки «нет переполнений».
Побитовый XOR (для целых без знакового UB)
x ← x XOR y
y ← x XOR y // old(x)
x ← x XOR y // old(y)
Свойства: не требует временной переменной, сохраняет инвариант x XOR y.
Ограничения:
x и y должны ссылаться на разные ячейки (иначе зануление).
Должна быть единая битность и представление (без UB).
Читабельность ниже, чем у swap_temp.
Обмен ссылочных значений (указатели/ссылки)
В языках со ссылочной семантикой, когда нужно поменять содержимое объектов по адресам (а не сами ссылки):
процедура swap_ref(ссылка x, ссылка y)
t ← значение_по(x)
значение_по(x) ← значение_по(y)
значение_по(y) ← t
конец
Важно: корректно различать «обмен указателей» (меняем адреса) и «обмен содержимого по адресам».
Обмен элементов массива
процедура swapA(ссылка A: массив, i, j: цел)
треб: 1 ≤ i, j ≤ |A|
если i = j то вернуть
t ← A[i]; A[i] ← A[j]; A[j] ← t
конец
Инвариант: мультисет элементов массива не меняется; меняются только позиции i и j.
Множественные обмены и циклические перестановки
Циклическая ротация трёх переменных x, y, z:
(x, y, z) ← (z, x, y) // цикл вправо
Через временную:
t ← z; z ← y; y ← x; x ← t
Общее правило: для перестановки π на k позициях разложите π в циклы и применяйте поцикловые ротации; число перемещений = k − #циклов.
Обмен структур/записей
Если типы – записи/структуры, корректнее использовать присваивание всего объекта (язык обязан корректно копировать поля), либо move-семантику (перемещение) там, где она определена.
С временной переменной (рекомендуется):
t ← x; x ← y; y ← t
Кортежный swap (если язык поддерживает):
(x, y) ← (y, x)
Арифметический (при отсутствии переполнений):
x ← x + y
y ← x − y
x ← x − y
XOR-swap (только для целых, &x ≠ &y):
x ← x XOR y
y ← x XOR y
x ← x XOR y
Массив:
t ← A[i]; A[i] ← A[j]; A[j] ← t
Ротация тройки:
(x, y, z) ← (z, x, y)

Упражнение 1. Докажите корректность swap с временной переменной
Условие. Дана процедура:
t ← x
x ← y
y ← t
Докажите, что после её выполнения x = old(y) и y = old(x) и никакие другие переменные не изменились.
Решение. Пусть старое состояние M₀, новое – M₁. После первой строки t = M₀(x), x, y неизменны. Вторая строка присваивает x := M₀(y). Третья – y := M₀(x) через сохранённое t. Остальные переменные не затрагиваются. Следовательно, M₁(x)=M₀(y), M₁(y)=M₀(x), а для z ≠ x, y, t – M₁(z)=M₀(z). QED.
Упражнение 2. Найдите ошибку в XOR-обмене
Условие. Рассмотрите фрагмент:
если &x = &y то
x ← x XOR y
иначе
x ← x XOR y
y ← x XOR y
x ← x XOR y
все
Корректен ли он? Обоснуйте.
Решение. При &x = &y первая строка даёт x ← x XOR x = 0 → значение теряется. Правильно: при &x = &y ничего не делать:
если &x = &y то вернуть
иначе
x ← x XOR y
y ← x XOR y
x ← x XOR y
все
Только так сохраняется инвариант x XOR y.
Упражнение 3. Арифметический обмен и переполнение
Условие. Пусть x и y – 32-битные знаковые целые. Применили:
x ← x + y
y ← x − y
x ← x − y
Приведите условие, при котором переполнение невозможно, и объясните, почему этот метод ненадёжен в общем случае.
Решение. Переполнение исключено, если x + y принадлежит диапазону [-2³¹, 2³¹−1]. Например, при |x|,|y| ≤ 10⁶ метод безопасен. В общем случае гарантировать отсутствие переполнений нельзя (особенно при суммах, близких к границам диапазона). Поэтому арифметический обмен не рекомендуется без явной проверки или использования более широкого типа.
Упражнение 4. Обмен в массиве: доказательство инварианта перестановочной сортировки
Условие. В сортировке выбором на шаге i меняют местами A[i] и A[pos], где pos – индекс минимального на суффиксе A[i..n]. Докажите, что после обмена префикс A[1..i] отсортирован по неубыванию.
Решение. По индукции: предположим, A[1..i−1] отсортирован и содержит i−1 наименьших элементов. На шаге находим минимум суффикса A[i..n] и ставим его на позицию i обменом. Тогда A[1..i] содержит i наименьших элементов в порядке неубывания (минимум суффикса не меньше всех в префиксе и не больше любого на суффиксе). Инвариант сохранён.
Упражнение 5. Кортежный swap и порядок вычисления
Условие. Рассмотрите кортежное присваивание:
(x, y) ← (y, f(x))
Предположим, f – чистая функция. При какой семантике операция корректна и чему равны итоговые x и y?
Решение. Корректно при параллельной
семантике кортежного присваивания: сначала вычисляются обе правые части в старом состоянии: R₁ = y_old, R₂ = f(x_old), затем одновременно записываются: x := R₁, y := R₂. Итог: x = y_old, y = f(x_old). При последовательной семантике (сначала x ← y) вторая правая часть могла бы зависеть от уже изменённого x, что ломает корректность.
Операция обмена – не просто «три строки кода», а строгое преобразование состояния, требующее учёта типов, семантики присваивания, инвариантов и краевых случаев. В учебной и экзаменационной практике корректный, типобезопасный и читаемый обмен – кирпичик любой перестановочной процедуры, от сортировок до генераторов комбинаций. Следуйте изложенным правилам: используйте временную переменную или кортежное присваивание как стандарт, прибегайте к арифметике/XOR только при формально гарантированных условиях. Это повысит надёжность решений и качество аргументации на ЕГЭ.