Домашняя страница Undo Do New Save Карта сайта Обратная связь Поиск по форуму
МИР MS EXCEL - Гость.xls

Вход

Регистрация

Напомнить пароль

 

= Мир MS Excel/Комбинаторика: Генерирование перестановок: Алгоритм Нарайаны - Мир MS Excel

Старая форма входа
  • Страница 1 из 1
  • 1
Модератор форума: _Boroda_, китин  
Комбинаторика: Генерирование перестановок: Алгоритм Нарайаны
Gustav Дата: Пятница, 29.08.2014, 12:33 | Сообщение № 1
Группа: Админы
Ранг: Участник клуба
Сообщений: 2793
Репутация: 1160 ±
Замечаний: ±

начинал с Excel 4.0, видел 2.1
Искал какой-нибудь простой способ генерирования перестановок, по возможности, без рекурсии. Наткнулся на алгоритм Нарайаны (статья в Википедии: https://ru.wikipedia.org/wiki....D%D1%8B ).

По ссылке приведены реализации на Си и Паскале. Изучил их, переложил на VBA, с возможностью использования массивов не только с основанием 0, как в исходных текстах, но и с более привычным для некоторых, включая меня, основанием 1.

Делюсь достигнутым:
[vba]
Код
Dim a() As Integer

Sub GeneratePermutations()

     Dim obs As Integer, n As Integer, j As Long
      
     n = 6 'количество элементов
      
     'управление основанием массива a(): либо от 0 до n-1, либо от 1 до n
     obs = 1 'здесь можно задать либо 0, либо 1 (а-ля Option Base)
         
     'начальная расстановка: элементы = натуральные числа от 1 до N по возрастанию
     Select Case obs
         Case 0:
             ReDim a(0 To n - 1): For j = 0 To n - 1: a(j) = j + 1: Next j
         Case 1:
             ReDim a(1 To n):     For j = 1 To n:     a(j) = j:     Next j
     End Select
     'следующую строку раскомментировать для тестирования случая остановки на строке 400
     'a(1) = 4: a(2) = 2: a(3) = 5: a(4) = 3: a(5) = 6: a(6) = 1
      
     'собственно генерирование всех перестановок
     j = 0
     Do
         j = j + 1
         'очередная расстановка: вывод на лист (или можно запоминать в какой-нибудь массив)
         Cells(j, 1).Resize(1, n) = a
     Loop Until NarayanaNextPerm(n, obs) = 0
      
End Sub

Function NarayanaNextPerm(n As Integer, Optional obs As Integer = 0) As Integer

     Dim i As Integer, k As Integer, t As Integer, tmp As Integer
      
     'Шаг 1
     k = n - 2 + obs
     Do While k >= 0 + obs
         If a(k) < a(k + 1) Then Exit Do
         k = k - 1
     Loop
      
     If k = -1 + obs Then
         NarayanaNextPerm = 0 'сигнал об окончании внешнего цикла генерирования
         Exit Function
     End If
          
     'Шаг 2
     t = n - 1 + obs
     Do While t >= k + 1 And a(k) >= a(t)
         t = t - 1
     Loop
     tmp = a(k): a(k) = a(t): a(t) = tmp
      
     'Шаг 3
     For i = k + 1 To (n + k + obs) \ 2
         t = n + k + obs - i
         tmp = a(i): a(i) = a(t): a(t) = tmp
     Next i
     NarayanaNextPerm = i
      
End Function
[/vba]
Сердцем алгоритма является функция NarayanaNextPerm, которая для текущей заданной расстановки, хранящейся в элементах внешнего массива a(), генерирует следующую расстановку (перестановку). Для генерации всех перестановок следует стартовать с начального монотонно возрастающего порядка элементов: от 1 до N. И затем дойти до конца процесса, получив ровно обратную последовательность - от N до 1.

Для 6 элементов (исходно: 1,2,3,4,5,6) процедура GeneratePermutations создает 720 строк (6!). Интересно, что процесс генерирования можно прервать в определенный момент, скажем, достигнув значения j = 400. Этому значению соответствует следующее состояние элементов массива a(1-6): 4,2,5,3,6,1. Если затем начать процесс с этого состояния, то он дойдёт до конца, создав оставшиеся 320 комбинаций.


МОИ: Ник, Tip box: 41001663842605
 
Ответить
СообщениеИскал какой-нибудь простой способ генерирования перестановок, по возможности, без рекурсии. Наткнулся на алгоритм Нарайаны (статья в Википедии: https://ru.wikipedia.org/wiki....D%D1%8B ).

По ссылке приведены реализации на Си и Паскале. Изучил их, переложил на VBA, с возможностью использования массивов не только с основанием 0, как в исходных текстах, но и с более привычным для некоторых, включая меня, основанием 1.

Делюсь достигнутым:
[vba]
Код
Dim a() As Integer

Sub GeneratePermutations()

     Dim obs As Integer, n As Integer, j As Long
      
     n = 6 'количество элементов
      
     'управление основанием массива a(): либо от 0 до n-1, либо от 1 до n
     obs = 1 'здесь можно задать либо 0, либо 1 (а-ля Option Base)
         
     'начальная расстановка: элементы = натуральные числа от 1 до N по возрастанию
     Select Case obs
         Case 0:
             ReDim a(0 To n - 1): For j = 0 To n - 1: a(j) = j + 1: Next j
         Case 1:
             ReDim a(1 To n):     For j = 1 To n:     a(j) = j:     Next j
     End Select
     'следующую строку раскомментировать для тестирования случая остановки на строке 400
     'a(1) = 4: a(2) = 2: a(3) = 5: a(4) = 3: a(5) = 6: a(6) = 1
      
     'собственно генерирование всех перестановок
     j = 0
     Do
         j = j + 1
         'очередная расстановка: вывод на лист (или можно запоминать в какой-нибудь массив)
         Cells(j, 1).Resize(1, n) = a
     Loop Until NarayanaNextPerm(n, obs) = 0
      
End Sub

Function NarayanaNextPerm(n As Integer, Optional obs As Integer = 0) As Integer

     Dim i As Integer, k As Integer, t As Integer, tmp As Integer
      
     'Шаг 1
     k = n - 2 + obs
     Do While k >= 0 + obs
         If a(k) < a(k + 1) Then Exit Do
         k = k - 1
     Loop
      
     If k = -1 + obs Then
         NarayanaNextPerm = 0 'сигнал об окончании внешнего цикла генерирования
         Exit Function
     End If
          
     'Шаг 2
     t = n - 1 + obs
     Do While t >= k + 1 And a(k) >= a(t)
         t = t - 1
     Loop
     tmp = a(k): a(k) = a(t): a(t) = tmp
      
     'Шаг 3
     For i = k + 1 To (n + k + obs) \ 2
         t = n + k + obs - i
         tmp = a(i): a(i) = a(t): a(t) = tmp
     Next i
     NarayanaNextPerm = i
      
End Function
[/vba]
Сердцем алгоритма является функция NarayanaNextPerm, которая для текущей заданной расстановки, хранящейся в элементах внешнего массива a(), генерирует следующую расстановку (перестановку). Для генерации всех перестановок следует стартовать с начального монотонно возрастающего порядка элементов: от 1 до N. И затем дойти до конца процесса, получив ровно обратную последовательность - от N до 1.

Для 6 элементов (исходно: 1,2,3,4,5,6) процедура GeneratePermutations создает 720 строк (6!). Интересно, что процесс генерирования можно прервать в определенный момент, скажем, достигнув значения j = 400. Этому значению соответствует следующее состояние элементов массива a(1-6): 4,2,5,3,6,1. Если затем начать процесс с этого состояния, то он дойдёт до конца, создав оставшиеся 320 комбинаций.

Автор - Gustav
Дата добавления - 29.08.2014 в 12:33
Gustav Дата: Пятница, 29.08.2014, 12:34 | Сообщение № 2
Группа: Админы
Ранг: Участник клуба
Сообщений: 2793
Репутация: 1160 ±
Замечаний: ±

начинал с Excel 4.0, видел 2.1
[поскольку в одно сообщение не поместился, продолжаю в этом...]

В процессе работы демонстрационной процедуры GeneratePermutations на рабочий лист выводятся генерируемые перестановки. Однако, строк для вывода всех комбинациий становится недостаточно уже при n = 10 - требуется 10! = 3 628 800 строк, что больше максимального кол-ва строк на листе 2^20 = 1 048 576. Для Excel до версии 2007 ограничение, разумеется, наступает гораздо раньше.

В следующей процедуре GeneratePermutations2 учтена возможность достижения последней строки листа, либо произвольного заданного ограничения rmax. Достигнув предельной строки, процесс вывода продолжается с первой строки того же листа, с отступом через пустой столбец вправо от уже выведенных перестановок, т.е. происходит такое "подворачивание" вывода. Группы столбцов, между которыми делаются пустые столбцы, я назвал "грядками", а сами пустые столбцы - "межами".

[vba]
Код
Sub GeneratePermutations2()

      'вариант GeneratePermutations с "подворачиванием" на листе
      'понадобится при n > 9, если комбинации хочется вывести на рабочий лист

      Dim obs As Integer, n As Integer, r As Long, rmax As Long, c As Integer
        
      n = 10 'количество элементов
        
      'управление основанием массива a(): либо от 0 до n-1, либо от 1 до n
      obs = 1 'здесь можно задать либо 0, либо 1 (а-ля Option Base)
      rmax = 2 ^ 20 'предел "подворачивания"
           
      'начальная расстановка: элементы = натуральные числа от 1 до N по возрастанию
      Select Case obs
          Case 0:
              ReDim a(0 To n - 1): For r = 0 To n - 1: a(r) = r + 1: Next r
          Case 1:
              ReDim a(1 To n):     For r = 1 To n:     a(r) = r:     Next r
      End Select
        
      r = 0: c = 1
      Do
          r = r + 1
          If r > Rows.Count Or r > rmax Then
              r = 1: c = c + n + 1
          End If
          'очередная расстановка: вывод на лист (с "подворачиванием")
          Cells(r, c).Resize(1, n) = a
      Loop Until NarayanaNextPerm(n, obs) = 0
        
End Sub
[/vba]
Процедура GeneratePermutations2 генерирует перестановки для 10 элементов, выводя первые 2^20 строк в первую "грядку", далее полностью заполняются еще две "грядки" и, наконец, четвертая "грядка" заполняется лишь по строку 483072. Итого 2^20*3+483072 = 3 628 800 строк. При n = 10 процедура работает порядка 4-х минут.


МОИ: Ник, Tip box: 41001663842605

Сообщение отредактировал Gustav - Пятница, 29.08.2014, 12:35
 
Ответить
Сообщение[поскольку в одно сообщение не поместился, продолжаю в этом...]

В процессе работы демонстрационной процедуры GeneratePermutations на рабочий лист выводятся генерируемые перестановки. Однако, строк для вывода всех комбинациий становится недостаточно уже при n = 10 - требуется 10! = 3 628 800 строк, что больше максимального кол-ва строк на листе 2^20 = 1 048 576. Для Excel до версии 2007 ограничение, разумеется, наступает гораздо раньше.

В следующей процедуре GeneratePermutations2 учтена возможность достижения последней строки листа, либо произвольного заданного ограничения rmax. Достигнув предельной строки, процесс вывода продолжается с первой строки того же листа, с отступом через пустой столбец вправо от уже выведенных перестановок, т.е. происходит такое "подворачивание" вывода. Группы столбцов, между которыми делаются пустые столбцы, я назвал "грядками", а сами пустые столбцы - "межами".

[vba]
Код
Sub GeneratePermutations2()

      'вариант GeneratePermutations с "подворачиванием" на листе
      'понадобится при n > 9, если комбинации хочется вывести на рабочий лист

      Dim obs As Integer, n As Integer, r As Long, rmax As Long, c As Integer
        
      n = 10 'количество элементов
        
      'управление основанием массива a(): либо от 0 до n-1, либо от 1 до n
      obs = 1 'здесь можно задать либо 0, либо 1 (а-ля Option Base)
      rmax = 2 ^ 20 'предел "подворачивания"
           
      'начальная расстановка: элементы = натуральные числа от 1 до N по возрастанию
      Select Case obs
          Case 0:
              ReDim a(0 To n - 1): For r = 0 To n - 1: a(r) = r + 1: Next r
          Case 1:
              ReDim a(1 To n):     For r = 1 To n:     a(r) = r:     Next r
      End Select
        
      r = 0: c = 1
      Do
          r = r + 1
          If r > Rows.Count Or r > rmax Then
              r = 1: c = c + n + 1
          End If
          'очередная расстановка: вывод на лист (с "подворачиванием")
          Cells(r, c).Resize(1, n) = a
      Loop Until NarayanaNextPerm(n, obs) = 0
        
End Sub
[/vba]
Процедура GeneratePermutations2 генерирует перестановки для 10 элементов, выводя первые 2^20 строк в первую "грядку", далее полностью заполняются еще две "грядки" и, наконец, четвертая "грядка" заполняется лишь по строку 483072. Итого 2^20*3+483072 = 3 628 800 строк. При n = 10 процедура работает порядка 4-х минут.

Автор - Gustav
Дата добавления - 29.08.2014 в 12:34
MCH Дата: Пятница, 29.08.2014, 18:11 | Сообщение № 3
Группа: Админы
Ранг: Старожил
Сообщений: 2004
Репутация: 752 ±
Замечаний: ±

Интересно будет потестить на скорость данный алгоритм и рекурсии, предполагаю, что рекурсии могут оказаться быстрее.
Проверил, рекурсия, которая есть у меня, работает раза в два медленнее.

Есть собственные разработки по генерации следующей перестановки в лексикографическом порядке (реализовывал сам, получилось похоже). Сравню по скорости.
Сравнил, скорость аналогична на 10-20% мой алгоритм медленнее

Т.к. мои реализации медленнее опубликованного здесь алгоритма Нарайаны, то опубликовывать их не буду


Сообщение отредактировал MCH - Пятница, 29.08.2014, 21:41
 
Ответить
СообщениеИнтересно будет потестить на скорость данный алгоритм и рекурсии, предполагаю, что рекурсии могут оказаться быстрее.
Проверил, рекурсия, которая есть у меня, работает раза в два медленнее.

Есть собственные разработки по генерации следующей перестановки в лексикографическом порядке (реализовывал сам, получилось похоже). Сравню по скорости.
Сравнил, скорость аналогична на 10-20% мой алгоритм медленнее

Т.к. мои реализации медленнее опубликованного здесь алгоритма Нарайаны, то опубликовывать их не буду

Автор - MCH
Дата добавления - 29.08.2014 в 18:11
MCH Дата: Воскресенье, 31.08.2014, 10:43 | Сообщение № 4
Группа: Админы
Ранг: Старожил
Сообщений: 2004
Репутация: 752 ±
Замечаний: ±

Набрался наглости и внес коррективы в код Константина: изменил типы, убрал obs, немного оптимизировал вычисления в циклах
Это дало ускорение более чем в 1,5 раза от первоначального кода (на моем компьютере: 13,5 сек. против первоначальных 21,55 сек.)

[vba]
Код
Option Explicit

Private prmArr&()

Function MyNarayanaNextPerm&(n&)
      Dim i&, k&, t&, tmp&
      
      For k = n - 1 To 1 Step -1
          If prmArr(k) < prmArr(k + 1) Then Exit For
      Next k
        
      If k Then
          t = n
          While t > k And prmArr(k) >= prmArr(t)
              t = t - 1
          Wend
          tmp = prmArr(k): prmArr(k) = prmArr(t): prmArr(t) = tmp
          t = n
          For i = k + 1 To (n + k) \ 2
              tmp = prmArr(i): prmArr(i) = prmArr(t): prmArr(t) = tmp
              t = t - 1
          Next i
          MyNarayanaNextPerm = i
      End If
End Function

Sub GeneratePermutations0()
      Dim n&, i&, tmr!
        
      n = 11
      ReDim prmArr&(1 To n)
      For i = 1 To n: prmArr(i) = i: Next i
      'i = 0
      tmr = Timer
      Do
          'i = i + 1
          'Cells(i, 1).Resize(1, n) = prmArr
      Loop While MyNarayanaNextPerm(n)
      Debug.Print Timer - tmr
End Sub
[/vba]


Сообщение отредактировал MCH - Воскресенье, 31.08.2014, 10:50
 
Ответить
СообщениеНабрался наглости и внес коррективы в код Константина: изменил типы, убрал obs, немного оптимизировал вычисления в циклах
Это дало ускорение более чем в 1,5 раза от первоначального кода (на моем компьютере: 13,5 сек. против первоначальных 21,55 сек.)

[vba]
Код
Option Explicit

Private prmArr&()

Function MyNarayanaNextPerm&(n&)
      Dim i&, k&, t&, tmp&
      
      For k = n - 1 To 1 Step -1
          If prmArr(k) < prmArr(k + 1) Then Exit For
      Next k
        
      If k Then
          t = n
          While t > k And prmArr(k) >= prmArr(t)
              t = t - 1
          Wend
          tmp = prmArr(k): prmArr(k) = prmArr(t): prmArr(t) = tmp
          t = n
          For i = k + 1 To (n + k) \ 2
              tmp = prmArr(i): prmArr(i) = prmArr(t): prmArr(t) = tmp
              t = t - 1
          Next i
          MyNarayanaNextPerm = i
      End If
End Function

Sub GeneratePermutations0()
      Dim n&, i&, tmr!
        
      n = 11
      ReDim prmArr&(1 To n)
      For i = 1 To n: prmArr(i) = i: Next i
      'i = 0
      tmr = Timer
      Do
          'i = i + 1
          'Cells(i, 1).Resize(1, n) = prmArr
      Loop While MyNarayanaNextPerm(n)
      Debug.Print Timer - tmr
End Sub
[/vba]

Автор - MCH
Дата добавления - 31.08.2014 в 10:43
Апострофф Дата: Четверг, 19.09.2024, 13:20 | Сообщение № 5
Группа: Проверенные
Ранг: Обитатель
Сообщений: 458
Репутация: 126 ±
Замечаний: 0% ±

Excel 1997
Прошло 10 лет. :'(
Скинем 10% от времени кода МСН? :)
[vba]
Код
Option Explicit

Private A&()

Function MyNarayanaNextPerm&(N&)
    Dim I&, J&, L&, T&
    
    For J = N - 1 To 1 Step -1
        If A(J) < A(J + 1) Then T = A(J): Exit For
    Next J
        
    If J Then
        L = N
        While T > A(L)
            L = L - 1
        Wend
        A(J) = A(L): A(L) = T
        L = N
        For I = (J + 1) To ((N + J) \ 2)
            T = A(I): A(I) = A(L): A(L) = T
            L = L - 1
        Next I
        MyNarayanaNextPerm = I
    End If
End Function

Sub GeneratePermutations0()
    Dim N&, I&, tmr!
        
    N = 11
    ReDim A&(1 To N)
    For I = 1 To N: A(I) = I: Next I
    I = 0
    tmr = Timer
    Do
'        i = i + 1
'        Cells(i, 1).Resize(1, n) = A
    Loop While MyNarayanaNextPerm(N)
    Debug.Print Timer - tmr
End Sub
[/vba]


Сообщение отредактировал Апострофф - Четверг, 19.09.2024, 13:21
 
Ответить
СообщениеПрошло 10 лет. :'(
Скинем 10% от времени кода МСН? :)
[vba]
Код
Option Explicit

Private A&()

Function MyNarayanaNextPerm&(N&)
    Dim I&, J&, L&, T&
    
    For J = N - 1 To 1 Step -1
        If A(J) < A(J + 1) Then T = A(J): Exit For
    Next J
        
    If J Then
        L = N
        While T > A(L)
            L = L - 1
        Wend
        A(J) = A(L): A(L) = T
        L = N
        For I = (J + 1) To ((N + J) \ 2)
            T = A(I): A(I) = A(L): A(L) = T
            L = L - 1
        Next I
        MyNarayanaNextPerm = I
    End If
End Function

Sub GeneratePermutations0()
    Dim N&, I&, tmr!
        
    N = 11
    ReDim A&(1 To N)
    For I = 1 To N: A(I) = I: Next I
    I = 0
    tmr = Timer
    Do
'        i = i + 1
'        Cells(i, 1).Resize(1, n) = A
    Loop While MyNarayanaNextPerm(N)
    Debug.Print Timer - tmr
End Sub
[/vba]

Автор - Апострофф
Дата добавления - 19.09.2024 в 13:20
  • Страница 1 из 1
  • 1
Поиск:

Яндекс.Метрика Яндекс цитирования
© 2010-2024 · Дизайн: MichaelCH · Хостинг от uCoz · При использовании материалов сайта, ссылка на www.excelworld.ru обязательна!