Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Posibnyk_C_sum.doc
Скачиваний:
13
Добавлен:
29.08.2019
Размер:
1.63 Mб
Скачать

11 Сортування

11.1 Методи сортування

Сортувати можна дані будь-якого базового типу, представлені в довільному вигляді, однак найчастіше сортувати потрібно масиви та зв’язані списки. Постановкою задачі може бути визначений будь-який порядок розташування елементів у посортованому наборі, найчастіше за зростанням або спаданням. Це не змінює суті методів сортування, але для спрощення викладу тут матеріалу будемо вважати, що – за зростанням.

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

Оцінювати ефективность алгоритму сортування можна за різними параметрами, з них найважливішим є його швидкодія або час виконання програми. Оскільки всі алгоритми сортування, як правило, містять цикли й розгалуження, то при цьому береться до уваги кількість порівнянь та число перестановок елементів у наборі даних. Як одиницю виміру ефективності методу застосовують 0-нотацію, вона враховує загальну кількість циклічного перебору елементів масиву даних відносно n – числа всіх елементів. Якщо, наприклад, під час сортування масиву довжиною n число перебору елементів дорівнює n або близьке до нього, то говорять, що 0-нотація має порядок n.

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

Розглянемо окремі методи сортування.

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

Алгоритм реалізований у вигляді програми мовою С, яка показана в прикладі 11.1.

Приклад 11.1 – Демонстрація методу вибору

# include<stdio.h> /* Метод вибору */

unsigned long int cykl = 0, kil = 0;

void s(int *a, int n)

{

int i, j, min, num;

for(i = 0; i<n-1; i++)

{

min = a[i]; num = i;

for(j = i; j<n; j++)

{

if(a[j] < min){min = a[j]; num = j;}

cykl++;

}

a[num] = a[i]; a[i] = min; kil++;

}

return;

}

int main(void)

{

int i, count = 999;

int m[999], dop[9] = {9, 3, 5, 1, 6, 7, 4, 2, 3};

clrscr();

for(i = 0; i<count; i++)m[i] = 0;

for(i = 0; i<9; i++)m[i] = dop[i]; /* cykl = 499499 kil = 998 */

/*for(i = 0; i<9;i ++)m[i+990] = dop[i]; */ /* cykl = 499499 kil = 998 */

for(i = 0; i<count; i++)printf("%2d", m[i]);

s(m, count);

for(i = 0; i<count; i++)printf("%2d", m[i]);

printf("\ncykl = %9ld kil = %d\n", cykl, kil);

getch( );

return 0;

}

На її початку оголошено дві глобальні змінні типу long int (для цього виду сортування просто тип int може виявитися закоротким): cykl та kil, ініціалізовані нулями. Вони служать для підрахунку відповідно кількості виконань циклів та числа перестановок елементів під час сортування масиву.

Програма складається з двох частин: головної програми main() та підпорядкованої функції s(). У головній програми оголошено дві локальні змінні: i – параметр циклу та count – кількість елементів масиву m[]. У нашому прикладі count=999. Також оголошено допоміжний масив dop[], ініціалізований 9 елементами. Тип даних не впливає на будову алгоритму, тут вибрано тип int.

Головна програма складається з чотирьох послідовних циклів та звернення до функції s(). У першому циклі відбувається ініціалізація масиву m нулями. У другому – допоміжний масив dop[] вставляється на початок масиву m. Таким чином, передбачається сортування масиву m[], перші 9 елементів якого дорівнюють елементам масиву dop[], а решта – нулю. Назвемо цей варіант масиву масив 1. Під час виконання програми дослідимо вплив вигляду даних на ефективність алгоритму, тому випробуємо її 2 рази: на вищеописаному масиві m[] та на цьому ж масиві, але вже останні 9 його елементів будуть містити масив dop[], а решта – нулі, назвемо його масив 2. Цикл формування другого варіанту даних тимчасово закоментарений. Решта два цикли служать для дворазового виводу масиву m[] – до і після сортування.

Процес сортування виконує функція s(), яка від час виклику з головної програми приймає два параметри: вказівник на масив m[] та змінну count – кількість елементів, які підлягають сортуванню. На її початку оголошено такі 4 локальні змінні типу int:

  • i, j – параметри циклів;

  • min – мінімальне значення масиву a[];

  • num – порядковий номер мінімального елемента.

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

Параметр зовнішнього циклу цього методу змінюється від 0 до n-1 (n разів), а внутрішнього – від i до n (загалом у два рази менше), тому сумарна кількість порівнянь дорівнює n*n/2 = n2/2, отже 0-нотація методу вибору має порядок n2. Це дуже багато, тому метод вибору вважається малоефективним.

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

Програма, яка демонструє бульбашковий метод, наведена в прикладі 11.2.

Приклад 11.2 – Сортування масиву бульбашковим методом

# include<stdio.h> /* Бульбашковий метод */

long int cykl = 0, kil = 0;

void s(int *a, int n)

{

int i, j, tmp, ind;

for(i = 0; i<n-1; i++)

{ind = 0;

for(j = 1; j<n; j++)

{

if(a[j]<a[j-1]){tmp = a[j]; a[j] = a[j-1]; a[j-1] = tmp; ind++; kil++;}

cykl++;

}

if(ind == 0)break;

}

return;

}

main( )

{int i, count = 999;

int m[999], dop[9] = {9, 3, 5, 1, 6, 7, 4, 2, 3};

clrscr();

for(i = 0; i<count; i++)m[i] = 0;

/* for(i = 0; i<9; i++)m[i] = dop[i]; cykl = 9980 kil = 8932 */

for(i = 0; i<9; i++)m[i+990] = dop[i]; /*cykl = 6986 kil =22 */

for(i = 0; i<count; i++)printf("%3d", m[i]);

s(m, count); getch();

for(i = 0; i<count; i++)printf("%3d", m[i]);

printf("\ncykl = %9ld kil = %d\n", cykl, kil);

getch( );

}

У процедурі s() цього методу застосовується змінна tmp для тимчасового запам’ятовування значення елемента під час його перестановки та ind – індикатор припинення сортування.

Як і стосовно кількості порівнянь, так і щодо кількості перестановок 0-нотація алгоритму бульбашкового сортування має порядок n2. Найкращий ефект (найменші затрати часу) досягається в тому разі, коли список майже відсортований і малі елементи наприкінці списку відсутні.

Цей метод можна покращити, застосувавши двостороннє бульбашкове сортування, яке іноді називають сортування перемішуванням. Для цього у внутрішній цикл прикладу 11.2 можна додати такий оператор:

if(a[n-j-1]>a[n-j]){tmp = a[n-j]; a[n-j] = a[n-j-1]; a[n-j-1] = tmp; ind++; kil++;}

Тоді перша ітерація проходить так же, як і при стандартному бульбашковому сортуванні, а друга – в протилежному напрямку. Висхідна ітерація дозволяє малим елементами “спливати” тільки на один рівень, а більшим – “потонути” швидше. Однак, це є перевагою лише в тому випадку, коли більша частина малих елементів розташована внизу (наприкінці) масиву.

Метод лінійних вставок до певної міри можна вважати комбінацією двох попередньо розглянених. У процесі виконання програми подібно до методу вибору масив ділиться на дві частини: вже посортовану і ще ні. Межею між ними є значення параметра зовнішнього циклу. Якщо черговий елемент, вибраний у зовнішньому циклі, виявиться меншим, ніж найбільший у вже посортованій частині масиву, то він у внутрішньому циклі пересувається на своє місце бульбашковим методом. Цей метод демонструє програма, подана в прикладі 11.3.

Приклад 11.3 – Метод лінійних вставок

# include<stdio.h> /* Метод лінійних вставок */

int cykl = 0, kil = 0;

void s(int *a, int n)

{

int i, j, tmp;

for(i = 1;i<n; i++)

for(j = i; j>0; j--)

{

cykl++;

if(a[j-1] > a[j]){kil++; tmp = a[j]; a[j] = a[j-1]; a[j-1] = tmp;}

else break;

}

return;

}

main( )

{int i, count = 999;

int m[999], dop[9] = {9, 3, 5, 1, 6, 7, 4, 2, 3};

clrscr( );

for(i = 0; i < count; i++)m[i] = 0;

for(i = 0;i < 9; i++)m[i] = dop[i]; /* cykl = 9927 kil = 8932 */

/* for(i = 0;i<9; i++)m[i+990] = dop[i]; cykl= 1020 kil = 22 */

for(i = 0; i<count; i++)printf("%2d", m[i]);

getch( );

s(m, count);

printf("\ncykl = %d kil = %d\n", cykl, kil);

for(i = 0; i<count; i++)printf("%2d", m[i]);

getch( );

}

Метод Шелла вважається оптимальним з погляду на складність, особливо для порівняно невеликих наборів даних (до 20 елементів).

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

Нехай масив складається з h окремих сегментів, тоді кожний з них міститиме приблизно n, поділене на h, елементів. Після того, як усі сегменти відсортовані, масив поділяється на меншу кількість сегментів (тобто довжина кроку зменшується). Процес повторюється до остаточного сортування всього масиву, коли h стане дорівнювати 1. Оскільки довжина останнього кроку досягає 1, то можна використовувати будь-яку послідовність довжин кроків. Тим не менше, деякі значення довжини кроків мають переваги над іншими. Хоча принцип сортування методом Шелла досить простий, його формальний аналіз утруднений, бо довжина сегментів не регламентована. Відомо, однак, що при вдалому виборі послідовності довжин кроків h його 0-нотація менша за n.

Використання цього методу сортування оправдане наявністю декількох великих елементів у масиві даних. Іноді найбільший елемент може стояти на першому місці. У такому випадку сортування методом вставок на кожному кроці буде переміщувати цей елемент на одну позицію правіше (нижче). Максимальний елемент буде переміщуватися n-1 разів. Іноді поруч з ним знаходяться інші великі елементи. Вони так же, як і максимальний елемент, будуть переміщуватися в кінець масиву. У деяких порівняно рідкісних випадках всі елементи масиву розташовуються в зворотному порядку, тоді метод вставок стає найнеефективнішим. Саме в цьому випадку переважає сортування методом Шелла. Значні переваги перед методом вставок він має при сортуванні майже впорядкованого масиву. Вважається, що час сортування за методом Шелла пропорціональний .

Реалізація методу Шелла мовою С приведена в прикладі 11.4. У цій програмі inc = h – крок, який після кожного сортування сегментів зменшується в 5/11 разів, поки не досягне значення 1.

Приклад 11.4 – Сортування методом Шелла

# include<stdio.h> /* Метод Шелла */

int cykl = 0, kil = 0;

void s(int *a, int n)

{

int i, inc, j, tmp;

for(inc = n; inc >0;)

{

for(i = inc; i<n; i++)

{

j = i; tmp = a[i];

while(j >= inc && (tmp < a[j-inc]))

{

a[j] = a[j-inc]; j -= inc;

cykl++;

}

a[j] = tmp; kil++;

}

inc = ((inc>1) && (inc<5)) ? 1 : 5*inc/11;

}

}

main( )

{

int i, count = 999;

int m[999], dop[9] = {9,3,5,1,6,7,4,2,3};

clrscr();

for(i = 0; i<count; i++)m[i] = 0;

for(i = 0; i<9; i++)m[i] = dop[i]; /* cykl = 47 kil = 7166 */

/* for(i = 0; i<9; i++)m[i+990] = dop[i]; cykl = 9 kil = 7166 */

for(i = 0; i<count; i++)printf("%2d", m[i]);

s(m, count); getch( );

printf("\ncykl=%d kil=%d\n", cykl, kil); getch( );

for(i = 0; i<count; i++)printf("%2d", m[i]);

getch();

}

Метод Хоара показано в прикладі 11.5, де він реалізований за допомогою рекурсії функцій. В даному випадку функція s() має три параметри: a[] – заданий масив, left – ліва та right – права межа діапазону сортування, який з кожним зверненням до цієї функції зменшується.

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

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

Приклад 11.5 – Сортування методом Хоара

# include<stdio.h> /* Метод Хоара */

int cykl1 = 0, cykl2 = 0, kil = 0;

void s(int *a, int left, int right)

{

int i, j, x, y;

i = left; j = right;

x = a[(left + right)/2];

cykl1++;

do{

while((a[i]<x) && (i<right))i++;

while((x < a[j]) && (j > left))j--;

if(i <= j){y = a[i]; a[i] = a[j]; a[j] = y; i++; j--; kil++;}

cykl2++;

}while(i <= j);

if(left < j)s(a, left, j);

if(i < right)s(a, i, right);

return;

}

main( )

{

int m[999], dop[9] = {9,3,5,1,6,7,4,2,3};

int i, k, count=999;

clrscr( );

for(i=0; i<count; i++)m[i] = 0;

/* for(i = 0; i<9; i++)m[i] = dop[i]; cykl1 = 518 cykl2 = 4522 kil = 4520*/

for(i = 0; i<9; i++)m[i+990] = dop[i]; /*cykl1 = 519 cykl2 = 4508 kil = 4506*/

for(i = 0; i<count; i++)printf("%2d", m[i]);

s(m, 0, count-1); getch( );

for(i = 0;i<count; i++)printf("%2d", m[i]);

getch( );

printf("\ncykl1=%d cykl2=%d kil=%d\n", cykl1, cykl2, kil);

getch( );

}

Відомо, що рекурсія функцій – циклічний процес, тому під час випробування програми варто підрахувати не лише число, так сказати, чистих циклів, але й кількість звернень до процедури. Для цього тут застосовуються змінні: cykl2 – загальна кількість виконань циклів та cykl1 – кількість звернень до процедури.

Метод Хоара на сьогодні вважається найефективнішим для даних з рівномірним законом розподілу, тому його ще називають методом швидкого сортування. Однак, у ньому застосовується рекурсія функцій, що суттєво його сповільнює, бо додається певний об’єм роботи, пов’язаний з їх обслуговуванням. Відомо, що вона і транжирить пам’ять, і виконується повільніше, ніж оператор циклу. Через це він матиме перевагу над іншими методами лише у випадку великих масивів даних, які нараховують сотні та більше елементів.

Вважається, що в методі Хоара число порівнянь приблизно дорівнює n*log(n), а кількість перестановок елементів – n*log(n)/6.

Як уже було вище сказано, під час випробування вищеописаних алгоритмів застосовувалися два варіанти даних: масив 1, перші 9 елементів якого були більші за нуль, а решта дорівнювали нулю, та масив 2, де більші за нуль елементи були розташовані наприкінці масиву. Оскільки в обох варіантах заданого масиву значна кількість елементів дорівнювала нулю, то вона не потребувала сортування. Це дозволило оцінити якість алгоритмів ще й за цією ознакою. Результати випробувань зведені в таблицю 11.1. Зауважимо, що це далеко не всі варіанти даних, які можна використати для оцінки якості алгоритмів, не врахована також кількість часу, затраченого на виконання програми.

Таблиця 11.1 – Дані для порівняння алгоритмів сортування

Назва методу

сортування

Кількість виконань циклів (cykl)

Кількість

перестановок (kil)

Масив 1

Масив 2

Масив 1

Масив 2

Вибору

499499

499499

998

998

Бульбашкове

9980

6986

8932

22

Перемішуванням

9980

3992

8932

22

Лінійних вставок

9927

1020

8932

22

Шелла

47

9

7166

7166

Хоара

4522

4508

4520

4506

Аналізуючи ці результати, можна зробити такі висновки:

  • у методі вибору вигляд даних не впливає ні на кількість виконань циклів, ні на число перестановок елементів. Крім того, він найгірший за числом виконань циклів (cykl = 499499). Число перестановок kil = 998, тобто kil = count-1;

  • бульбашкове сортування та метод лінійних вставок більш ефективні, якщо великі елементи масиву знаходяться в кінці масиву (тут kil = 22);

  • порівняно з просто бульбашковим методом варіант двостороннього сортування дозволяє скоротити число cykl майже в два рази;

  • метод Шелла має значну перевагу тоді, коли малі елементи знаходяться на початку масиву та коли масив майже посортований;

  • метод Хоара можна вважати найкращим для даних з рівномірним законом розподілу.

Перелік розглянених тут методів сортування далеко не вичерпаний і його можна доповнити. Це, зокрема, такі методи, як Сінглтона, пірамідальне сортування, сортування підрахунком, метод множинних вставок, порозрядне сортування, сортування злиттям та інші. Сьогодні відомо більше десятка різних методів сортування, одні з них кращі в одних випадках, інші переважають у інших. Наприклад, сортування злиттям вигідно застосовувати при зовнішньому сортуванні, коли під час виконання програми в оперативній пам’яті знаходиться лише частина даних, а решта – на диску.

Пошуки ефективних алгоритмів сортування тривають.

11.2 Сортування масиву структур

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

Інша особливість структур полягає в наявності елементів літерного типу, що ускладнює програму їх сортування. Для виконання операцій порівняння рядків та заодно спростити програму можна застосувати функцію strncmp(), розглянену вище в розділі 8. Скористаємося цією можливістю, наведемо приклад, у якому посортуємо дані таблиці 10.1 за назвами підприємств у порядку зростання. Візьмемо метод вибору. Програма показана в прикладі 11.6, де операцію сортування виконує функція s(). У ньому для порівняння слів враховуються дві перші букви (3-й параметр функції strncmp() дорівнює 2), аналізуючи назви, бачимо, що цього достатньо.

Приклад 11.6 – Сортування масиву структур методом вибору

# include<stdio.h> /* Сортування масиву структур методом вибору */

# include<string.h>

struct pidpr{int kod;

char naz[25];

float stf;};

void s(struct pidpr *a, int n)

{

int i, j, num;

struct pidpr tmp, min;

for(i = 0; i<n-1; i++)

{

min = a[i]; num = i;

for(j = i; j<n; j++)if(strncmp(a[j].naz, min.naz, 2)<0){min = a[j]; num = j;}

a[num] = a[i]; a[i] = min ;

}

return;

}

int main(void)

{int i, count=6;

struct pidpr m[6]={{1, "Харківтрансгаз ", 34.60},

{2, "Прикарпаттрансгаз", 22.15},

{3, "Київтрансгаз ", 75.00},

{4, "Львівтрансгаз ", 42.12},

{5, "Експорттрансгаз ", 1.85},

{6, "Гадячгазпром ", 12.48}};

clrscr(); puts(" Заданий масив: ");

for(i = 0; i<count; i++)printf("%3d %20s %5.2f\n", m[i].kod, m[i].naz, m[i].stf);

s(m, count); puts(" Посортований масив: ");

for(i = 0; i<count; i++)printf("%3d %20s %5.2f\n", m[i].kod, m[i].naz, m[i].stf);

getch( );

return 0;

}

Ця програма практично не відрізняється від вищенаведеної в прикладі 11.1, за виключенням того, що замість масиву чисел маємо масив структур. Вони мають тип pidpr. Відповідно, цей тип має заданий масив m[] у головній програмі, змінні tmp і min та масив a[] – у підпрограмі s(). Для перестановки елементів структури використано ще й ту властивість компілятора, що вона дозволяє переприсвоювати всю структуру в цілому, наприклад, a[i] = min, а не кожний її елемент окремо. Це, однак, не зменшує кількість перестановок, а лише скорочує текст програми, звичайно, що ті перестановки виконує компілятор.

11.3 Сортування зв’язаного списку

Можна виділити два підходи до побудови алгоритму сортування зв’язаного списку. При першому змінюються посилання на адреси даних, тобто дані залишаються на місці, в тих же комірках пам’яті, а змінюються лише посилання на них у списку. Цей спосіб можна уявити за такою аналогією: якщо знадобилося б посортувати декілька екземплярів рідини, то прийшлося б переставляти їх разом з посудинами. За другим способом зв’язки між структурами зберігаються, а переставляються дані. За нашою аналогією посудини залишаться на місці, а переливати прийдеться рідину.

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

Другий спосіб передбачає переприсвоєння даних, тому він буде невигідним при наявності у списку великих, об’ємистих структур і матиме перевагу лише у випадку негромізких. Адже, набагато простіше переставити дві адреси, ніж дві структури, які, як правило, містять по декілька елементів.

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

Це показано на рисунку 11.1, де зверху маємо вигляд списку до перестановки адреси, а внизу – після. Тут k, i – порядкові номери структур у списку.

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

Перестановка адрес показана у прикладі 11.7. В ньому ставиться задача створити та посортувати зв’язаний список, створений за даними таблиці 10.1, у порядку зростання назв підприємств. Для її реалізації застосовується метод вибору мінімального елемента.

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

  • *head – початок звязаного списку;

  • *curr – параметр зовнішнього циклу, гортання списку для вставки адреси з мінімальним елементом;

  • *prev – адреса, яка передує curr;

  • *curv – параметр внутрішнього циклу, гортання списку під час пошуку мінімального елемента;

  • *sort – адреса, яка передує curv;

  • *mini – адреса мінімального елемента;

  • *prem – адреса, яка передує mini.

Основою алгоритму сортування (функції s()) є два вкладені цикли типу for. Внутрішній цикл служить для пошуку адреси структури з мінімальним значенням змінної naz (назви підприємства) подібно до того, як це робилося в програмі прикладу 11.6, і присвоєння цієї адреси змінній mini. Гортання структур у цьому циклі відбувається адресою curv від наступної за адресою структури з мінімальним елементом, знайденим на попередньому кроці пошуку, тобто від curr->dali до останньої. Кількість його виконань зменшується на одиницю при кожному виконанні зовнішнього циклу. Цей цикл містить розгалуження, яке й шукає та запам’ятовує адресу мінімального елемента.

У зовнішньому циклі відбувається гортання списку адресою curr і присвоєння їй адреси чергової структури з меншим значенням naz, починаючи від head. Він має два розгалуження, з яких перше запобігає перестановку адрес, якщо поточна структура з адресою curr вже має мінімальний елемент. Така перестановка просто спричинить зайві затрати часу. Друге – або вставляє структуру з мінімальним елементом на перше місце (її адреса стає head), або прив’язує до prev, яка передує curr.

Приклад 11.7 – Сортування зв’язаного списку шляхом

перестановки адрес

# include<stdio.h> /* Сортування зв’язаного списку, перестановка адрес */

# include<stdlib.h>

# include<string.h> /* Для під’єднання функції strcpy() */

struct pidpr{int kod;

char naz[25];

float stf;

struct pidpr *dali;};

struct pidpr m[6]={{1,"Харківтрансгаз ", 34.60},

{2,"Прикарпаттрансгаз ", 22.15},

{3,"Київтрансгаз ", 75.00},

{4,"Львівтрансгаз ", 42.12},

{5,"Експорттрансгаз ", 1.85},

{6,"Гадячгазпром ", 12.48}};

struct pidpr *head=NULL,*prev, *curr, *curv, *prem, *mini, *sort;

int i;

void write(void) /* Функція виводу результатів */

{

for(i = 0, curr = head; curr != NULL; curr = curr->dali)

printf("і = %d adr = %p kod = %d naz = %s stf = %f dali = %p\n",

i++, curr, curr->kod, curr->naz, curr->stf, curr->dali);

return;

}

void s(void) /* Функція сортування */

{

for(prev = curr = head; curr->dali != NULL; prev = curr, curr = curr->dali)

{prem = prev; mini = curr;

for(sort = curr, curv = curr->dali; curv != NULL; sort = curv, curv = curv->dali)

if(strncmp(curv->naz, mini->naz, 2) < 0){prem = sort; mini = curv;}

if(mini == curr)continue;

if(curr == head)head=mini; else prev->dali = mini;

prem->dali = mini->dali;

mini->dali = curr; curr = mini;

}

return;

}

int main(void) /* Головна програма main */

{

int count = 6;

clrscr();

for(i = 0; i<count; i++)

{

curr = malloc(sizeof(struct pidpr));

if(head == NULL)head = curr; else prev->dali = curr;

*curr = dano[i];

curr->dali = NULL;

prev = curr;

}

puts("Створений список:");

write();

s();

puts("Посортований список:");

write();

return 0;

}

Перестановка структур – порівняно рідкісна операція, як уже було сказано вище, вона невигідна. Функція s() для цього випадку прийме вигляд, показаний у прикладі 11.8. Тут для перестановки структур служить адреса sort як допоміжна.

Приклад 11.8 – Сортування шляхом перестановки структур

void s(void) /* Сортування шляхом перестановки структур */

{

sort = malloc(sizeof(struct pidpr));

for(curr = head; curr->dali != NULL; curr = curr->dali)

{

for(curv = curr->dali, mini = curr; curv != NULL; curv = curv->dali)

if(strncmp(curv->naz, mini->naz, 2) < 0)mini = curv;

*sort = *curr; *curr = *mini; *mini = *sort;

sort->dali = curr->dali; curr->dali = mini->dali; mini->dali = sort->dali;

}

return;

}

Запитання для самоперевірки

  1. Що таке 0-нотація?

  2. Який метод сортування можна вважати найбільш ефективним? Від чого залежить ця ефективність?

  3. Чим відрізняються алгоритми та відповідні їм програми сортування чисел від символьних рядків? Яка досягається вигода від застосування функції strncmp() під час сортування масивів рядків? масивів структур? зв’язаних списків?

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

  5. Використайте для аналізу запропонованих тут алгоритмів сортування функцію clock(), подану в розділі 8.

  6. Наскільки складнішою виявиться програма прикладу 11.7 для двосторонньозв’язаного списку?

  7. Функція s() прикладу 11.8 виглядає коротшою за ту ж функцію прикладу 11.7. Чи не є це ознакою того, що алгоритм сортування з перестановкою структур кращий за перестановку адрес?

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