Программирование >>  Унарные и бинарные операторы 

1 ... 12 13 14 [ 15 ] 16 17 18 ... 37


быстрее, чем это делает функция dict find{)? Что может быть быстрее линейного перебора элементов?

Ответ таков: если элементы расположены хаотично, то ничего другого не остается. Но если они отсортированы, можно применить замечательный aлгopггм, которым восхищались еще древние греки, - бинарный поиск.

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

Задача 6.1. Целые числа расположены в массиве в порядке возрастания. Напишите программу бинарного поиска заданного числа.

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

Конечно, контейнер тар не похож на отсортированный контейнер vector. Но прелесть стандартных контейнеров в том и состоит, что нам не нужно знать, как они устроены, достаточно знать о том, что собственная функция i nsertc) контейнера map работает быстро (на манер 1;воичного поиска), и после каждой вставки нового элемента контейнер оказывается отсортированным. Операция поиска в контейнере тар так же быстра, как и

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

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

di ct. i nsert (pai r<str i ng. str i ng>( b . )):

A слово zzzzzzzzz , включаемое в словарь, помечается буквой п :

dict.insertCpair<string.string>Cw. п )):

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

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

Например, для получения слов можно считывать целую строку с помощью функции getl ine{), а затем выделять из нее последовательности идущих подряд букв. Такой подход относительно быстр (читается целая строка), но очень зависит от структуры текста. Слишком длинная строка не уместится в буфере, и некоторые слова могут потеряться или оказаться обрезанными.

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



Добавление функций

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

Теперь нужно рсшить, как будет выглядеть функщ\я, каковы ее имя, параметры и возврап1асмое значение. Чтобы лучше понимать программу, нужно отразить в названии функции то, что она делает, поэтому хорошо назвать ее GetWord (в переводе с английского - получить слово ). Функция будет возвращать значение тина bool (true - при удачном чтснгн!, false - при достижении конца файла). А параметро.м функции обязательно должна быть ссылка на строку, где окгшется очередное слово. Если бы у нас была такая функция, то цикл, в котором новые слова включаются в словарь, выглядел бы очень просто:

while (1) {

if (GetWord(nw)==false) break:

else

diet.insert{pair<string.String>(nw. n )); }

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

Ьоо1 GetWordCstring bw )

Однако первый вариант програм.мы с участием новой функции компилятор отверг на том основании, что внуфи функции GetWord обнаружился неизвестный объект infile- файл, из которого считываются буквы. Здесь меня подвело знание языка С, где файлы глобальны, то есть доступны всем функциям. Но в С++ это не так, и приходится вводить в функцию enie один параметр. То есть ее прототип должен выглядеть так:

bool GetWordCstring 8iw. ifstream Infile):

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

Поставив оператор & перед словом infile, я получил наконец функцию (листинг 6.2), к которой компилятор отнесся благосклонно.

Листинг 6.2

bool GetWordCstring &w. ifstream &infile){

char ch:

w.eraseC):

whileCl){

infile.get(ch):

ifCinfile.eofO)

проАопжение

ла, в котором может пе быть строк и даже самих букв. В этом случае программа тоже будет работать, по не найдет ни одного слова. Недостаток такого подхода - низкая скорость, ведь чтение из файла - медленная операция, а здесь приходится читать каж;и>1Й символ.

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



Листинг 6.2 (проАОлжвниё)

ifCw!= ) return true; else return false: if{is1etter(ch))

w+=ch: else if{w != ) return true:

Функция GetWordO оказалась довольно сложной из-за того, что при чтении каждого символа приходится проверять, не достигнут ли конец файла.

Все начинается с очистки строки, где окажется только что вьщеленное слово. Это делает собственная функция w.eraseO. Далее из файла читается симвал и запоминается в переменной ch. После этого нужно npoBcptrrb, не достигнут ли конец фа11ла. Если чтение неудачно, то вызов infile.eofO возвран1ает true. Но просто выйти в этот момент из функции нельзя, ведь в строке w может храниться последнее слово файла. Поэтому проверка выглядит так:

ifCinfile.eofO)

if{w!= ) return true: else return false:

Если достигнут конец файла, но строка не пустая (W != ), возвращается true и делается попыгка включить последнее слово в словарь. Если же строка пуста, возвращается false - файл кончился, слов больше нет!

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

ifCisletter(ch))

w+-ch: else if(w != )

return true:

Обратите внимание, значение true возвращается, лишь когда строка не пуста. Если же в строке ничего нет.

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

Вы, наверное, уже обратили внимание на функцию isletterCch), возвращающую true, когда ch - буква, И false - в противном случае. Эту функцию я придумал, когда писал функцию GetWord(), чтобы не отвлекаться и думать только над одной задачей. Проверять ошибки компиляции это не меншет, нужно только в начале программы поместить прототип функции is1etter().

Но когда фугпция GetWordO готова (или мы просто думаем так, потому что не проверяли ее в деле ), приходит черед и функции islettero. А чтобы ее написать, нужно знать, какие латинские символы - буквы, а какие - нет. Можно, конечно, найти какую-нибудь справочную книгу, где есть различные кодировки. Но можно понять, как кодируиэтся буквы, и экспериментально с по-моии>ю простой программы, показанной в листинге 6.3. Листинг 6.3

#include <iostream> using namespace std; int mainC) { char ch:

forCint i=l; i<127:i++) { ch=i:

cout 1 ch endl:

В этой программе перебираются числа сгг 1 до 126, каждое число записывается в переменную ch, а затем дважды вьшодится на экран: как переменная 1 nt (cout i) и как символ (cout ch). Запустив программу, мы увидим, что прописные латинские буквы щут плотно, без просветов и кодируются числами от 65 (буква А) до 90 (Z). Строчные латинские буквы расположены также

Вывод программы лучше перенаправить в файл командой I63.exe > Z. Об этом уже говорилось в начале главы.



1 ... 12 13 14 [ 15 ] 16 17 18 ... 37

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика