Интересная проблема!
Если вам нужно уложиться во все эти временные рамки в амортизированном смысле, массив Dynami c поддерживает произвольный доступ за время O (1) и поддерживает добавление элементов и удаление из конца списка за амортизированное время O (1). Это отвечало бы всем вашим требованиям. Это, вероятно, самый простой вариант.
Если вам нужно выполнить все эти требования в смысле наихудшего , единственный вариант данных, который я могу придумать, который соответствует ограничениям, - это расширяемый массив структура данных, которая, по сути, является деамортизированной версией массива c Dynami. Его можно построить из двух массивов, но предполагается, что вы можете выделить блок памяти размером n за время O (1).
Я думал над другими вариантами, чтобы посмотреть, есть ли что-то попроще, но ничего из того, что я пробовал, не получается. Например, skiplist позволяет вам получить (ожидаемый случай) O (log k) поиск k-го элемента. Для этого начните с нижнего слоя и двигайтесь вверх, пока не дойдете до элемента, который находится за k-й позицией, затем выполните обычный поиск skiplist, начиная с этого уровня или ниже. Идея состоит в том, что в среднем при первом превышении позиции k вы будете в позиции примерно 2k, и поиск в этом субскиплисте займет время O (log 2k) = O (log k), чтобы найти то, что вы ищете.
У такого подхода есть две проблемы. Во-первых, скиплисты рандомизируются, хотя это можно преодолеть, используя детерминированную c версию скиплиста (они существуют, хотя и не так широко известны). Во-вторых, стоимость вставки в skiplist не равна O (1) из-за затрат на подключение соответствующих указателей. Вы можете показать, что амортизированная стоимость вставки в такой скиплист будет O (1), если вы используете детерминированную схему c, но если вы стремитесь к амортизированной эффективности, вы должны просто использовать динамический c array.
Другой вариант, который я рассматривал, был чем-то вроде VList - связанного списка блоков элементов, размеры которых экспоненциально растут по мере продвижения вперед. Затем поиск k-го элемента может быть выполнен за время O (log k), просто пройдя вперед по связанному списку, пока не найдете блок подходящего размера, а затем загляните в этот блок. Количество блоков, которые вы посетите таким образом, равно O (log k), потому что после посещения i блоков вы пройдете более 2 i + 1 - 1 элементов. Проблема здесь в том, что это предполагает, что вы можете выделить блок памяти за время O (1), и если это так, вы можете просто использовать расширяемый массив, упомянутый ранее.
Если я думаю о чем-то еще , Я обязательно обновлю этот ответ. Интересно, не хватает ли мне какой-нибудь более простой стратегии?