Участник:Qtr — различия между версиями
Qtr (обсуждение | вклад) (→Описание) |
Qtr (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
− | + | <tex dpi = "200"> 1 \mid prec,pmtn,r_i \mid f_{max}</tex> | |
+ | |||
+ | {{Задача | ||
+ | |definition = <wikitex>Дано $n$ работ, которые надо выполнить на одной машине, причем $i$-ая работа выполняется $p_i$ времени. Для каждой работы задана монотонно неубывающая функция $f_i$. Работы можно прерывать, у каждой работы есть время появления $r_{i}$. Также между работами заданы отношения в виде ориентированного графа без циклов: если существует ребро $a \to b$, то работа $a$ должна завершиться до начала выполнения работы $b$. Необходимо построить такое расписание, чтобы величина $f_{max} = \max\limits_{j=1..n}{f_j(C_j)}$, где $C_j$ {{---}} время окончания выполнения $j$-ой работы, была минимальна.</wikitex> | ||
+ | }} | ||
+ | |||
+ | Задача <tex> 1 \mid prec, pmtn, r_i \mid f_{max} </tex> является обобщением [[Правило Лаулера|<tex>1 \mid prec \mid f_{max}</tex>]], но здесь у работ также есть времена появления, раньше которых их делать запрещено, и их можно прерывать. | ||
== Алгоритм == | == Алгоритм == | ||
+ | Работу будем обозначать просто ее номером (<tex> i </tex>), при этом, номера работ могут меняться в зависимости от того, по какому параметру они отсортированы. Время появления работы — <tex> r[i]</tex>, время, требуемое для ее выполнения — <tex> p[i] </tex>. Множество ребер графа обозначается как <tex> E </tex>. | ||
− | === | + | === Modify === |
+ | Для начала, модифицируем времена появления работ. Если работа <tex> j </tex> зависит от <tex> i </tex>, то, очевидно, она не может быть начата раньше, чем закончится выполнение <tex> i </tex>, поэтому нужно заменить <tex> r_j </tex> на <tex> \max(r_j, r_i + p_i) </tex>. Алгоритм, делающий это, представлен ниже (работы рассматриваются в порядке топологической сортировки): | ||
− | + | Modify() | |
+ | '''for''' i = 1 '''to''' n | ||
+ | '''for''' j: ij <tex> \in E </tex> | ||
+ | r[j] = max(r[j], r[i] + p[i]) | ||
− | + | После выполнения этого алгоритма для любых двух работ <tex> i, j </tex>, таких, что <tex> j </tex> зависит от <tex> i </tex>, выполняется <tex> r[j] > r[i] </tex>, поэтому, при рассмотрении работ в порядке неубывания времен их появления, они также будут топологически отсортированы. | |
− | + | === Blocks === | |
+ | Здесь и далее считается, что работы отсортированы в порядке неубывания модифицированных <tex> r_i </tex>. | ||
− | + | Станок, выполняющий работы, выполняет работу в некоторые интервалы времени и простаивает в остальное время. Следующий алгоритм разбивает множество работ на блоки, внутри которых станок работает без простоя. | |
− | + | Blocks(<tex> \{ 1 \ldots n \} </tex>) | |
+ | j = 0 | ||
+ | t = 0 | ||
+ | '''for''' i = 1 '''to''' n | ||
+ | '''if''' <tex> t < r[i] </tex> | ||
+ | t = r[i] | ||
+ | j = j + 1 | ||
+ | B[j].add(i) | ||
+ | t = t + p[i] | ||
+ | '''return''' B | ||
− | + | Если алгоритм Blocks вызывается от пустого множества, то считаем, что он возвращает также пустое множество. | |
− | + | ||
+ | Определим время начала блока <tex> B_j </tex> как <tex>s_j = \min\limits_{i \in B_j} r_i </tex>, а время конца — как <tex> e_j = s_j + \sum\limits_{i \in B_j} p_i </tex>. | ||
{{Лемма | {{Лемма | ||
|statement= | |statement= | ||
− | + | Существует оптимальное расписание, такое, что все во все временные интервалы <tex> [s_j; e_j] </tex>, соответствующие блокам <tex> B_j </tex>, построенным алгоритмом Blocks, станок работает без простоя. | |
|proof= | |proof= | ||
+ | Возьмем произвольное оптимальное расписание <tex> S </tex>, в нем деление на блоки может также быть произвольным. Найдем первый такой временной интервал <tex> [s_j; e_j] </tex>, что в <tex> S </tex> есть период простоя внутри <tex> [s_j; e_j] </tex> (если таких периодов несколько, будем рассматривать первый из них). Обозначим его за <tex> [s; e] </tex>. | ||
− | + | Возьмем некоторую работу <tex> i </tex>, такую, что она начинается позже, чем в момент времени <tex> s </tex>, не имеет в графе зависимостей предков, завершаемых позже, чем в момент <tex> s </tex> и <tex> r_i \le s </tex>. Такая работа обязательно существует, иначе для множества работ, выполняемых позже, чем в момент <tex> s </tex>, было бы <tex> r = \min\limits_{k \in T} r_k > s </tex>, и внутри блока <tex> B_j </tex> был бы простой <tex> [s_j; r] </tex>, что невозможно по построению алгоритма Blocks. Очевидно, мы можем начать выполнять ее в момент времени <tex> s </tex> и полностью, либо частично заполнить простой <tex> [s; e] </tex>; так как <tex> f_i </tex> — неубывающая функция, то ответ останется оптимальным. Повторяя этот процесс, мы за конечное число шагов придем к оптимальному расписанию с требуемым свойством. | |
+ | }} | ||
− | : | + | === Decompose === |
+ | Допустим, у нас есть блок работ, который можно выполнить без прерываний. Общая идея алгоритма Decompose следующая: найдем работу <tex> i </tex>, которую выгоднее всего выполнить последней. Разобъем оставшееся множество работ на блоки, решим задачу для этих блоков рекурсивно и вставим <tex> i </tex> в промежутки между ними, до них и после них, начиная с <tex> r_i </tex>. Псевдокод этого алгоритма представлен ниже. | ||
+ | |||
+ | Decompose(B) | ||
+ | find <tex> l: f[l](e) = \min \{f[j](e) \mid j \in B, \overline\exists\ k: jk \in E \} </tex> | ||
+ | ans = f[l](e) | ||
+ | G = Blocks(<tex> B \setminus l </tex>) | ||
+ | вставить l в промежутки между блоками G, начиная с <tex> r_l </tex> | ||
+ | '''for''' B[j] <tex>\in</tex> G | ||
+ | ans = max(ans, Decompose(B[j])) | ||
+ | '''return''' ans | ||
+ | |||
+ | {{Теорема | ||
+ | |statement= | ||
+ | Расписание для блока, построенное алгоритмом Decompose, является корректным и оптимальным. | ||
+ | |proof= | ||
+ | Докажем сначала корректность. | ||
− | + | Убедимся, что порядок выполнения работ, заданный графом зависимостей, не нарушается. Заметим, что в разбиении <tex> B \setminus l </tex> на блоки существует не более одного блока <tex> B_0 </tex>, расположенного до момента времени <tex> r_l </tex> — иначе после вставки <tex> l </tex> в промежутки между блоками, <tex> B </tex> выполнялся бы с прерываниями. Далее, заметим, что все интервалы времени, на которые назначается работа из блока <tex> B_j </tex>, находятся внутри интервала <tex> [s_j; e_j] </tex>; это относится и к блоку <tex> B_0 </tex>. Из этих двух наблюдений, а также того, что все работы со временами появления меньше, чем <tex> r_l </tex>, будут помещены в блок <tex> B_0 </tex>, следует, что порядок выполнения будет правильным. | |
− | + | Также для корректности требуется, чтобы работы выполнялись не раньше, чем они появляются. Так как время выполнения работы определяется только в строке 4 алгоритма, которая соответствует этому требованию, то условие выполняется. | |
− | + | Найдем теперь нижнюю оценку на <tex> f_{max} </tex>. Пусть <tex> f_{max}(J) </tex> — ответ для множества работ <tex> J </tex>. | |
− | + | Очевидно, для любой работы <tex> j \in J </tex> выполняется <tex> f_{max}(J) \ge f_{max}(J \setminus j) </tex>, значит, <tex> f_{max}(J) \ge \max\limits_{j \in J} f_{max}(J \setminus j) </tex>. | |
− | + | Также, так как в оптимальном решении какая-то работа без потомков обязательно заканчивается в блоке <tex> B </tex>, то <tex> f_{max}(J) \ge f_l(e) </tex>. | |
− | + | Отсюда следует <tex> f_{max}(J) \ge \max(f_l(e), \max\limits_{j \in J} f_{max}(J \setminus j)) </tex>. По псевдокоду алгоритма видно, что его ответ достигает этой нижней оценки. | |
}} | }} | ||
− | === | + | === Общий алгоритм === |
− | |||
− | |||
− | + | Выполним Modify, после чего разобъем все множество работ на блоки и для каждого блока запустим Decompose(): | |
− | + | MakeSchedule() | |
+ | Modify() | ||
+ | B = Blocks(<tex> \{1 \ldots n \} </tex>) | ||
+ | ans = <tex> -\infty </tex> | ||
+ | '''for''' B[j] <tex>\in</tex> B | ||
+ | ans = max(ans, Decompose(B[j])) | ||
+ | '''return''' ans | ||
− | <tex>\ | + | Из доказанной ранее леммы следует, что <tex> f_{max}(\{ 1 \ldots n \}) = \max\limits_{j} f_{max}(B_j) </tex>, поэтому расписание для всего множества работ, поделенного на блоки, также будет оптимальным и корректным. |
− | + | == Время работы == | |
+ | {{Теорема | ||
+ | |statement= | ||
+ | Время работы алгоритма MakeSchedule — <tex> O(n^2) </tex> операций. | ||
+ | |proof= | ||
+ | Обозначим за <tex> P(n) </tex> время, необходимое для выполнения алгоритма MakeSchedule на n работах. Очевидно, для корректно определенной функции P в силу структуры алгоритма должно выполняться неравенство: | ||
− | <tex>\ | + | <tex> P(n) \ge сn + \sum\limits_{i = 1}^{k} P(n_i) </tex> |
− | + | Здесь <tex> n_i </tex> - размер блока с номером <tex> i </tex>, построенного алгоритмом Blocks(). Заметим, что <tex> \sum\limits_{i = 1}^{k} n_i = n - 1</tex>. | |
− | + | Если <tex> P(n) = an^2 </tex>, то имеем: | |
− | + | <tex> an^2 \ge cn + a \sum\limits_{i = 1}^{k} n_i^2 </tex> | |
− | + | Так как <tex> n^2 > (n - 1)^2 = (\sum\limits_{i = 1}^{k} n_i)^2 = \sum\limits_{i = 1}^{k} n_i^2 + 2\sum\limits_{\substack{i, j = 1\\ i \ne j}}^{k} n_i n_j </tex>, то можно переписать неравенство в следующем виде: | |
− | }} | + | <tex> 2a \sum\limits_{\substack{i, j = 1\\ i \ne j}}^{k} n_i n_j \ge cn </tex> |
− | = | + | Чтобы получить максимальную нижнюю оценку на <tex> a </tex>, оценим снизу <tex> \sum\limits_{i, j = 1}^{k} n_i n_j </tex>: |
− | + | <tex> \sum\limits_{\substack{i, j = 1\\ i \ne j}}^{k} n_i n_j \ge \sum\limits_{\substack{i, j = 1\\ i \ne j}}^{k} 1 \cdot n_j = \sum\limits_{j = 1}^{k} (k - 1) n_j = (k - 1) (n - 1) \ge \cfrac{nk}{4}</tex> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | Значит, при <tex> a \ge \cfrac{cn}{2} \cfrac{4}{nk} = \cfrac{2c}{k} </tex> требуемое неравенство будет выполняться. | |
− | + | }} | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | == Источники | + | ==Источники== |
− | * | + | * Peter Brucker. «Scheduling Algorithms» {{---}} «Springer», 2006 г. {{---}} 379 стр. {{---}} ISBN 978-3-540-69515-8 |
− | |||
− | [[Категория: | + | [[Категория: Дискретная математика и алгоритмы]] |
− | [[Категория: | + | [[Категория: Теория расписаний]] |
Версия 16:59, 28 мая 2016
Задача: |
<wikitex>Дано $n$ работ, которые надо выполнить на одной машине, причем $i$-ая работа выполняется $p_i$ времени. Для каждой работы задана монотонно неубывающая функция $f_i$. Работы можно прерывать, у каждой работы есть время появления $r_{i}$. Также между работами заданы отношения в виде ориентированного графа без циклов: если существует ребро $a \to b$, то работа $a$ должна завершиться до начала выполнения работы $b$. Необходимо построить такое расписание, чтобы величина $f_{max} = \max\limits_{j=1..n}{f_j(C_j)}$, где $C_j$ — время окончания выполнения $j$-ой работы, была минимальна.</wikitex> |
Задача является обобщением , но здесь у работ также есть времена появления, раньше которых их делать запрещено, и их можно прерывать.
Содержание
Алгоритм
Работу будем обозначать просто ее номером (
), при этом, номера работ могут меняться в зависимости от того, по какому параметру они отсортированы. Время появления работы — , время, требуемое для ее выполнения — . Множество ребер графа обозначается как .Modify
Для начала, модифицируем времена появления работ. Если работа
зависит от , то, очевидно, она не может быть начата раньше, чем закончится выполнение , поэтому нужно заменить на . Алгоритм, делающий это, представлен ниже (работы рассматриваются в порядке топологической сортировки):Modify()
for i = 1 to n
for j: ij
r[j] = max(r[j], r[i] + p[i])
После выполнения этого алгоритма для любых двух работ
, таких, что зависит от , выполняется , поэтому, при рассмотрении работ в порядке неубывания времен их появления, они также будут топологически отсортированы.Blocks
Здесь и далее считается, что работы отсортированы в порядке неубывания модифицированных
.Станок, выполняющий работы, выполняет работу в некоторые интервалы времени и простаивает в остальное время. Следующий алгоритм разбивает множество работ на блоки, внутри которых станок работает без простоя.
Blocks() j = 0 t = 0 for i = 1 to n if t = r[i] j = j + 1 B[j].add(i) t = t + p[i] return B
Если алгоритм Blocks вызывается от пустого множества, то считаем, что он возвращает также пустое множество.
Определим время начала блока
как , а время конца — как .Лемма: |
Существует оптимальное расписание, такое, что все во все временные интервалы , соответствующие блокам , построенным алгоритмом Blocks, станок работает без простоя. |
Доказательство: |
Возьмем произвольное оптимальное расписание Возьмем некоторую работу , в нем деление на блоки может также быть произвольным. Найдем первый такой временной интервал , что в есть период простоя внутри (если таких периодов несколько, будем рассматривать первый из них). Обозначим его за . , такую, что она начинается позже, чем в момент времени , не имеет в графе зависимостей предков, завершаемых позже, чем в момент и . Такая работа обязательно существует, иначе для множества работ, выполняемых позже, чем в момент , было бы , и внутри блока был бы простой , что невозможно по построению алгоритма Blocks. Очевидно, мы можем начать выполнять ее в момент времени и полностью, либо частично заполнить простой ; так как — неубывающая функция, то ответ останется оптимальным. Повторяя этот процесс, мы за конечное число шагов придем к оптимальному расписанию с требуемым свойством. |
Decompose
Допустим, у нас есть блок работ, который можно выполнить без прерываний. Общая идея алгоритма Decompose следующая: найдем работу
, которую выгоднее всего выполнить последней. Разобъем оставшееся множество работ на блоки, решим задачу для этих блоков рекурсивно и вставим в промежутки между ними, до них и после них, начиная с . Псевдокод этого алгоритма представлен ниже.Decompose(B) findans = f[l](e) G = Blocks( ) вставить l в промежутки между блоками G, начиная с for B[j] G ans = max(ans, Decompose(B[j])) return ans
Теорема: |
Расписание для блока, построенное алгоритмом Decompose, является корректным и оптимальным. |
Доказательство: |
Докажем сначала корректность. Убедимся, что порядок выполнения работ, заданный графом зависимостей, не нарушается. Заметим, что в разбиении на блоки существует не более одного блока , расположенного до момента времени — иначе после вставки в промежутки между блоками, выполнялся бы с прерываниями. Далее, заметим, что все интервалы времени, на которые назначается работа из блока , находятся внутри интервала ; это относится и к блоку . Из этих двух наблюдений, а также того, что все работы со временами появления меньше, чем , будут помещены в блок , следует, что порядок выполнения будет правильным.Также для корректности требуется, чтобы работы выполнялись не раньше, чем они появляются. Так как время выполнения работы определяется только в строке 4 алгоритма, которая соответствует этому требованию, то условие выполняется. Найдем теперь нижнюю оценку на . Пусть — ответ для множества работ .Очевидно, для любой работы выполняется , значит, .Также, так как в оптимальном решении какая-то работа без потомков обязательно заканчивается в блоке Отсюда следует , то . . По псевдокоду алгоритма видно, что его ответ достигает этой нижней оценки. |
Общий алгоритм
Выполним Modify, после чего разобъем все множество работ на блоки и для каждого блока запустим Decompose():
MakeSchedule() Modify() B = Blocks() ans = for B[j] B ans = max(ans, Decompose(B[j])) return ans
Из доказанной ранее леммы следует, что
, поэтому расписание для всего множества работ, поделенного на блоки, также будет оптимальным и корректным.Время работы
Теорема: |
Время работы алгоритма MakeSchedule — операций. |
Доказательство: |
Обозначим за время, необходимое для выполнения алгоритма MakeSchedule на n работах. Очевидно, для корректно определенной функции P в силу структуры алгоритма должно выполняться неравенство:
Здесь - размер блока с номером , построенного алгоритмом Blocks(). Заметим, что .Если , то имеем:
Так как , то можно переписать неравенство в следующем виде:
Чтобы получить максимальную нижнюю оценку на , оценим снизу :Значит, при требуемое неравенство будет выполняться. |
Источники
- Peter Brucker. «Scheduling Algorithms» — «Springer», 2006 г. — 379 стр. — ISBN 978-3-540-69515-8