Розробка уроку

Тема: cпособи реалізації структур даних. Застосування на практиці різних структур даних. Відмінність між формальними і фактичними параметрами. Поняття масиву, списку, стеку, черги, хеш-таблиці (словника) мовою Pascal.

Учень повинен пояснювати:

Учень повинен вміти:

Обладнання: комп’ютери зі встановленими ОС та інтегрованим середовищем програмування мовою Pascal (наприклад, Free Pascal, Lazarus, Pascal ABC) або стійким сполученням з інтернетом для роботи з online-сервісами.

Структура уроку

  1. Організаційний момент.
  2. Актуалізація опорних знань.
  3. Вивчення нового матеріалу.
  4. Закріплення вивченого матеріалу.
  5. Підведення підсумків уроку.
  6. Домашнє завдання.

Хід уроку

1. Організаційний момент
Перевірка присутності та готовності учнів до уроку. Перевірка виконання домашнього завдання.

Мотивація навчання: на цьому уроці ми познайомимося з різними структурами даних і навчимося використовувати їх для розв'язування задач. Ми проаналізуємо переваги їхнього використання. Це сприятиме розвитку логічного мислення і надасть можливість використовувати програми для ефективного розв'язання і навчальних, і практичних задач.

2. Актуалізація опорних знань

Величинаодиниця даних, якими оперує програма. Вона має такі властивості:

З кожною величиною (або її складовою для структурованої величини) пов'язана ділянка оперативної пам’яті, куди її під час роботи програми заносять та у якій зберегають. Цю ділянку визначено її адресою. Тут під адресою розуміють адресу першого байту з послідовновних щодо адресації байтів, відведених для збереження величини. Назва змінної слугує назвою цієї послідовності байтів під час виконання програми.

Сталавид даних, значення яких заборонено змінювати протягом виконання програми.

Зміннавид даних, значення яких дозволено змінювати протягом виконання програми.

У мові Pascal кожна стала чи змінна має тип, який оголошують на початку програми, процедури чи функції в розділі опису змінних чи сталих. Наприклад, таким чином

program own
var  
  a: integer;
  b: boolean;
  c: char;
  r: real;
  s: string;
  t: set of byte;
  u: array [0..99] of qword;
  v: record j: integer;
            r: real
     end;
  w: text;
BEGIN END.

Типи даних мови Pascal поділяють на стандартні (вбудовані у мови програмування) та нестандартні, які потрібно описати в коді.

Стандартні типи даних мови Pascal

Для порядкового типу множина значень скінчена, а кожний її елемент має свій порядковий номер — невід'ємне ціле число.

Всі величини поділяють на:

Підпрограмалогічно незалежний фрагмент коду, оформлений спеціальним чином згідно з правилами мови програмування, до якого можна (багаторазово) звернутися з будь-якого місця програми.

Процедурапідпрограма, призначена лише для виконання певних дій.

Функціяпідпрограма, що повертає у місце виклику певне значення, що є результатом її виконання.

В описі підпрограми необхідно:

Синтаксис виклику підпрограми

procedure назва_процедури
  (   назва_параметра-сталої  : тип_параметра-сталої;
  var назва_параметра-змінної : тип_параметра-змінної);
  {опис локальних змінних при потребі}
begin 
  {тіло процедури}
end;

function назва_фукції
  (   назва_параметра-сталої  : тип_параметра-сталої;
  var назва_параметра-змінної : тип_параметра-змінної):
  тип значення функції;
  {опис локальних змінних при потребі}
begin 
  {тіло функції}
end;

Тіло підпрограми функціївказівки підпрограми, записані між службовими словами begin та end.

Різницю між параметром-сталою і параметром-змінною ілюструє така програма.

var a,b: byte;
procedure p(x: byte; var y: byte);
begin 
  x:= 1;
  y:= 1;
end;
begin
  a:=0;
  b:=0;
  p(a, b);
  writeln(a,' ',b);
end.

Ця програма має таке виведення:

0 1

Побічний ефект функціїбудь-яка зміна функцією стану програмного середовища, крім повернення результату (зміна значень глобальних змінних, виділення й звільнення пам'яті, введення-виведення тощо).

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

3. Вивчення нового матеріалу

Структура данихце спосіб організації даних і операцій з ними.

Відповідним чином побудовані структури даних надають можливість оптимізувати використання машинного часу й пам’яті комп’ютера. Раніше вже розглянуто вбудовану (тобто описану у стандарті мови програмування) структуру: масив.

Списокце лінійно упорядкована даних, кількість яких можна змінювати під час виконання програми. Така структура даних втілює математичне поняття змінюваної послідовності з цілими індексами (номерами).

Пам’ять для розташування нового елемента буде виділено чи вивільнено динамічно при потребі. Для реалізації такої структури даних необхідно використовувати покажчики (на область пам'яті), тому основним елементом такого списку є запис, що має одне або кілька полів, які є покажчиками на його власний тип. Наприклад,

type
  p=^t;
  t=record
      r: real;
      n: p;
    end;

У поданому прикладі коду описано тип-запис t. Його поле n (англійською next — наступний) має тип p і є покажчиком на дані типу t. Наявність таких полів у записах дає можливість зв’язувати окремі записи у списки.

Для ефективного використання списків необхідно навчитися виконувати над списками операції створення, заповнення даними, додавання та вилучення запису — див. детально прокоментований приклад з таким виведенням даних.

З цього прикладу видно, що навіть найпростіші операції вимагають уваги до роботи зі списком.

Cтек (stack) — структура даних, що є сукупністю елементів і в якій останній доданий елемент буде вилучено першим.

Інакше кажучи, діє принцип: «останнім увійшов, першим вийшов». Структура даних «стек» є структурою послідовного доступу.

Зазвичай стек подають лінійним (одновимірним) масивом. Індекс останнього додученого елемента стека називають вершиною. Хоча можливі й інші реалізації. Наприклад, з використанням динамічного розподілу пам'яті як у поданій вище реалізації списку.

Для пояснення поняття «стек» проведемо аналогію зі стосом книг. Додаючи нову книжку у стос, ми кладемо її зверху (новий елемент у стек додають до кінця масиву). Для того, щоб узяти книжку із середини стосу, потрібно всі верхні книжки по одній зняти у зворотному порядку щодо їх складання. Таким чином, операція читання зі стека відбувається також із кінця масиву, але у порядку, зворотному до порядку заповнення стека.

Можливі такі критичні ситуації:

У поданих далі прикладах процедур мовою Pascal стек подано такими глобальними змінними: лінійним масивом a з вершиною i. Передбачено виведення або зчитування значення елемента перед виконанням відповідної дії:

Черга (queue) — лінійно упорядкована структура даних, у в якій перший доданий елемент буде вилучено першим.

Інакше кажучи, діє принцип: «першим увійшов, першим вийшов». Структура даних «черга» є структурою послідовного доступу.

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

Нераціонально дослівно таким чином опрацьовувати елементи черги: після вилучення з черги першого елемента всі інші потрібно зсунути на одну позицію. Така схема опрацювання забирає багато часу. Є простий вихід: після опрацювання елемента черги індекс початку черги має вказувати на наступний елемент.

Наступним елементом після досягнення кінця масиву буде його перший елемент.
Без такого зациклення програма зможе опрацювати лише таку кількість елементів, що дорівнює довжині масиву. А з таким зацикленням — довільну кількість елементів за умови, що у довільний момент довжина черги n не перевищує довжину масиву na. Інакше кажучи, черга ніби повзає по колу, вздовж якого розташовано елементи масиву. Для стислості коду доцільно діапазон значень індексів масиву n вибрати таким: 0..(n ‒ 1). Саме так зроблено у поданих нижче прикладах.

Запис y чергу неможливий, коли кількість елементів черги дорівнює кількості елементів масиву. Читання з вилученням елемента черги неможливе, коли черга порожня. У поданих далі прикладах процедур мовою Pascal чергу подано такими глобальними змінними: лінійним масивом a, початком черги j0, кінцем черги j1 та кількістю елементів черги n. Передбачено виведення або зчитування значення елемента перед виконанням відповідної дії:

Чергу використовують у багатьох випадках. Наприклад, при встановленні, які вершини графа можна досягнути з даної вершини за 1, 2, 3, ... кроки. Графом може бути певна транспортна схема з вершинами — населеними пунктами.

Запровадимо теоретичне поняття для опису структур даних, заснованих на геш-таблицях.

Геш-таблицяструктура даних, що дозволяє зберігати пари (ключ, значення) і здійснювати три операції: операцію додавання нової пари, операцію пошуку і операцію видалення за ключем.

Інакше кажучи, геш-таблиця — це звичайний масив з незвичайною адресацією, яку задають геш-функцією.

Ефективні методи пошуку мінімізують число порівнянь. В ідеалі бажано мати організацію таблиці без непотрібних порівнянь. Чи можлива така організація? Розглянемо це на прикладі ведення обліку організації виробництва.

Нехай деяка фірма випускає деталі і вимушена кодувати їх послідовністю з 7 цифр. Для застосування прямої індексації з використанням повного 7-значного ключа потрібно використати масив довжини 107 = 10 000 000. Малоймовірно, що фірма має більше ніж тисячу найменувань виробів. Для оптимального використання пам'яті необхідно перетворювати ключ-код, наприклад, у ціле число всередині істотно меншого діапазону — від 1 до 1000. Тоді для зберігання даних достатньо масиву з 1000 елементів з цілими індексами від 0 до 999 включно. При використанні для індексації тррьох останніх цифр початкового коду хеш-функція h може мати такий вигляд:

h(k): = k mod 1000;

Але може трапитися таке: для двох різних найменувань товару останні 3 їхніх кодів збігаються. Такий збіг називають колізією.

Колізіязбіг значень геш-функції при різних значеннях її аргументів.

Досконала хеш-функція — це хеш-функція, яка не породжує колізій.

Усунути колізії при гешуванні можна двома способами:

Метод відкритої адресації. Найпростішим способом усунення колізій є приміщення запису в наступний вільний елемент масиву. Нехай потрібно внести у масив a значення, що відповідає ключу k за умови, що комірка масиву a з індексои h(k) вже містить запис з іншим ключем. У цьому випадку можна застосувати функцію rh до значення h(k) для переходу до наступної клітини. І застосовувати цю функцію потрібно до тих пір, поки не буде знайдено вільну клітинку, куди можна розташувати запис. Цей підхід проілюструємо наступним кодом для хешування масивом а довжини 10.

const n=10;
      c: array[0..n-1] of integer=(0,5,8,15,18,10,20,21,22,32);
var   a: array[0..n-1] of integer;
      b: array[0..n-1] of boolean; {чи зайнято місце?}
      j: integer;
function  h(k: integer): integer;  begin  h :=       k mod 10; end;
function rh(k: integer): integer;  begin rh := (k + 1) mod 10; end;
procedure i(k: integer); {внесення значення k}
var j: integer;
begin
  j:=h(k);
  while (a[j]<>k) and b[j] do j:= rh(j); 
  a[j]:=k;
  b[j]:=true 
end;
Begin
  for j:=0 to n-1 do b[j]:=false;
  for j:=0 to n-1 do i(c[j]);
  for j:=0 to n-1 do write(j:4);     writeln;
  for j:=0 to n-1 do write(a[j]:4);
End.

Ця програма здійснює таке виведення.

 
   0   1   2   3   4   5   6   7   8   9
   0  10  20  21  22   5  15  32   8  18

Замість пошуку вільного місця у безпосередній близкості інколи здійснюють багаторазове (зазвичай подвійне) хешування, коли послідовно застосовують набір різних хеш-функцій. Тоді і заповнення хеш-таблиці, і доступ до елементів може виявитися вельми хитромудрим. Хеш-адреса елемента з даними ключем ніби відкрита у тому розумінні, що її поступово уточнюють. При цьому всі операції проводять в одновимірному масиві.

Недоліки методу відкритої адресації

Метод ланцюгів полягає у створенні списку (ланцюга) з усіх пар "над значенням" геш-функції, для якого виявлено колізію.

Розглянемо приклад програмної реалізації цього методу.

const n=10;
      c: array[0..n-1] of integer=(0,5,8,15,18,10,20,21,22,32);
      v: array[0..n-1] of string=('v0','v1','v2','v3','v4',
                                  'v5','v6','v7','v8','v9');
var   j: integer;
      s: string;
type
link = ^node;
node = record    // вузол для зберігання пари
   key: integer; // ключ
   val: string;  // значення
  next: link;    // вказівник на наступний вузол
end;     // для того самого значення геш-функції 

var a: array [0..n-1] of link;
    p: link;

function h(k: integer): integer;
begin
  h:= (k mod n);
end;

function search (key1: integer; val1: string): link; 
// Повернення адреси вузла з ключем key1.
// Якщо такого немає, створення нового вузла
// списку "над значенням геш-функції".
var q, p, s: link;
          i: integer;
begin
  i:=h(key1);
  q := nil;
  p := a[i];
  while p <> nil do
  begin
    if (p^.key = key1) then begin search := p;  exit; end;
    q := p;
    p := p^.next;
  end;
  new (s); {Якщо ключ не знайдено, вставляємо новий запис}
  s^.key  := key1;
  s^.val  := val1;
  s^.next := nil;
  if (q = nil) then a[i] := s
               else q^.next := s;
  search := s;
end;

Begin
  for j:=0 to n-1 do a[j]:=nil; // Оголошення таблиці порожньою
  for j:=0 to n-1 do search(c[j], v[j]); // Заповнення таблиці
  for j:=0 to n-1 do                     // Виведення таблиці
  begin  
    writeln('j = ',j);
    p := a[j];
    while p <> nil do
    begin
      writeln('key =',p^.key:2,'  value =',p^.val);
      p := p^.next;
    end;
  end;
End.

Ця програма має таке виведення.

j = 0
key = 0  value =v0
key =10  value =v5
key =20  value =v6
j = 1
key =21  value =v7
j = 2
key =22  value =v8
key =32  value =v9
j = 3
j = 4
j = 5
key = 5  value =v1
key =15  value =v3
j = 6
j = 7
j = 8
key = 8  value =v2
key =18  value =v4
j = 9

Тут після рядка: j = … відображено вміст пар (ключ, значення) з одним і тим самим значенням j геш-функції.

Видалення вузла з таблиці, побудованої методом ланцюгів, полягає у видаленні вузла зі списку. При потребі список можна переупорядкувати для збільшення ефективності пошуку.

ОсновниЙ недолік методу ланцюгівдля вузлів покажчиків потрібно використати додатковий простір.

Вибір хеш-функції. Геш-функція тим краща, чим менше колізій створоено при хешуванні. Не можливо визначити, чи буде конкретна геш-функція розподіляти ключі правильно, якщо ці ключі заздалегідь не відомі. Часто використовують такі геш-функції для цілих ключів.

Інколи вдається взагалі уникнути колізій. Наприклад, якщо всі ключі елементів відомо заздалегідь. Тоді для них можна знайти деяку досконалу геш-функцію, яка розподілить їх за комірками геш-таблиці без колізій. Геш-таблиці, які використовують подібні геш-функції, не потребують механізму розв'язання колізій. Їх називаються геш-таблицями з прямою адресацією.

Важлива властивість геш-таблиць полягає у тому, що при деяких розумних припущеннях всі три операції (пошук, вставлення і видалення елементів) зазвичай виконують за час O(1). Але при цьому не гарантовано, що час виконання окремої операції малий, з певною імовірністю час може бути сумірним із пошуком у списку. З ростом коефіцієнта заповнення таблиці ця імовірність, і, відповідно, середній час виконання операцій, ростуть. Тому при досягненні деякого значення коефіцієнта заповнення необхідно здійснювати перебудову індексу геш-таблиці: збільшити розмір таблиці і заново додати у порожню геш-таблицю всі пари.

Асоціати́вний маси́в (associative array), словник, хеш (dictionary, hash, associative container, map, mapping, finite map) — абстрактний тип даних (інтерфейс до сховища даних), що дозволяє зберігати дані у вигляді набору пар ключ — значення та доступом до значень за їх ключем.

Втілення асоціативних масивів зазвичай підтримують операції додавання пари, пошуку й видалення пари за ключем. Ці три операції часто доповнюються іншими: видалення всіх записів; перебір усіх наявних пар знаходження пару з найменшим чи найбыльшим значенням ключа.

Підтримка асоціативних масивів з допомогою вбудованих засобів є в багатьох інтерпретованих мовах програмування високого рівня (наприклад, PHP, Python, Ruby, JavaScript). У C++ асоціативний масив підтримано за допомогою класу map, реалізованого на основі дерева пошуку. У мовах Ruby й Python використовують хеш-таблиці. Є й інші реалізації.

У Pascal'і не передбачає вбудованих засобів втілення асоціативних словників. У цій навчальній мові їх потрібно втілювати самостійно.

Найпростіше втілення можна здійснити з допомогою звичайного масиву, елементами якого є пари (ключ, значення). Для прискорення операції пошуку можна упорядкувати елементи масиву за зростанням ключа і здійснювати пошук методом поділу навпіл. На жаль, у цьому випадку додавання нової пари вимагатиме багато часу, бо вимагатиме «розсування» елементів масиву, щоб в порожнє місце, що утвориться, розташувати нову пару.

4. Інструктаж з ТБ
5. Вироблення практичних навичок


Завдання 1. Створити програму з описом і викликом таких процедур:

Програми повинна:

Виведення має бути таким:

 1 2 3 4 5 6 7 8 9
 1 2 3 4 5 0 6 7 8 9
 1 2 3 5 0 6 7 8 9

Порівняти створену програму з демонстраційним розв'язанням.

Завдання 2. Створити програму з описом і викликом таких процедур:

Програми повинна:

Виведення має бути таким:

9 8 7 6 5 4 3 2 1 
0 9 8 7 6 5 4 3 2 1 
6 5 4 3 2 1

Порівняти створену програму з демонстраційним розв'язанням.

Завдання 3. Дано текстовий файл input.txt. Кожний рядок файлу містить слова, розділені пробілами. За один перегляд файлу вивести у перший рядок файлу output.txt слова, які складаються не більше ніж з 3 літер, а у другий рядок — решту, зберігаючи порядок розташування слів у файлі.

Вказівки до виконання

  1. Зчитувати файл посимвольно, формуючи слова.

  2. Формування слова:
    • починати з першого символа, відмінного від пробілу;
    • завершити тоді, коли черговий символ пробіл.
  3. Якщо отримано слово довжиною не більше 3 символів, то зразу виводити його у файл. Інакше заносити його до черги.

  4. Чергу реалізувати за допомогою вказівників і динамічного розподілу пам'яті.

  5. Після завершення перегляду файлу, вивести слова з черги на екран.

Порівняти створену програму з демонстраційним розв'язанням.

Завдання 4. Кожний рядок файлу dictionary.txt два непорожніх слова з літер латиниці, розділені пробілом. Кожний рядок файлу query.txt містить по одному слову з літер латиниці. Створити програму, яка:

Порівняти створену програму з демонстраційним розв'язанням.

6. Підведення підсумків уроку
Виставлення оцінок.

7. Домашнє завдання
Вивчити навчальний матеріал уроку. При потребі доробити розв’язання задач.


Текст упорядкувала Котелевець Наталія Валентинівна, вчитель Броварської спеціалі­зованої школи І-ІІІ ступенів № 7 міста Бровари Київської області, під час виконання випускної роботи на курсах підвищення кваліфікації з 29.10.2018 по 02.11.2018.