Tablouri. Particularizare - vectori. Aplicaţii

Obiective

În urma parcurgerii acestui laborator, studentul va fi capabil:

  • să declare şi să iniţializeze vectori (din declaraţie şi prin structuri iterative)
  • să implementeze algoritmi simpli de sortare şi căutare pe vectori
  • să folosească practici recunoscute şi recomandate pentru scrierea de cod sursă care implică lucrul cu vectori
  • să recunoască şi să evite erorile comune de programare legate de aceste structuri
  • să aplice lucrul cu vectori pentru rezolvarea unor probleme cu dificultate medie

Noţiuni teoretice

Vectori

Printr-un vector se înţelege o colecţie liniară şi omogenă de date. Un vector este liniar pentru că datele(elementele) pot fi accesate în mod unic printr-un index. Un vector este, de asemenea, omogen, pentru că toate elementele sunt de acelaşi tip. În limbajul C, indexul este un număr întreg pozitiv şi indexarea se face începând cu 0.

Declaraţia unei variabile de tip vector se face în felul următor:

<tip_elemente> <nume_vector>[<dimensiune>];

De exemplu, avem următoarele declaraţii de vectori:

int a[100];
float vect[50];
 
#define MAX 100
...
unsigned long numbers[MAX];

Este de remarcat că vectorul este o structură statică: dimensiunea acestuia trebuie să fie o constantă la compilare şi nu poate fi modificată în cursul execuţiei programului. Astfel, programatorul trebuie să estimeze o dimensiune maximă pentru vector, şi aceasta va fi o limitare a programului. De obicei, se folosesc constante simbolice (ca în ultimul exemplu) pentru aceste dimensiuni maxime, pentru ca ele să poată fi ajustate uşor la nevoie. De asemenea, în cadrul unei declaraţii, se pot iniţializa cu valori constante componente ale vectorului, iar în acest caz, dimensiunea vectorului poate rămâne neprecizată (compilatorul o va determina din numărul elementelor din listă). De exemplu:

int a[3] = {1, 5, 6};                     // Toate cele 3 elemente sunt initializate 
float num[] = {1.5, 2.3, 0.2, -1.3};      // Compilatorul determina dimensiunea - 4 - a vectorului
unsigned short vect[1000] = {0, 2, 4, 6}; // Sunt initializate doar primele 4 elemente, iar toate celelalte sunt initializate cu valoarea 0

În cazul special în care specificăm dimensiunea şi doar un singur element la initializare, primul element va fi cel specificat, iar toate celelalte elemente ale vectorului vor fi iniţializate la 0:

char string[100] = {97}; // Sirul va fi initializat cu: 97 (caracterul 'a') pe prima poziţie şi 99 de 0
int v[100] = {0};        // Vectorul va fi umplut cu 0.

Este important de remarcat faptul că elementele neiniţializate pot avea valori oarecare. La alocarea unui vector, compilatorul nu efectuează nici un fel de iniţializare şi nu furnizează nici un mesaj de eroare dacă un element este folosit înainte de a fi iniţializat. Un program corect va iniţializa, în orice caz, fiecare element înainte de a-l folosi. Elementele se accesează prin expresii de forma <nume_vector>[<indice>]. De exemplu, putem avea:

char vect[100];
vect[0] = 1;
vect[5] = 10;
 
int i = 90;
vect[i] = 15;
vect[i + 1] = 20;

Stil de programare

Exemple de programe

Citirea unui vector de intregi de la tastatura:

int main() 
{
  int a[100], n, i; /* vectorul a are maxim 100 de intregi */
 
  scanf("%d", &n); /* citeste nr de elemente vector */
 
  for (i = 0; i < n; i++) {
    scanf("%d", &a[i]); /* citire elemente vector */
  }
 
  for (i = 0; i < n; i++) {
    printf("%d ", a[i]); /* scrie elemente vector */
  }
 
  return 0;
}

Generarea unui vector cu primele n numere Fibonacci:

#include <stdio.h>
int main() 
{
  long fib[100] = {1, 1};
  int n, i;
 
  printf("n = "); 
  scanf("%d", &n);
 
  for (i = 2; i < n; i++) {
    fib[i] = fib[i - 1] + fib[i - 2];
  }
  for (i = 0; i < n; i++) {
    printf("%ld ", fib[i]);
  }
 
  return 0;
}

Deşi modul în care se manifestă erorile din program poate fi surprinzător şi imprevizibil, cauzele care produc aceste erori sunt destul de comune şi pot fi grupate în mai multe categorii. Câteva dintre acestea sunt prezentate mai jos

  • Depăşirea limitelor indicilor (index out of bounds) este o eroare frecventă, ce poate duce la blocarea programului sau a sistemului şi poate fi evitată prin verificarea încadrării în intervalul valid.
  • Indici folosiţi greşit în bucle imbricate (index cross-talk). Sunt multe cazuri în care pe un nivel al buclei se foloseşte, de exemplu vect[i], şi pe nivelul imbricat vect[j], când de fapt se dorea folosirea lui i. Mare atenţie şi în astfel de cazuri!

Definiţi dimensiunile prin constante şi folosiţi-le pe acestea în locul tastării explicite a valorilor în codul sursă. Astfel veţi evita neconcordanţe în cod dacă doriţi ulterior să modificaţi dimensiunile şi uitaţi să modificaţi peste tot prin cod.

#define MAX   100
 
int vect[MAX];

va fi de preferat în locul lui

int vect[100];

Verificaţi că indicii se încadrează între marginile superioară şi inferioară a intervalului de valori valide. Acest lucru trebuie în general făcut în cazul în care datele provin dintr-o sursă externă: citite de la tastatură sau pasate ca parametri efectivi unei funcţii, de exemplu.

Exemplu:

// program care citeşte un index şi o valoare, şi atribuie valoarea elementului din vector care se găseşte la poziţia respectivă
#include <stdio.h>
#define N 10
 
int main() 
{
  int i, val;
  int v[N];
 
  scanf("%d%d", &i, &val);
 
  /* !!! Verific daca indexul este valid */
  if (i >= 0 && i < N) {
   v[i] = val;
  } else {
    printf("Introduceti un index >= 0 si < %d\n", N);
  }
 
  return 0;
}

Folosiţi comentarii pentru a explica ce reprezintă diverse variabile. Acest lucru vă va ajuta atât pe voi să nu încurcaţi indici, de exemplu, cât şi pe ceilalţi care folosesc sau extind codul vostru.

Exemplu:

#include <stdio.h>
#define N 100
 
int main() 
{
  int v[N];
  int i, j; /* indecsii elementelor ce vor fi interschimbate */
  int aux;  /* variabila ajutatoare pentru interschimbare */
 
  /*... initializari */
 
  /* Interschimb */
  aux = v[i];
  v[i] = v[j];
  v[j] = aux;
 
  return 0;
}

Aplicaţii cu vectori

Căutări

Căutare secvenţială

Când avem de a face cu un vector nesortat (şi nu numai în acest caz), cea mai simplă abordare pentru a găsi o valoare, este căutarea secvenţială. Cu alte cuvinte, se compară, la rând, fiecare valoare din vector cu valoarea căutată. Dacă valoarea a fost găsită, căutarea se poate opri (nu mai are sens să parcugem vectorul până la capăt, dacă nu se cere acest lucru explicit).

Exemplu:

#define MAX 100
 
...
 
int v[MAX], x, i;
 
/* initializari */
...
 
int found = 0;
for (i = 0; i < MAX; i++) {
  if (x == v[i]) {
    found = 1;
    break;
  }
}
 
if (found) {
   printf("Valoarea %d a fost gasita in vector\n", x);
} else { 
   printf("Valoarea %d nu a fost gasita in vector\n", x);
}
...
Căutare binară iterativă

Dacă vectorul pe care se face căutarea este sortat, algoritmul mai eficient de folosit în acest caz este căutarea binară. Presupunem că vectorul este sortat crescător (pentru vectori sortaţi descrescător, raţionamentul este similar).

Valoarea căutată, x, se compară cu valoarea cu indexul N/2 din vector, unde N este numărul de elemente. Dacă x este mai mic decât valoarea din vector, se caută în prima jumătate a vectorului, iar dacă este mai mare, în cea de-a doua jumătate. Căutarea în una dintre cele două jumătăţi se face după acelaşi algoritm.

Conceptual, căutarea binară este un algoritm recursiv, dar poate fi implementat la fel de bine într-un mod iterativ, folosind indecşii corespunzători bucăţii din vector în care se face căutarea. Aceşti indecşi se modifică pe parcursul algoritmului, într-o buclă, în funcţie de comparaţiile făcute. Evoluţia algoritmului este ilustrată în imaginea de mai jos.

Pseudocodul pentru căutarea binară:

// cauta elementul x in vectorul sortat v, intre pozitiile 0 si n-1  si returneaza pozitia gasita sau -1
int binary_search(int n, int v[NMAX], int x) {
  int low = 0, high = n - 1;
 
  while (low <= high) {
    // De ce preferăm această formă față de (low + high) / 2 ?
    int middle = low + (high - low) / 2;
 
    if (v[middle] == x) {
      // Am gasit elementul, returnam pozitia sa
      return middle;
    }
 
    if (v[middle] < x) {
      // Elementul cautat este mai mare decat cel curent, ne mutam in jumatatea
      // cu elemente mai mari
      low = middle + 1;
    } else {
      // Elementul cautat este mai mic decat cel curent, ne mutam in jumatatea
      // cu elemente mai mici
      high = middle - 1;
    }
  }
 
  // Elementul nu a fost gasit
  return -1;
}

Preferăm calcularea mijlocului intervalului [low, high] folosind formula x = low + (high - low) / 2 deoarece formula perfect analogă x = (low + high) / 2 poate da overflow pentru valori mari ale low si high. De altfel, acest bug a existat în biblioteca Java timp de 9 de ani. Puteți citi mai mult despre asta în acest articol.

Sortări

Bubble Sort

Metoda bulelor este cea mai simplă modalitate de sortare a unui vector, dar şi cea mai ineficientă. Ea funcţionează pe principiul parcurgerii vectorului şi comparării elementului curent cu elementul următor. Dacă cele două nu respectă ordinea, sunt interschimbate. Această parcurgere este repetată de suficiente ori până când nu mai există nici o interschimbare în vector.

Sortarea prin selecţie

Sortarea prin selecţie oferă unele îmbunătăţiri în ceea ce priveşte complexitatea, însă este departe de a fi considerat un algoritm eficient. Presupunând că se doreşte sortarea crescătoare a vectorului, se caută minimul din vector, şi se interschimbă cu primul element - cel cu indexul 0. Apoi se reia acelaşi procedeu pentru restul vectorului. Motivul pentru care algoritmul de sortare prin selecţie este mai eficient este acela că vectorul în care se caută minimul devine din ce în ce mai mic, şi, evident, căutarea se face mai repede la fiecare pas.

Studiul unor algoritmi mai avansaţi de sortare, precum şi studiul complexităţii lor nu constituie obiectul acestui laborator. Acestea se vor relua mai detaliat în cadrul altor cursuri (AA/PA).

Exerciții Laborator CB/CD

  1. Primul exercitiu presupune modificarea/adaugarea de instructiuni unui cod existent pentru a realiza anumite lucruri. In momentul actual programul aduna la elementul curent vecinul din dreapta sa (daca acesta exista).
    • Nu uitati ca trebuie sa utilizam un coding style adecvat atunci cand scriem sursele.
ex1.c
#include <stdio.h>
 
#define N 100
 
void sum_right_neighbour(int v[N], int n)
{
    int i;
 
    for (i = 0; i < n - 1; i++) {
        v[i] += v[i+1];
    }
}
 
void print_vector(int v[N], int n)
{
    int i;
 
    for (i = 0; i < n; i++) {
	printf("%d ", v[i]);
    }
    printf("\n");
}
 
int main(void)
{
    int v[N] = {1, 2, 3, 4, 5};
 
    print_vector(v, 5);
    sum_right_neighbour(v, 5);
    print_vector(v, 5);
 
    return 0;
}

Cerinte:

  • Sa se creeze o functie care sa adauge la fiecare element din vector vecinul din stanga sa (daca acesta exista).
  • Sa se creeze o functie care sa construiasca un alt vector ce contine in vector[i] produsul tuturor elementelor, mai putin elementul de pe pozitia i din vectorul initial.
  • Se citesc de la tastatura caractere (se va citi cate un caracter pe rand). In functie de valoarea acestuia se va realiza una din actiunile urmatoare:
    • q - iesire din program
    • m - eliminare element minim
    • M - eliminare element maxim
    • p - printare vector

Următoarele două probleme vă vor fi date de asistent în cadrul laboratorului.

Checker laborator 5 Tutorial folosire checker laborator

Cum se foloseste checkerul

Cum se foloseste checkerul

Pentru utilizarea checkerului:

  • Se va scrie cate un fisier sursa pentru fiecare problema;
  • La finalul fiecarui printf utilizat pentru afisarea rezultatului trebuie sa existe un newline;
  • Sursa nu trebuie sa contina alte printf-uri in afara de cele care scriu rezultatul asteptat la stdout.
  • Se va dezarhiva arhiva specifica exercitiului;
  • In directorul curent se afla checkerul, executabilul generat, folderele de input si output specifice problemei;
  • Se va rula “bash checker.sh <executabil>” unde <executabil> este numele executabilului generat;

Exerciţii de Laborator

  • Exercitiul 1 [1 pct]: Citiți un vector de întregi strict mai mari ca 0 de la tastatură. Vă veți opri din citit elemente ale vectorului într-una din situațiile următoare:
    • Ați citit deja N = 20 de elemente
    • Ați citit de la tastatură valoarea 0 sau o valoare negative (aceasta NU trebuie adăugată în vector)

Afișați la consolă, pe câte o linie nouă, indicele fiecărei valori impare din vector împreună cu valoarea propriu-zisă sub forma „<indice> : <valoare>”. La final, pe o linie nouă, afișați numărul total de elemente impare din vector. Considerați că primul element din vector se află pe poziția 0.

Exemplu:

Input: 		3 5 2 7 10 5 0
Output: 	0 : 3
		1 : 5
		3 : 7
		5 : 5
		Numar elemente impare: 4
  • Exercitiul 2 [1 pct]: Pentru un vector citit similar cu citirea de la Execițiul 1, afișați doar acele elemente din vector pentru care elementul este mai mare decât indicele său (păstrați ordinea din vectorul inițial). Considerați că primul element din vector se află la indicele 0.
Exemplu:

Input: 		3 5 1 7 10 5 0
Output: 	3 5 7 10
  • Exercitiul 3 [1 pct]: Pentru un vector citit similar cu citirea de la Execițiul 1, afișați doar acele elemente din vector cu vecinii având valori mai mici decât el (păstrați ordinea din vectorul inițial). Prin vecini pentru elementul de pe poziția i se înțelege elementul de pe poziția i – 1, respectiv i + 1, cu două excepții: primul și ultimul element din vector (care au doar câte un vecin) – pentru aceste două elemente se va considera doar vecinul existent care trebuie să întrunească condiția pentu a afișa elementul. Atenție la ultimul element din vector (care nu este 0 sau valoarea negativă citită la final ci elementul anterior)
Exemplul 1:

Input: 		3 5 1 7 10 5 0
Output: 	5 10

Exemplul 2:

Input: 		7 5 10 7 11 50 0
Output: 	7 10 50

Exemplul 3:

Input: 		7 0
Output: 	7
  • Exercitiul 4 [1 pct]: Pentru un vector citit similar cu citirea de la Execițiul 1, calculați media aritmetică a valorilor pare din vector. Dacă toate elementele vectorului sunt impare, afișați la consolă mesajul „Niciun element par in vector!”. Folosiți 2 variabile pentru calculul mediei aritmetice: sum pentru suma elementelor și counter pentru a număra căte elemente intră în calculul mediei. Veți afișa la consolă, rezultatul folosind instrucțiunea printf(“%f\n”, sum / counter); Studiați și explicați ce se afișează în fiecare din situațiile următoare pentru vectorul: 3 5 4 7 2 4 0
    • int sum; int counter;
    • float sum; int counter;
  • Exercitiul 5 [2 pct]: Pentru un vector citit similar cu citirea de la Execițiul 1, implementați algoritmul de sortare Bubble Sort și afișați vectorul sortat crescător
Exemplu:

Input: 		3 5 1 7 10 5 0
Output: 	1 3 5 5 7 10
  • Exercitiul 6 [2 pct]: Pentru doi vectori aflați în memorie (în scopul acestui exercițiu, puteți hardcoda în memorie cei doi vectori fără să fie nevoie de citirea lor de la tastatură), implementați algoritmul de interclasare. Interclasarea presupune existența a doi vectori sortați (NU uitați să apelați algoritmul de sortare de la Exercițiul 5 pe cei doi vectori inițiali dacă aceștia nu sunt sortați) care ulterior vor fi reuniți într-un singur vector de lungime egală cu suma lungimilor celor doi vectori inițiali (declarați voi acest vector rezultat), vector ce este de asemenea sortat. Soluția trebuie implementată astfel încât fiecare din cei doi vectori inițiali este parcurs o singură, iar vectorul rezultat este construit deja sortat – NU copiați elementele celor doi vectori în vectorul soluție după care să apelați vreun algoritm de sortare, generați vectorul rezultat direct sortat.
Exemplu:

Vector 1: 	1 3 3 5 6 7
Vector 2: 	2 3 4 4 8
Output: 	1 2 3 3 3 4 4 5 6 7 8
Exemplu:

Output:		2 3 5 7 11 13 17 19
BONUS
  • Bonus 1 [2 pct]: Se dă un vector de numere naturale (hardcodat sau citit de la tastatură, la alegerea voastră). Reordonați vectorul astfel încât fiecare element par se află înaintea tuturor elementelor impare: vectorul reordonat va conține toate elementele pare din vectorul initial (în ordinea din vector) urmate de toate elementele impare (în orice ordine). NU folosiți un vector adițional pentru implementare și parcurgeți vectorul dat o singură dată pentru a genera rezultatul, iar apoi îl mai puteți parcurge o dată pentru afișare.
Pentru vectorul:	3 5 1 7 10 5 0
O soluție validă:	10 0 1 7 3 5 5
O soluție invalid:	0 10 1 7 3 5 5
  • Bonus 2 [2 pct]: Se dă un vector de numere naturale de lungime N în care se regăsesc toate numerele de la 1 la N - 1 (fiecare număr o singură data, cu excepția unui singur număr care apare de 2 ori; elementele NU sunt sortate). Găsiți elementul duplicat eficient. Pentru implementare, puteți hardcoda un vector în cod, având caracteristicile descrise în enunț. (Indiciu: suma elementelor).

Referinţe

programare/laboratoare/lab05.txt · Last modified: 2021/11/08 18:27 by marius.vintila
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0