Сложность алгоритмов различных парадигм программирования

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

чтобы сделать мой ответ более явным с примером: есть ли какая-либо проблема, которая может быть решена с помощью императивного алгоритма сложности x (скажем O(n)), но не может быть разрешен функциональным алгоритмом с той же сложностью (или наоборот)?

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

6 ответов


В общем, нет, не все алгоритмы могут быть реализованы с помощью того же порядка сложности на всех языках. Это можно тривиально доказать, например, с помощью гипотетического языка, который запрещает O (1) доступ к массиву. Однако, насколько мне известно, нет алгоритмов, которые не могут быть реализованы с оптимальным порядком сложности на функциональном языке. Анализ сложности псевдокода алгоритма делает определенные предположения о том, какие операции являются законными и какие операции O (1). Если вы нарушите одно из этих предположений, вы можете изменить сложность реализации алгоритма, даже если язык Тьюринга завершен. Тьюринг-полнота не дает никаких гарантий относительно сложности любой операции.


алгоритм имеет измеренную среду выполнения, такую как O(n) как вы сказали, реализации алгоритма должны придерживаться той же среды выполнения или они не реализуют алгоритм. Язык или реализация по определению не изменяет алгоритм и, следовательно, не изменяет асимптотическую среду выполнения.

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


Я думаю, что ваш первый абзац-это неправильно. И я думаю, что твое редактирование этого не изменит.

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

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

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

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

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

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

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

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

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

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


Я думаю, что язык может иметь различные базилярные операции, которые стоят O (1), например математические операции (+, -, *, /), или доступ к переменной / массиву (a[i]), вызов функции и все, что вы можете подумать.

Если язык не один Это O(1) действий (как мозг гнуть, что не за O(1) время доступа) это могут не все можно сделать C с такой же сложности, но если язык уже более O(1) действий (например, язык с O(1) время поиска) он может сделать больше, чем С.--1-->

Я думаю, что все "серьезные" языки имеют одинаковые операции basilar O (1), поэтому они могут решить проблему с одинаковой сложностью.


Если вы считаете Brainfuck или сама машина Тьюринга, есть одна фундаментальная операция, которая занимает там O(n) время, хотя в большинстве других языков это можно сделать в O(1) – индексирование массива.

Я не совсем уверен в этом, но я думаю, что у вас не может быть истинного массива в функциональном программировании (имея O (1) "получить элемент в позиции" и O (1) "установить элемент в положение"). Из-за неизменности вы можете иметь структуру это может измениться быстро, но доступ к нему требует времени, или вам придется копировать большие части структуры при каждом изменении, чтобы получить быстрый доступ. Но я думаю, что вы могли бы обмануть это, используя монады.


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

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

Я все еще помню один пример из многих лет назад, написание APL (на мейнфрейме). Задача состояла в том, чтобы найти (и устранить) дубликаты в отсортированном массиве чисел. На в то время большая часть программ, которые я делал, была на Fortran, с несколькими битами и кусочками на Pascal (все еще последняя и самая большая вещь в то время) или BASIC. Я сделал то, что казалось очевидным: написал цикл, который прошел через массив, сравнивая array[i] до array[i+1] и отслеживание нескольких индексов, копирование каждого уникального элемента обратно в соответствующее количество мест, в зависимости от того, сколько элементов уже было удалено.

пока это работало бы довольно хорошо в языки, к которым я привык, были едва ли не катастрофой в APL. Решение, которое работало намного лучше, было основано больше на том, что было легко в APL, чем на вычислительной сложности. В частности, вы сравнили первый элемент массива с первым элементом массива после того, как он был "повернут" одним элементом. Затем вы либо сохранили массив, как он был, либо устранили последний элемент. Повторяйте это, пока не пройдете через весь массив (как я помню, обнаруженный, когда первый элемент был меньше первого элемента во вращающемся массиве).

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