Блог пользователя JKeeJ1e30

Автор JKeeJ1e30, 15 лет назад, По-русски

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


Шаг 1. Произведем конденсацию графа. Если в компоненте связности есть цикл отрицательного веса - значит, в этой компоненте связности из любой вершины можно добраться в саму себя по неограниченно малому пути. Помечаем все вершины этой компоненты черным цветом. Если же в компоненте нет такой вершины - помечаем все ее вершины белым цветом.

Шаг 2. Запускаемся из каждой вершины 0-1 обходом.

Собственно, вот обновленная версия алгоритма.

Недавно я заинтересовался, насколько медленно/быстро работает мой алгоритм в сравнении с алгоритмом Флойда. Я взял код Эдварда Давтяна и протестировал его Флойд и мой алгоритм(генератор тестов). Тестирование проводилось на почти полных, разреженных в 4 раза и на разреженных в 20 раз графах. Результат превзошел все мои ожидания:

 n m тип ребер(все положительные +, если есть отрицательные -) время работы Флойда, мсвремя работы моего алгоритма, мс 
 1009000 +38  36
 1009000 - 48 68
 20039000 + 263 160 
 20039000 - 280 513 
 400159000 + 2080 1224 
 400159000 - 2033 4054 
 500240000 + 4043 2336 
 500240000 - 3880 7482 
 1002500 + 31 93 
 1002500 - 47 110 
 20010000 + 265 125 
 20010000 - 276 226 
 40040000 + 2068 409 
 40040000 - 2028 1096 
 50062500 + 4027 701 
 50062500 - 3861 2019 
 100500 + 29 93 
 100500 - 37 94 
 2002000 + 237 105 
 2002000 - 265 120 
 4008000 + 1968 180 
 4008000 - 1954 305 
 50012500 + 3902 251 
 50012500 - 3766 492 
  • Проголосовать: нравится
  • -24
  • Проголосовать: не нравится

»
15 лет назад, скрыть # |
 
Проголосовать: нравится -18 Проголосовать: не нравится
Интересует еще какая-нибудь информация-спрашивайте.
»
15 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится
Еще, просьба: помогите с переводом.
»
15 лет назад, скрыть # |
 
Проголосовать: нравится +8 Проголосовать: не нравится
Наверное критическими являются 2 строки в Вашей таблице:
 

 n

 m

 тип ребер(все положительные +, если есть отрицательные -)

 время работы Флойда, мс

время работы Вашего алгоритма, мс 

 400

159000 

- 

2033 

4054 

 500

240000 

- 

3880 

7482 


Собственно они, наверное, ставят пока под вопрос и его эффективность...
  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится -19 Проголосовать: не нравится
    Почему это? Они, наоборот, говорят, что он даже на почти полных графах примерно столь же эффективен, как и Флойд(уступает-то всего в 1,5-2 раза). А на разреженных он оставляет Флойда далеко позади.
    • »
      »
      »
      15 лет назад, скрыть # ^ |
       
      Проголосовать: нравится +5 Проголосовать: не нравится
      Алгоритм конечно эффективнее флойда на неполных графах. Но, имхо, на полном графе 2сек и 4сек при n=400 совсем не "примерно столь же эффективен", по крайней мере в рамках олимпиадного соревнования.
      • »
        »
        »
        »
        15 лет назад, скрыть # ^ |
         
        Проголосовать: нравится -15 Проголосовать: не нравится
        Во-первых, если бы кто-нибудь помог пооптимизить мне мой код, то вполне возможно от двойки мы бы избавились. Во-вторых, все-таки обычно ТЛ ставят с запасом.
        • »
          »
          »
          »
          »
          15 лет назад, скрыть # ^ |
           
          Проголосовать: нравится +5 Проголосовать: не нравится
          Это смотря какое решение они хотят там увидеть. Сабмит в стиле "как-нибудь пропихнуть" покажет, что флойд значительно эффективнее. Насчет правильных решений: не всегда бывает такой большой запас TL, если я конечно не ошибаюсь.
          • »
            »
            »
            »
            »
            »
            15 лет назад, скрыть # ^ |
             
            Проголосовать: нравится -18 Проголосовать: не нравится
            Не всегда для моего алгоритма бывают критичные графы. На макстесте, где соединено каждое ребро с каждым, то есть КСС - это весь граф, да еще и отрицательный цикл есть - он работает в 2 раза медленнее чем Флойд. А на макстесте, где нет отрицательного цикла - он работает в 2 раза быстрее.
            • »
              »
              »
              »
              »
              »
              »
              15 лет назад, скрыть # ^ |
               
              Проголосовать: нравится +5 Проголосовать: не нравится
              Ну это все хорошо конечно :) я к тому что это немного преувеличение - говорить "примерно столь же эффективен" когда реальное время выполнения в 2 раза больше (причем когда время на гране TL).
»
15 лет назад, скрыть # |
 
Проголосовать: нравится +27 Проголосовать: не нравится
250 строк кода вместо 4-5 у флойда, молодец, чо
  • »
    »
    15 лет назад, скрыть # ^ |
    ← Rev. 2  
    Проголосовать: нравится 0 Проголосовать: не нравится

    BPSW тоже 250 строк кода вместо проверки за корень на 3 строки. Укконен тоже 100500 строк кода. И вот че? 

    • »
      »
      »
      15 лет назад, скрыть # ^ |
       
      Проголосовать: нравится +10 Проголосовать: не нравится
      Ты увеличил код в 50 раз, уменьшив время работы в 5 раз. Поэтому сравнение с BPSW и Укконеном некорректно - они такой фигней не страдают.
      • »
        »
        »
        »
        15 лет назад, скрыть # ^ |
         
        Проголосовать: нравится +7 Проголосовать: не нравится
        Я уменьшил асимптотику. n3 против n*m. Ты бы потрудился прочитать прежде чем критиковать.
        • »
          »
          »
          »
          »
          15 лет назад, скрыть # ^ |
          ← Rev. 3  
          Проголосовать: нравится +22 Проголосовать: не нравится

          O(n3) и O(n * m) - это как ложка и вилка. Иногда лучше одно, иногда другое, но по сути одна фигня.


          Ты предлагаешь есть спагетти вилкой, улучшил ее аэродинамику, добавил 10 зубьев вместо 4х (тоже для скорости), но при этом привесил к ней пудовую гирю.

          Может, вилка удобнее, быстрее, но мне гиря немного мешает решать задачи кушать.
          • »
            »
            »
            »
            »
            »
            15 лет назад, скрыть # ^ |
            ← Rev. 3  
            Проголосовать: нравится +11 Проголосовать: не нравится

             JKeeJ1e30 суров! JKeeJ1e30 не страшит гиря!

          • »
            »
            »
            »
            »
            »
            15 лет назад, скрыть # ^ |
             
            Проголосовать: нравится 0 Проголосовать: не нравится
            Первое. Ты можешь мне помочь уменьшить размер гири. У тебя олимпиадный опыт много больше, чем у меня. Я знаю, что у меня написано не оптимально (потому что я не умею писать, я краб). Подкинь идею, как это написать быстрее.

            Второе. По сути, мой алгоритм состоит из трех боянов. Боян 1- Форд-Беллман. Боян 2 - топсорт. Боян 3 - 0-1 обход. Бояны пишутся легко.
  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится +3 Проголосовать: не нравится
    Решение Флойдом из топика ровно в 1.928 раза короче (125 строк против 241). Не такая уж и большая разница, правда? Если код почистить и отделить алгоритм от ввода, объявлений и прочей рутины, то он будет занимать не более 100 строчек, которые чуть менее, чем полностью состоят из боянов, как уже было ранее отмечено. Не придирайтесь.
»
15 лет назад, скрыть # |
← Rev. 3  
Проголосовать: нравится 0 Проголосовать: не нравится

Вот еще такая таблица. Понятное дело, сравнения с Флойдом нет, потому что на таких тестах Флойд до завтра не отработает.

 n m тип ребер время работы моего алгоритма
 10002000 +  162
 10002000 -  267
 10005000 +  377
 10005000 -  511
 15005000 +  627
 15005000 -  780
 150010000 +  795
 150010000 -  1284
 300010000 +  2479
 300010000 -  3447
 50005000 +  462
 50005000 -  471
 500020000 +  7327
 500020000 -  11156
»
15 лет назад, скрыть # |
← Rev. 2  
Проголосовать: нравится 0 Проголосовать: не нравится

Векторы на массивы поменять попробуй, ускориться же должно. (хотя вряд ли сильно)

  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится +1 Проголосовать: не нравится
    Попробовать и правду можно, но уже не уменьшение константы должно являться дальнейшей целью. Коля в чем-то прав, нужно пытаться упростить код.
    • »
      »
      »
      15 лет назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится
      Исправил. И в правду быстрее, но ненамного. Что самое странное - на положительных тестах стало сильно быстрее, на отрицательных почти не ускорилось. Код прилагается. Вот результаты:
       n m тип ребер время работы моего алгоритма
       10002000 +  53
       10002000 -  282
       10005000 +  282
       10005000 -  512
       15005000 +  349
       15005000 -  785
       150010000 +  607
       150010000 -  1309
       300010000 +  968
       300010000 -  3415
       50005000 +  313
       50005000 -  309
       500020000 +  3137
       500020000 -  10482
      • »
        »
        »
        »
        15 лет назад, скрыть # ^ |
         
        Проголосовать: нравится 0 Проголосовать: не нравится
        Если уж погнаться за скоростью, то можно ещё
         vector < vector < pair < int,int > > >
        заменить на 3 обычных массива. Правда,  обход будет в пару строк длиннее.
        Иногда такая замена изменяет вердикт с TL на AC.
  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится
    Особенно эпический vector<bool>
    • »
      »
      »
      15 лет назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится
      А чем он так эпичен?
      • »
        »
        »
        »
        15 лет назад, скрыть # ^ |
         
        Проголосовать: нравится +11 Проголосовать: не нравится
        "Vector<bool> как контейнер STL обладает лишь двумя недостатками. Во-первых, это вообще не контейнер STL. Во-вторых, он не содержит bool". Моя любимая цитата из Мейерса.

        Если кратко, то он битово сжимает булены и из-за этого имеет ряд спецэффектов. Короче, не нужен в 99% случаев. Заодно известен как эпик фейл стандартизации в STL.
»
15 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится
зачем?
»
15 лет назад, скрыть # |
 
Проголосовать: нравится +3 Проголосовать: не нравится
Вопрос (классический для любой научной работы) какие алгоритмы решения этой проблемы существуют на настоящий момент?
  • »
    »
    15 лет назад, скрыть # ^ |
    ← Rev. 2  
    Проголосовать: нравится +3 Проголосовать: не нравится

    Описано в предыдущей статье. Из всех известных мне-только Флойд-Уоршелл, работающий за O(n3)

    • »
      »
      »
      15 лет назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится
      Понятно, это я прочитал. Там было написано "единственный алгоритм, который я нашел на емаксе", посему надеялся, что есть что-то ещё.
      • »
        »
        »
        »
        15 лет назад, скрыть # ^ |
         
        Проголосовать: нравится 0 Проголосовать: не нравится
        Поискал и в английском варианте, и в русском - я во всяком случае ничего не нашел. Может, у китайцев что-то есть, но они же скромные, ничего не публикуют.
        • »
          »
          »
          »
          »
          15 лет назад, скрыть # ^ |
           
          Проголосовать: нравится +5 Проголосовать: не нравится
          Дам совет - извините, если в курсе это способа.

          Для поиска у китайцев луше всего использовать их поисковик. Итак загружаем: http://baidu.com и в строке поиска вводим интересующий Вас алгоритм (естественно, на английском языке).

          Проверено неоднократно - выгребает не только "и мох и болото", но попадаются и очень и даже очень стоящие вещи! :)
      • »
        »
        »
        »
        15 лет назад, скрыть # ^ |
        ← Rev. 2  
        Проголосовать: нравится +3 Проголосовать: не нравится

        Вроде как вот тут http://solab.kaist.ac.kr/files/seminar/1st/All-Pairs%20Shortest%20Paths%20and%20the%20essential%20subgraph.pdf обещают за O(ns + n2logn) , где s=o(m) (s - подмножество ребер)

        • »
          »
          »
          »
          »
          15 лет назад, скрыть # ^ |
          ← Rev. 2  
          Проголосовать: нравится 0 Проголосовать: не нравится

          Спасибо, плохо искал значит:) Но у меня нет добавочной асимптотики, везде O(n*m). Предыдущий вариант решения был O(n1*m1 + n2*n2), где n1,n2 = O(n), m1 = O(m), но от такого варианта решения отказался - тормозное было ужасно.

        • »
          »
          »
          »
          »
          15 лет назад, скрыть # ^ |
           
          Проголосовать: нравится -11 Проголосовать: не нравится
          Кстати, опять: на почти деревянных графах мой алгоритм намного лучше.
          • »
            »
            »
            »
            »
            »
            15 лет назад, скрыть # ^ |
             
            Проголосовать: нравится +11 Проголосовать: не нравится
            Исходя из чего? Из-за того что у этого алгоритма + n^2 log n в асимптотике? Ты наверняка даже не померил константу. Нужно смотреть на реальное время.
            • »
              »
              »
              »
              »
              »
              »
              15 лет назад, скрыть # ^ |
               
              Проголосовать: нравится 0 Проголосовать: не нравится
              Моя константа явно не убийственно высокая, раз уж почти наравне с Флойдом. Можно даже сказать примерно чему она равна. Если 108 - стандартное количество операций(мой домашний компьютер, на котором производились измерения, явно стандартный), то получается константа не больше 10. n2logn при n = 1000 - это 107. Если брать константу 10(хотя меньше, ну ладно) и 1000 вершин и примерно столько же ребер, получаем те же 107. Ну не верю я что там константа 1.Не может такого быть.
              • »
                »
                »
                »
                »
                »
                »
                »
                15 лет назад, скрыть # ^ |
                ← Rev. 2  
                Проголосовать: нравится +8 Проголосовать: не нравится

                Надо же как все просто. По твоей логике тогда вообще и с флойдом можно было не тестить, там же очевидно какая константа. Ты ведь даже алгоритм не вкуривал, это может быть оценка сверху или еще чего, и по секрету: константа иногда бывает < 1.

                Пример: как ты думаешь, почему поиск max flow работает в 100500 раз быстрее времени, обещанному асимптотикой, а полный перебор нет?
                • »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  »
                  15 лет назад, скрыть # ^ |
                   
                  Проголосовать: нравится -6 Проголосовать: не нравится
                  екараныедритьбатонввашуедрить
                  Ты думаешь, я этого не знаю и не понимаю? Спасибо, открыл Америку. Даже если там константа 1/10, и че? На графе 105 тогда загнется. И кроме того, НЕТУ там константы меньше единицы. В графе циклы надо найти, без этого никак(а это, кстати, O(n*s) как раз - я бы кстати тоже эту же оценку мог бы написать, если цикла отрицательного нет Форд выйдет раньше чем после такой оценки), потом еще все равно что-нибудь да придется делать, потому что надо найти все пары вершин, лежащие вне КСС. За ноль-время это не сделаешь.

                  Вместо того чтобы умничать лучше бы помог: код пооптимизил, потестил. А то критиковать все тут умные, а как дело до помочь доходит - сколько просил, только Родион и помог.
»
15 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится
В Беллмане, можно добавить такое : не пытаться релаксировать из вершин до которых расстояние бесконечность, интересно будет узнать уменьшится ли время на ваших тестах.
  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится -8 Проголосовать: не нравится
    Ой, блин, как я в коде мог так набажить:)
    Вот результаты. Ускорилось, но малоощутимо:
     n m тип ребер время работы моего алгоритма
     10002000 +  47
     10002000 -  269
     10005000 +  258
     10005000 -  500
     15005000 +  333
     15005000 -  729
     150010000 +  587
     150010000 -  1169
     300010000 +  900
     300010000 -  3140
     50005000 +  262
     50005000 -  265
     500020000 +  2855
     500020000 -  10299
»
15 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится
Ещё, что за магия в 61, 62 строчек, все расстояние инициализируешь нулями?
»
15 лет назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится
пользуясь случаем, спрошу, что такое cerr и как он работает и чем он отличается от cout?
  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится
    Тонкостей не знаю,  лично я cerr использую чтобы вывести в консоль интересующие меня вещи. То есть при открытом output.txt cerr выводит нужный текст в консоль(в коде моей программы - clock(), то есть время работы, выводится в консоль, ответ на задачу - в output.txt)
  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится
    Ну это стандартный поток зарезервированный под вывод ошибок. Обычный поток вывода как и cout, clog.
  • »
    »
    15 лет назад, скрыть # ^ |
     
    Проголосовать: нравится +10 Проголосовать: не нравится
    Помимо сказанного важно, что cerr - не буферизированный, т.е. если пытаться писать в cout и не делать перевод строки, то в случае падения программы можно не увидеть своего сообщения, а cerr долетает всегда, что делает его более подходящим для всякого дебаг-вывода.