К содержанию

goldline.GIF (303 bytes)

3. РЕШЕНИЕ АЛГОРИТМИЧЕСКИХ ЗАДАЧ

goldline.GIF (303 bytes)


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

Задача 1

     Рассмотрим одну из таких задач. Имеется одномерный массив чисел. Необходимо найти наименьшее из них.

     Для решения этой задачи нам потребуется какой-нибудь компонент, при помощи которого можно было бы вводить эти числа, сохранять и открывать их. В качестве такого компонента можно выбрать ранее используемые нами компоненты StringGrid или Memo, либо более мощный текстовый редактор RichEdit. Этот компонент находится на закладке Additional. Выберем, например, последний из них.

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

     Создадим новое приложение. Сразу сохраним его модуль под именем Main.pas, а приложение под именем Algs.dproj в новой папке с именем Algs.

Конструирование формы для решения задачи 1

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

     Дайте форме имя fMain, положите на нее этот компонент и дайте его свойству Align значение alClient. При этом компонент накроет всю клиентскую часть формы. Щелкните на компоненте правой клавишей мыши и создайте на нем новую страницу (команда New Page), дайте странице название Задача 1. Положите на эту страницу панель и прижмите ее к верхнему краю формы, положив свойство Align равным значению alTop. Уберите надпись на панели и измените ее цвет (свойство Color) на серый.

    Положите под панелью компонент RichEdit и измените его свойство Align на значение alClient. Компонент накроет всю оставшуюся часть страницы. Измените его имя на Re (сокращение от RichEdit) либо дайте ему другое подходящее с Вашей точки зрения имя. Замените шрифт компонента на Courier New, размер 10.

     Положите на панель 4 кнопки SpeedButton. Они потребуются нам для

1) загрузки чисел из файла,
2) сохранения чисел в файле,
3) запуска процесса решения задачи и
4) очистки редактора RichEdit от данных.

      Дайте кнопкам подходящие имена (свойство Name). Можно первую из них назвать sbOpenData. Здесь sb - краткое напоминание от типе компонента, т. е. SpeedButton, Open - открыть, Data - данные. Вторую кнопку можно назвать sbSaveData, третью - sbExec, четвертую - sbClearData.

     Положите на кнопки подходящие картинки.

     Установите свойство ShowHint (показывать всплывающую подсказку) каждой кнопки в true. Далее для 1-й кнопки дайте Hint (всплывающая подсказка) значение Открыть, для 2-й кнопки - Сохранить, для 3-й - Решить задачу, для 4-й - Стереть данные.

     Кроме того положите на страницу еще 2 компонента, которые в работающем приложении скрыты - это диалоговые компоненты OpenDialog и SaveDialog. Они находятся на закладке Dialogs. Первый из них позволит нам открывать файл данных, а второй сохранять его. Для краткости дайте им имена Od и Sd, соответственно.   

     Конструирование интерфейса для решения задачи 1 закончено.

     В результате форма примет вид, который показан на рис. 3.1.

Рис. 3.1. Вид формы со страницей для решения задачи 1

     Запустите приложение и проверьте работают ли всплывающие подсказки - при наведении на кнопку должна на мгновение появляться подсказка.    

Программирование решения задачи 1

     Сначала запрограммируем процесс стирания данных из редактора Re, который будем запускать щелчком на кнопке sbClearData. Для этого создадим для этой кнопки событие OnClick и внесем необходимый программный код:

procedure TfMain.sbClearDataClick(Sender: TObject);
begin
 if MessageDlg('Удалить данные?',mtWarning, mbOKCancel,0) = mrOK
  then Re.Lines.Clear;
end;

     Поскольку программа удаления данных является опасной операцией, то прежде чем их удалять попросим чтобы программа дала разрешение на эту операцию. При выполнении этого кода будет вызвана функция Delphi с именем MessageDlg, как показано на рис. 3.2. Заголовок окна имеет тип предупреждения (mtWarning), в его окне будет текст Удалить данные? и две кнопки OK и Cancel (mbOKCancel).

Рис. 3.2. Диалоговое окно подтверждения операции удаления

     Если (if) пользователь щелкнет на кнопке OK (mrOK), то (then) будет выполнен оператор Re.Lines.Clear, т. е. очистка (Clear) строк (Lines) компонента Re. В противном случае очистка не будет выполняться.

     Запустите приложение, наберите в редакторе какой-нибудь текст и проверьте работу этого программного кода. 

     Теперь можно составить код для сохранения данных, которые будут содержаться в редакторе Re. Создайте событие для щелчка на кнопке sbSaveData. Код события показан ниже.

procedure TfMain.sbSaveDataClick(Sender: TObject);
begin
 if Sd.Execute then Re.Lines.SaveToFile(Sd.FileName);
end;

     При выполнении оператора тела обработчика события сначала будет открыто окно для сохранения файла. Это осуществляется логической функцией Sd.Execute диалога Sd сохранения файла. Здесь необходимо выбрать папку и имя файла. Пример окна диалога показан на рис. 3.3, где файлу дано имя data.rtf поскольку формат файла .rtf является самым подходящим для данных текстового редактора RichEdit. Если теперь в окне сохранения нажать кнопку Сохранить, то будет выполнена команда сохранения (SaveToFile) строк (Lines) редактора Re в том файле, который выбран в окне диалога (Sd.FileName).

Рис. 3.3. Диалог сохранения файла с данными

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

     Теперь запрограммируем кнопку чтения данных из файла.

procedure TfMain.sbOpenDataClick(Sender: TObject);
begin
 if Od.Execute then Re.Lines.LoadFromFile(Od.FileName);
end;

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

     Осталось запрограммировать нахождение наименьшего из чисел, которые расположены в столбик в редакторе.

     Этот процесс разобьем на несколько процедур:

1) чтение строк из редактора в числовой массив,

2) нахождение наименьшего элемента в массиве,

3) нахождение и выделение наименьшего числа в редакторе.

     Сначала объявим целочисленный массив, например, с именем a. Для этого вставьте в текст программного кода файла Main.pas после объявления переменной формы fMain объявление этого массива. Теперь фрагмент кода будет выглядеть так

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

     Таким образом функция чтения строк в массив является логической и примет значение true, если в строках редактора Re есть целые числа, и false, если это не так.

     Ниже приведен код функции ReadArray с комментариями, текст которой следует расположить под текстом последней процедуры обработчика событий.

function ReadArray: boolean; // логическая функция чтения данных
 var i,j,k: Integer; // объявление целочисленных переменных
begin
 a:= Nil; // очищаем массив от элементов
 for i:= 0 to fMain.Re.Lines.Count-1 do // проход по строкам редактора Re
  if TryStrToInt(Trim(fMain.Re.Lines[i]),j) then // если строка - число, то
   begin
    k:= Length(a); // определяем число элементов в массиве
    SetLength(a,k+1); // добавляем новый элемент в массив
    a[k]:= j; записываем число j в ячейку k массива a
   end;
 
Result:= Length(a) > 0; // функция истинна, когда в массиве есть элементы
end
;

     Теперь составим и расположим ниже целочисленную функцию GetMinValue нахождения наименьшего элемента в массиве

function GetMinValue(var j: Integer): Integer; // функция GetMinValue
 var i: Integer; // объявление вспомогательных переменных
begin
 Result:= Length(a); // результат равен числу элементов в массиве
 if Result = 0 then exit; // в массиве нет чисел, выходим из функции
 j:= a[0]; // сначала номер min элемента равен 0
 for i:= 1 to Result-1 do // проход по массиву
  if a[i] < j then j:= a[i]; // текущий номер min элемента 
end;

     Составим далее процедуру SelMinValue выделения в Re строки с номером k жирным и красным цветом

procedure SelMinValue(k: Integer);
 var i,j: Integer; // объявление вспомогательных переменных
begin
 With fMain.Re do // работа с компонентом Re формы fMain
  begin
   for i:= 0 to Lines.Count-1 do // проход по строкам Re
    if TryStrToInt(Trim(fMain.Re.Lines[i]),j) then // определения значения в строке
     if k = j then // если число совпадает с k
      begin
       SelStart:= SendMessage(Handle, EM_LINEINDEX, i, 0); // отсюда ...
       SelLength:= Length(Lines[i]); // .. до сюда выделить в Re
       SelAttributes.Color := clRed; // выделенное покрасить красным цветом
       SelAttributes.Style := [fsBold]; // выделенное сделать жирным
       SelLength:= 0; // снять выделение с фрагмента
      end;
  end;
end;

     Составим теперь процедуру, которая снимет жирный и красный цвет со строк, если такие есть

procedure SetSimpleFont;
begin
 With fMain.Re do // работа с компонентом Re формы fMain
  begin
   SelStart:= 0; // выделяем с 0-позиции
   SelLength:= Length(Text); // на длину всего текста
   SelAttributes.Style:= []; // снимаем с выделенного жирное
   SelAttributes.Color := clBlack; // цвет выделенного делаем черным
   SelLength:= 0; // снимаем выделение
  end;
end
   

     Наконец, составим и расположим под ними обработчик события для щелчка на кнопке sbExec

procedure TfMain.sbExecClick(Sender: TObject);
 var j: Integer;
begin
 SetSimpleFont; // убираем в редакторе жирное и красный цвет
 if ReadArray then // если данные прочитаны
  if GetMinValue(j) > 0 then // если их число > 0 то
   SelMinValue(j); // выделяем min элемент жирным и красным
 a:= Nil; // освобождаем память, занятую массивом
end;
 

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

Рис. 3.4. Найдены и выделены наименьшие элементы массива

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

Задача 2

     Рассмотрим вторую алгоритмическую задачу. Имеется два одномерных числовых массива одинаковой длины. Найти третий массив, который равен поэлементной сумме двух первых массивов.

     Модернизация формы для решения задачи 2

     Добавьте на компонент PageControl новую страницу и дайте ей название Задача 2, как показано на рис. 3.5.

     Скопируйте с первой страницы панель на вторую страницу. Для этого щелкните на панели чтоб выделить ее и выполните команду Edit / Copy. Перейдите на вторую страницу и щелкните на ней. Далее выполните команду Edit / Paste. При этом панель скопируется с первой страницы вместе с расположенными на ней кнопками. Уберите у этих кнопок ссылки на события. Для этого щелкните на кнопке, перейдите в Инспекторе объектов на закладку Events и уберите ссылку на событие OnClick, которая тоже скопировалось.

     При решении задачи для размещения массивов удобно воспользоваться уже известным нам компонентом StringGrid. Положите сетку под панель, задайте в нем число строк 8, столбцов 3 и растяните ее на всю оставшуюся клиентскую часть формы подбором нужно значения свойства Align.

     Чтоб можно было менять число строк положите на панель компонент SpinEdit (закладка Samples) и дайте его свойству Value значение 8 (равное числу строк StringGrid). Слева от него положите метку и создайте на ней надпись Строк, как показано на рис. 3.5.

Рис. 3.5. Интерфейс приложения для решения задачи 2

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

     Для сохранения данных в StringGrid можно использовать процедуру

procedure SaveStringGridToTabbedTextFile(Sg: TStringGrid; FileName: TFileName);
 var F: TextFile;
 i,j: Integer;
begin
 AssignFile(F,FileName); Rewrite(F);
 for i:= 0 to Sg.RowCount-1 do
  begin
   for j:= 0 to Sg.ColCount-1 do
    Write(F,Sg.cells[j,i]+#9);
   Writeln(F);
  end;
 CloseFile(F);
end;

     Тогда обработчик события для сохранения сетки в файле может иметь вид

procedure TfMain.sbSgSaveClick(Sender: TObject);
begin
 if Sd.Execute then SaveStringGridToTabbedTextFile(Sg,Sd.FileName);
end;

     Данные сетки лучше сохранять в текстовом файле, например в файле с именем sg.txt.   

     Для загрузки данных  из файла в StringGrid можно использовать процедуру

procedure LoadStringGridFromTabbedTextFile(var Sg: TStringGrid; FileName: TFileName);
 var f: TextFile;
 i,j,k: Integer;
 s: AnsiString;
begin
 AssignFile(F,FileName);
 Reset(F);
 i:= -1; j:=0;
 While not Eof(F) do
  begin
   Readln(F,s);
   j:= 0; Inc(i);
   While Trim(s) <> '' do
    begin
     k:= Pos(#9,s);
     Sg.cells[j,i]:= Copy(s,1,k-1);
     s:= Copy(s,k+1,MaxInt);
     Inc(j);
    end;
  end;
 Sg.RowCount:= i+1;
 Sg.ColCount:= j;
 CloseFile(f);
end;

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

     Для очистки сетки от данных можно использовать процедуру

procedure SgClear;
 var i,j: Integer;
begin
 With fMain.Sg do
  if MessageDlg('Очистить сетку?',mtWarning, mbOKCancel,0) = mrOK then
   for i:= 0 to RowCount-1 do
    for j:= 0 to ColCount-1 do
     Cells[j,i]:= '';
end;

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

     Материал, который касается решения задачи 1 позволяет решить задачу 2. Пример решения показан на рис. 3.6.

   

Рис. 3.6. Пример решения задачи 2

     Ниже приведен обработчик события для решения задачи.

procedure TfMain.sbSgExecClick(Sender: TObject);
 var i,k1,k2: Integer;
begin
 Sg.ColCount:= 4; // число столбцов сетки делаем равным 4
 With Sg do
  for i:= 1 to RowCount-1 do // проход по строкам сетки
   begin
    Cells[3,i]:= 'Ошибка'; // значение текущей ячейки результирующего массива
    if TryStrToInt(Cells[1,i],k1) then // если в 1-м массиве число
     if TryStrToInt(Cells[2,i],k2) then // и если во 2-м массиве число
      Cells[3,i]:= IntToStr(k1+k2); // то складываем числа и заносим в 3-й массив
   end;
end;

     Приведем пример сетки с ошибочными исходными данными и результатом его обработки

Рис. 3.7. Пример решения задачи 2 с ошибочными данными

    

     Закройте формы и вернитесь в среду Delphi.

 


Индивидуальные задания

     Добавьте дополнительную кнопку и решите задачу по одному из следующих вариантов     

     Вариант 01. Решите задачу 2 для случая произведения массивов.

     Вариант 02. Решите задачу 1 закраской наименьшего числа зеленым цветом.

     Вариант 03. Решите задачу 2 для случая разности массивов.

     Вариант 04. Решите задачу 2 для случая наибольшего элемента соседних пар чисел в массивах.

     Вариант 05. Решите задачу 2 для случая наименьшего элемента соседних пар чисел в массивах.

     Вариант 06. Решите задачу 2 для случая перестановки соседних чисел в исходных массивах.

     Вариант 07. Решите задачу 1 для случая нахождения наименьшего среди положительных чисел.

     Вариант 08. В задаче 2 во всей сетке замените положительные числа нулями.

     Вариант 09. В задаче 2 во всей сетке замените отрицательные числа на противоположные им положительные.

     Вариант 10. В задаче 2 очистите сетку от отрицательных чисел.

     Вариант 11. В задаче 2 замените все числа звездочками.

     Вариант 12. В задаче 1 найдите сумму всех чисел и выведите ее в окне MessageBox.

     Вариант 13. В задаче 2 найдите наименьшее число сетки выведите его в окне MessageBox.

     Вариант 14. В задаче 2 подсчитайте количество неправильно заданных чисел выведите его в окне MessageBox.

   Вариант 15. В задаче 2 найдите наибольшее число сетки выведите его в ячейку с координатами [0,0].

    Вариант 16. В задаче 2 замените отрицательные числа столбца 1 на сумму чисел столбца 2.

    Вариант 17. В задаче 2 замените нули столбца 1 суммой чисел столбца 2.

    Вариант 18. В задаче 2 замените нули столбца 2 наибольшим из чисел столбца 2.

К содержанию