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

Programmirovanie_i_osnovyi_algo

.pdf
Скачиваний:
10
Добавлен:
05.03.2016
Размер:
9.5 Mб
Скачать

//

должны быть ал фа витно-упорядоченными по ключам

void

Round(

 

 

 

 

 

 

 

 

±nt

root

)

// Корень дерева

 

{

±£( size

< root

)

 

 

 

 

 

 

Выход из рекурсии

 

 

{

 

 

 

//

!!!

 

 

}

 

 

 

 

 

 

 

 

 

Round(

2*root );

//

Обойти вершины левого

поддерева

 

//

Приписать корню очередную по алфавиту строку таблицы

 

±f(

fscanf(

pStructFilelnp,

"%8s%62s",

 

 

 

рТаЫе[

root-1

].key,

рТаЫе[ root-1 ],data

) ! =2 )

 

{

printf(

 

 

 

 

 

 

 

 

 

 

 

 

 

 

"\n

Ошибка 120.

Ошибка чтения строки таблицы \п"

);

 

 

exit ( 120

);

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

Round(

2*root+l );

//

Обойти вершины правого поддерева

return/

//Бинарный (логарифмический) поиск в таблице, подготовленной

//в форме алфавитно-упорядоченного двоичного дерева

void. LogariphmSearch

(

 

 

// Ключ для поиска

строки в таблице

сЪаг

Keyword [

] ,

 

±nt

&found,

 

//

1 - нашли

±Tib

Scline )

 

//

Индекс найденной строки

{

i,

//

Индекс вершины дерева

int

 

EndTab;

 

// 1 - достигнут конец таблицы

//Подготовка к поиску found = 0; EndTab = 0;

//Поиск

i = 1;

while ( ! found && .'EndTab )

{

±f( !strcmp( Keyword, pTable[ i-1 ] . key ) )

{//Нашли

found = 1; line = i-1;

}

else

{//Шаг вперед по таблице

±f( strcmp( Keyword, pTable [ i-1 ] , key ) < 0 )

{

i = 2*i/

}

300

else

 

 

{

=

2*i-i-l/

1

}

 

 

EndTab

=

(i > size ) ,

 

return;

 

 

 

 

 

 

 

 

 

 

//

Преобразование

 

 

символа

 

ключа - строчная

латинская

буква,

//

цифра

или

пробел

- в

его порядковый

номер

(целое

число)

±п\t

Kod(

 

symbol

)

//

Порядковый номер

символа

 

(

char

 

//

Преобразуемый

символ

 

switch (

S3/mbol

)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{

' a

':

return

0;

 

 

 

 

case

 

 

 

 

case

'b'

 

:

return

1;

 

 

 

 

case

'c'

 

:

return

2;

 

 

 

 

case

d'

:

return

3;

 

 

 

 

case

'e'

 

:

return

4;

 

 

 

 

case

f

 

:

return

5;

 

 

 

 

case

g'

:

return

6;

 

 

 

 

case

h';

return

7;

 

 

 

 

case

'i ': return

8;

 

 

 

 

case

J'.•

return

9;

 

 

 

 

case

;c' :

return

10

 

 

 

 

case

1 ' :

return

11

 

 

 

 

case

m ':

return

12

 

 

 

 

case

n

 

':

return

13

 

 

 

 

case

o'

:

return

14

 

 

 

 

case

p':

 

 

return

15

 

 

 

 

case

q':

:

return

16

 

 

 

 

case

r'

return

17

 

 

 

 

case

s'

:

return

18

 

 

 

 

case

t':

return

19

 

 

 

 

case

u':

return

 

20

 

 

 

 

case

V* :

return

21

 

 

 

 

case

w'

:

return

22

 

 

 

 

case

X'

 

:

return

 

23

 

 

 

 

case

y

'

:

return

 

24

 

 

 

 

case

z

 

':

return

 

25

 

 

 

 

case

0'

:

return

 

26

 

 

 

 

case

1 ':

return

 

27

 

 

 

 

case

2':

:

return

 

28

 

 

 

 

case

3'

return

 

29

 

 

 

 

case

4 ' :

return

 

30

 

 

 

 

case

5'

:

return

 

31

 

 

 

 

case

6'

:

return

 

32

 

 

 

 

case

7':

return

 

33.

 

 

 

301

Keyword [ ] )

case

'8*:

return

34;

case

'9':

return

35;

case

' ': return

36;

 

 

dLefaul t:

 

 

 

 

 

 

"\n

Ошибка

printf(

поиска

содержит недопустимый

символ.

\п"

130.

Ключ

"\п

Ключ

может

содержать

толька строчные

латинские

буквы,

"

" цифры

и

пробел

\п"

) ;

;

 

 

 

 

 

 

 

exit

( 130

)

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

return

 

-1;

 

 

//

Этот оператор

не будет

 

 

 

 

 

 

 

 

//

выполняться

 

 

}

//

Хэш-функция

ключа "KeyWord" из "LKEY-1" символа

для

(символ -

//

строчная

латинская

буква,

цифра

или пробел)

таблицы

//

из size

строк

// Возвращает

индекс строки

таблицы

int

Hash(

 

//Ключ

char

{

unsigned.

 

±nt

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

 

Индекс

 

символа

 

в

ключе

 

±nt

 

 

I Key;

0;

 

 

 

 

 

 

 

 

ih

=

 

//

 

Значение

 

хэш-функции

 

//

Вычисление

 

индекса

 

строки

таблицы

 

 

 

 

for(

IKey

=

0;

I Key

<

strlen

(

KeyWord

)

;

IKey-h-h )

 

{

ih

=

ih

*

37

-f Kod(

KeyWordf

IKey

]

) ;

 

 

 

}

ih

=

ih

%

 

size;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

return

ih;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

Начальная

 

подготовка

 

хэш-таблицы

 

 

 

 

 

void

BeginTable

 

(

 

 

 

 

 

Указатель

на

файл

ввода

 

char

 

 

*pFileInp,//

 

 

 

int

 

 

 

ТаЫеЬеп

 

) /

/ Число

вводимых

 

строк

{

int

 

 

i ,

 

 

 

//

 

Индекс

 

строки

 

таблицы

 

 

 

 

 

 

 

 

 

 

 

 

 

line,

 

 

 

//

 

Номер

текуш,ей

 

строки

 

//

 

 

found;

 

 

//

 

1

- найдена

позиция

вставки

 

Заносимое

 

слово

[

LKEY

];

 

 

 

 

 

 

 

char

 

Keyword

 

 

 

 

 

 

 

//

Инициализация

 

таблицы

нуль-символ

а ми

 

 

 

£ог(

i

=

О;

i

<

size;

 

i++

)

 

 

 

 

 

 

 

{

£or(

 

int

il

 

 

0;

il

 

<

LKEY;

il++

)

 

 

 

 

 

 

=

 

 

 

302

рТаЫе[

i

] . key

[ 11

]

=

'\0';

fox:( int 12

=

0; 12

< LDATA;

12 + + )

pTablef

1

] .data[

12

]

=

'\0';

}

//

Отметка

строк

таблицы

как

свободных

for(

1 = О;

1

<

size;

1++

)

 

(

pTablef

1

J.keyf

О ]

^

'

'/

 

}

//Открытие файла для чтения

pStructFllelnp

 

== OpenFlle(

pFllelnpr

 

 

 

"г", 140

) ;

 

//

Занесение

в

таблицу

исходных

строк

 

 

)

 

 

 

 

for(

line

=

0;

line

<

TableLen;

 

llne++

 

 

 

строк

 

{

 

 

 

 

 

 

//

Цикл

чтения

исходных

 

 

 

±f(

 

fscanf(

pStructFllelnp,

 

 

"

%s",

 

 

Keyword

)

!=

1 )

 

{

 

printf(

"\n

Ошибка

150.

Ошибка

 

 

чтения

ключа

из"

 

 

 

 

 

 

}

 

 

 

" строки

с

индексом

 

%d

\п", line

) ;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

Поиск

индекса

'1'

строки

таблицы

 

для

ее

 

заполнения

 

found

=

О;

 

 

//

 

Пока

индекс

 

не

 

 

найден

 

 

 

 

while(

/found

)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{

 

±f(

pTablef

 

1

].key[

 

0 ]

==

'

 

'

)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{

 

 

 

//

Индекс

найден

 

 

 

 

 

 

 

 

 

 

 

found

-

1;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

else

 

 

 

//

 

Конфликт

- шаг

 

 

по

таблице

 

 

 

 

{

1++;

1

1

?

 

 

 

 

 

= (

>

(

slze-1

 

)

0:

1 )

;

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

Чтение

данного

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

±f(

 

fscanf(

pStructFllelnp,

 

 

"

%s",

 

 

 

 

 

 

 

 

 

 

pTablef

1

] .data

)

!=

1

)

 

 

 

 

 

 

 

 

 

 

{

 

printf(

"\n

Ошибка

160.

Ошибка

 

чтения

ключа

из"

 

 

 

 

 

 

 

 

 

"

строки

с

индексом

 

%d

 

\л", line

) /

 

 

 

 

exlt

(

160

)

;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

Занесение

 

ключа

в

строку

"1"

таблицы

 

 

 

 

 

strcpyi

pTablef

 

1

].key,

 

KeyWord

)

/

 

 

 

 

 

 

//Закрытие файла ввода

CloseFlle(

pStructFllelnp,

pFllelnp,

170 ) ;

геЬмтсп;

303

}

// Поиск

В

хэш-таблице

 

 

 

 

 

 

void

HashSearch(

для

 

поиска

 

 

 

 

// Ключевое слово

],

 

 

 

 

char

 

Keyword

[

1 -

нашли

 

 

 

±nt

 

бе founds

//

 

строки

в таблице

±nt

 

&11пе

)

//

Индекс

найденной

{

 

 

 

 

 

 

 

 

 

±nt

 

1,

 

//

Индекс

строки

 

таблицы

 

 

 

EndTah;

 

//

1 - достигли

свободной

строки

//Подготовка к поиску

found = О; EndTab = О; i = Hash( KeyWord ) ;

//Поиск в таблице

while ( ( .'found ) && ( .'EndTab ) )

{

±f( pTablel

1

J.keyf

0 ]

==

' '

)

 

{

 

 

//

Достигли

свободной

строки

EndTab

=

1;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

else

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

±£( ! strcmp

( pTablef

1

] . key,

KeyWord ) )

{

 

 

//

Нашли

 

 

 

found

=

1;

line

= i;

 

 

}

else

{// Шаг no таблице

i++; i = ( i > ( size-1

) ? 0: i ) ;

}

}

}

retujni/

 

Для файла исходных данных, имеющего вид

call

вызов

type

тип

word

слово

work

работа

был

получен следующий файл результатов:

 

Состояние та блицы:

call

вызов

type

тип

word

слово

work

работа

304

Тестирование

 

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

поиска

Результаты

поиска

для

в

ключевого

слова:

and

Строка

с ключом

"and"

таблице

не

найдена.

Результаты

поиска

для

 

ключевого

слова:

word

Индекс

строки

в

таблице:

2.

Найденная

 

строка:

word

слово

 

 

 

 

 

 

 

 

 

 

 

Состояние

та блицы:

 

 

 

word

 

слово

 

 

 

 

 

 

 

 

type

тип

 

 

 

 

 

 

 

 

 

work

работа

 

 

 

 

 

 

 

 

call

 

вызов

 

 

 

 

 

 

 

 

Тестирование

 

логарифмического

поиска

 

Результаты

поиска

для

в

ключевого

слова:

and

Строка

с ключом

"and"

таблице

не

найдена.

.Результаты

поиска

для

 

ключевого

слова:

word

Индекс

строки

в

таблице:

О. Найденная

 

строка:

word

слово

 

 

 

 

 

 

 

 

 

 

 

Со стояние

та блицы:

 

 

 

call

 

вызов

 

 

 

 

 

 

 

 

type

тип

 

 

 

 

 

 

 

 

 

Тестирование

 

хэш-поиска

 

 

 

 

Результаты

поиска

для

 

ключевого

слова:

work

Строка

с ключом

"work"

 

в таблице

не

найдена.

Результаты

поиска

для

 

ключевого

слова:

type

Индекс

строки

в

таблице:

2.

Найденная

 

строка:

type

тип

 

 

 

 

 

 

 

 

 

17.3. Логарифмический (бинарный) поиск

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

Пример. В реальных словарях, например в англо-русском сло­ варе, человек быстро находит нужные сведения, используя их упо­ рядоченность по алфавиту. Указанный подход и использован при

305

логарифмическом (с помощью двоичного дерева) поиске в таблице. Если исходную таблицу (словарь) предварительно подготовить

в форме двоичного дерева так, чтобы ключи левого поддерева были раньше по алфавиту, чем ключ корня, а ключи правого поддерева - позже, то число обращений к таблице для сравнения с заданным ключевым словом не может превышать log^isize). При этом, после каждого обращения к таблице, область поиска сокращается в общем случае примерно в два раза.

Начальная подготовка таблицы в форме двоичного дерева.

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

Пусть, например, читаемые данные содержат в каждой отдель­ ной строке информацию для заполнения одной строки таблицы, причем они отсортированы по ключам по не убыванию (size=\0):

А

Data

for

string

0

 

В

Data

for

string

1

 

С

Data

for

string

2

 

D

Data

for

string

3

 

Е

Data

for

string

4

 

F

Data

for

string

5

 

G

Data

for

string

6

 

Н

Data

for

string

7

 

I

Data

for

string

8

(size-1)

J

Data

for

string

9

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

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

те внимание на то, что функция

Round является вспомогательной

для функции InpTabLog.

 

 

Бинарный

(логарифмический)

поиск в таблице,

подготов­

ленной в форме

двоичного дерева.

Идея поиска состоит

в следую­

щем.

 

 

 

1. Исходный ключ сравнивается с ключом, соответствующим корню дерева (номер соответствующей вершины дерева / = 1, а ин­ декс элемента массива - (/ - 1) ). Если при этом ключи совпадают, то нужная строка найдена {found = 1), ее индекс line = i - 1 и поиск за-

306

вершен.

} 2

]\

/ /

3

D

 

 

1

В

F

н

J

А/ Лс-

-ЕV

 

 

10

Первые слова по алфавиту (A-B-C-D-E-F) присваиваются левому поддереву

Последние слова по алфавиту (H-I-J) присваиваются правому поддереву

Среднее слово по алфавиту (G) присваивается корню.

Аналогично заполняются левые и правые поддеревья для частичных корней

Рис. 98. Двоичное дерево после начального заполнения

2. Если поиск не завершен, то определяется поддерево для продолжения поиска путем сравнения KeyWord < рТаЫо[ /-1 ].кеу. При положительном итоге необходимо вести поиск в левом подде­ реве и номер следующей вершины / = /*2. При выполнении же про­ тивоположного условия KeyWord > рТаЫе[ /-1 ].кеу, поиск следует вести в правом поддереве и номер следующей вершины дерева / = /*2+1.

3. При выполнении условия i > size (вершину / дерево не со­ держит) поиск следует прекратить, так как строка с ключевым сло­

вом KeyWord

отсутствует {EndTab = 1 w found = 0). Иначе - выполня­

ется переход к п. 1 с новым значением /, соответствующим

корню

левого или правого поддерева.

 

Легко заметить, что после каждого сравнения KeyWord

с клю­

чом рТаЫе[

/-1 ].кеу область поиска сокращается примерно

в два

раза и среднее число обращений к таблице (средняя длина поиска) составляет 1^^.,, =\og^{size), что существенно эффективнее, чем при по­ следовательном поиске.

307

в соответствии со сказанным, прототип, определение функции LogariphmSearch и пример ее вызова имеют вид, показанный в про­ грамме из подразд. 17.2.

17.4. Поиск с использованием перемешанной таблицы (хэш-таблицы)

при поиске с использованием хэш-таблицы используется ор­ ганизация данных в виде массива. Основная идея поиска состоит в преобразовании заданного ключа Key Word в индекс Hash( Key Word ) соответствующей строки в таблице. Поэтому такой способ поиска иногда называют поиском с преобразованием ключей (рис. 99).

рТаЫе

Keyword

Hash(KeyWord)

 

 

Индекс строки в

 

Исходный

таблице с

 

key= Keyword

 

ключ

 

 

 

Size-1

 

 

Ключ (key)

Данное (data)

Рис. 99. Хэш-поиск в таблице

Основная трудность преобразования ключей состоит в том, что множество возможных значений ключей намного обширнее, чем множество индексов строк в таблице. Так, например, если ключ со­ держит восемь символов, в качестве которых используются строч­ ные латинские буквы, цифры и пробел (всего 37 возможных значе­ ний каждого символа в ключе), то всего имеется 37^ возможных значений ключей, что, естественно, во много раз превышает реаль­ ный размер таблицы size. Из сказанного следует, что функция Hash является отображением "много в один".

Идея поиска с использованием хэил-таблицы состоит в сле­ дующем. Первый этап в операции поиска - вычисление соответст­ вующего индекса Hash{ KeyWord ) в таблице, а второй - очевидно необходимый этап - проверка, действительно ли элемент с ключом KeyWord находится в таблице в строке с индексом Hash( KeyWord ).

При этом сразу же возникают два вопроса.

308

1. Какую функцию Hash( KeyWord ) следует использовать?

2. Как поступать в ситуации, когда функция Hash{ KeyWord ) не дает местонахождения нужного элемента (! много ключей дают одинаковый индекс)?

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

добный случай, когда

в строке Hash{ KeyWord ) находится другой

ключ, а не ключ KeyWord^ называется конфликтом,

а задача получе­

ния альтернативных индексов li^зыв2iQTCЯ разрешением

конфликтов.

Выбор функции

преобразования.

Основное

требование

к хо­

рошей функции преобразования Hash{

KeyWord

) состоит

в том,

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

Пусть ih определяет порядковый номер ключевого слова KeyWord во множестве всех возможных значений ключей и вычисляется следующим образом:

unsigned.

±nt

 

 

 

 

 

 

 

 

Int

 

I Key;

//

Индекс

 

символа

в

ключе

 

ih

= О;

//

Значение

хэш-функции

//

Вычисление

индекса

 

строки

 

таблицы

 

 

for(

IKey

= 0;

I Key

<

strlen

(

KeyWord

)

; IKey-h-h )

{

ih = ih * 37 + Kod( KeyWord[ IKey ] ) ;

}

ih = ih % size;

Врезультате вычислений ih получает значение из диапазона 0-

36.К сожалению, величина ih существенно превышает максимально допустимое целое значение (2'^-1 или 2^'~1). По этой причине функцию Hash{ KeyWord ) следует построить несколько иначе — вы­ числение

ih = ih % size;

перенести в блок цикла. Прототип полученной таким образом хэшфункции и ее определение приведены в примере программы в подразд. 17.2. Функция Hash{ KeyWord ) также является вспомогатель­ ной и используется при хэш-поиске. Эта функция обладает тем свойством, что значения ключей равномерно распределяются во всем интервале индексов строк таблицы. Исследованиями показано, что для большей равномерности распределения желательно, чтобы

309

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]