Участник:Qtr — различия между версиями
Qtr (обсуждение | вклад)  (→Decompose)  | 
				Qtr (обсуждение | вклад)   (→Алгоритм)  | 
				||
| Строка 8: | Строка 8: | ||
== Алгоритм ==  | == Алгоритм ==  | ||
| − | Работу будем обозначать просто ее номером   | + | Работу будем обозначать просто ее номером <tex>(i)</tex>, при этом, номера работ могут меняться в зависимости от того, по какому параметру они отсортированы. Время появления работы — <tex> r[i]</tex>, время, требуемое для ее выполнения — <tex> p[i] </tex>. Множество ребер графа обозначается как <tex> E </tex>.  | 
| − | ===   | + | === Препроцессинг ===  | 
| − | Для начала, модифицируем времена появления работ. Если работа <tex> j </tex> зависит от <tex> i </tex>, то, очевидно, она не может быть начата раньше, чем закончится выполнение <tex> i </tex>, поэтому нужно заменить <tex> r_j </tex> на <tex> \max(r_j, r_i + p_i) </tex>. Алгоритм, делающий это, представлен ниже (работы рассматриваются в порядке топологической сортировки):  | + | Для начала, модифицируем времена появления работ. Если работа <tex> j </tex> зависит от <tex> i </tex>, то, очевидно, она не может быть начата раньше, чем закончится выполнение <tex> i </tex>, поэтому нужно заменить <tex> r_j </tex> на <tex> \max(r_j, r_i + p_i) </tex>. Алгоритм, делающий это, представлен ниже (работы рассматриваются в порядке [[Использование_обхода_в_глубину_для_топологической_сортировки|топологической сортировки]]):  | 
| − | + |   '''void''' modify({1...n})  | |
| − |       '''for'''   | + |      rm = r  | 
| − |         '''for'''   | + |       '''for''' u = 1 '''to''' n  | 
| − | + |         '''for''' (u, v) <tex> \in </tex> E   | |
| + |            rm[v] = max(rm[v], rm[u] + p[u])    | ||
| − | После выполнения этого алгоритма для любых двух работ <tex> i, j </tex>, таких, что <tex> j </tex> зависит от <tex> i </tex>, выполняется <tex>   | + | После выполнения этого алгоритма для любых двух работ <tex> i, j </tex>, таких, что <tex> j </tex> зависит от <tex> i </tex>, выполняется <tex> rm[j] > rm[i] </tex>, поэтому, при рассмотрении работ в порядке неубывания времен их появления, они также будут топологически отсортированы.  | 
| − | ===   | + | === Разбиение на блоки ===  | 
| − | Здесь и далее считается, что работы отсортированы в порядке неубывания модифицированных <tex>   | + | Здесь и далее считается, что работы отсортированы в порядке неубывания модифицированных <tex> rm_i </tex>.  | 
Станок, выполняющий работы, выполняет работу в некоторые интервалы времени и простаивает в остальное время. Следующий алгоритм разбивает множество работ на блоки, внутри которых станок работает без простоя.  | Станок, выполняющий работы, выполняет работу в некоторые интервалы времени и простаивает в остальное время. Следующий алгоритм разбивает множество работ на блоки, внутри которых станок работает без простоя.  | ||
| − | + | '''Структура блока'''  | |
| − |       j = 0  | + |   '''struct''' Block  | 
| − |       t = 0  | + |      '''int''' start  <font color = "darkgreen">// Время начала выполнения блока</font>  | 
| + |      '''int''' time   <font color = "darkgreen">// Время, затрачиваемое на соответствующий блок</font>  | ||
| + |      '''int''' end   <font color = "darkgreen"> // Время конца выполнения блока</font>  | ||
| + |      '''int[]''' jobs <font color = "darkgreen">// Номера работ</font>  | ||
| + | |||
| + | '''Алгоритм разбиения'''  | ||
| + | |||
| + |   '''Block''' blocks({1...n})  | ||
| + |       '''int''' j = 0  | ||
| + |       '''int''' t = 0  | ||
| + |      '''Block[]''' b  | ||
      '''for''' i = 1 '''to''' n  |       '''for''' i = 1 '''to''' n  | ||
| − |         '''if'''   | + |         '''if'''  t < r[i]    | 
| − |           t =   | + |           t = rm[i]    | 
          j = j + 1    |           j = j + 1    | ||
| − | + |           b[j].start = r[i]  | |
| − | + |           b[j].time = 0  | |
| − | + |         b[j].add(i)  | |
| − | + |         b[j].time = b[j].time + p[i]  | |
        t = t + p[i]    |         t = t + p[i]    | ||
| − |       '''return'''    | + |       '''return'''  b  | 
| − | Если алгоритм Blocks вызывается от пустого множества, то считаем, что он возвращает также пустое множество.  | + | Если алгоритм <tex>\mathrm{Blocks}</tex> вызывается от пустого множества, то считаем, что он возвращает также пустое множество.  | 
| − | Определим время начала блока <tex> B_j </tex> как <tex>s_j = \min\limits_{i \in B_j}   | + | Определим время начала блока <tex> B_j </tex> как <tex>s_j = \min\limits_{i \in B_j} rm_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, станок работает без простоя.  | + | Существует оптимальное расписание, такое, что все во все временные интервалы <tex> [s_j; e_j] </tex>, соответствующие блокам <tex> B_j </tex>, построенным алгоритмом <tex>\mathrm{Blocks}</tex>, станок работает без простоя.  | 
|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> 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>   | + | Возьмем некоторую работу <tex> i </tex>, такую, что она начинается позже, чем в момент времени <tex> s </tex>, не имеет в графе зависимостей предков, завершаемых позже, чем в момент <tex> s </tex> и <tex> rm_i \le s </tex>. Такая работа обязательно существует, иначе для множества работ, выполняемых позже, чем в момент <tex> s </tex>, было бы <tex> r = \min\limits_{k \in T} rm_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 следующая: найдем работу <tex> i </tex>, которую выгоднее всего выполнить последней. Разобъем оставшееся множество работ на блоки, решим задачу для этих блоков рекурсивно и вставим <tex> i </tex> в промежутки между ними, до них и после них, начиная с <tex>   | + | Допустим, у нас есть блок работ, который можно выполнить без прерываний. Общая идея алгоритма <tex>\mathrm{Decompose}</tex> следующая: найдем работу <tex> i </tex>, которую выгоднее всего выполнить последней. Разобъем оставшееся множество работ на блоки, решим задачу для этих блоков рекурсивно и вставим <tex> i </tex> в промежутки между ними, до них и после них, начиная с <tex> rm_i </tex>. Псевдокод этого алгоритма представлен ниже.  | 
| − | + |   '''int''' decompose('''Block''' b)  | |
| − |       e =   | + |       '''int''' e = b.end <font color = "darkgreen"> // e — время завершения работ блока B.</font>  | 
| − |       find   | + |       find  l: f[l](e) = <tex> \min  \{f[j](e) \mid j \in B, \overline\exists\ k: jk \in E \} </tex>    | 
| − |       ans = f[l](e)    | + |       '''int''' ans = f[l](e)    | 
| − | + |       Block g = blocks(<tex> b \setminus l </tex>)  | |
| − |       '''for''' i = 2 '''to'''   | + |       '''for''' i = 2 '''to''' b.size  | 
| − |         '''for''' j =    | + |         '''for''' j =  g[i - 1].end '''to''' g[i].begin - 1        | 
| − |           schedule[j] = l <font color = "darkgreen"> //Вставляем работу в расписании между блоками</font>  | + |           schedule[j] = l <font color = "darkgreen"> // Вставляем работу в расписании между блоками</font>  | 
| − |       schedule[  | + |       schedule[g[g.size].end to b.end - 1] = l  | 
| − |       '''for'''   | + |       '''for''' b[j] <tex>\in</tex> g  | 
| − |         ans = max(ans,   | + |         ans = max(ans, decompose(b[j]))  | 
      '''return''' ans  |       '''return''' ans  | ||
| Строка 74: | Строка 85: | ||
Докажем сначала корректность.  | Докажем сначала корректность.  | ||
| − | Убедимся, что порядок выполнения работ, заданный графом зависимостей, не нарушается. Заметим, что в разбиении <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>   | + | Убедимся, что порядок выполнения работ, заданный графом зависимостей, не нарушается. Заметим, что в разбиении <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> rm_l </tex>, будут помещены в блок <tex> B_0 </tex>, следует, что порядок выполнения будет правильным.  | 
Также для корректности требуется, чтобы работы выполнялись не раньше, чем они появляются. Так как время выполнения работы определяется только в строках 5-8 алгоритма, которая соответствует этому требованию, то условие выполняется.  | Также для корректности требуется, чтобы работы выполнялись не раньше, чем они появляются. Так как время выполнения работы определяется только в строках 5-8 алгоритма, которая соответствует этому требованию, то условие выполняется.  | ||
| Строка 89: | Строка 100: | ||
=== Общий алгоритм ===  | === Общий алгоритм ===  | ||
| − | Выполним Modify, после чего разобъем все множество работ на блоки и для каждого блока запустим Decompose  | + | Выполним <tex>\mathrm{Modify}</tex>, после чего разобъем все множество работ на блоки и для каждого блока запустим <tex>\mathrm{Decompose}></tex>:  | 
| − | + |   ('''int''', '''int[]''') makeSchedule('''Job[]''' jobs)     | |
| − | + |       '''int'''[] schedule <font color="darkgreen">// Расписание работ</font>  | |
| − | + |      topSort(jobs)  | |
| − |       ans = <tex> -\infty </tex>  | + |      modify(jobs)  | 
| − |       '''for'''   | + |      b = blocks(jobs)  | 
| − |         ans = max(ans,   | + |       '''int''' ans = <tex> -\infty </tex>  | 
| − |       '''return''' ans  | + |       '''for''' b[j] <tex>\in</tex> b  | 
| + |         ans = max(ans, decompose(b[j]))  | ||
| + |       '''return''' (ans, schedule)  | ||
Из доказанной ранее леммы следует, что <tex> f_{max}(\{ 1 \ldots n \}) = \max\limits_{j} f_{max}(B_j) </tex>, поэтому расписание для всего множества работ, поделенного на блоки, также будет оптимальным и корректным.  | Из доказанной ранее леммы следует, что <tex> f_{max}(\{ 1 \ldots n \}) = \max\limits_{j} f_{max}(B_j) </tex>, поэтому расписание для всего множества работ, поделенного на блоки, также будет оптимальным и корректным.  | ||
Версия 17:07, 29 мая 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> | 
Задача  является обобщением , но здесь у работ также есть времена появления, раньше которых их делать запрещено, и их можно прерывать.
Содержание
Алгоритм
Работу будем обозначать просто ее номером , при этом, номера работ могут меняться в зависимости от того, по какому параметру они отсортированы. Время появления работы — , время, требуемое для ее выполнения — . Множество ребер графа обозначается как .
Препроцессинг
Для начала, модифицируем времена появления работ. Если работа зависит от , то, очевидно, она не может быть начата раньше, чем закончится выполнение , поэтому нужно заменить на . Алгоритм, делающий это, представлен ниже (работы рассматриваются в порядке топологической сортировки):
void modify({1...n})
    rm = r
    for u = 1 to n
      for (u, v)  E 
         rm[v] = max(rm[v], rm[u] + p[u]) 
После выполнения этого алгоритма для любых двух работ , таких, что зависит от , выполняется , поэтому, при рассмотрении работ в порядке неубывания времен их появления, они также будут топологически отсортированы.
Разбиение на блоки
Здесь и далее считается, что работы отсортированы в порядке неубывания модифицированных .
Станок, выполняющий работы, выполняет работу в некоторые интервалы времени и простаивает в остальное время. Следующий алгоритм разбивает множество работ на блоки, внутри которых станок работает без простоя.
Структура блока
 struct Block
    int start  // Время начала выполнения блока
    int time   // Время, затрачиваемое на соответствующий блок
    int end    // Время конца выполнения блока
    int[] jobs // Номера работ
Алгоритм разбиения
 Block blocks({1...n})
    int j = 0
    int t = 0
    Block[] b
    for i = 1 to n
      if  t < r[i] 
        t = rm[i] 
        j = j + 1 
        b[j].start = r[i]
        b[j].time = 0
      b[j].add(i)
      b[j].time = b[j].time + p[i]
      t = t + p[i] 
    return  b
Если алгоритм вызывается от пустого множества, то считаем, что он возвращает также пустое множество.
Определим время начала блока как , а время конца — как .
| Лемма: | 
Существует оптимальное расписание, такое, что все во все временные интервалы , соответствующие блокам , построенным алгоритмом , станок работает без простоя.  | 
| Доказательство: | 
| 
 Возьмем произвольное оптимальное расписание , в нем деление на блоки может также быть произвольным. Найдем первый такой временной интервал , что в есть период простоя внутри (если таких периодов несколько, будем рассматривать первый из них). Обозначим его за . Возьмем некоторую работу , такую, что она начинается позже, чем в момент времени , не имеет в графе зависимостей предков, завершаемых позже, чем в момент и . Такая работа обязательно существует, иначе для множества работ, выполняемых позже, чем в момент , было бы , и внутри блока был бы простой , что невозможно по построению алгоритма Blocks. Очевидно, мы можем начать выполнять ее в момент времени и полностью, либо частично заполнить простой ; так как — неубывающая функция, то ответ останется оптимальным. Повторяя этот процесс, мы за конечное число шагов придем к оптимальному расписанию с требуемым свойством. | 
Декомпозиция
Допустим, у нас есть блок работ, который можно выполнить без прерываний. Общая идея алгоритма следующая: найдем работу , которую выгоднее всего выполнить последней. Разобъем оставшееся множество работ на блоки, решим задачу для этих блоков рекурсивно и вставим в промежутки между ними, до них и после них, начиная с . Псевдокод этого алгоритма представлен ниже.
 int decompose(Block b)
    int e = b.end  // e — время завершения работ блока B.
    find  l: f[l](e) =  
    int ans = f[l](e) 
    Block g = blocks()
    for i = 2 to b.size
      for j =  g[i - 1].end to g[i].begin - 1     
        schedule[j] = l  // Вставляем работу в расписании между блоками
    schedule[g[g.size].end to b.end - 1] = l
    for b[j]  g
      ans = max(ans, decompose(b[j]))
    return ans
| Теорема: | 
Расписание для блока, построенное алгоритмом Decompose, является корректным и оптимальным.  | 
| Доказательство: | 
| 
 Докажем сначала корректность. Убедимся, что порядок выполнения работ, заданный графом зависимостей, не нарушается. Заметим, что в разбиении на блоки существует не более одного блока , расположенного до момента времени — иначе после вставки в промежутки между блоками, выполнялся бы с прерываниями. Далее, заметим, что все интервалы времени, на которые назначается работа из блока , находятся внутри интервала ; это относится и к блоку . Из этих двух наблюдений, а также того, что все работы со временами появления меньше, чем , будут помещены в блок , следует, что порядок выполнения будет правильным. Также для корректности требуется, чтобы работы выполнялись не раньше, чем они появляются. Так как время выполнения работы определяется только в строках 5-8 алгоритма, которая соответствует этому требованию, то условие выполняется. Найдем теперь нижнюю оценку на . Пусть — ответ для множества работ . Очевидно, для любой работы выполняется , значит, . Также, так как в оптимальном решении какая-то работа без потомков обязательно заканчивается в блоке , то . Отсюда следует . По псевдокоду алгоритма видно, что его ответ достигает этой нижней оценки. | 
Общий алгоритм
Выполним , после чего разобъем все множество работ на блоки и для каждого блока запустим :
 (int, int[]) makeSchedule(Job[] jobs)   
    int[] schedule // Расписание работ
    topSort(jobs)
    modify(jobs)
    b = blocks(jobs)
    int ans = 
    for b[j]  b
      ans = max(ans, decompose(b[j]))
    return (ans, schedule)
Из доказанной ранее леммы следует, что , поэтому расписание для всего множества работ, поделенного на блоки, также будет оптимальным и корректным.
Время работы
| Теорема: | 
Время работы алгоритма MakeSchedule —  операций.  | 
| Доказательство: | 
| 
 Обозначим за время, необходимое для выполнения алгоритма MakeSchedule на n работах. Очевидно, для корректно определенной функции P в силу структуры алгоритма должно выполняться неравенство: 
 Здесь - размер блока с номером , построенного алгоритмом Blocks(). Заметим, что . Если , то имеем: 
 Так как , то можно переписать неравенство в следующем виде: 
 Чтобы получить максимальную нижнюю оценку на , оценим снизу : Значит, при требуемое неравенство будет выполняться.  | 
Источники
- Peter Brucker. «Scheduling Algorithms» — «Springer», 2006 г. — 379 стр. — ISBN 978-3-540-69515-8