Добавил:
t.me Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп

.pdf
Скачиваний:
9
Добавлен:
16.07.2023
Размер:
31.34 Mб
Скачать

220

Глава

12.

Алгоритмы

12.6.

Обзор

аnrоритмов

Общее определение алгоритма -

"набор конечного числа

правWI,

ющих последовательность выполнения операций для решения

задачи

зада­ опре­

деленного типа Определенность

".

...

[и] и.1wеет пять

важных особенностей: Конечность ."

Ввод ". Вывод ...

Эффективность" [31, §1.1 ]. В контексте

стандартной

библиотеки

С++

алгоритм

является

шаблоном

функции,

работа­

ющим с последовательностями элементов.

Стандартная библиотека предоставляет

десятки

алгоритмов.

Алгоритмы

определены

в

пространстве

имен

s

td

и

представлены

в

заголовочном

фай­

ле

<algori

thm>.

Эти

алгоритмы

стандартной

библиотеки

принимают

в

ка­

честве

входных

данных

последовательности.

Полуоткрытая

последователь­

ность

от

Ь до

е

записывается

как

[Ь:е).

Вот

несколько

примеров.

Избранные

стандартные

алгоритмы

f=for_each(b,e,f)

 

p=find(b,e,x)

 

p=find_if(b,e,f)

 

n=count(b,e,x)

 

n=count_if(b,e,f)

 

replace(b,e,v,v2)

 

replace_if(b,e,f,v2)

 

p=copy(b,e,out)

 

p=copy_if(b,e,out,f)

 

p=move(b,e,out)

 

p=unique_copy(b,e,out)

 

sort (Ь, е)

 

sort (Ь, е, f)

 

(pl, р2) =equal range (Ь,

е, v)

p=merge (Ь, е, Ь2, е2, out)

 

p=merge (Ь, е, Ь2, е2, out, f)

Для каждого элементах в [Ь:е) выполнить f (х)

 

р -

первый элемент в [Ь:е), такой, что *р==х

 

р -

первый элемент в [Ь:е), такой, что f (*р)

 

п -

количество элементов *q в [Ь:е), таких, что *q==x

п -

количество элементов *q в [Ь:е), таких, что f

(*q)

Замена элементов *q в

[Ь:е), таких, что *q==v, на v2

Замена элементов *q в

[Ь:е), таких, что f (*q), на v2

Копирование

[Ь:е) в [out:p)

 

Копирование элементов *q

из [Ь:е), таких, что f (*q),

в [out:p)

 

 

 

Перемещение

[Ь:е) в [out:p)

 

Копирование

[Ь:е) в [out:p);

соседние дубликаты не

копируются

 

 

 

Сортировка элементов

[Ь:е)

с использованием <

в качестве критерия сортировки

Сортировка элементов

[Ь:е)

с испольэованием f

в качестве критерия сортировки

[pl:p2) является подпоследовательностью отсортиро­

ванной последовательности [Ь:е) со значением v; по

сути, бинарный поиск v

 

 

Слияние двух отсортированных последовательностей,

[Ь:е) и [Ь2:е2),

в [out:p)

 

 

Слияние двух отсортированных последовательностей,

[Ь:е) и [Ь2:е2),

в [out:p)

с использованием f для

сравнения

12.7.

Концепты

(С++20)

221

Эти

и

многие

другие

алгоритмы

(например,

§

14.3)

могут

применяться

к

элементам

контейнеров,

строк

и

встроенных

массивов.

Некоторые

алгоритмы,

такие

как

replace

()

и

sort

(),

изменяют

значе­

ния элементов,

но

ни

один

алгоритм

не

добавляет

или

не

удаляет

элементы

контейнера.

Причина

в

том,

что

последовательность

не

идентифицирует

кон­

тейнер,

который

содержит

ее

элементы.

Чтобы

добавить

или

удалить

элемен­

ты,

вам

нужно

что-то,

знающее

о

конкретном

контейнере

(например,

back

_

inserter;

§12.1),

или

непосредственное

обращение

к контейнеру

(например,

push_back () или erase (); §11.2).

Для операций, передаваемых в качестве

ются лямбда-выражения. Например:

аргументов,

очень

часто

применя­

vector<int> v = {0,1,2,3,4,5);

for_each(v.begin() ,v.end(), [] (int&x)

{х=х*х;

})

;

11

v=={0,1,4,9,16,25}

Алгоритмы

стандартной

библиотеки,

как

правило,

более

тщательно

разра­

ботаны,

специфицированы

и

реализованы,

чем

средний

созданный

вручную

цикл, поэтому

их нужно

му на "голом"

языке.

знать и

использовать,

предпочитая

коду,

написанно­

12.7.

Концепты

(С++20)

В

конце

концов

алгоритмы

стандартной

библиотеки

будут

определены

с

использованием

концептов

(глава

7,

"Концепты

и

обобщенное

программиро­

вание").

Предварительные

обсуждения

этого

вопроса

можно

найти

в

Ranges

Technical Specification [37],

а

реализации

можно

найти

в

Интернете.

Концеп­

ты

определены

в

<experimental/Ranges>,

но,

надеюсь,

что-то

очень

похо­

жее

будет

добавлено

в

пространство

имен

std

в

С++20.

Диапазоны

Range

представляют

собой

обобщение

последовательностей

С++98,

определяемых

парами

begin

()

/end ().

Диапазон

-

это

понятие,

определяющее,

что

может

представлять

собой

последовательность

элемен­

тов.

Его

можно

определить

как

• •

пару

итераторов {beg in, end};

 

 

 

 

 

пару

{beg in, n}, где beg in

представляет собой итератор, а n -

коли­

чество элементов;

 

 

 

 

 

 

 

пару

{beg in, pred},

где

beg in

представляет собой

итератор,

а

pred-

предикат; если pred (р) возвращает true для итератора р, мы

достигли

конца последовательности.

Это позволяет

нам

иметь

беско­

нечные последовательности

и последовательности,

генерируемые "на

лету".

 

 

 

 

 

 

 

 

12.7.

Концепты

(С++20)

223

шанными

типами,

для

которых

библиотека

(пока

еще)

не

имеет

подходящих

определений.

Cornrnon или

большинства

концептов и

CornrnonReference используется в определениях

алгоритмов, которые могут сравнивать значения

разных типов. На концепты,

связанные

со

сравнением,

сильно

повлияла

книга

[40].

Концепты

сравнений

Boolean<T>

WeaklyEqualityComparaЬleWith<T, U>

т может использоваться как булева величина

т и u моrут сравниваться на равенство с использованием операторов == и ! =

WeaklyEqualityComparaЬle<T>

WeaklyEqualityComparaЬleWith<T,T>

Equali tyComparaЬleWith<T, U>

EqualityComparaЬle<T>

т и u моrут сравниваться на равенство с

использованием оператора == EqualityComparaЬleWith<T,T>

StrictTotallyOrderedWi th<T, U>

StrictTotallyOrdered<T>

т и u моrут сравниваться с использованием

операторов <, <=, > и >=,дающих полное упорядочение StrictTotallyOrderedWith<T,T>

Применение

как

WeaklyEquali

tyComparaЬleWi

th,

так

и

WeaklyEqua

li

tyComparaЫe

демонстрирует

отсутствие

(до

настоящего

времени)

воз­

можности

перегрузки.

Концепты,

связанные

с

объектами

DestructiЫe<T>

ConstructiЫe<T, Args>

Defaul tConstructiЫe<T>

MoveConstructiЫe<T>

CopyConstructiЬle<T>

МоvаЫе<Т>

т может быть уничтожен, а его адрес может быть

получен с помощью унарного оператора &

 

т может быть построен из списка арrументов типа Args

т может быть создан с помощью конструктора

по

умолчанию

 

т может быть создан с помощью перемещающего

конструктора

 

т может быть создан с помощью копирующего

 

и перемещающего конструкторов

 

MoveConstructaЬle<T>,AssignaЫe<T&,T>

 

и SwapaЬle<T>

 

СоруаЫе<Т>

Semiregular<T>

Regular<T>

CopyConstructaЫe<T>,MoveaЬle<T>

и AssignaЬle<T, const Т&>

СоруаЫе<Т> и Defaul tConstructaЬle<T>

SemiRegular<T> и Equali tyComparaЬle<T>

224

Глава

12.

Алгоритмы

Regular

-

идеал

для

типов.

Тип,

соответствующий

концепту

Regular,

грубо

говоря,

работает

как

in t

и

упрощает

большую

часть

наших

размыш­

лений о том, оператора ==

как

использовать этот

тип

(§7.2). Отсутствие по

умолчанию

для

классов означает,

что

большинство классов

относятся к

SemiRegular,

хотя

большинство

из

них

могут

и

должны

быть

Regular.

Концепты,

связанные

с

вызовами

InvocaЬle<F, Args>

InvocaЬleRegular<F, Args>

Predicate<F, Args>

F может быть вызван со списком аргументов типа Args

InvocaЬle<F, Args> и сохраняет равенство

F может быть вызван со списком аргументов типа Args

и возвращает bool

Relation<F,T,U>

StrictWeakOrder<F, т, U>

Predicate<F,T,U>

 

Relation<F, т, U>,

предоставляющий строгое слабое

упорядочение

 

Функция

f

( )

называется

сохраняющей

равенство

(equality

preserving),

если

из

х==у следует, что f (х) ==f (у).

Строгое слабое упорядочение (strict weak ordering) -

это

то,

что

стандарт­

ная

библиотека

обычно

предполагает

для

сравнений,

таких

как

<;

поищите

соответствующую

информацию

в

учебниках

или

Интернете,

если

вас

заинте­

ресовал этот вопрос.

Relation и StrictWeakOrder

различаются

только

семантикой.

Мы

не

можем

настоящее

время)

представить

это

различие

в

коде,

поэтому

просто

выражаем

наши

намерения

с

помощью

имен.

Концепты,

связанные

с

итераторами

Iterator<I>

Sentinel<S,I>

SizedSentinel<S,I>

Inputlterator<I>

Outputlterator<I>

Forwardlterator<I>

Bidirectionallterator<I>

К I

могут быть применены операторы инкремента

(++)

и разыменования(*)

 

 

 

s является ограничителем для типа итератора, т.е. s

является предикатом над значением типа I

 

 

Ограничитель s, где к I

может быть применен

 

 

оператор-

 

 

 

I -

входной итератор;

оператор * может быть

 

 

применен только для чтения

 

 

I -

выходной итератор; оператор * может быть

 

 

применен только для записи

 

 

I -

однонаправленный итератор, поддерживающий

многопроходность

 

 

 

I -

Forwardlterator

с поддержкой оператора

--

 

12.7.

Концепты

(С++20)

225

Окончание

табл.

Концепты,

связанные

с итераторами

RandomAccessiterator<I>

PermutaЫe<I>

MergeaЬle<Il,I2,R,O>

SortaЫe<I>

SortaЫe<I,R>

I -Bidirectionaliterator с подцержкой

операторов+,-,+=,-= и [J

I - Forwarditerator<I>, где I разрешает

перемещать и обменивать элементы

Можно сливать отсортированные последовательности,

определяемые I 1 и I2, в о с использованием

Relation<R>

Можно сортировать последовательности, определяе­

мые I, с использованием отношения меньше

Можно сортировать последовательности,

определяемые I, с использованием Relation<R>

ра

Различные

разновидности (категории) итераторов используются для

выбо­

наилучшей

реализации для данного алгоритма; см. §7.2.2 и §13.9.1.

При­

мер Inputiterator см. в §12.4.

Основная идея ограничителя

состоит

в

том,

что

мы

можем

перебирать

диапазон,

начиная

с

определенного

итератора,

пока

для

элемента

не

станет

истинным

указанный

предикат.

Таким

образом,

итератор

р

и

ограничитель

s

определяют

диапазон

[p:s

(

*р)

).

Например,

мы

могли

бы

определить

преди­

кат

для ограничения

для

обхода

строки

в

стиле

С,

используя

в

качестве

итера­

тора

указатель:

[]

(const char*

р)

{return

*р==О;

)

Изложение информации щено по сравнению с [37].

о

MergeaЫe

и

SortaЫe

в

данной

книге

упро­

Range<R>

SizedRange<R>

View<R>

BoundedRange<R>

InputRange<R>

Концепты диапазонов

Rпредставляет собой диапазон с начальным итератором

и ограничителем

Rпредставляет собой диапазон с получением размера за

константное время

Rпредставляет собой диапазон с копированием,

перемещением и прием за константное время

Rпредставляет собой диапазон с идентичными типами

итератора и ограничителя

Rпредставляет собой диапазон, тип итератора которого

удовлетворяет концепту Inputiterator

12.9.

Параллельные

алгоритмы

227

12.9.

Параллельные

алгоритмы

Если

одна

и та же

задача

должна

быть

выполнена

для

многих

элементов

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

при

параллельное выполнение: задания выполняются несколькими потоками

выполнения (зачастую работающими на разных ядрах процессора);

векторизованное выполнение: задания

выполняются

на одном потоке с

использованием

векторизации, известной как SIMD

("Single Instruction,

Multiple Data" -

одна команда, много

данных).

 

Стандартная

библиотека

предлагает

поддержку

для

обоих

вариантов,

и

мы

можем

точно

указать

требование

последовательного

выполнения;

в

заголо­

вочном

файле

<execution>

имеются

следующие

индикаторы

стратегий:

• • •

seq: последовательное выполнение;

 

par: параллельное выполнение (если таковое возможно);

 

par_ unseq: параллельное и/или непоследовательное

(векторизован­

ное) выполнение (если таковое возможно).

 

Рассмотрим

std::

sort

():

sort (v .begin (), v. end ());

/ / Последовательное

sort(seq,v.begin(),v.end());

//Последовательное

sort(par,v.begin(),v.end());

//Параллельное

11 Параллельное и/или векторизованное:

sort(par_unseq,v.begin(),v.end());

(как

и

по умолчанию)

Стоит

ли

распараллеливать

и/или

векторизовать

выполнение,

зависит

от

алгоритма,

количества

элементов

в

последовательности,

аппаратного

обеспе­

чения

и

использования

этого

аппаратного

обеспечения

программами, работа­

ющими

на

нем.

Следовательно,

индикаторы

стратегии

выполнения

являют­

ся

просто

подсказками.

Компилятор

и/или

планировщик

времени

выполнения

решат,

какой

параллелизм

использовать.

Это

все

очень

нетривиальные

задачи,

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

применения

Для

большинства

алгоритмов

стандартной

библиотеки,

включая

все

табли­

цы

в

§12.6,

за

исключением

equal

Range,

может

быть

запрошено

распарал­

леливание

и

векторизация

с

использованием

par

и

par

_

unseq,

как

в

случае

sort

()

.

Почему

не

equal

_

Range

()?Потому

что

до

сих

пор

никто

не

приду­

мал для него достойного параллельного алгоритма.

Многие параллельные алгоритмы

используются

данных; см. §14.3.1.

 

в

основном

для

числовых

228

Глава

12.

Алгоритмы

При запросе параллельного выполнения следует убедиться

возможности гонки данных(§15.2) и взаимоблокировки(§15.5).

в

отсутствии

12.10.

Советы

[1]

[2]

[3]

[4]

[5]

[6]

[7] [8]

[9]

Алгоритмы STL работают с

одной или несколькими последовательно­

стями; §12.1.

 

 

 

 

 

 

Входная последовательность

является

полуоткрытой и определяется

па­

рой итераторов; §12.1.

 

 

 

 

 

 

Алгоритм поиска обычно возвращает

конец входной последовательно­

сти как указатель неудачности выполненного поиска; §12.2.

 

 

 

Алгоритмы непосредственно

не добавляют и не убирают элементы

из

последовательностей,

переданных им в качестве аргументов;

§12.2,

§12.6.

 

 

 

 

 

 

При

написании цикла

подумайте, не может ли он быть выражен

как

об­

щий

алгоритм; §12.2.

 

 

 

 

 

 

Используйте предикаты и другие функциональные объекты

для при­

дания стандартным алгоритмам более широкого диапазона

смыслов;

§12.5, §12.6.

 

 

 

 

 

 

Предикат не должен модифицировать

свой аргумент; §12.5.

 

 

 

Следует знать алгоритмы стандартной библиотеки и предпочитать

их

написанным вручную

циклам; §12.6.

 

 

 

 

Если

написание пары

итераторов становится утомляющим, вводите

ал­

горитмы для контейнеров/диапазонов; §12.8.

 

 

 

13 Утилиты

Время,

которое вы тратите

с удовольствием,

не

тратится

впустую.

-

+

Введение

 

+

Управление ресурсами

 

unique_Ptr И shared_Ptr

+

move()

И forward()

Проверка

выхода эа границы диапаэона: gsl::spaп

+

Специалиэированные контейнеры

 

array

 

 

Ьitset

 

+

pair И tuple

Альтернативы

 

variant

 

optional

 

+

any

 

Аллокаторы

+

Время

 

+

Адаптация

функций

 

Лямбда-выражения

в

качестве

адаптеров

 

mem_fn ()

 

+

function

 

Функции типов

 

iterator

traits

 

Предикаты типов

 

еnаЫе if

 

Бертран

Рассел

+

Советы