алгоритм поиска длинных неперекрывающихся последовательностей

Я пытаюсь найти лучший способ решить следующую проблему. В лучшем случае я имею в виду менее сложный.

в качестве входных данных список кортежей (начало,длина) такой:

[(0,5),(0,1),(1,9),(5,5),(5,7),(10,1)]

каждый элемент represets последовательность его старт и длина, например (5,7) эквивалентно последовательности (5,6,7,8,9,10,11) - список из 7 элементов, начиная с 5. Можно предположить, что кортежи сортируются по start элемент.

выход должен возвращать неперекрывающуюся комбинацию кортежей, представляющих самые длинные непрерывные последовательности. Это означает, что решение представляет собой подмножество диапазонов без перекрытий и пробелов и является максимально длинным - хотя может быть несколько.

например, для данного ввода решение:

[(0,5),(5,7)] эквивалентно (0,1,2,3,4,5,6,7,8,9,10,11)

является ли это лучшим подходом для решения этой проблемы ?

Я заинтересован в любом другом подходы, которые могут предложить люди.

также, если кто-нибудь знает формальную ссылку на эту проблему или другую, похожую, я хотел бы получить ссылки.

кстати - это не домашнее задание.

редактировать

просто, чтобы избежать некоторых ошибок, это еще один пример ожидаемого поведения

для ввода типа [(0,1),(1,7),(3,20),(8,5)] правильный ответ [(3,20)] эквивалентно (3,4,5,..,22) с длиной 20. Некоторые ответы получил дал бы [(0,1),(1,7),(8,5)] эквивалентно (0,1,2,...,11, 12) как правильный ответ. Но этот последний ответ неверен, потому что короче [(3,20)].

9 ответов


повторите список кортежей, используя заданный порядок (по элементу start), при этом используя hashmap для отслеживания длины самой длинной непрерывной последовательности конец по определенному индексу.

псевдо-код, пропуская детали, такие как элементы, не найденные в hashmap (предположим, что 0 возвращено, если не найдено):

int bestEnd = 0;
hashmap<int,int> seq // seq[key] = length of the longest sequence ending on key-1, or 0 if not found
foreach (tuple in orderedTuples) {
    int seqLength = seq[tuple.start] + tuple.length
    int tupleEnd = tuple.start+tuple.length;
    seq[tupleEnd] = max(seq[tupleEnd], seqLength)
    if (seqLength > seq[bestEnd]) bestEnd = tupleEnd
}
return new tuple(bestEnd-seq[bestEnd], seq[bestEnd])

это алгоритм O(N).

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

UPDATE: мои знания python довольно ограничены, но на основе кода python, который вы вставили, я создал этот код, который возвращает фактическую последовательность, а не только длину:

def get_longest(arr):
    bestEnd = 0;
    seqLengths = dict() #seqLengths[key] = length of the longest sequence ending on key-1, or 0 if not found
    seqTuples = dict() #seqTuples[key] = the last tuple used in this longest sequence
    for t in arr:
        seqLength = seqLengths.get(t[0],0) + t[1]
        tupleEnd = t[0] + t[1]
        if (seqLength > seqLengths.get(tupleEnd,0)):
            seqLengths[tupleEnd] = seqLength
            seqTuples[tupleEnd] = t
            if seqLength > seqLengths.get(bestEnd,0):
                bestEnd = tupleEnd
    longestSeq = []
    while (bestEnd in seqTuples):
        longestSeq.append(seqTuples[bestEnd])
        bestEnd -= seqTuples[bestEnd][1]
    longestSeq.reverse()
    return longestSeq


if __name__ == "__main__":
    a = [(0,3),(1,4),(1,1),(1,8),(5,2),(5,5),(5,6),(10,2)]
    print(get_longest(a))

Это частный случай самая длинная задача пути для взвешенных направленных ациклических графов.

узлы на графике являются начальными точками и точками после последнего элемента в последовательности, где может начаться следующая последовательность.

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


просто думая об алгоритме в основных терминах, будет ли это работать?

(извиняюсь за ужасный синтаксис, но я пытаюсь оставаться независимым от языка здесь)

сначала самая простая форма: найти самую длинную смежную пару.

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

затем возьмите каждую из этих пар и сравните их со всеми одиночными членами с более высоким startpos и повторите, формируя новый набор непрерывных троек (если таковые существуют).

продолжайте этот шаблон, пока у вас нет новых наборов.

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

Я уверен, что это не так эффективно, как другие методы, но я считаю, что это жизнеспособный подход к грубому принуждению этого решения.

Я был бы признателен за обратную связь по этому вопросу и любые ошибки, которые я, возможно, пропустил.


пересмотрен алгоритм:

create a hashtable of start->list of tuples that start there
put all tuples in a queue of tupleSets
set the longestTupleSet to the first tuple
while the queue is not empty
    take tupleSet from the queue
    if any tuples start where the tupleSet ends
        foreach tuple that starts where the tupleSet ends
            enqueue new tupleSet of tupleSet + tuple
        continue

    if tupleSet is longer than longestTupleSet
        replace longestTupleSet with tupleSet

return longestTupleSet

реализация c#

public static IList<Pair<int, int>> FindLongestNonOverlappingRangeSet(IList<Pair<int, int>> input)
{
    var rangeStarts = input.ToLookup(x => x.First, x => x);
    var adjacentTuples = new Queue<List<Pair<int, int>>>(
        input.Select(x => new List<Pair<int, int>>
            {
                x
            }));

    var longest = new List<Pair<int, int>>
        {
            input[0]
        };
    int longestLength = input[0].Second - input[0].First;

    while (adjacentTuples.Count > 0)
    {
        var tupleSet = adjacentTuples.Dequeue();
        var last = tupleSet.Last();
        int end = last.First + last.Second;
        var sameStart = rangeStarts[end];
        if (sameStart.Any())
        {
            foreach (var nextTuple in sameStart)
            {
                adjacentTuples.Enqueue(tupleSet.Concat(new[] { nextTuple }).ToList());
            }
            continue;
        }
        int length = end - tupleSet.First().First;
        if (length > longestLength)
        {
            longestLength = length;
            longest = tupleSet;
        }
    }

    return longest;
}

тесты:

[Test]
public void Given_the_first_problem_sample()
{
    var input = new[]
        {
            new Pair<int, int>(0, 5),
            new Pair<int, int>(0, 1),
            new Pair<int, int>(1, 9),
            new Pair<int, int>(5, 5),
            new Pair<int, int>(5, 7),
            new Pair<int, int>(10, 1)
        };
    var result = FindLongestNonOverlappingRangeSet(input);
    result.Count.ShouldBeEqualTo(2);
    result.First().ShouldBeSameInstanceAs(input[0]);
    result.Last().ShouldBeSameInstanceAs(input[4]);
}

[Test]
public void Given_the_second_problem_sample()
{
    var input = new[]
        {
            new Pair<int, int>(0, 1),
            new Pair<int, int>(1, 7),
            new Pair<int, int>(3, 20),
            new Pair<int, int>(8, 5)
        };
    var result = FindLongestNonOverlappingRangeSet(input);
    result.Count.ShouldBeEqualTo(1);
    result.First().ShouldBeSameInstanceAs(input[2]);
}

отредактировано для замены псевдокода фактическим кодом Python

отредактировано снова, чтобы изменить код; исходный алгоритм был на решении, но я не понял, какое второе значение в парах было! К счастью, основной алгоритм тот же, и я смог его изменить.

вот идея, которая решает проблему в O (N log N) и не использует хэш-карту (поэтому нет скрытых времен). Для памяти мы будем использовать N * 2 "вещи."

мы добавим еще два значения к каждому кортежу: (BackCount, BackLink). В удачном сочетании обратную ссылку слева от самой правой Кортеж в кортеж. BackCount будет значением накопленного количества для данной обратной ссылки.

вот код python:

def FindTuplesStartingWith(tuples, frm):
    # The Log(N) algorithm is left as an excersise for the user
    ret=[]
    for i in range(len(tuples)):
        if (tuples[i][0]==frm): ret.append(i)
    return ret

def FindLongestSequence(tuples):

    # Prepare (BackCount, BackLink) array
    bb=[] # (BackCount, BackLink)
    for OneTuple in tuples: bb.append((-1,-1))

    # Prepare
    LongestSequenceLen=-1
    LongestSequenceTail=-1

    # Algorithm
    for i in range(len(tuples)):
        if (bb[i][0] == -1): bb[i] = (0, bb[i][1])
        # Is this single pair the longest possible pair all by itself?
        if (tuples[i][1] + bb[i][0]) > LongestSequenceLen:
            LongestSequenceLen = tuples[i][1] + bb[i][0]
            LongestSequenceTail = i
        # Find next segment
        for j in FindTuplesStartingWith(tuples, tuples[i][0] + tuples[i][1]):
            if ((bb[j][0] == -1) or (bb[j][0] < (bb[i][0] + tuples[i][1]))):
                # can be linked
                bb[j] = (bb[i][0] + tuples[i][1], i)
                if ((bb[j][0] + tuples[j][1]) > LongestSequenceLen):
                    LongestSequenceLen = bb[j][0] + tuples[j][1]
                    LongestSequenceTail=j

    # Done! I'll now build up the solution
    ret=[]
    while (LongestSequenceTail > -1):
        ret.insert(0, tuples[LongestSequenceTail])
        LongestSequenceTail = bb[LongestSequenceTail][1]
    return ret

# Call the algoritm
print FindLongestSequence([(0,5), (0,1), (1,9), (5,5), (5,7), (10,1)])
>>>>>> [(0, 5), (5, 7)]
print FindLongestSequence([(0,1), (1,7), (3,20), (8,5)])    
>>>>>> [(3, 20)]

ключ для всего алгоритма - это то, где комментарий" это ключ " находится в коде. Мы знаем, что наш текущий StartTuple может быть связан с EndTuple. Если более длинная последовательность, которая заканчивается на EndTuple.Чтобы существовать, он был найден к тому времени, когда мы добрались до этого момента, потому что он должен был начинаться с меньшего стартового числа.From, а массив сортируется на "From"!


Я удалил предыдущее решение, потому что оно не было протестировано.

задача состоит в нахождении самого длинного пути в "взвешенном направленном ациклическом графе", его можно решить в линейное время:

http://en.wikipedia.org/wiki/Longest_path_problem#Weighted_directed_acyclic_graphs

набор {стартовые позиции} объединение {(исходное положение + положение)} вершин. Для вашего примера это будет {0, 1, 5, 10, 11, 12}

для вершин v0, v1 если есть конечное значение w, которое составляет v0 + w = v1, добавьте направленный край, соединяющий v0 с v1, и поместите w в качестве его веса.

теперь следуйте псевдокоду на странице Википедии. поскольку число вершин является максимальным значением 2xn (n-число кортежей), задача все еще может быть решена в линейное время.


это простая операция уменьшения. Учитывая пару последовательных кортежей, они могут или не могут быть объединены. Поэтому определите попарную комбинационную функцию:

def combo(first,second):
    if first[0]+first[1] == second[0]:
        return [(first[0],first[1]+second[1])]
    else:
        return [first,second]

это просто возвращает список либо одного элемента, объединяющего два аргумента, либо исходных двух элементов.

затем определите функцию для итерации по первому списку и объединения пар:

def collapse(tupleList):
    first = tupleList.pop(0)
    newList = []
    for item in tupleList:
        collapsed = combo(first,item)
        if len(collapsed)==2:
            newList.append(collapsed[0])
        first = collapsed.pop()
    newList.append(first)
    return newList

это сохраняет первый элемент для сравнения с текущим элементом в списке (начиная на втором элементе), и когда он не может их объединить, он опускает первый в новый список и заменяет first со вторым из двух.

позвони collapse со списком кортежей:

>>> collapse( [(5, 7), (12, 3), (0, 5), (0, 7), (7, 2), (9, 3)] )
[(5, 10), (0, 5), (0, 12)]

[Edit] наконец, повторите результат, чтобы получить самую длинную последовательность.

def longest(seqs):
    collapsed = collapse(seqs)
    return max(collapsed, key=lambda x: x[1])

[/Edit]

Сложность O (N). Для бонусных отметок сделайте это в обратном порядке, чтобы начальное pop(0) становится pop() и вам не нужно переиндексировать массив, или вместо этого переместите итератор. Для верхних отметок сделать его работать как попарно reduce операция для многопоточного добра.


Это звучит как идеальная проблема "динамического программирования"...

самой простой программой было бы сделать это грубой силой (например, рекурсивной), но это имеет экспоненциальную сложность.

с помощью динамического программирования вы можете настроить массив a длины n, где n-максимум всех значений (start+length) вашей задачи, где A[i] обозначает самую длинную неперекрывающуюся последовательность до a[i]. Затем вы можете шагнуть все Кортежи, обновляя a. Сложность этого алгоритма будет O (n*k), где k-количество входных значений.


  • создайте упорядоченный массив всех начальных и конечных точек и инициализируйте их все до одного
  • для каждого элемента вашего кортежа сравните конечную точку (начало и конец) с упорядоченными элементами в вашем массиве, если какая-либо точка находится между ними (например, точка в массиве 5, и у вас есть начало 2 с длиной 4) измените значение на ноль.
  • после завершения цикла начните перемещение по упорядоченному массиву и создайте полосу, когда вы видите 1 и пока вы видите 1, добавьте к существующему прокладка, с любым нул, закрывает прокладку и etc.
  • в конце проверьте длину полос

Я думаю, что сложности вокруг O (4-5*N)

(СМ. ОБНОВЛЕНИЕ)

С N - количество элементов в кортеж.


обновление

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

поэтому, если N-количество отрезков строки, сортировка-O (2N * log2N). Сравнение-O (2N). Нахождение отрезков линии также O (2N). Так что в целом O (2N (log2N + 2)).