알고리즘/알고리즘

탐욕 알고리즘 Greedy Algorithm

호두밥 2021. 9. 20. 19:19

탐욕 알고리즘

현재 상황에서 가장 좋아보이는 답을 선택하는 방법.

상황마다 최적을 선택하면 전체 결과값도 최적이 될것이라는 가정을 전제로 함.

 

① 각 단계에 도달할 때 마다, 최적의 결과를 도출한다.

② 결과를 저장한다.

③ 이미 저장한 결과는 다시 판단하지 않는다.

 

탐욕 알고리즘이 적용된 알고리즘들

  • 선택정렬 Selection Sort
  • 배낭문제 Knapsack Problem
  • 단일 출발지 최단 경로 문제 Single-Source Shortest Path Proble,
  • 최소 신장 트리 Minimum Spanning Tree
  • 프림 최소 신장 트리 알고리즘  Prim's Minimum Spanning Tree Algorithm
  • 쿠스칼 최소 신장 트리 알고리즘 Kruskal's Minimum Spanning Tree Algorithm
  • 다익스트라 최소 신장 트리 알고리즘 Dijkstra's Minimum Spanning Tree Algorithm
  • 허프만 코딩 Huffman Coding
  • 포드 풀커슨 알고리즘 Ford-Fulkerson Algorithm

 

예시  : 동전 고르기

Q : 500원, 100원, 50원, 10원짜리 동전이 있다. 4종류의 동전을 이용하여 1800원을 만들 때, 최소 동전의 갯수는 얼마인가?

A : 값이 가장 큰 동전 순서로 1800원을 만든다. 

    가장 값이 큰 동전을 최대한 사용했을 때, 총 동전의 갯수는 최소가 된다.

① 가장 큰 500원 동전을 3개 사용한다.

② 두번째로 큰 100원 동전을 3개 사용한다.

③ 최소 동전의 갯수는 6개이다. (500원 3개, 100원 3개)

 

 

예시 : 최소 신장 트리 Minimum Spanning Tree

신장트리 Spanning Tree : 가중치가 있는 무방향성 그래프의 모든 정점을 연결하는 간선의 집합

최소 신장 트리 Minimum Spanning Tree : 신장 트리 중 최소 비용을 가진 것

 

① 시작점에서 최소비용인 간선을 하나 추가한다.  

② ①에서 추가된 간선에 의해 순환이 생기는지 체크한다.

 

프림 알고리즘 Prim's Algorithm

매 단계마다 가중치 최소값인 간선을 추가하여 최소 신장 트리를 구하는 알고리즘

 

① 현재 정점과 이어진 모든 간선을 중 최소값을 구한다.

② ①에서 구해진 최소값을 결과에 추가한다.

③ 모든 정점을 연결할때까지 ①과②를 반복한다.

 

 구현 JAVA

package test;

import java.util.Arrays;

public class Prim {
	
	public static void main(String[] args) {
		//인접행렬로 구현한 그래프
		int[][] graph = {{0,2, 3 , 1, 0},
				{2, 0, 7, 0, 4},
				{3, 7, 0, 0, 5},
				{1, 0, 0, 0, 6},
				{0, 4, 5, 6, 0}};
		
		//최소 신장 트리 결과값
		int[] spanningTree = new int[graph.length-1];
		
		// 프림 알고리즘 수행
		Prim f = new Prim();
		f.prim(graph, spanningTree);
		
		System.out.println("최소 신장 트리 결과값");
		System.out.println("최소 신장 트리 리스트 : " + Arrays.toString(spanningTree));
		System.out.println("최소 신장 트리 비용   : " 
        					+ Arrays.stream(spanningTree).sum());
	}
	
	void prim(int[][] graph, int[] spanningTree) {
		
		//정점의 방문여부를 저장할 배열
		boolean[] isVisited = new boolean[graph.length]; 
		//시작점 지정 : 방문여부 True
		isVisited[0] = true;
		//최소신장트리 갯수
		int spanningTreeCount=0;
		
		//정점를 모두 연결하는 엣지의 갯수 = 노드갯수 - 1
		//정점의 갯수 : graph.length
		//최소신장트리로 저장된 엣지의 갯수 : spanningTreeCount
		//저장된 엣지의 갯수가 노드갯수-1 개일 때까지 탐색 수행 
		while(spanningTreeCount < graph.length-1) {
			
			int visitedNode = Integer.MIN_VALUE; //최소값 엣지와 연결된 정점
			int minWeight   = Integer.MAX_VALUE; //엣지 최소 가중치
			
			// 방문여부가 true인 정점을 찾기		
			for(int i = 0; i < graph.length; i++) {				
				if(isVisited[i]) {
					for(int j = 0; j < graph.length; j++) {
					//정점 j가 정점 i와 연결되어 있고 (엣지가 있고, 가중치 > 0)
					//정점 j의 방문여부가 false인 (방문한 적이 없는) 것들을 탐색
						if(graph[i][j]>0 && !isVisited[j]) {
						//탐색한 엣지 중 최소값을 찾기
							if(minWeight > graph[i][j]) {
								minWeight = graph[i][j];
								visitedNode = j;
							}
						}
					}					
				}
			}
			//최소신장트리 결과값 저장
			spanningTree[spanningTreeCount] = minWeight;
			spanningTreeCount++;
			isVisited[visitedNode] = true;
			
		}
		
	}

}
최소 신장 트리 결과값
최소 신장 트리 리스트 : [1, 2, 3, 4]
최소 신장 트리 비용 : 10

 

쿠루스칼 알고리즘 Kruskal's Algorithm

① 모든 엣지를 가중치가 작은 값 순으로 정렬한다.

② 가장 작은 엣지부터 최소신장트리(결과값)에 저장한다. 만약 순환이 발생하면 저장하지 않는다.

    * 순환 판별 방법

    1) 각 노드가 속한 그룹의 부모노드를 저장할 배열을 선언하고 값 자기자신으로 초기화한다.

    2) 최소신장트리를 저장하면서 현재 노드가 속한 그룹의 부모노드값을 저장한다.

       (부모노드 : 현재 노드가 속한 엣지집합의 반대편 끝 노드) 

    3) 현재 엣지의 Source 노드와 Target 노드의 부모노드 값이 같다면 엣지를 최소신장트리에 추가하지 않는다.

        * 부모노드 값이 같다면, 이미 같은 그룹에 속한 것인데, 두 노드를 연결하면 순환이 발생하게 된다.

③ 모든 정점을 탐색할 때까지 ②를 반복한다.

 

구현 JAVA

package test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Kruskal {
	
	static int[] parents;//부모노드
	static Edge[] spanningTree; //최소신장트리 
	
	public static void main(String[] args) {
		
		Kruskal f = new Kruskal();
		
		//그래프 선언
		List<Edge> graph = new ArrayList<>();
		graph.add(f.new Edge(0, 1, 2));
		graph.add(f.new Edge(0, 2, 3));
		graph.add(f.new Edge(0, 3, 1));
		graph.add(f.new Edge(1, 0, 2));
		graph.add(f.new Edge(1, 2, 7));
		graph.add(f.new Edge(1, 4, 4));
		graph.add(f.new Edge(2, 0, 3));
		graph.add(f.new Edge(2, 1, 7));
		graph.add(f.new Edge(2, 4, 5));
		graph.add(f.new Edge(3, 0, 1));
		graph.add(f.new Edge(3, 4, 6));
		graph.add(f.new Edge(4, 1, 4));
		graph.add(f.new Edge(4, 2, 5));
		graph.add(f.new Edge(4, 3, 6));

		
		//쿠루스칼 알고리즘 수행
		f.kruskal(graph, 5);
		
		//결과값 출력
		for(Edge edge : spanningTree) {
			System.out.println(edge.toString());
		}
		
	}

	void kruskal(List<Edge> graph, int nodeCount) {
		
		//초기화
        //저장된 최소신장트리 간선의 갯수
		int spanningTreeCount = 0;					
		spanningTree = new Edge[nodeCount-1]; 
		parents = new int[nodeCount];
		
		for(int i = 0; i<nodeCount; i++) {
			parents[i] = i;  	//부모노드를 저장할 배열			
		}
		
		// 가중치가 작은 순으로 정렬
		Collections.sort(graph);
		
		//엣지 source와 target 노드의 부모노드 값이 다르면 최소신장트리에 값을 추가
		for(Edge edge : graph) {			
			if(findParent(edge.getSource()) != findParent(edge.getTarget())) {
				parents[edge.getTarget()] = findParent(edge.getSource());				
				spanningTree[spanningTreeCount++] = edge;
			}
		}
	}
	
	/**
	 * 노드의 부모노드를 찾는 함수
	 * @param source
	 * @return
	 * 
	 * ①정점 번호와 부모노드 값이 같으면 최상위 부모노드
	 * ②정점 번호와 부모노드 값이 다르면 하위노드 
	 *   최상위 부모노드를 찾도록 재귀 호출
	 */
	private int findParent(int source) {
		if(parents[source] == source) {
			return parents[source];
		}else {
			return findParent(parents[source]);			
		}
	}


	class Edge implements Comparable<Edge>{
		int source;
		int target;
		int weight;
		
		Edge(int source, int target, int weight){
			this.source = source;
			this.target = target;
			this.weight = weight;
		}
		
		public int getSource() {
			return source;
		}

		public void setSource(int source) {
			this.source = source;
		}

		public int getTarget() {
			return target;
		}

		public void setTarget(int target) {
			this.target = target;
		}

		public int getWeight() {
			return weight;
		}

		public void setWeight(int weight) {
			this.weight = weight;
		}

		@Override
		public int compareTo(Edge edge) {
			
			return this.weight - edge.weight;
		}

		@Override
		public String toString() {
			return "Edge [source=" + source + ", target=" + target + ", weight=" + weight + "]";
		}
		
		
	};
}

 

Edge [source=0, target=3, weight=1]
Edge [source=0, target=1, weight=2]
Edge [source=0, target=2, weight=3]
Edge [source=1, target=4, weight=4]

 

출처