Асимптотическая временная сложность List.fold и foldBack

62
6

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


List.foldBack (@) [[1];[2];[3];[4]] [] => [1] @ List.foldBack (@) [[2];[3];[4]] []
=> [1] @ ([2] @ List.foldBack (@) [[3];[4]] [])
=> [1] @ ([2] @ ([3] @ List.foldBack (@) [4] []))
=> [1] @ ([2]@([3] @ ([4] @ List.foldBack[])))
=> [1]@([2]@([3]@([4]@([])))
=> [1; 2; 3; 4]

List.fold (@) [] [[1];[2];[3];[4]]
=> List.fold (@) (([],[1])@ [2]) [[3];[4]]
=> List.fold (@) ((([]@[1])@[2])@[3]) [[4]]
=> List.fold (@) (((([]@[1])@[2])@[3])@[4]) []
=> (((([]@[1])@[2])@[3])@[4])


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

спросил(а) 2012-03-06T00:06:00+04:00 8 лет, 1 месяц назад
1
Решение
74

Функции List.fold и List.foldBack - это оба обращения T (n) к их аргументу функции, где n - длина списка. Однако вы передаете им функцию (@), которая не является T (1), но T (m), где m - длина первого списка аргументов.


В частности, это:

(((([]@[1])@[2])@[3])@[4])

есть T (n²), потому что [1]@[2] - одна операция, а затем [1;2]@[3] - еще две операции, а затем [1;2;3]@[4] - еще три операции.

ответил(а) 2012-03-06T16:23:00+04:00 8 лет, 1 месяц назад
89

Если каждая внутренняя операция равна Θ (1), List.fold и List.foldBack равно O (n), где n - длина списка.


Однако для оценки асимптотической временной сложности вам нужно полагаться на операции Θ (1). В вашем примере все немного тонкое.


Предположим, вам нужно объединить списки n, где в каждом списке есть элементы m. Поскольку @ имеет длину O(n) длины левого операнда, мы имеем сложность foldBack:


  m + ... + m // n occurences of m
= O(m*n)

а < <29 > :

  0 + m + 2*m + ... + (n-1)*m // each time length of left operand increases by m
= m*n*(n-1)/2
= O(m*n^2)

Следовательно, с наивным способом использования @, foldBack является линейным, а fold является квадратичным по размеру входных списков.


Следует отметить, что @ является ассоциативным (a @(b @c) = (a @b) @c); поэтому в этом случае результаты fold и foldBack совпадают.


На практике, если внутренний оператор неассоциативен, нам нужно выбрать правильный порядок либо с помощью fold, либо foldBack. И List.foldBack в F # производится хвостовым рекурсивным путем преобразования списков в массивы; есть также некоторые накладные расходы этой операцией.

ответил(а) 2012-03-06T00:34:00+04:00 8 лет, 1 месяц назад
50

В наивной реализации FoldBack есть O(n^2), поскольку вам нужно продолжать перемещение по списку. В F # компилятор фактически создает временный массив и отменяет его, а затем вызывает Fold, поэтому временная сложность (в терминах O) равна O(n) для обоих, хотя Fold будет немного быстрее на постоянную сумму

ответил(а) 2012-03-06T00:09:00+04:00 8 лет, 1 месяц назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема