Minimum spanning tree 10/100 (risolto)

Stavo provando a fare un algoritmo per il minimum spanning tree. Ho cercato di seguire il ragionamento del kruskal’s algorithm (lo ho trovato solo implementato con classi e strutture di cui capisco poco), quel che ho fatto è stato inserire i due nodi con la distanza in una priority queue, in modo da ottenere le distanze ordinate, poi finchè la queue non è vuota controlla se i due nodi sono visitati, o raggiungibili, vi allego il programma perchè penso sia più facile comprendere così che a parole:

#include <bits/stdc++.h>
#define MAXN 10001
using namespace std;
vector<int> adj[MAXN];
priority_queue<pair<int ,pair<int, int> > > pq;
priority_queue<pair<int,int> > pr;
int vis[MAXN];
int c=0;
int vis2[MAXN];
long long Matrix[MAXN][2];
long long N;

void DFS(int s,int end){ //se trova il collegamento che porta il nodo s al nodo end, finisce 
//restituendo 1 a sol, che lo restituisce dove viene chiamato
	if(s==end){
		c=1;
	}
	if(vis2[s]==1 || c==1) return;
	vis2[s]=1;
	for(int u=0;u<adj[s].size();u++){
		DFS(adj[s][u], end);
	}
}

int sol(int s, int end){
	c=0;
	for(int u=0;u<N;u++) vis2[u]=0; 
	DFS(s,end);
	return c;
}

int main(void)
{
	FILE *fr, *fw;
    fr = fopen("input.txt", "r");
    fw = fopen("output.txt", "w");
	long long M,i;
	long long a,b,c,r=0;
	assert(2 == fscanf(fr, "%lld%lld", &N, &M)); 
	for(i=0;i<M;i++){
		assert(3 == fscanf(fr, "%lld%lld%lld", &a, &b, &c));
		pq.push(make_pair(-c,make_pair(a,b))); //ordina la queue per distanza
	}
	while(!pq.empty()){
		if((vis[pq.top().second.first]==0 || vis[pq.top().second.second]==0) || (!sol(pq.top().second.first,pq.top().second.second))){ //controlla se entrambi i nodi che hanno tra loro distanza
// minore siano visitati. la funzione invece chiama una DFS perchè
// nel caso siano entrambi già stati visitati bisogna vedere se sono collegati
			vis[pq.top().second.first]=1; //i due nodi sono visitati
			vis[pq.top().second.second]=1;
			adj[pq.top().second.first].push_back(pq.top().second.second); //crea il tree aggiungendo gli archi
			adj[pq.top().second.second].push_back(pq.top().second.first);
			r+=-pq.top().first;	 //aumenta il risultato della distanza di tale arc
			if(pq.top().second.first<pq.top().second.second)pr.push(make_pair(-pq.top().second.first,-pq.top().second.second)); //normalmente non servirebbe ma il problema 
//sul sito richiede anche i nodi che formano l'albero perciò li salva in un altra queue
// (so che non servirebbe ma dai casi di esempio vedevo che li voleva ordinati e non so se sarebbe errore o meno non metterli così)
			else pr.push(make_pair(-pq.top().second.second,-pq.top().second.first));
		}		
		pq.pop(); //rimuove dalla queue nodi e arco controllati
	}
	fprintf(fw, "%d\n", r);
	while(!pr.empty()){
		fprintf(fw, "%d %d\n", -pr.top().first, -pr.top().second);
		pr.pop();
	}
		
return 0;
}

Dall’esercizio mi da qualche risultato sbagliato, se trovate qualche caso in cui non funziona il programma fatemi sapere. (in alcuni casi va anche out di tempo ma onestamente non saprei come velocizzare, al massimo potrei fare un altra if che fa controllare tutto il grafo solo quando entrambi i nodi sono già stati visitati)

Non riesco a debuggare il codice perché non ho il pc ma ti so dire che l’algoritmo di kruskal è molto molto più facile utilizzando le disjoint set union (dsu)
Da quello che vedo sembra tu sia incappato in un’implementazione un po’ molto forzata e inutilmente complessa
[PO2022] Algoritmi e nozioni base sui grafi - YouTube qua trovi una implementazione pulita delle dsu e da lì è molto facile implementare kruskal (fatte le 3 funzioni make_set, find e join della dsu sono letteralmente 5 o 6 righe)

2 Mi Piace

Wow che bella lezione! :eyes:

Ci sono anche i capitoli, quindi puoi saltare subito a Minimum Spanning Tree (1:11:40).

1 Mi Piace

Slides gentilmente prese in prestito da Giada Franz :eyes:

ho visto il video, grazie, spiegato molto bene (per quello che dici (se sei tu a spiegare nel video) al min 1:47:57 si lo sei, vedendo che sei riuscito a farlo capire ad uno studente delle superiori), più tardi proverò ad implementarlo e vi farò sapere se ho problemi

stavo rivedendo l’implementazione fatta nel video, e non mi è chiaro il funzionamento della funzione sort(A.begin(), A.end(), comp); , come già detto non so come funzionino le strutture perciò volevo capire esattamente cosa prende di A quando esegue questa parte? E la funzione comp che si usa per ordinarlo come va scritta? mi da sempre un errore rimandandomi a questo template<typename _Iterator1, typename _Iterator2> bool operator()(_Iterator1 __it1, _Iterator2 __it2) const { return *__it1 < *__it2; } };

Ho provato sia togliendo la funzione comp , che crearla così

bool comp (int i,int j) { return (i<j); }

Come posso fare?
Il programma intero è questo:

#include <bits/stdc++.h>
#define MAXN 10001
using namespace std;
struct arco_t{
	int a,b;
	long long w;
};


vector<arco_t> A;
long long sol;
vector <arco_t> archi_sol;
int P[MAXN], R[MAXN];


void MAKE_SET(int x){
	P[x] = x;
	R[x] = 0;
}
int FIND(int x){
	if (P[x]==x) return x;
	else return P[x] = FIND(P[x]);
}

void UNION(int a, int b){
	a = FIND(a);
	b = FIND(b);
	if(a!=b){
		if(R[a] < R[b]){
			P[a]=b;
		}else{
			P[b]=a;
			if(R[a]== R[b]) R[a]++;
		}
	}
}
int main(){
	int i, N,M;
	cin >> N >> M;
	for(i=0;i<M;i++){
		cin >> A[i].a >> A[i].b >> A[i].w;
	}
	sort(A.begin(), A.end(), comp);
	for(i=0;i<N;i++) MAKE_SET(i);
	for(i=0;i<M;i++){
		if(FIND(A[i].a) != FIND(A[i].b)){
			UNION(A[i].a, A[i].b);
			sol = sol + A[i].w;
			archi_sol.push_back(A[i]);
		}
	}
return 0;
}

Dobbiamo ordinare gli archi crescenti per il loro peso w:
La funzione comp (compara) lavora col campo w delle due istanze di tipo arco_t che le vengono passate come parametri ed il valore che restituisce permette alla funzione sort (che la chiama tutte le volte che le serve) di sapere quale dei due parametri ha il w maggiore.
più o meno dovrebbe essere così

non azzecco mai il verso della disequazione ma basta provare se con < ordina crescente
col > ordina decrescente o viceversa.

D’altra parte la sort è pensata per ordinare qualunque tipo di dato ma l’unica cosa che spesso non sa fare è stabilire fra due dati di uno stesso tipo chi sia il maggiore…

1 Mi Piace

ok ora me lo fa eseguire, grazie, però ora ho un altro problema , cioè quando vado ad eseguire ed incollo i dati (il caso di esempio nel problema sul sito) smette di funzionare(si chiude il terminale), probabilmente ho sbagliato qualcosa qua:

for(i=0;i<M;i++){
		cin >> A[i].a >> A[i].b >> A[i].w;
	}

oppure in qualche passaggio prima

Sto guardando e da problemi anche sul mio sistema di sviluppo comunque è un vecchio problema e questi leggono i dati dal file “input.txt” e scrivono il risultato sul file “output.txt”
quindi in testa subito sotto al al main():

freopen(“input.txt”, “r”, stdin);
freopen(“output.txt”, “w”, stdout);

a parte questo qualcosa non mi convince:
chiami le FIND prima di chiamare La UNION e dentro la UNION richiami le FIND

1 Mi Piace

si si li avrei aggiunti dopo, non mi trovo molto da file quindi faccio sempre senza e prima di sottoporre aggiungo le varie cose, comunque anche aggiungendoli mi si chiude dando errore.
Per quanto riguarda le FIND quelle dell’if nel main servono per controllare se i due nodi appartengono allo stesso albero, e in caso non lo siano le unisce tramite UNION. Le due FIND all’interno dell UNION invece servono per vedere se collegare l’albero a cui appartiene il nodo a all’albero del nodo b o viceversa (in pratica prende il nodo più “alto” del proprio albero)

Sono andato avanti col debug:
Ti si pianta perchè non hai dimensionato A:
non puoi mettere i dati dentro A in quel modo (E’ un vettore di dimensione 0):
O prima fai A.resize(M); // gli archi sono M
o metti dentro i dati a suon di push_back.

con A.resize(M) dopo aver letto M legge, ordina e va fino in fondo peccato che non stampi niente.
Infine mi risulta che dovrebbe essere la Union a chiamare al suo interno le Find per sapere se quell’arco crea un loop o meno per poi regolarsi di conseguenza.

1 Mi Piace

ok grazie, effettivamente non avevo dato dimensione, aggiungedolo e aggiungendo i cout mi da 100/100. Comunque cosa intendi qua

Ma il metodo in italiano sta per Unione di Insiemi Disgiunti:
Passo alla Union i vertici che l’arco collega e la Union mediante le due chiamate alla Find viene a sapere se i due vertici fanno parte dello stesso insieme (se i due vertici hanno lo stesso progenitore, la stessa radice) oppure no.
SE si no aggiunge all’albero quell’arco e in questo caso non conteggia il suo peso.
Se si lo aggiunge all’albero, ne conteggia il peso, infine unisce i due insiemi e in base al rank (R) dei due insiemi stabilisce quale quale dei due (pro)genitori diventa il progenitore anche di tutti gli elementi dell’altro insieme

1 Mi Piace

ed è ciò che fa il mio programma in teoria, magari mi sono espresso male prima o ho confuso qualcosa, grazie dell’aiuto :slight_smile:

Se vuoi ti mando in privato la soluzione con le Find chiamate dentro la Union.
In generale la union è come la sort è aspecifica ma di volta la si può in parte modificare per renderla ad hoc per il dato problema, oppure se chiami le Find prima di chiamare la union prova a passare alla union direttamente i progenitori così la Union non dovrà richiamare di nuovo le Find.
Tieni presente per quanto riguarda i tempi di elaborazione la più onerosa è la find (è ricorsiva), va usata il minimo possibile.

1 Mi Piace

Come preferisci, a questo punto sono curioso ma dove sono le chat private?

la find non va in tempo O(n/ackermann(n)) che e’ quindi O(1)? (ovviamente con path compression e merge ottimale)

Sì, con path compression ed union by rank la complessità delle varie operazioni corrisponde all’inversa di ackermann. Da un punto di vista teorico è diverso da O(1) ma in pratica ci è moooolto vicino.