Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Условия применимости алгоритмов сортировки  (Прочитано 13243 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« : 08-08-2007 21:56 » 

Началось всё с проблемы синхронизации данных между клиентом и БД на сервере.

В БД имеется таблица и связь этой таблицы самой с собой (ID, ParentID), в результате чего имеется возможность, выражаясь языком математики, хранить в БД лес некоторых объектов.

Клиент, имея выборку из этой таблицы, может помечать ветви деревьев для удаления, после чего начинается процесс синхронизации. Если допустить удаление каждой строки в произвольном порядке, может возникнуть ошибка, когда родительский объект, имеющий потомков, не может быть удалён без порождения аномалии удаления, когда в таблице остаются записи-"сироты", ссылающиеся на уже несуществующую запись.

Таким образом, возникла задача. Массив помеченных на удаление записей таблицы, в общем случае содержащих лес объектов, нужно упорядочить так, чтобы первыми в массиве оказались крайние потомки (листья деревьев), затем их непосредственные родители и т.д. до корней деревьев.

Задачу попытались решить без особых раздумий -- воспользоваться библиотечной функцией sort, реализующей быструю сортировку Хоара. Аргументом функции является функция сравнения двух элементов (компаратор), возвращающая для любых x,y -1, если x<y, 1 если x>y и 0 в остальных случаях.

Была написана функция сравнения, которая возвращала следующие значения:
  • если x -- предок любого уровня для y, то вернуть 1, поскольку предок должен быть "больше" потомка и в результате сортировки оказаться в конце массива;
  • если x -- потомок любого уровня для y, то вернуть -1, поскольку потомок должен быть "меньше" предка и в результате сортировки оказаться в начале массива;
  • в остальных случаях возвращать 0, поскольку либо x и y -- один и тот же объект, либо эти объекты не находятся в общей иерархии, и их взаимный порядок для нас безразличен.

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

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

Математически мы имеем множество элементов, между которыми установлено бинарное отношение порядка. Для принципиального решения задачи сортировки элементов такого множества (по Д. Кнуту "Искусство программирования", т.3) необходимо выполнить два условия:
  • должен действовать закон трихотомии: либо истинно a<b, либо a=b, либо a>b, но никогда не могут быть истинными два или три этих отношения вместе;
  • должен действовать закон транзитивности: если a<b и b<c, то a<c.

Несложно доказать, что для любых a,b из нашего массива закон трихотомии выполняется. Также несложно доказать, что для любых a,b,c из нашего массива закон транзитивности также выполняется. Но быстрая сортировка Хоара не работает.

Детальный анализ быстрой сортировки Хоара показывает следующее.

На каждом шаге алгоритма имеются 3 активных позиции в массиве: левая и правая границы и опорный элемент. Левая граница сдвигается вправо до тех пор, пока либо не зайдёт за правую границу, либо не найдётся находящийся на левой границе элемент, больший опорного. Аналогично правая граница сдвигается влево либо до захода за левую границу, либо до нахождения элемента, меньше опорного. Если границы не зашли друг за друга, то элементы на левой и правой границах меняются местами.

Ключевым моментом здесь является то, что обменивающиеся местами элементы не сравниваются между собой. Между тем заданное над множеством бинарное отношение таково, что даже если a=b и b=c, то может быть a<c. Если a -- потомок любого уровня для c, и b -- элемент из параллельной ветви дерева, то, в самом деле, нам безразлично, будет ли a расположено раньше b или наоборот, будет ли c расположено раньше b или наоборот, но совершенно не безразлично взаимное расположение a и c. Именно здесь алгоритм и допускает ошибку, выполняя неправильный обмен.

На нашем множестве с описанным бинарным отношением условия Д. Кнута для решения задачи методом быстрой сортировки Хоара не являются достаточными (что, впрочем, Д. Кнут и не обещал, но и не указал специально для этого метода). Алгоритм неявно полагает, что если не выполняются оба неравенства a<b<c, то справедливы неравенства a>=b>=c и их можно будет заменить на c<=b<=a. Это, в самом деле, справедливо, но в то же время c<=b<=a не означает (c<b<a или c=b=a или c<b=a или c=b<a).

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

Можно пойти и другим путём: сменить отношение, например, сортировать все элементы по убыванию глубин в деревьях -- тогда можно будет применить и быструю сортировку.

Думаю, мораль сей басни очевидна.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #1 : 09-08-2007 06:36 » 

Ну... тут для затравки вопросик.
Цитата
Задачу попытались решить без особых раздумий -- воспользоваться библиотечной функцией sort, реализующей быструю сортировку Хоара.
Это понятно - что под рукой лежало Улыбаюсь
Потом наткнулись на проблему.
Была ли попытка поменять алгоритм сортировки, или сразу пошли искать причину?

Кроме того, удаление родительского объекта при наличии у него потомков - не должно происходить, в данном случае должен срабатывать триггер, или же должна удаляться все ветвь в том же триггере.
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #2 : 09-08-2007 10:56 » 

а чем не устраивает рекурсивный обход дерева?
или написать деструктор узла, такой что удаляет всех своих потомков, деструкторы потомков удлят своих потомков и т.д.
Записан

Странно всё это....
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #3 : 09-08-2007 12:57 » 

а также хочется увидеть результат сортировки
Код:
  ID               ParentId

  1                   0
  2                   0
  3                   0
  4                   1
  5                   2
  6                   3
  7                   2
  8                   1
  9                   4
После сортировки
Код:

  ID               ParentId

   1 2 3...n

   9                    4
     4                    1
     8                    1
       1                    0
     5                    2
     7                    2
       2                    0
     6                    3
       3                    0
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #4 : 09-08-2007 13:35 » 

Цитата: Sla
Была ли попытка поменять алгоритм сортировки, или сразу пошли искать причину?
Не совсем понял вопрос... У нас методом тыка (перебором до первого работающего метода) обычно вопросы не решаются -- стараются найти причину ошибки, и только потом уже можно начать рассуждать о способах обхода/устранения этой причины.


Цитата: Sla
Кроме того, удаление родительского объекта при наличии у него потомков - не должно происходить, в данном случае должен срабатывать триггер, или же должна удаляться все ветвь в том же триггере.
Триггер -- это на уровне БД. На уровне БД каскадные операции запрещены (защита от дурака и багов). Но клиент может выполнить каскадное удаление по команде пользователя. Как раз триггер и срабатывает, заваливая процесс удаления с ошибкой, если удалять записи в произвольном порядке.

Цитата: LogRus
а чем не устраивает рекурсивный обход дерева?
Топологическая сортировка на базе рекурсивного обхода дерева в порядке "потомки, предок" всем устраивает. Тема не о выборе метода сортировки, а об условиях применимости алгоритмов сортировки Улыбаюсь. Удаление же прямо в рекурсии не совсем удобно -- испортит архитектуру, поскольку в начале выполняются операции с объектами, и только потом вызывается служба синхронизации состояния.

Цитата: LogRus
или написать деструктор узла, такой что удаляет всех своих потомков, деструкторы потомков удлят своих потомков и т.д.
Неприменимо. Объекты не удаляются, а помечаются как удалённые. Физически удаляет их лишь служба синхронизации (и потом сборщик мусора).

Цитата: Sla
а также хочется увидеть результат сортировки
Каким алгоритмом?


Фактически, на заданном множестве заданное отношение транзитивно лишь для строгих неравенств, и нетранзитивно для нестрогих неравенств и равенства. Посему для ряда алгоритмов требуется очень внимательно подходить к их реализации, аккуратно используя заданное отношение, поскольку транзитивное отношение a<b при отрицании превращается в нетранзитивное a>=b. Либо дополнить необходимый закон транзитивности "если a<b и b<c, то a<c" ещё одним утверждением "если (не a<b) и (не b<c), то (не a<c)" -- оно для вышеописанного случая не выполняется.
« Последнее редактирование: 09-08-2007 13:37 от dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #5 : 09-08-2007 18:56 » 

Хотелось бы уточнить: задача в переупорядочении элементов массива в памяти? Можно еще раз сформулировать задачу покороче?
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #6 : 09-08-2007 19:38 » 

Цитата: RXL
Хотелось бы уточнить: задача в переупорядочении элементов массива в памяти?
Память тут ни при чём. Можно сугубо математически разбирать.

Цитата: RXL
Можно еще раз сформулировать задачу покороче?
Дано множество некоторых элементов, собранных в структуру леса (можно взять и одиночное дерево -- роли не играет). Деревья леса являются направленными графами, в которых направление ребёр идёт от листьев к корню дерева. Над множеством задано такое отношение порядка, которое для всякой пары элементов, между которыми можно проложить маршрут, устанавливает, что исходящая вершина маршрута "меньше" входящей вершины. Требуется получить любое упорядоченное перечисление элементов множества.

Или, в терминах программирования, если заданы массив вышеописанных объектов и вышеописанная функция их сравнения, нужно выполнить сортировку этого массива.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #7 : 10-08-2007 05:19 » 

dimka, вот хоть, что ты со мной делай, но я не понимаю нафига тебе сортировка при решении этой задачи синхронизации с базой?

я бы сделал так:
1. создал метод у узла дераве помечаюший узел и потомков, как удалённые (возможно с подсчетом числа удаляемых элеменов или потом их посчитать по флажку), т.е. метод MarkAsDeleted устанавливает флаг forDelete = true и и вызывает MarkAsDeleted у всех потомков
2. посчитать число элеменов для удаления
3. начинаем транзакцию
4. вызваем sql запрос навроде такого delete from table t start with t.id = :rootID connect by prior t.id = t.parent;
5. если число удалённых записей не совпадает с числом записей в контенере помеченных на удаление, то делаем откат транзакции и сообщаем пользователю(по хорошему тут можно заблокировать таблицу на вставку данных, что бы из другой сесси народ данные не добавил)
6. переподгружаем данные и повторяем процес удаления
Записан

Странно всё это....
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #8 : 10-08-2007 06:08 » 

 LogRus,  Задачи разные бывают, задачи разные нужны Улыбаюсь


dimka, я нарисовал указанный лес несоритрованный и сортированный, приведи твое дерево, в котором неправильно выполняется сортировка.

зы... здесь нужно человеку не помочь, а понять самому - зачем?
условия применимости алгоритмов сортировки
Ты нам компас  не показывай - ты рукой покажи.
« Последнее редактирование: 10-08-2007 06:54 от Sla » Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #9 : 10-08-2007 06:22 » 

dimka, скажи еще раз пожалуйсто, какого результата ты хочешь добится сортировкой
что должно быть в начале, что в конце
расскажи о контенерах которые ты сортируешь(контенер указателей, два контенера один указателей второй с объектами)

мне думается если ты задашь правильный предикат, то сортировка пройдёт, как надо, только вот сложность предиката может съесть всю скорость сортировки. возможно в предикат нужно внести глубину нахождния узла относительно корня.
Записан

Странно всё это....
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 10-08-2007 10:59 » 

Цитата: LogRus
я не понимаю нафига тебе сортировка при решении этой задачи синхронизации с базой?
Мне не нужна сортировка при решении этой задачи с базой. Улыбаюсь Я всего лишь сказал, что быстрая сортировка Хоара при решении этой задачи с вышеописанным компаратором работает неправильно. И вся тема посвящена исключительно этому обстоятельству.

Цитата: LogRus
delete from table t start with t.id = :rootID connect by prior t.id = t.parent;
Это нестандартная конструкция. Никаких "start with" и "connect by prior" в стандарте SQL-92 и более ранних нет.

Цитата: Sla
зы... здесь нужно человеку не помочь, а понять самому - зачем?
условия применимости алгоритмов сортировки
Ты нам компас  не показывай - ты рукой покажи.
Не понял. Если намёк на необходимость примера Улыбаюсь, то пожалуйста.

Есть лес из двух деревьев:
Код:
1   2
    |
    0

Вершины леса собраны в список
Код:
0 1 2

Сортируем быстрой сортировкой Хоара:
Код:
|x| -- опорный элемент
(x) -- граничный элемент

(0) |1| (2)
   0<1 - ложь, т.к. находятся в разных деревьях, остановка левой границы;
   1<2 - ложь, т.к. находятся в разных деревьях, остановка правой границы;
   обмен левой и правой границ, поскольку алгоритм полагает, что 0>=1>=2.

Результат:
Код:
2 1 0
Результат неправильный. В самом деле, верно, что 0>=1>=2, но отношение ">=" нетранзитивно, и 0<2. Алгоритм этого "не замечает". При этом отношение "<" является транзитивным, и необходимые условия применимости сортировки выполняются.

Цитата: LogRus
скажи еще раз пожалуйсто, какого результата ты хочешь добится сортировкой
Из произвольного линейного набора объектов, логически связанных в лес (или дерево), получить упорядоченный линейный набор так, чтобы всякий потомок находился раньше всякого своего предка.

Цитата: LogRus
расскажи о контенерах которые ты сортируешь(контенер указателей, два контенера один указателей второй с объектами)
Причём тут указатели? Разве я где-нибудь говорил про C++? Исходная задача писалась на C# .NET 1.1, там нет указателей как типа Улыбаюсь.

Цитата: LogRus
мне думается если ты задашь правильный предикат, то сортировка пройдёт, как надо, только вот сложность предиката может съесть всю скорость сортировки. возможно в предикат нужно внести глубину нахождния узла относительно корня.
Конечно пройдёт, если изменить отношение. Например, каждой вершине леса приписать глубину от корня своего дерева и сортировать в порядке убывания глубин. Дело не в этом Улыбаюсь, а в том, что при описанном мною в начале отношении между вершинами сортировка не работает, хотя само отношение правильное.
« Последнее редактирование: 10-08-2007 11:04 от dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines