Подтвердить что ты не робот

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

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

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

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

Каждый элемент представляет последовательность по началу и по длине, например (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)

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

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

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

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

Edit

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

для ввода типа [(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)].

4b9b3361

Ответ 1

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

псевдокод, пропуская детали, такие как элементы, не найденные в хэшмапе (предположим, что 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))

Ответ 2

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

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

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

Ответ 3

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

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

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

Поместите набор {start position} union {(начальная позиция + конечная позиция)} в виде вершин. Для вашего примера это будет {0, 1, 5, 10, 11, 12}

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

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

Ответ 4

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

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

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

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

Здесь приведен код 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.To существует, она была найдена к моменту, когда мы дошли до этого момента, потому что она должна была начинаться с меньшего StartTuple.From, и массив сортируется по "От"!

Ответ 5

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

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 для многопоточной доработки.

Ответ 6

Пересмотренный алгоритм:

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

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

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]);
}

Ответ 7

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

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

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

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

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

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

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

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

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

Ответ 8

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

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

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

Ответ 9

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

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

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

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


UPDATE

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

Итак, если N - количество строк, сортировка - O (2N * log2N). Сравнение O (2N). Нахождение линий растяжения также O (2N). Итак, все в целом O (2N (log2N + 2)).