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

Информатика

.pdf
Скачиваний:
26
Добавлен:
03.05.2015
Размер:
3.59 Mб
Скачать

51

For i = 1 To n

For j = 1 To n

If i = j Then A(i, j) = 2 Else A(i , j) = 1 / (i - j)

Next

Next : s = 0

For i = 1 To n - 1

For j = i + 1 To n

For k = 1 To n

s = s + A(k, i) *A(k, j)

Next

Next

Next: Scalar = s

End Function .

В Mcd все можно записать значительно проще:

Scalar(n) : =

 

for

i 0 .. n - 1

 

 

 

 

 

 

 

for j 0 .. n – 1

 

 

 

 

 

 

1

 

 

 

 

Ai, j ← if i = j, 2,

i - j

 

 

 

n - 2

n - 1

 

 

 

 

(A‹i›∙A‹j›)

 

 

 

 

i = 0

j = i + 1

 

 

 

 

 

 

Здесь использован символ суммирования, который выбирается в меню анализа (см. рис. 8), вызываемом щелчком мышью по иконке .

Как уже отмечалось (и это заметно по приводимым примерам), что в Mcd особенно удобно ра-

ботать с векторами и матрицами. Пусть, например, Рис. 8 необходимо вычислять экспоненту матрицы с по-

мощью ряда exp(A) = eA = ∑ n= 0 n!1 An. В VBA пришлось бы создавать

подпрограммы умножения матриц (см. пример 2), сложения матриц, оценки максимального по модулю элемента (нормы матрицы), деления матрицы на число. Mcd справляется с этим совсем просто:

Exp(A) : = ε ← 0.00001, n ← 0

q ← identity(cols(A)), S ← q

()

while max |q| > ε

n ← n + 1 q ← n-1∙q∙A

S ← S + q

S

Помимо уже использованных ранее встроенных функций здесь участвует функция identity(), возвращающая единичную матрицу указанно-

го (в качестве аргумента) размера, и операция векторизации (f(M), см.

52

рис. 6, - функция f применяется к каждому элементу массива М). Функция max() возвращает наибольший элемент из списка аргумен-

 

 

 

 

2

-1

0

 

 

 

 

 

-1

3

-2

 

функция вычислит зна-

тов. В частности, для матрицы A=

 

 

 

 

 

0

-2

1

 

 

15.096

-23.829

12.071

 

 

 

 

 

 

чение Exp(A) = -23.829

63.066

-35.588

.

 

 

 

 

12.829

-35.588

21.443

 

 

 

 

 

 

Упражнение. Создайте соответствующую программу в VBA.

Пример 6. Рассмотрим еще пример, где использование массивов играет вспомогательную роль. Пусть для заданного набора n натуральных чисел a1, a2, … , an требуется вычислить сумму

a 1

a 2

a n

1

 

 

S =

 

.

1+ j1 + j2 +… + jn

j 1 = 0 j 2 = 0

j n = 0

 

Каждая отдельная сумма, естественно, вычисляется с помощью цикла For … Next, но количество таких циклов заранее неизвестно. Можно создать вектор счетчиков и с его помощью выполнить суммирование. Приведем пример программы (функция Ubound() возвращает верхнюю границу одномерного массива) в VBA.

Function Summarize(a() As Integer) As Single

 

Dim n As Integer, p() As Integer, sum As Single

 

Dim r As integer, i As Integer, j As Integer

 

n = Ubound(a): Redim p(n): sum = 1

'***подготовка информации

Do: r = 0

 

For i = n To 0 Step -1

 

If p(i) < a(i) Then

 

p(i) = p(i) + 1

'***заполнение массива

For j = 0 To n: If j > i Then p(j) = 0

r = r + p(j)

'***суммирование знаменателя

Next

 

sum = sum + 1 / (r + 1): Exit For

'***прерывание цикла

End If

 

Next

 

Loop Until r = 0: Summarize = sum

End Function .

В Mcd, как уже отмечалось выше, средства циклических вычислений весьма упрощены. Предусловие (по своему смыслу) требует предварительной подготовки условия для запуска цикла, конструкция Do ...

Loop отсутствует полностью, прерывание цикла (вместо Exit For) выполняет оператор break. В результате программа в Mcd выглядит несколько иначе (напомним еще раз, что на рабочем листе Mcd разрешается записывать лишь один оператор в строке, здесь это правило нарушается в целях компактности и экономии места):

53

:

Summarize(a) : = n ← last(a), pn ← 0, sum ← 1

r ← 1 while r > 0

r ← 0

for i n .. 0

if pi < ai

pi ← pi + 1

for j 0 .. n

pj ← 0 if j > i r ← r + pj

sum ← sum + 1 r + 1

break

sum

1.7.1.Программные стэки

Впрограмме можно организовать неизменяемый стэк данных, используемых локально в данном месте. В DOS-версии Бейсика для этой цели использовалась инструкция DATA, за которой следовал произвольный набор данных. Данные можно было прочитать с помощью оператора READ по порядку слева направо (счетчик стэка можно восстановить с помощью инструкции RESTORE). В VBA эти служебные слова несут другую нагрузку, поэтому такая удобная конструкция упразднена. Использование типа Variant позволяет организовать аналогичную службу с более широкими возможностями. В переменную типа Variant можно поместить информацию любого основного типа с помощью оператора присваивания и "функции" Array, параметрами которой являются элементы информации, разделенные запятыми. Ссылка на них осуществляется так же, как на элементы массива, индексированного с нуля (можно изменить начало индексации инструкцией Option Base 1). Функция Ubound() возвращает верхний индекс этого массива. При использовании его в программе можно не знать объем содержимого, при этом допускается следующий вид цикла пересчета:

For Each ‹переменная› In ‹массив› ... Next,

в этом случае будут выбираться все элементы массива по порядку. Пример. Пусть, например, квадратный массив n×n заполнен

случайными целыми числами. Элемент массива будет называться "горкой", если его значение больше всех значений его ближайших соседей. Нужно посчитать количество горок. Будем индексировать все массивы с нуля. Заметим, что индексы ближайших соседей различаются лишь на единицу. Для элемента Ai, j его ближайшими соседями

будут Ai, j+1, Ai+1, j+1, Ai+1, j, Ai+1, j-1, Ai, j-1, Ai-1,j-1, Ai-1, j, Ai-1, j+1. Поэтому

54

можно завести массив изменения индексов Ind = Array("Инкремент", 0, 1, 1, 1, 1, 0, 1, -1, 0, -1, -1, -1, -1, 0, -1, 1) и с его помощью сравнивать содержимое соседних ячеек. Приведем код VBA.

Function Hills(n As Integer) As Integer

Dim A() As Integer, i As Integer, j As Integer, k As Integer, b As Boolean Dim ii As Integer, jj As Integer, s As Integer, Ind As Variant

Ind = Array("increment",0,1,1,1,1,0,1,-1,0,-1,-1,-1,-1,0,-1,1)

Redim A(n - 1, n - 1): Randomize Timer '***подготовка генерации сл. чисел For i = 0 To n - 1

For j = 0 To n - 1

 

 

A(i, j) = Int(100*Rnd – 50)

'***создан массив случайных чисел

Next

 

 

Next

 

 

For i = 0 To n - 1

 

 

For j = 0 To n - 1

 

 

b = True

 

 

For s = 1 To 8

 

 

ii = Ind(2*s - 1): jj = Ind(2*s)

'***выбор индексов

If i+ii >=0 And i+ii < n And j+jj >=0 And j+jj < n Then

If A(i, j) <= A(i+ii, j+jj) Then

'***проверка на горку

b = False: Exit For

 

'***это – не горка

End If

 

 

End If

 

 

Next: If b Then k = k + 1

 

'***а это – горка

Next

 

 

Next: Hills = k

End Function .

Как уже указывалось, в Mcd массивы также имеют (по умолчанию) тип Variant, но отсутствует логический тип, так что код выглядит несколько иначе:

 

 

 

 

 

Hills(n) : =

Ind ← ("Increment" 0 1 1 1 1 0 1 -1 0 -1 -1 -1 -1 0 -1 1)T

 

for

i 0 .. n – 1

 

for j 0 .. n – 1

 

 

 

Ai, j ← trunc(rnd(100) – 50)

 

k ← 0

 

for

i 0 .. n – 1

 

for j 0 .. n – 1

 

 

 

b ← 1

 

 

 

 

 

 

for s 1 .. 8

 

 

 

 

ii ← Ind2∙s – 1, jj ← Ind2∙s

 

 

 

 

 

 

 

 

if Ai, j ≤ Ai + ii, j + jj if i+ii ≥ 0 i+ii < n j+jj ≥ 0 j+jj < n

 

 

 

 

 

b ← 0, break

 

 

 

 

 

 

k

 

k ← k + 1 if b > 0

 

 

 

 

 

 

 

Замечательно, что в отличие от стэков DATA содержимое массивов Array можно произвольно менять в программе.

55

1.7.2. Тип пользователя

Пользователь имеет право определить свой тип, обладающий свойствами некоторых стандартных типов или типов, определенных пользователем ранее. Свойства типа (они называются "полями") указываются внутри "скобок" Type ‹имя типа› ... End Type, а сам тип должен быть определен в отдельном модуле для того, чтобы компилятор создал соответствующую конструкцию. При обращении к переменной пользовательского типа название конкретного свойства (или поля)

отделяется от имени типа переменной точкой. Рассмотрим

Пример. Пусть имеется 10 фамилий некоторых лиц, о которых следует хранить такую информацию: фамилия, возраст, пол, номер телефона. В отдельном модуле определим тип Person следующим образом:

Type Person

 

Name As String*10

'***фамилия (не более 10 символов)

Aged As Byte

'***возраст

Sex As Boolean

'***пол (муж.): истина - ложь

Telef As Long

'***длинное целое число

End Type .

 

Теперь можно создать массив в 10 персон стандартным образом, например, Dim Staff(9) As Person (этот тип уже определен). Организуем ввод необходимой информации в цикле:

For i = 0 To 9

Staff(i). Name = InputBox("Введите фамилию", "","") Staff(i). Aged = Val(InputBox("Введите возраст", "", ""))

Staff(i). Sex = InputBox("Введите пол: True (муж.), False (жен.)", "", "") Staff(i). Telef = Val(InputBox("Введите номер телефона", "", ""))

Next .

Аналогичным образом можно и выводить информацию. Для сокращения записи можно использовать конструкцию With ‹имя› ... End With, внутри которой указываются лишь обрабатываемые поля, предваренные точкой. Таким образом, предыдущий код может быть записан несколько короче:

For i = 0 To 9 With Staff(i)

.Name = InputBox("Введите фамилию", "","")

.Aged = Val(InputBox("Введите возраст", "", ""))

.Sex = InputBox("Введите пол: True (муж.), False (жен.)", "", "")

.Telef = Val(InputBox("Введите номер телефона", "", "")) End With

Next .

Предположим теперь, что из всех данных нужно выбрать лишь мужчин старше 50 лет:

56

For i = 0 To Ubound(Staff)

With Staff(i)

If .Sex And .Aged > 50 Then _

MsgBox .Name & ", Aged " & Format(.Aged)

End With

Next .

Теперь предположим, что одно из свойств нашего типа тоже пользовательское. Пусть это тип Student с полями: факультет, курс, наличие стипендии (да, нет). Определим типы:

Type Student

Department As String

Course As Byte

Scholarship As Boolean

End Type

Type Person

Name As String*10

Aged As Byte

Sex As Boolean

Business As Student

Telef As Long

End Type .

и переменную Dim Casanova As Person. Теперь поле Business само имеет тип пользователя и свои собственные поля. Для того, чтобы отметить факт получения стипендии, следует выполнить присваивание

Casanova.Business.Scholarship = True,

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

рукции With ... End With:

With Casanova

.Name = ...

.Aged = ...

.Sex = ...

With .Business

.Department = ...

.Course = ...

.Scholarship = ...

End With

End With .

Отметим, что цикл For Each ... In ... Next с пользовательской переменной не работает, он используется лишь с типом Variant.

57

1.7.3.Сортировка массивов

Впрактике обработки массивов часто требуется (для облегчения поиска, например), чтобы информация содержалась в массиве в определенном порядке (числовая, например, в порядке возрастания значений, текстовая – в алфавитном порядке, и т.д.). Сортировка – довольно распространенная операция над массивами, поэтому в Excel, например, она включена в стандартное меню, а Mcd имеет функции sort() – сортировка одномерного массива (вектора) в порядке возрастания, csort() – сортировка матрицы по заданному столбцу, rsort() – сортировка матрицы по данной строке.

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

А. Выбор главного элемента. Будем упорядочивать массив по возрастанию элементов: просматривается весь (одномерный) ряд значений, выбирается наименьшее из них и ставится на первое место, затем среди оставшихся выбирается наименьшее и ставится на второе место, и т.д. Для одномерного числового массива А(n) в VBA алгоритм имеет вид:

For i = 0 To n: p = A(i): k = i

 

For j = i + 1 To n: q = A(j)

 

If q < p Then

'***выбор минимального элемента

p = q: k = j

 

End If

 

Next

 

q = A(i): A(i) = A(k): A(k) = q

'***перестановка элементов

Next .

 

Если бы требовалось упорядочить массив по убыванию, то следовало бы знак неравенства в третьей строке заменить на противоположный. Если массив двумерный, например, А(m, n) и его следует отсортировать по столбцу с номером s, то пришлось бы ввести дополнительный цикл:

For i = 0 To m: p = A(i, s): k = i

 

For j = i + 1 To m: q = A(j, s)

 

If q < p Then

'***выбор минимального элемента

p = q: k = j

 

End If

 

Next

 

For j = 0 To n

 

q = A(i, j): A(i, j) = A(k, j): A(k, j) = q '***перестановка элементов Next

Next .

Б. Метод "пузырька". В предыдущей ситуации в одномерном массиве просматриваются все соседние пары. Если в какой-нибудь па-

58

ре нарушается условие порядка, то элементы этой пары меняются местами. После такого просмотра самый большой элемент переместится в конец массива (бегущий "пузырек"). Далее просматривается массив с начала до предпоследнего элемента и выполняется та же процедура проверки. Затем до предпредпоследнего, и т.д. Приведем соответствующий код для одномерного массива A(n).

For i = n To 1 Step -1

For j = 0 To i – 1

If A(j) > A(j + 1) Then

q = A(j): A(j) = A(j + 1): A(j + 1) = A(j)

End If

Next

Next .

Аналогично предыдущему, если необходимо упорядочить двумерный массив А(m, n) по столбцу с номером s, то следует поступить в соответствии с кодом:

For i = m To 1 Step -1

For j = 0 To i – 1

If A(j, s) > A(j + 1, s) Then For k = 0 To n

q = A(j, k): A(j, k) = A(j + 1, k): A(j + 1, k) = A(j, k) Next

End If

Next Next .

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

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

Рассмотрим популярный алгоритм двоичного выбора: выбранный диапазон индексов массива делится пополам и выясняется, в какую половину следует вводить данные, выбранная половина снова делится пополам, и т.д. до тех пор, пока окончательный участок не будет содержать единственный элемент, сравнение с которым очевидно. Например, в список (-2, 0, 1.5, 7, 7.2, 11) нужно ввести число 8.6. Очевидно, (так как 8.6 > 1.5) нужно выбрать вторую половину списка (7, 7.2, 11). В ней в середине стоит число 7.2 < 8.6, значит, число

59

помещается между 7.2 и 11. Осталось передвинуть последнее число в следующую позицию (возможно, увеличив размер массива), а в освободившееся место поместить 8.6. Таким образом, процесс вставки в массив состоит из двух операций: первая – поиск места вставки, вторая – освобождение места для новой информации.

Запишем первую операцию – поиск места, т.е. индекса элемента массива, после которого нужно вставлять новый. Идея алгоритма допускает рекурсивный поиск (см. п. 1.4). Запишем его в виде функции.

Function Index(a() As ‹тип›, b As ‹тип›, p As Integer, q As Integer) As Integer Dim res As Integer, k As Integer

If b < a(p) Then res = p – 1

ElseIf b > a(q) Then res = q

ElseIf q – p = 1 Then res = p

Else

k = (p + q) \ 2 '***целочисленное деление

If b < a(k) Then res = Index(a(), b, p, k) Else res = Index(a(), b, k, q) End If: Index = res

End Function .

Вторая операция – вставка – реализуется проще: если возвращаемый предыдущей функцией индекс отрицательный (при индексации с нуля), то все элементы массива нужно сдвинуть и в первую позицию вставить новую информацию, иначе это нужно сделать с указанного индекса (предполагается, что размер массива это позволяет):

Sub Insert(ByRef a() As ‹тип›, b As ‹тип›, n As Integer) Dim i As Integer, m As Integer

m = Index(a(), b, 0, n) If m < 0 Then

For i = n To 0 Step -1 a(i + 1) = a(i)

Next: a(0) = b

ElseIf m = n Then a(n + 1) = b

Else

For i = n To m + 1 Step -1 a(i + 1) = a(i)

Next: a(m + 1) = b End If

End Sub .

Текстовые массивы (списки) сортируются точно также, поскольку знаки "<", ">" сравнивают коды символов (лексикографически). Если сортируются массивы пользовательского типа, то в сравнении указывается то поле, по которому выполняется сортировка. Например, если необходимо организовать массив данных типа Person (пример п. 1.7.2) в алфавитном порядке (по фамилиям), то в функции

60

Index(a() As Person, b As Person, p As Integer, q As Integer)

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

If b.Name < a(p).Name Then

...

В Mcd, в целом, функции выглядят проще. Кроме того, встроенная функция stack(, , ) (объединение массивов по вертикали) позволяет автоматически увеличивать массив данных до необходимых размеров.

Index(a, b, p, q) : = res ← p - 1 if b < ap

otherwise

res ← q if b > aq otherwise

res ← p if q - p = 1 otherwise

k← trunc p+q

2

res ← if(b < ak, Index(a, b, p, k), Index(a, b, k, p)

res

Insert(a, b) : = a ← stack(a, (0)), n ← last(a), m ← Index(a, b, 0, n - 1) if m < 0

for i n - 1 .. 0

ai + 1 ← ai

a0 ← b

an ← b if m = n – 1 otherwise

for i n - 1 .. m + 1

ai + 1 ← ai am + 1 ← b

a

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

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

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