그림 이론 - 최소 생 성 트 리 문제 해결 (Kruskal 알고리즘 & Prim 알고리즘)

지난 주말 블 루 브리지 컵 시 뮬 레이 션 에서 마지막 문 제 는 최소 생 성 트 리 문 제 였 다. 마침 모 수업 에서 이곳 을 처음 봤 기 때문에 지금 은 Prim 알고리즘 을 배 워 서 이 문 제 를 해결 했다. 경기 후에 이런 문 제 를 많이 다 듬 을 계획 이다.제목 링크 의 최소 생 성 트 리 문 제 는 그림 G = (V, E) 을 지정 하고 G 의 모든 점 을 연결 하 며, 사 이 드 집합 은 E 의 부분 집합 트 리 를 G 의 생 성 트 리 (Spanning Tree) 라 고 하고, 가중치 가 가장 작은 생 성 트 리 를 최소 생 성 트 리 (Minimal Spanning Tree, MST) 라 고 한다.
1. Kruskal 알고리즘
Kruskal 알고리즘 은 작성 하기 쉽 고 효율 이 높 습 니 다. 자서 p356 설명 은 다음 과 같 습 니 다.
Kruskal 알고리즘 의 첫 번 째 단 계 는 모든 변 을 작은 것 부터 큰 것 까지 순서대로 배열 하 는 것 입 니 다. 이 단 계 는 라 이브 러 리 함수 qsort 또는 sort 를 직접 사용 할 수 있 습 니 다.다음은 어 릴 때 부터 차례대로 각 변 (u, v) 을 고찰 한다.
4. 567917. 상황 1: u 와 v 가 같은 연결 분량 에 있 으 면 (u, v) 를 넣 으 면 링 이 형성 되 기 때문에 선택 할 수 없습니다
4. 567917 상황 2: u 와 v 가 서로 다른 연결 분량 에 있다 면 가입 (u, v) 이 가장 좋 을 것 이다.왜 일 까요?다음은 반증 법 을 사용 합 니 다. 이 변 을 추가 하지 않 으 면 가장 좋 은 해 를 얻 을 수 있 습 니 다. T + (u, v) 는 반드시 있 고 하나의 고리 만 있 습 니 다. 그리고 링 에 적어도 한 개의 변 (u, v) 의 가중치 가 (u, v) 의 가중치 보다 크 거나 같 습 니 다.이 쪽 을 삭제 하면 받 은 새 트 리 T = T + (u, v) - (u, v) 는 T 보다 나 쁘 지 않 습 니 다.따라서 가입 (u, v) 은 가입 하지 않 는 것 보다 나 쁘 지 않 을 것 이다
다음은 위조 코드 입 니 다.
    (   )  ,  i    e[i](1 <= i < m)
   MST  
       ,               
for(int i = 0; i < m; i++) {
	if(e[i].u   e[i].v          ) {
		  e[i]  MST
		  e[i].u e[i].v       
	}
}

위의 위조 코드 에서 가장 관건 적 인 부분 은 '연결 분량 의 조회 와 합병' 이다. 임의의 두 점 이 같은 연결 분량 에 있 는 지, 그리고 이 두 연결 분량 을 합병 해 야 하 는 지 알 아야 한다.간단 하고 효율 적 인 알고리즘 은 이 문 제 를 처리 하 는 데 사용 할 수 있 습 니 다. 그리고 여기 서 길 을 가리 키 는 큰 사람 을 찾 아 설명 할 수 있 습 니 다. 매우 사랑 하고 집 을 찾 습 니 다. 모든 연결 분량 을 하나의 집합 으로 볼 수 있 습 니 다. 이 집합 은 연결 분량 중의 모든 점 을 포함 합 니 다.이 점 들 은 서로 통 하지만 구체 적 인 연결 방식 은 중요 하지 않다.그림 에서 모든 점 은 하나의 연결 분량 에 속 하고 집합 표시 에 대응 하 며 모든 요소 가 마침 하나의 집합 에 속한다.그림 의 모든 연결 분량 은 교차 하지 않 는 집합 으로 표시 할 수 있다 는 얘 기다.자기 의 것 을 바 치고 집 판 을 조사 하 다
#include 
using namespace std;
#define div 1000000007
typedef long long ll;
const int maxn = 1001;
int fa[maxn];//
inline void init(int n) {
    for (int i = 1; i <= n; i++)
        fa[i] = i;
}
int find(int x) {//  +                       
    return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
inline void merge(int i, int j) {
    fa[find(i)] = find(j);//          
}

집합 을 찾 은 후에 Kruskal 알고리즘 의 전체 코드 는 어렵 지 않 게 제시 되 었 다.i 조 변 의 두 점 번호 와 가중치 가 각각 u [i], v [i] 와 w [i] 에 저장 되 고 정렬 후 i 작은 변 의 번 호 는 r [i] 에 저장 된다 고 가정 합 니 다.
전체 코드:
int cmp(const int i, const int j) {	return w[i] < w[j]; }	//      
int find(int x) { return p[x] == x ? x : p[x] = find(p[x])}	//    find
int Kruskal() {
	int ans = 0;
	for(int i = 0; i < n; i++) p[i] = i;	//      
	for(int i = 0; i < m; i++) r[i] = i;
	sort(r,r+m,cmp);
	for(int i = 0; i < m; i++) {
		int e = r[i];
		int x = find(u[e]);//            
		int y = find(v[e]);//             
		if(x != y) {		//      ,  
			ans += w[e];
			p[x] = y;
		}
	}
	return ans;
}

연습 문제:
번호
제목 이름 (영어)
비고
hdu1863
원활 한 공사
나무판 자 문제 최소 생 성
hdu1879
계속 원활 한 공사
나무판 자 문제 최소 생 성
hdu1875
원활 한 공사 재 개
나무판 자 문제 최소 생 성
낙 곡 P3366
【 템 플 릿 】 최소 생 성 트 리
나무판 자 문제 최소 생 성
1. hdu 1863 원활 한 공사
Problem Description 성 정부의 '원활 한 공사' 목 표 는 성 전체 두 마을 간 에 도로 교통 을 실현 할 수 있 도록 하 는 것 이다.조사 평 가 를 통 해 얻 은 통계 표 에는 도 로 를 건설 할 수 있 는 몇 개의 도로 비용 이 열거 되 어 있다.지금 당신 이 프로그램 을 작성 하여 성 전체 가 원활 하 게 통 하 는 데 필요 한 최저 원 가 를 계산 해 주 십시오.Input 테스트 입력 에는 몇 가지 테스트 용례 가 포함 되 어 있 습 니 다.각 테스트 용례 의 첫 줄 에 평 가 된 도로 개수 N, 마을 수 M (< 100) 을 제시 합 니 다.그 다음 에 N 행 은 마을 간 도로 의 원가 에 대응 하여 각 줄 에 한 쌍 의 정 수 를 제시 했다. 각각 두 마을 의 번호 와 이 두 마을 간 도로 의 원가 (정수) 이다.간단하게 말하자면, 마을 은 1 부터 M 까지 번호 가 있다.N 이 0 일 때 모든 입력 이 끝나 고 해당 결 과 는 출력 하지 마 십시오.Output 은 모든 테스트 사례 에 대해 1 줄 에서 성 전체 가 원활 하 게 통 하 는 데 필요 한 최소 비용 을 출력 합 니 다.통계 데이터 가 원활 함 을 보장 하지 못 하면 출력 "?"
#include 
#include 
using namespace std;
const int maxn = 105;
int N,M,cnt;
int u[maxn],v[maxn],w[maxn];
int r[maxn],fa[maxn];
bool cmp(const int r1, const int r2) {
    return w[r1] < w[r2];
}
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int Kruskal() {
    int ans = 0;
    for(int i = 0; i < M; i++) fa[i] = i;//      ,            
    for(int i = 0; i < N; i++) r[i] = i;//     
    sort(r, r+N, cmp);//           
    for(int i = 0; i < N; i++) {
        int e = r[i];
        int x = find(u[e]);
        int y = find(v[e]);
        if(x != y) {
            ans += w[e];
            cnt++;
            fa[x] = y;
        }
    }
    return ans;
}
int main() {
    while(cin >> N >> M) {
        cnt = 0;
        if(N == 0) break;
        for(int i = 0; i < N; i++) {
            cin >> u[i] >> v[i] >> w[i];
        }
        int ans = Kruskal();
        if(cnt != M-1) cout << "?" << endl;
        else cout << ans << endl;
    }
    return 0;
}

2. hdu 1879 계속 원활 한 공사
이 문 제 는 위의 문제 에 비해 이미 만들어 진 도 로 를 0 으로 설정 하면 되 며, 동시에 본 문제 의 입 출력 은 cincout 를 사용 할 수 없 을 것 같 아서 시간 을 초과 할 수 있 습 니 다.
Problem Description 성 정부의 '원활 한 공사' 목 표 는 성 전체 두 마을 간 에 도로 교통 을 실현 할 수 있 도록 하 는 것 이다.현재 도시 도로 통계 표를 얻 었 는데 표 에는 임의의 두 도시 간 에 도 로 를 건설 하 는 비용 과 이 도로 가 이미 뚫 렸 는 지 의 상태 가 열거 되 어 있다.지금 당신 이 프로그램 을 작성 하여 성 전체 가 원활 하 게 통 하 는 데 필요 한 최저 원 가 를 계산 해 주 십시오.Input 테스트 입력 에는 몇 가지 테스트 용례 가 포함 되 어 있 습 니 다.각 테스트 용례 의 첫 번 째 줄 은 마을 수 N (1 < N < 100) 을 드 립 니 다.그 다음 에 N (N - 1) / 2 줄 은 마을 간 도로 의 원가 와 건설 상태 에 대응 하여 각 줄 에 4 개의 정 수 를 주 었 는데 각각 두 마을 의 번호 (1 번호 에서 N 까지) 이다. 이 두 마을 간 도로 의 원가 와 건설 상 태 는 1 은 이미 건설 되 었 고 0 은 건설 되 지 않 았 음 을 나타 낸다.N 이 0 일 때 입력 이 끝 납 니 다.Output 의 모든 테스트 용례 의 출력 은 한 줄 을 차지 하고 전 성의 원활 한 출력 에 필요 한 최저 원 가 를 차지한다.
#include 
#include 
using namespace std;
const int maxn = 5008;
int fa[101];
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
struct Edge {
public:
    int u, v, w;
    bool operator<(const Edge &a)const{ 
        return w < a.w;
    }
}E[maxn];

int main() {
    int N;
    while(scanf("%d", &N) != EOF) {
        int ans = 0;
        if(N == 0) break;
        int M = N*(N-1)/2;
        for(int i = 1; i <= M; ++i) {
            int flag;
            scanf("%d%d%d%d", &E[i].u, &E[i].v, &E[i].w, &flag);
            if(flag) E[i].w = 0;
        }
        for(int i = 1; i <= N; ++i) fa[i] = i;//      ,            
        sort(E+1,E+1+M);//           
        for(int i = 1; i <= M; ++i) {
            int x = find(E[i].u);
            int y = find(E[i].v);
            if(x != y) {
                fa[x] = y;
                ans += E[i].w;
            }
        }
        printf("%d
"
,ans); } return 0; }

3. hdu 1875 원활 한 공사 재 개
먼저 모든 정점 을 입력 한 다음 에 두 개의 정점 이 적당 한 변 을 구성 할 수 있 는 지 판단 한다.
Problem Description '백도 호' 라 는 곳 에 대해 들 어 보 셨 을 거 라 고 믿 습 니 다. 백도 호 주민 들 은 서로 다른 섬 에 살 고 있 습 니 다. 다른 섬 에 가 고 싶 을 때 작은 배 를 저어 이 루어 집 니 다.현재 정 부 는 백도 호 를 대대적으로 발전 시 키 기로 결정 했다. 발전 에 있어 서 먼저 해결 해 야 할 문 제 는 당연히 교통 문제 이다. 정 부 는 백도 호의 원활 한 소통 을 실현 하기 로 결정 했다!탐사 팀 RPRush 를 통 해 백도 호의 상황 을 충분히 파악 한 뒤 조건 에 맞 는 작은 섬 사이 에 다 리 를 놓 기로 했다. 조건 에 맞 는 다 는 것 은 작은 섬 2 곳 사이 의 거리 가 10m 보다 작 으 면 안 되 고 1000 m 이상 이면 안 된다 는 것 이다.물론 돈 을 아 끼 기 위해 서 는 임 의 2 개 작은 섬 사이 에 길이 있 으 면 된다.그 중 다리 의 가격 은 미터 당 100 위안 이다.Input 입력 은 여러 그룹의 데 이 터 를 포함한다.입력 은 먼저 정수 T (T < = 200) 를 포함 하고 T 조 데이터 가 있 음 을 의미 합 니 다.각 조 의 데 이 터 는 먼저 하나의 정수 C (C < = 100) 로 작은 섬의 개 수 를 대표 하고 그 다음은 C 조 좌표 로 모든 작은 섬의 좌 표를 대표 한다. 이런 좌 표 는 모두 0 < = x, y < = 1000 의 정수 이다.Output 각 조 의 입력 데이터 출력 줄 은 다 리 를 건설 하 는 최소 비용 을 대표 하고 결 과 는 작은 숫자 를 유지 합 니 다.모든 원활 한 공 사 를 이 루 지 못 하면 수출 "oh!".
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 5008;
typedef long long ll;
struct Point {
    int x,y;
} V[101];
struct Edge {
public:
    int u, v;
    double w;
    bool operator<(const Edge &a)const{ 
        return w < a.w;
    }
}E[maxn];
int fa[105];
int find(int x) {
    return fa[x] == -1 ? x : fa[x] = find(fa[x]);
}
int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int N;
        scanf("%d", &N);
        for(int i = 0; i < N; ++i) {
            scanf("%d%d", &V[i].x, &V[i].y);
        }
        int k = 0;
        for(int i = 0; i < N-1; ++i) {
            for(int j = i+1; j < N; ++j) {
                double d = sqrt( (V[i].x-V[j].x)*(V[i].x-V[j].x) + 
                (V[i].y-V[j].y)*(V[i].y-V[j].y) );
                if (d <= 1000 && d >= 10) {
                    E[k].u = i;
                    E[k].v = j;
                    E[k].w = d*100;
                    k++;
                }
            }
        }
        for(int i = 0; i < N; ++i) fa[i] = -1;//      ,            
        sort(E,E+k);//           
        double ans = 0;
        int cnt = 0;
        for(int i = 0; i < k; ++i) {
            int x = find(E[i].u);
            int y = find(E[i].v);
            if(x != y) {
                fa[x] = y;
                ans += E[i].w;
                cnt++;
            }
        }
        if (cnt == N-1) printf("%.1f
"
,ans); else printf("oh!
"
); } return 0; }

4. 낙 곡 P3366 【 템 플 릿 】 최소 생 성 트 리
판자 문제
#include 
#include 
using namespace std;
const int maxm = 200000;
int fa[5005];//     5000
int N,M,cnt;//      
struct edge {
    int u,v,w;
    bool operator<(const edge& a) const {
        return w < a.w;
    }
} E[maxm];//    maxn
int find(int x) {
    return fa[x] == -1 ? x : fa[x] = find(fa[x]);
}
int Kruskal() {
    for(int i = 0; i < N; ++i) fa[i] = -1;
    int ans = 0;
    sort(E,E+M);
    for(int i = 0; i < M; ++i) {
        int x = find(E[i].u);
        int y = find(E[i].v);
        if(x != y) {
            fa[x] = y;
            ans += E[i].w;
            cnt++;
        }
    }
    if(cnt == N-1) return ans;
    else return -1;
}
int main() {
    scanf("%d%d", &N, &M);
    for(int i = 0; i < M; ++i) {
        scanf("%d%d%d", &E[i].u, &E[i].v,&E[i].w);
    }
    int ans = Kruskal();
    if(ans == -1) printf("orz
"
); else printf("%d
"
,ans); return 0; }

2. Prim 알고리즘
진 월 할머니 의 데이터 구조 인 Prim 알고리즘 의 기본 적 인 사고방식 은 하나의 노드 에서 시작 하여 작은 나 무 를 자라 게 하 는 것 이다.Dijkstra 알고리즘 과 비슷 합 니 다.Dijkstra 알고리즘 위조 코드:
void Dijkstra(Vertex s) {
	while(1) {
		V =       dist   
		if (   V   )
			break;
		collected[V] = true;// V  
		for(V      W) {
			if(collected[W] == false && dist[V] + E(V,W) < dist[W]) {
				dist[W] = dist[V]+E<V,W>;
				path[W] = V;
			} 
		}
}

Prim 알고리즘 위조 코드:
void Prim(Vertex s) {
	while(1) {
		V =       dist   
		if (   V   )
			break;
		 V   MST
		for(V      W) {
			if(W     && E(V,W) < dist[W]) {
				dist[W] = E<V,W>;
				parent[W] = V;
			} 
		}
}

전체 코드
double edge[maxn][maxn];//     ~
int vis[maxn];//        
double dist[maxn];
double ans;
double Prim() {
    for(int i = 2; i <= n; i++) {
        dist[i] = edge[1][i];//   dist    1       
    }
    vis[1] = 1;////     1 vis[i] == 0       
    for(int i = 2; i <= n; i++) {
        int min = INF;
        int v = -1;
        for(int j = 2; j <= n; j++) {// v————      dist   
            if(!vis[j] && dist[j] < min) {
                min = dist[j];
                v = j;
            }
        }
        if(v != -1) {//   v~  MST
            vis[v] = 1;
            ans += dist[v];
            for(int j = 2;j <= n; j++) {//    dist
                if(!vis[j] && edge[v][j] < dist[j]) {//                            
                    dist[j] = edge[v][j];
                }
            }
        }
    }
    return ans;
}

좋은 웹페이지 즐겨찾기