Главная » Просмотр файлов » В. Рубанцев - Как решать комбинаторные задачи на компьютере

В. Рубанцев - Как решать комбинаторные задачи на компьютере (1127108), страница 6

Файл №1127108 В. Рубанцев - Как решать комбинаторные задачи на компьютере (В. Рубанцев - Как решать комбинаторные задачи на компьютере) 6 страницаВ. Рубанцев - Как решать комбинаторные задачи на компьютере (1127108) страница 62019-05-11СтудИзба
Просмтор этого файла доступен только зарегистрированным пользователям. Но у нас супер быстрая регистрация: достаточно только электронной почты!

Текст из файла (страница 6)

Такая запись называется стандартной формой разбиения числа n наk слагаемых.В англоязычной литературе разбиения чисел называют Integer Partitions.61Если речь идет обо всех вариантах разбиения, то их число обозначают P(n).Мы легко найдём, чтоP(1)= 1> 1, потому что единицу невозможно представить иначе.P(2)= 2 > 2 11P(3)= 3 > 3 21 111P(4)= 5 > 4 31 22 211 1111P(5)= 7 > 5 41 32 311 221 2111 11111Эту процедуру можно продолжить и дальше, но уже сейчас явно прослеживается её рекурсивная сущность.

Однако мы начнём с итерационного алгоритма, описанного Витольдом Липским в книге [ЛВ88, Алгоритм 1.22]. Намнужно только перевести его на Си-шарп.Заданное число мы будем, как обычно, получать из компонента udNum иобозначим его буквой n. Все слагаемые мы поместим в массив adds, их общее число в текущем разбиении мы сохраним в поле nAdds, а числонайденных вариантов – в поле nVar. И для алгоритма Липского нам потребуется ещё один массив r, который показывает число повторов каждогослагаемого в разбиении. Например, для разбиения пятёрки 311 – тройкаповторяется 1 раз, а единица – дважды.//слагаемые:int[] adds;//число слагаемых:int nAdds = 0;//число повторов каждого слагаемого:int[] r;//число разбиений:int nVar = 0;Нажав на кнопку Все разбиения (Ит)!, мы по заданному числу n создаёмоба массива и переходим в метод part://РАЗБИВАЕМ ЧИСЛО ИТЕРАЦИОННОprivate void btnGenerate_Click(object sender, EventArgs e){int n = (int)udNum.Value;adds = new int[n + 1];r= new int[n + 1];string s = "Все разбиения числа " + n.ToString() + ": ";lstVar.Items.Add(s);part(n);s = "Всего " + nVar.ToString();lstVar.Items.Add(s);62lstVar.Items.Add("");lstVar.TopIndex = lstVar.Items.Count - 22;}//Генерируем все разбиения числа n//и выводим в списокvoid part(int n){nVar = 0;//первое разбиение равно числу n:adds[1] = n;r[1] = 1;nAdds = 1;print(nAdds);//находим следующие разбиения:while (adds[1] > 1){int sum = 0;if (adds[nAdds] == 1){sum += r[nAdds];--nAdds;}sum += adds[nAdds];--r[nAdds];int l = adds[nAdds] - 1;if (r[nAdds] > 0) ++nAdds;adds[nAdds] = l;r[nAdds] = sum / l;l = sum % l;if (l != 0){++nAdds;adds[nAdds] = l;r[nAdds] = 1;}print(nAdds);}}Всякий раз, когда метод part сгенерирует новое разбиение, мы его печатаем на экране://ПЕЧАТАЕМ РАЗБИЕНИЕvoid print(int d){++nVar;string s = "";for (int i = 1; i <= d; ++i)for (int j = 1; j <= r[i]; ++j)63{s += (adds[i].ToString() + " ");}lstVar.Items.Add(s);//прокручиваем список вниз:lstVar.TopIndex = lstVar.Items.Count - 22;this.lstVar.Invalidate();Application.DoEvents();}И вот уже без особого напряжения мы получаем полный список разбиенийчисла 12 (Рис.

9.1).Рис. 9.1. Разбитое число 12Попробуем подойти к решению задачи с другого конца - с рекурсивного.Здесь нам на помощь спешит другой алгоритм [KS98, Algorithm 3.1], которыймы слегка заточим под наши нужды.64Чтобы не нагружать излишне нашу единственную кнопку, подселим к нейсоседку. Она вызывает другой метод и обходится без массива r://РАЗБИВАЕМ ЧИСЛО РЕКУРСИВНОprivate void btnGenRec_Click(object sender, EventArgs e){int n = (int)udNum.Value;adds = new int[n + 1];string s = "Все разбиения числа " + n.ToString() + ": ";lstVar.Items.Add(s);nVar = 0;RecPartition(n,n,0);s = "Всего " + nVar.ToString();lstVar.Items.Add(s);lstVar.Items.Add("");lstVar.TopIndex = lstVar.Items.Count - 22;}А вот и рекурсивная разбивалка чисел:void RecPartition(int n, int B, int N){if (n == 0){nAdds = N;print(adds);}else{for (int i = 1; i <= Math.Min(B, n); ++i){adds[N + 1] = i;RecPartition(n - i, i, N + 1);}}}Заметьте, насколько сократился алгоритм! Добавляем метод печати очередного разбиения:void print(int[] a){++nVar;string s = "";for (int i = 1; i <= nAdds; ++i){int j = a[i];s += j.ToString();65if (i != nAdds) s += "+";}lstVar.Items.Add(s);//прокручиваем список вниз:lstVar.TopIndex = lstVar.Items.Count - 22;this.lstVar.Invalidate();Application.DoEvents();}Нажимаем кнопку Все разбиения (Рек)! – и вуаля, как говорят французы(Рис.

9.2).Рис. 9.2. Рекурсивная разбивалка в действии!Более того, рекурсивный алгоритм получился даже более универсальным,чем итерационный. Например, мы можем, иначе подготовив его вызов,сгенерировать не все разбиения, а только те, в которых наибольшее слагаемое не превышает заданного числа k.66Дополним интерфейс кнопкой btnK Все разбиения! и компонентом NumericUpDown. В методе btnK_Click обратите внимание на выделенные строчки://ГЕНЕРИРУЕМ ВСЕ РАЗБИЕНИЯ,//В КОТОРЫХ НАИБОЛЬШЕЕ СЛАГАЕМОЕ РАВНО kprivate void btnK_Click(object sender, EventArgs e){int n = (int)udNum.Value;adds = new int[n + 1];int k = (int)udK.Value;if (k > n){udK.Value=n;k = n;}string s = "Все разбиения числа " + n.ToString();lstVar.Items.Add(s);s= "k = " + k + ":";lstVar.Items.Add(s);nVar = 0;adds[1] = k;RecPartition(n-k, k, 1);s = "Всего " + nVar.ToString();lstVar.Items.Add(s);lstVar.Items.Add("");lstVar.TopIndex = lstVar.Items.Count - 22;}Итак, перед вызовом нашего рекурсивного метода мы присваиваем первому слагаемому значение k, которое и остаётся неизменным во всех разбиениях, а все остальные слагаемые дополняют первое до числа n, но при этомне превышают k (Рис.

9.3).В нашем разменном примере эта ситуация может возникнуть при отсутствии «крупной» мелочи.Диаграммы ФеррерсаРазбиения чисел – для наглядности – принято изображать диаграммойФеррерса (Ferrers Diagram или Ferrers-Young Diagram). Для разбиения (1)она состоят из k строк, каждая из которых соответствует слагаемому вразбиении и состоит из последовательности точек, число которых равнозначению слагаемого.Например, для разбиения 8 = 4 + 3 + 1 можно построить такую диаграмму(Рис. 9.4).67Рис. 9.3. Разбиваем на меткие кусочки!Рис. 9.4. Разбиение числа 8Если перевернуть диаграмму Феррерса так, чтобы столбцы и строки поменялись местами (эта операция называется транспозицией), то мы получимсопряжённое разбиение заданного числа (Рис.

9.5).Рис. 9.5. Сопряжённое разбиение числа 868Легко видеть, что исходному разбиению соответствует сопряжённое 8 = 3+ 2 +2 + 1.Вы пока периферийно думайте над свойствами этих разбиений, а мы в этовремя научимся рисовать диаграммы Феррерса. Я не думаю, что онинастолько важны для нас, чтобы мы расщедрились на полноценную графику, так что мы будем просто ставить вместо кружочков буквы О.Подпишите под вызовом метода print ещё и новый метод printFerrers, который для каждого разбиения составит диаграмму Феррерса:void RecPartition(int n, int B, int N){if (n == 0){nAdds = N;print(adds);printFerrers(adds);}. .

.В методе printFerrers мы для каждого слагаемого создаём новую строкуиз букв О, взятых в количестве, равном очередному слагаемому a[i]://ПЕЧАТАЕМ ДИАГРАММУ ФЕРРЕРСАvoid printFerrers(int[] a){string s = "";for (int i = 1; i <= nAdds; ++i){s = new String('O', a[i]);lstVar.Items.Add(s);}lstVar.Items.Add("");//прокручиваем список вниз:lstVar.TopIndex = lstVar.Items.Count - 22;this.lstVar.Invalidate();Application.DoEvents();}И вот что у нас получилось (Рис. 9.6).69Рис. 9.6. Наше разбиение!Теперь давайте транспонируем диаграмму, а затем ещё раз подумаем, зачем нам это нужно.Поскольку задача чисто геометрическая, то мы справляемся с ней одниммахом:int nAdds2;int[] ConjPartition(int[] a){int[] b = new int[a.Length];int n = a.Length;nAdds2 = a[1];for (int i = 1; i < n; ++i)b[i] = 1;for (int j = 2; j <= nAdds; ++j)for (int i = 1; i <= a[j]; ++i)++b[i];70return b;}Обратите внимание: нам пришлось ввести новое поле nAdds2 для хранениячисла слагаемых в массиве b, что повлекло за собой правки в методахprintFerrers и RecPartition://ПЕЧАТАЕМ ДИАГРАММУ ФЕРРЕРСАvoid printFerrers(int[] a, int nPart){string s = "";for (int i = 1; i <= nPart; ++i).

. .void RecPartition(int n, int B, int N){if (n == 0){nAdds = N;print(adds);printFerrers(adds, nAdds);printFerrers(ConjPartition(adds), nAdds2);. . .Зато мы можем печатать не только «прямые» диаграммы Феррерса, но исопряжённые (Рис. 9.7).И вот пришла пора вернуться к нашему животрепещущему вопросу об этихдиаграммах. И по ним, и по методу ConjPartition мы видим, что количестворазбиений числа n на k частей в точности равно числу его разбиений, в которых наибольшая часть имеет значение k:nAdds2 = a[1];Число разбиений числа n на k частей обозначают P(n,k).Несколько полезных равенств:P(0,0) = 1P(n,0) = 0n>=1P(n,1) = P(n,n) = 1 n>=1Очевидно, что сумма всех возможных разбиений числа n на k частей равнаP(n), то естьP(n) = P(n,1) + P(n,2) + … + P(n,n)71Рис.

9.7. Оттранспонировались успешно!Ставим четвёртую - кнопку btnK2 Все разбиения на k!, - которая действуетподобно кнопке Все разбиения!, но вызывает рекурсивный метод RecPartition2://РАЗБИВАЕМ ЧИСЛО НА k ЧАСТЕЙprivate void btnK2_Click(object sender, EventArgs e){int n = (int)udNum.Value;adds = new int[n + 1];int k = (int)udK.Value;if (k > n){udK.Value = n;k = n;}string s = "Все разбиения числа " + n.ToString();lstVar.Items.Add(s);s = "на заданное число частей " + k + ":";lstVar.Items.Add(s);72nVar = 0;adds[1] = k;RecPartition2(n - k, k, 1);s = "Всего " + nVar.ToString();lstVar.Items.Add(s);lstVar.Items.Add("");lstVar.TopIndex = lstVar.Items.Count - 22;}Он, в свою очередь, почти близнец метода RecPartition, но печатает сопряжённое разбиение (Рис.

9.8), которое нам предоставляет метод ConjPartition:void RecPartition2(int n, int B, int N){if (n == 0){nAdds = N;print(ConjPartition(adds), nAdds2);}else{for (int i = 1; i <= Math.Min(B, n); ++i){adds[N + 1] = i;RecPartition2(n - i, i, N + 1);}}}И тут нам опять пришлось поправить уже готовый метод print, чтобы онумел работать с разными массивами, а не только с adds:void print(int[] a, int nPart){.

Характеристики

Тип файла
PDF-файл
Размер
9,17 Mb
Тип материала
Высшее учебное заведение

Список файлов книги

Свежие статьи
Популярно сейчас
Как Вы думаете, сколько людей до Вас делали точно такое же задание? 99% студентов выполняют точно такие же задания, как и их предшественники год назад. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
6487
Авторов
на СтудИзбе
303
Средний доход
с одного платного файла
Обучение Подробнее