Какой контейнер STL лучше всего подходит для std :: sort? (Это вообще имеет значение?) - PullRequest
14 голосов
/ 01 апреля 2009

Название говорит само за себя ....

Влияет ли выбор контейнера на скорость алгоритма std :: sort по умолчанию или нет? Например, если я использую список, алгоритм сортировки просто переключает указатели узла или он переключает все данные в узлах?

Ответы [ 9 ]

14 голосов
/ 01 апреля 2009

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

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

12 голосов
/ 01 апреля 2009

Я не думаю, что std::sort работает со списками, поскольку для этого требуется итератор произвольного доступа, который не предоставляется list<>. Обратите внимание, что list<> предоставляет метод sort, но он полностью отделен от std::sort.

Выбор контейнера имеет значение. STL std::sort использует итераторы для абстрагирования способа хранения данных в контейнере. Он просто использует предоставленные вами итераторы для перемещения элементов. Чем быстрее эти итераторы работают с точки зрения доступа и присваивания элемента, тем быстрее будет работать std::sort.

8 голосов
/ 01 апреля 2009

std::list определенно не является хорошим (действительным) выбором для std::sort(), потому что std::sort() требует итераторов с произвольным доступом. std::map и друзья тоже не годятся, потому что позиция элемента не может быть усилена; то есть положение элемента на карте не может быть принудительно установлено пользователем при вставке в конкретную позицию или при обмене. Среди стандартных контейнеров мы до std::vector и std::deque.

std::sort() подобен другим стандартным алгоритмам в том, что он действует только путем обмена значениями элементов вокруг (*t = *s). Поэтому, даже если list магически поддерживает O (1) доступ, ссылки не будут реорганизованы, а их значения будут поменяны местами.

Поскольку std::sort() не меняет размер контейнера, это не должно иметь никакого значения в производительности во время выполнения, используете ли вы std::vector или std::deque. Примитивные массивы также должны сортироваться быстро, возможно, даже быстрее, чем стандартные контейнеры, но я не ожидаю, что разница в скорости будет достаточно значительной, чтобы оправдать их использование.

5 голосов
/ 01 апреля 2009

Зависит от типа элемента.

Если вы просто храните указатели (или POD), вектор будет самым быстрым. Если вы храните объекты, сортировка списка будет быстрее, поскольку это поменяет узлы, а не физические элементы.

1 голос
/ 29 ноября 2016

Я полностью согласен с утверждениями, которые ребята разместили выше. Но как лучше узнать что-то новое? Привет!!!! конечно, не читая текст и не выучив наизусть, но .... ПРИМЕРЫ: D Поскольку я недавно погрузился в контейнеры, указанные в STL, вот быстрый тестовый код, который не требует пояснений, я надеюсь:

#include <iostream>
#include <vector>
#include <deque>
#include <array>
#include <list>
#include <iterator>
#include <cstdlib>
#include <algorithm>
#include "Timer.h"

constexpr int SIZE = 1005000;

using namespace std;

void test();

int main(){
    cout<<"array allocates "<<static_cast<double>(SIZE)/(1024*1024)<<" MB\n";
    test();


    return 0;
}


void test(){
    int values[SIZE];
    int size = 0;

    //init values to sort:
    do{
        values[size++] = rand() % 100000;
    }while(size < SIZE);

    //feed array with values:
    array<int, SIZE> container_1;
    for(int i = 0; i < SIZE; i++)
        container_1.at(i) = values[i];

    //feed vector with values
    vector<int> container_2(begin(values), end(values));
    list<int> container_3(begin(values), end(values)); 
    deque<int> container_4(begin(values), end(values)); 

    //meassure sorting time for containers
    {
       Timer t1("sort array");
       sort(container_1.begin(), container_1.end());
    }

    {
       Timer t2("sort vector");
       sort(container_2.begin(), container_2.end());
    }

    {
       Timer t3("sort list");
       container_3.sort();
    }

    {
       Timer t4("sort deque");
       sort(container_4.begin(), container_4.end());
    }

}

И код для таймера:

#include <chrono>
#include <string>
#include <iostream>

using namespace std;

class Timer{

public:
    Timer(string name = "unnamed") : mName(name){ mStart = chrono::system_clock::now();}
    ~Timer(){cout<<"action "<<mName<<" took: "<<
             chrono::duration_cast<chrono::milliseconds>(
                     chrono::system_clock::now() - mStart).count()<<"ms"<<endl;}
private:
    chrono::system_clock::time_point mStart;
    string mName;
};

Вот результат, когда оптимизация не используется ( g ++ --std = c ++ 11 file.cpp -o a.ou t):

массив выделяет 0,958443 МБ
Массив сортировки действий занял: 183 мс
вектор сортировки действия занял: 316мс
список сортировки действий занял: 725мс
deque action deque: 436ms

и с оптимизацией ( g ++ -O3 --std = c ++ 11 file.cpp -o a.out ):

массив выделяет 0,958443 МБ
Массив сортировки действий занял: 55мс
вектор сортировки действия занял: 57ms
список сортировки действий занял: 264мс
deque action deque: 67ms

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

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

1 голос
/ 02 апреля 2009

Vector.

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

Теперь спросите себя - что еще вы делаете со своим контейнером? Вам нужны сильные исключения? Список, набор и карта, вероятно, будут лучшими вариантами (хотя все они имеют свои собственные процедуры сортировки). Вам нужно регулярно добавлять элементы в передней части вашего контейнера? Рассмотрим deque. Ваш контейнер должен всегда быть отсортирован? Набор и карта, вероятно, будут лучше подходить.

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

1 голос
/ 01 апреля 2009

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

std :: sort не будет работать с std :: list, так как для сортировки требуются итераторы с произвольным доступом. Для этого случая вы должны использовать одну из функций-членов std :: list. Эти функции-члены будут эффективно обмениваться указателями на связанных списках, а не копировать элементы.

0 голосов
/ 01 апреля 2009

std :: sort требует итераторов произвольного доступа, так что вы можете использовать только векторные или deque. Он поменяет значения, и, вероятно, вектор догадки будет работать немного быстрее, чем deque, потому что он, как правило, имеет более простую базовую структуру данных. Хотя разница, вероятно, очень незначительная.

Если вы используете std :: list, есть специализация (std :: list :: sort), которая должна поменять местами указатели, а не значения. Однако, поскольку это не произвольный доступ, он использует сортировку слиянием вместо быстрой сортировки, что, вероятно, будет означать, что сам алгоритм немного медленнее.

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

0 голосов
/ 01 апреля 2009

Это, безусловно, имеет значение, просто потому, что разные контейнеры имеют разные схемы доступа к памяти и т. Д., Которые могут сыграть свою роль.

Однако std::sort не работает на std::list<>::iterators, поскольку они не являются RandomAccessIterators. Более того, хотя можно было бы реализовать специализацию для std::list<>, которая бы перетасовывала указатели узлов, это могло бы иметь странные и удивительные семантические последствия - например, если у вас есть итератор внутри отсортированного диапазона в векторе, его значение изменится после сортировки, что было бы неверно при этой специализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...