Sunteți pe pagina 1din 8

Lectia 2. Vector in STL. Grafuri Neorientate.

Vectori in STL
Vectorii sunt containăre alocate dinamic exact ca tablourile statice având o organizare
secvențială a elementelor.

Pentu declararea unui vector trebuie inclusă biblioteca <vector> unde se află funcții


de manipulare a acestui container.

 Structura generală a unui astfel de vector este

vector<tip_date> nume;
exemplu
vector<int> v;
Inițial vectorul este gol având 0 poziții. Se obișnuiește ca în lucrul cu tablouri statice
din C să se prealoce numărul de poziții pentru cât se crede că va fi nevoie și folosirea
poziților de la 1. Ambele sunt obiceiuri proaste deși poate face problemele de numărare mai
ușoare se pierde o poziție în zadar care deși pare puțină memorie câte un pic de aici și
deacolo într-un program mai complex și se adună iar despre prealocare nu trebuie să mai
zic, ori prea multă memorie alocata inutil pentru un caz în care se folosesc doar 10 poziții
sau unul mult prea mare, de aceea este folositor o astfel de clasa.

 După declarare se pot adăuga elemente în vector folosind funcția membru ca orice
membru cu .(punct)

for(int i=1;i<=10;i++)
v.push_back(i);

 Parcurgerea vectorului unde am adugat elemente și afișarea lui

for(int i=0;i<v.size();i++)
cout<<v[i]<<" "; | cout<<v.at(i)<<" ";
pre.
1 2 3 4 5 6 7 8 9 10

.at() este un membru de random-access specific vectorului care înlocuiește operatorul []


având avantajul că dacă se întreabă de o poziție invalidă aruncă excepție.
.size() este un membru care reține câte elemente sunt în vector. 

Atenție Elementele sunt mereu indexate de la 0, astfel .size() va fi mereu o poziție invalidă,
ex. v={5,2,1}, v[0]=5,v[1]=2,v[2]=1. .size()=3, sunt 3 elemente însă de la 0-
2 sunt 3 elemente.

 Parcurgerea vectorului și afișarea lui cu ajutorului unui Iterator.

Ce este un Iterator?
Un iterator este un pointer la adresă specific fiecărui tip de container pentru
parcurgerea lui din adresă în adresă.

Cum funcționeaza?
Iteratorul se declară pentru fiecare tip de container și fiecare tip de date a
containerului. NU se pot folosi pentru alte containere de alt tip. După declarare se atribuie
Lectia 2. Vector in STL. Grafuri Neorientate.

adresa de început .begin() și se se incrementează, vectorul fiind declarat secvential se va


oprii atunci cand se termină segmentual pe care este declarat vectorul.

Fiecare vector are 2 membrii .begin(), .end().


.begin() are adresa de început a vectorului și .end() adresa de sfârșit a vectorului care se
află cu o poziție după ultimul element și ea este o poziție invalidă care nu face parte
din vector. Fiecare element se parcurge prin dereferinta iteratorului.

! Orice operatiune de modificare a secventei din interiorul vectorului va devaliza iteratorul


deoarece spatiul va fi realocat.

Exemplu
vector<int> v;
vector<int>::iterator it;
for(it=v.begin();it!=v.end();it++)
cout<<*it<<" ";
Mai Mult
Se poate forta prin instructiunea auto să determine ce fel iterator este nevoie pentru
a parcurge containerul.
vector<int> v;
for(auto i:v)
cout<<i<<" ";

 Stergerea elementului din coada in timp constant folosind pop_back())

Considerând că avem elementele de la 1-10 în vector. Vom șterge 5 elemente astfel:

while(v.size()>5)
v.pop_back();

 Constructor și alocare
Vectorul nu este obligatoriu să fie gol el poate să fie declarat cu un anumit număr de
poziții exact cum s-ar face la cei statici și să fie modificate pe parcurs. De asemenea
el are în componență un constructor care spune cu ce să umple fiecare poziție.

vector<int> v(100)
Vector cu 100 de elemente cu pozitii de la 0-99 cu valoarea 0 implicită.
vector<int> v(100,1)
Vector cu 100 de elemente cu pozitii de la 0-99 cu valoarea 1.

Schimbarea pe parcurs a lungii se face ori cu push_back ori cu pop_back care mareste sau


micsorează marimea ori de cate ori este chemata functia. Însă ea poate fi facută și manual
cu funcția .resize().

vector<int> v; // 0 elemente
v.push_back(5) // 1 element {5}
v.resize(2) // 2 elemente {5,0}
v.push_back(6), v.push_back(10) // 4 elemente {5,0,6,7}
v.resize(3) // 3 elemente {5,0,6}
v.resize(10) // 10 elemente {5,0,6,0,0,0,0,0,0,0}
Acest lucru se poate folosi in cazul vectorilor de fecventă.
Lectia 2. Vector in STL. Grafuri Neorientate.

vector<int> v;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>x;
if(x>=v.size())
v.resize(v.size())
v[x]=1;
}

 Stergeri și inserare
Acest lucru se poate realiza în aproximativ o(n), limitata de modalitate de structurare
a vectorului folosind .erase(), .insert().

Pentru inserarea pe poziția 2 a numarului 10 considerând că aceasta există trebuie trimis


pointer de la sfârșit sau începtul vectorlui.
v.insert(v.begin()+2,10);
La fel și la stergere.
v.erase(v.begin()+2);
Pentru stergerea tuturor elementelor pare din vector.
for(int i=0;i<v.size();i++)
if(v.at(i)%2==0)
v.erase(v.begin()+i),i--;
Pentru inserarea dupa fiecare numar par dublul lui.
for(int i=0;i<v.size();i++)
if(v.at(i)%2==0)
v.insert(v.begin()+i+1,v.at(i)*2),i++;

 Alte categorii de vectori


vector<vector<int>> v; // matrici
vector<string> v; // vector de cuvinte
vector<pair<int,int>> v; // vector de perechi, elementele se aceseaza ca fiind
de exemplu v[1].first, v[1].second dintr-o pereche.

Grafuri neorientate.
Reprezentarea grafuri neorientate se face prin:
Matricea de adiacenta
Cod ce construieste si afiseaza matricea de adiacenta
#include <bits/stdc++.h>
using namespace std;
ifstream fin("adiacenta.in");
ofstream fout("adiacenta.out");
vector< vector<bool> >a(101,vector<bool>(101));
int main()
{
int n,m,i,j;
fin >> n >> m;
for(int p = 1 ;p <= m ; p++)
{
Lectia 2. Vector in STL. Grafuri Neorientate.

fin >> i >> j;


a[i][j]=1;
a[j][i]=1;
}
for(int i = 1 ; i <= n ; i++){
for(int j = 1; j <= n ; ++j)
fout << a[i][j] << " ";
fout << "\n";
}

return 0;
}
Lista de adiacenta
#include < bits/stdc++.h>
using namespace std;

typedef bool matrice[105][105];


ifstream fin("listavecini.in");
ofstream fout("listavecini.out");

int n, m, a, b;
matrice G;
vector <int> vecini;
vector <int>::iterator it;

int main()
{
fin >> n;
while (fin >> a >> b)
G[a][b] = G[b][a] = true;
for (int i = 1; i <= n; i++)
{
vecini.clear();
for (int j = 1; j <= n; j++)
if (graf[i][j]) vecini.push_back(j);
fout << vecini.size() << " ";
for (it = vecini.begin(); it != vecini.end(); it++)
fout << *it << " ";
fout << "\n";
}
}

Probleme pbinfo: 412, 413, 414, 416,430,417

Parcurgerea grafurilor
Parcurgerea unui graf presupune examinarea sistematică a vârfurilor grafului, cu scopul
prelucrării informațiilor asociate vârfurilor.
Există două metode fundamentale de parcurgere a grafurilor:
parcurgerea în adâncime (Depth First Search - DFS)
parcurgerea în lățime (Breadth First Search - BFS).

Parcurgerea în lățime (BFS)


Parcurgerea în lățime începe, de asemenea, cu un vârf inițial, denumit vârf de start. Se
vizitează mai întâi vârful de start. Se vizitează în ordine toți vecinii nevizitați ai vârfului de start. Apoi
Lectia 2. Vector in STL. Grafuri Neorientate.

se vizitează în ordine toți vecinii nevizitați ai vecinilor vârfului de start și așa mai departe, până la
epuizarea tuturor vârfurilor accesibile din vârful de start.
Exemplu:

Considerăm nodul 3 ca nod de start.


Se vizitează mai întâi vârful de start 3. Apoi se vizitează, în ordine, vecinii nevizitați ai lui 3,
deci 4, 5 şi 9. Se vizitează apoi, în ordine, vecinii nevizitați ai lui 4 (vârfurile 1 și 2), apoi ai lui 5
(vârful 10) și apoi ai lui 9 (care nu are vecini nevizitați). Se vizitează apoi vecinii vârfului 1 (vârfurile
6 și 7) şi parcurgerea s-a încheiat (deoarece vârful 2 nu mai are vecini nevizitați, nici vârful 10 și nici
vârfurile 6 și 7).
Concluzionând, ordinea în care sunt vizitate vârfurile grafului la parcurgerea BFS cu vârful de
start 3 este : 3,4,5,9,1,2,10,6,7.
Implementare:
Graful va fi reprezentat prin liste de adiacență (în una dintre cele trei variante). Pentru a reţine
care vârfuri au fost deja vizitate în timpul parcurgerii vom utiliza un vector 𝑣𝑖𝑧, cu 𝑛 componente din
mulţimea {0,1}, cu semnificaţia 𝑣𝑖𝑧[𝑖] = 1 dacă vârful 𝑖 a fost deja vizitat, respectiv 0, în caz contrar.
Observând că ordinea de parcurgere completă a vecinilor unui nod este exact ordinea
“atingerii” lor, abordarea cea mai simplă folosită pentru parcurgerea efectivă este cea care folosește o
coadă (pentru a reține ordinea vizitării elementelor). Această coadă poate fi implementată “clasic”
(printr-un vector 𝐶 cu 𝑛 elemente; variabilele 𝑝𝑟𝑖𝑚 și 𝑢𝑙𝑡𝑖𝑚 rețin poziția de început, respectiv de
sfârșit a cozii) sau cu ajutorul STL-ului (𝑞𝑢𝑒𝑢𝑒).
#include <bits/stdc++.h>
using namespace std;
ifstream fin("BFS.in");
ofstream fout("BFS.out");
int n,m,x;
int t[101];
bitset<N>viz;
vector<int> G[101];
void Bfs(int x);
int main()
{
fin>>n>>m>>x;
int a,b;
for(int i=1;i<=m;i++)
{
fin>>a>>b;
G[a].push_back(b);
G[b].push_back(a);
}
for(int i=1;i<=n;i++)
sort(G[i].begin(),G[i].end());
Bfs(x);
return 0;
}
void Bfs(int x)
{
queue<int> q;
viz[x]=1;
q.push(x);
Lectia 2. Vector in STL. Grafuri Neorientate.

while(!q.empty())
{
int k=q.front();
for(vector<int> :: iterator it=G[k].begin();it<G[k].end();it++)
if(viz[*it]==0)
{
viz[*it]=1;
t[*it]=k;
q.push(*it);
}
fout<<k<<" ";
q.pop();
}
}

Observații:
Parcurgerea în lățime are o proprietate remarcabilă: fiecare vârf este vizitat pe cel mai scurt
drum / lanț începând din vârful de start.
Complexitatea parcurgerii în lățime (BFS) în cazul reprezentării prin liste de adiacență este
𝑂(𝑛+𝑚) (în cazul reprezentării prin matrice de adiacență complexitatea este 𝑂(𝑛2)).

Implementare cu matrici de adiacenta


#include <fstream>
#define N 101
using namespace std;
ifstream fin("BFS.in");
ofstream fout("BFS.out");
int A[N][N],n,m,k;

void citirem()
{
int x,y;
fin>>n>>m>>k;
while(fin>>x>>y)
A[x][y]=A[y][x]=1;
///A[x][y]=1; pt orientate
}

int coada[N],viz[N],ic,sfc;
void bfsr()
{
if(ic<=sfc)
{
for(int k=1;k<=n;k++)
if(A[coada[ic]][k]==1 && viz[k]==0)
{
sfc++;
coada[sfc]=k;
viz[k]=1;
}
ic++;
bfsr();
}
}

int main()
{
citirem();
ic=sfc=1;
Lectia 2. Vector in STL. Grafuri Neorientate.

coada[ic]=k;
viz[k]=1;
bfsr();
for(int i=1;i<=sfc;i++) fout<<coada[i]<<" ";
return 0;
}

Parcurgerea în adâncime (DFS)


Parcurgerea începe cu un vârf inițial, denumit vârf de start. Se vizitează mai întâi vârful de
start. La vizitarea unui vârf se efectuează asupra informațiilor asociate vârfului o serie de operații
specifice problemei.
Se vizitează apoi primul vecin nevizitat al vârfului de start. Vârful 𝑦 este considerat vecin al
vârfului 𝑥 dacă există muchia [𝑥,𝑦] (pentru graf neorientat), respectiv arcul (𝑥,𝑦) (pentru graf orientat).
Se vizitează în continuare primul vecin nevizitat al primului vecin al vârfului de start, şi aşa
mai departe, mergând în adâncime până când ajungem într-un vârf care nu mai are vecini nevizitaţi.
Când ajungem într-un astfel de vârf, revenim la vârful său părinte (vârful din care acest nod a
fost vizitat). Dacă acest vârf mai are vecini nevizitaţi, alegem primul vecin nevizitat al său şi
continuăm parcurgerea în acelaşi mod. Dacă nici acest vârf nu mai are vecini nevizitaţi, revenim în
vârful său părinte şi continuăm în acelaşi mod, până când toate vârfurile accesibile din vârful de start
sunt vizitate.
Exemplu:

Parcurgând graful din figură în adâncime considerând drept vârf de start vârful 3 putem obține
următoarea ordinea de vizitare a vârfurilor accesibile din nodul de start: 3,4,1,2,6,7,10,5,9
(Pentru această succesiune, ordinea de vizitare a vecinilor unui vârf este ordinea crescătoare a
numerelor lor)
Implementare:
Graful va fi reprezentat prin liste de adiacență (în una dintre cele trei variante). Pentru a reţine
care vârfuri au fost deja vizitate în timpul parcurgerii vom utiliza un vector 𝑣𝑖𝑧, cu 𝑛 componente din
mulţimea {0,1}, cu semnificaţia
𝑣𝑖𝑧[𝑖] = 1 dacă vârful 𝑖 a fost deja vizitat, respectiv 0, în caz contrar.
Observând că ordinea de parcurgere completă a vecinilor unui nod este ordinea inversă a
“atingerii” lor, abordarea cea mai simplă folosită pentru parcurgerea efectivă este cea recursivă.

#include <bits/stdc++.h>
using namespace std;
ifstream fin("dfs.in");
ofstream fout("dfs.out");
const int N=101;
bitset <N> viz;
vector <int> G[N];
inline void dfs(int x)
{
viz[x]=1;
fout<<x<<" ";
for(vector <int>::iterator it=G[x].begin();it!=G[x].end();it++)
Lectia 2. Vector in STL. Grafuri Neorientate.

{
if(viz[*it]==0)
{
dfs(*it);
}
}
}
int main()
{
int n,m,i,X,x,y;
fin>>n>>m>>X;
for(i=1;i<=m;i++)
{
fin>>x>>y;
G[x].push_back(y);
G[y].push_back(x);
}
for(i=1;i<=n;i++) sort(G[i].begin(),G[i].end());
dfs(X);
}

Observații:
Complexitatea parcurgerii în adâncime (DFS) în cazul reprezentării prin liste de adiacență este
𝑂(𝑛+𝑚) (în cazul reprezentării prin matrice de adiacență complexitatea este 𝑂(𝑛2)).
Implementare cu matrici de adiacenta
#include <fstream>
#define N 101
using namespace std;
ifstream fin("dfs.in");
ofstream fout("dfs.out");
int A[N][N],n,m,k,viz[N];

void citirem()
{
int x,y;
fin>>n>>m>>k;
while(fin>>x>>y)
A[x][y]=A[y][x]=1;
///A[x][y]=1; pt orientate
}

void dfs(int nod)


{
fout<<nod<<" ";
viz[nod]=1;
for(int k=1;k<=n;k++)
if(A[nod][k]==1 && viz[k]==0) dfs(k);
}

int main()
{
citirem();
dfs(k);
return 0;
}

Probleme pbinfo: 19,539,437,438

S-ar putea să vă placă și