트리/힙/그래프(2) - 힙&그래프

1. 힙

  • 원소 삽입 삭제에 대한 O(log N)의 시간 복잡도를 만족하기 위해 완전 이진 트리 사용
  • 힙 구현 시 항상 최대원소가 트리의 루트에 위치

1) 힙 연산

(1) 원소 삽입하기

  • 완전 이진 트리를 유지 → 트리의 마지막 레벨, 마지막 노드 오른쪽에 원소 추가
  • 최대 원소가 트리의 루트에 위치하기 위해 새로 삽입한 원소의 크기를 비교하여 swap하는 과정을 거침

(2) 원소 삭제하기

  • 가장 큰 원소만 삭제 가능
  • 루트 노드와 마지막 노드를 교환한 후 마지막 노드를 삭제
  • 최대 원소가 트리의 루트에 위치하기 위해 다시 루트 노드와 자식 노드의 크기를 비교하여 swap

(3) 힙 초기화하기

  • 힙은 불변성을 유지해야함→ 힙 생성 알고리즘으로 업데이트
  • 아래쪽 서브 트리부터 힙 불변 속성을 만족하도록 힙을 업데이트

2) 연습 문제 10: 중앙값 구하기

#include <iostream>
#include <queue>
#include <vector>

struct median
{
	std::priority_queue<int> maxHeap;
	std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;

	void insert(int data)
	{
		if (maxHeap.size() == 0)
		{
			maxHeap.push(data);
			return;
		}

		if (maxHeap.size() == minHeap.size())
		{
			if (data <= get())
				maxHeap.push(data);
			else
				minHeap.push(data);

			return;
		}

		if (maxHeap.size() < minHeap.size())
		{
			if (data > get())
			{
				maxHeap.push(minHeap.top());
				minHeap.pop();
				minHeap.push(data);
			}
			else
				maxHeap.push(data);

			return;
		}

		if (data < get())
		{
			minHeap.push(maxHeap.top());
			maxHeap.pop();
			maxHeap.push(data);
		}
		else
			minHeap.push(data);
	}

	double get()
	{
		if (maxHeap.size() == minHeap.size())
			return (maxHeap.top() + minHeap.top()) / 2.0;

		if (maxHeap.size() < minHeap.size())
			return minHeap.top();

		return maxHeap.top();
	}
};

int main()
{
	median med;

	med.insert(1);
	std::cout << "1 삽입 후 중앙값: " << med.get() << std::endl;

	med.insert(5);
	std::cout << "5 삽입 후 중앙값: " << med.get() << std::endl;

	med.insert(2);
	std::cout << "2 삽입 후 중앙값: " << med.get() << std::endl;

	med.insert(10);
	std::cout << "10 삽입 후 중앙값: " << med.get() << std::endl;

	med.insert(40);
	std::cout << "40 삽입 후 중앙값: " << med.get() << std::endl;
}

3) 실습 문제 5: 힙을 이용한 데이터 리스트 병합

#include <iostream>
#include <algorithm>
#include <vector>

struct node
{
	int data;
	int listPosition;
	int dataPosition;
};

std::vector<int> merge(const std::vector<std::vector<int>>& input)
{
	auto comparator = [](const node& left, const node& right) {
		if (left.data == right.data)
			return left.listPosition > right.listPosition;
		return left.data > right.data;
	};

	std::vector<node> heap;
	for (int i = 0; i < input.size(); i++)
	{
		heap.push_back({input[i][0], i, 0});
		std::push_heap(heap.begin(), heap.end(), comparator);
	}

	std::vector<int> result;
	while (!heap.empty())
	{
		std::pop_heap(heap.begin(), heap.end(), comparator);
		auto min = heap.back();
		heap.pop_back();

		result.push_back(min.data);
		int nextIndex = min.dataPosition + 1;
		if (nextIndex < input[min.listPosition].size())
		{
			heap.push_back({input[min.listPosition][nextIndex], min.listPosition, nextIndex});
			std::push_heap(heap.begin(), heap.end(), comparator);
		}
	}

	return result;
}

int main()
{
	std::vector<int> v1 = {1, 3, 8, 15, 105};
	std::vector<int> v2 = {2, 3, 10, 11, 16, 20, 25};
	std::vector<int> v3 = {-2, 100, 1000};
	std::vector<int> v4 = {-1, 0, 14, 18};

	auto result = merge({v1, v2, v3, v4});

	for (auto i : result)
		std::cout << i << ' ';
	std::cout << std::endl;

	return 0;
}

2. 그래프

  • 트리에서 표현하지 못하는 원형 또는 순환적 종속성을 표현하는 방법
  • 필요한 노드와 에지가 있는 그래프를 비가중 그래프
  • 에지에 가중치 또는 더 많은 정보를 부여하는 가중 그래프 → 최단 경로 탐색
  • 방향성이 없는 무방향 그래프와 방향성이 있는 방향 그래프

1) 인접 행렬로 그래프 표현하기

  • 노드의 집합이 있고 노드끼리 서로 연결이 가능
  • 노드 N개 → 그래프는 N*N 크기의 이차원 배열
  • 이차원 배열에 저장되는 값은 노드를 잇는 엣지의 가중치

2) 연습 문제 11: 그래프를 구성하고 인접행렬로 표현하기

#include <iostream>
#include <vector>

enum class city : int
{
	MOSCOW,
	LONDON,
	SEOUL,
	SEATTLE,
	DUBAI,
	SYDNEY
};

std::ostream& operator<<(std::ostream& os, const city c)
{
	switch (c)
	{
	case city::LONDON:
		os << "런던";
		return os;
	case city::MOSCOW:
		os << "모스크바";
		return os;
	case city::SEOUL:
		os << "서울";
		return os;
	case city::SEATTLE:
		os << "시애틀";
		return os;
	case city::DUBAI:
		os << "두바이";
		return os;
	case city::SYDNEY:
		os << "시드니";
		return os;
	default:
		return os;
	}
}

struct graph
{
	std::vector<std::vector<int>> data;

	graph(int n)
	{
		data.reserve(n);
		std::vector<int> row(n);
		std::fill(row.begin(), row.end(), -1);

		for (int i = 0; i < n; i++)
		{
			data.push_back(row);
		}
	}

	void addEdge(const city c1, const city c2, int dis)
	{
		std::cout << "에지 추가: " << c1 << "-" << c2 << "=" << dis << std::endl;

		auto n1 = static_cast<int>(c1);
		auto n2 = static_cast<int>(c2);
		data[n1][n2] = dis;
		data[n2][n1] = dis;
	}

	void removeEdge(const city c1, const city c2)
	{
		std::cout << "에지 삭제: " << c1 << "-" << c2 << std::endl;

		auto n1 = static_cast<int>(c1);
		auto n2 = static_cast<int>(c2);
		data[n1][n2] = -1;
		data[n2][n1] = -1;
	}
};

int main()
{
	graph g(6);
	g.addEdge(city::LONDON, city::MOSCOW, 2500);
	g.addEdge(city::LONDON, city::SEOUL, 9000);
	g.addEdge(city::LONDON, city::DUBAI, 5500);
	g.addEdge(city::SEOUL, city::MOSCOW, 6600);
	g.addEdge(city::SEOUL, city::SEATTLE, 8000);
	g.addEdge(city::SEOUL, city::DUBAI, 7000);
	g.addEdge(city::SEOUL, city::SYDNEY, 8000);
	g.addEdge(city::SEATTLE, city::MOSCOW, 8400);
	g.addEdge(city::SEATTLE, city::SYDNEY, 12000);
	g.addEdge(city::DUBAI, city::SYDNEY, 1200);

	g.addEdge(city::SEATTLE, city::LONDON, 8000);
	g.removeEdge(city::SEATTLE, city::LONDON);

	return 0;
}

3) 인접 리스트로 그래프 표현하기

  • 행렬 이용시 문제점 : 노드 개수의 제곱에 비례하는 메모리 사용
  • 직접 연결되어 있는 엣지만 저장하는 방식의 그래프 → 인접 리스트

4) 연습 문제 12: 그래프를 구성하고 인접 리스트로 표현하기

#include <iostream>
#include <vector>
#include <algorithm>

enum class city : int
{
	MOSCOW,
	LONDON,
	SEOUL,
	SEATTLE,
	DUBAI,
	SYDNEY
};

std::ostream& operator<<(std::ostream& os, const city c)
{
	switch (c)
	{
	case city::LONDON:
		os << "런던";
		return os;
	case city::MOSCOW:
		os << "모스크바";
		return os;
	case city::SEOUL:
		os << "서울";
		return os;
	case city::SEATTLE:
		os << "시애틀";
		return os;
	case city::DUBAI:
		os << "두바이";
		return os;
	case city::SYDNEY:
		os << "시드니";
		return os;
	default:
		return os;
	}
}

struct graph
{
	std::vector<std::vector<std::pair<int, int>>> data;

	graph(int n)
	{
		data = std::vector<std::vector<std::pair<int, int>>>(n, std::vector<std::pair<int, int>>());
	}

	void addEdge(const city c1, const city c2, int dis)
	{
		std::cout << "에지 추가: " << c1 << "-" << c2 << "=" << dis << std::endl;

		auto n1 = static_cast<int>(c1);
		auto n2 = static_cast<int>(c2);
		data[n1].push_back({n2, dis});
		data[n2].push_back({n1, dis});
	}

	void removeEdge(const city c1, const city c2)
	{
		std::cout << "에지 삭제: " << c1 << "-" << c2 << std::endl;

		auto n1 = static_cast<int>(c1);
		auto n2 = static_cast<int>(c2);
		std::remove_if(data[n1].begin(), data[n1].end(), [n2](const auto& pair) {
			return pair.first == n2;
			});
		std::remove_if(data[n2].begin(), data[n2].end(), [n1](const auto& pair) {
			return pair.first == n1;
			});
	}
};

int main()
{
	graph g(6);
	g.addEdge(city::LONDON, city::MOSCOW, 2500);
	g.addEdge(city::LONDON, city::SEOUL, 9000);
	g.addEdge(city::LONDON, city::DUBAI, 5500);
	g.addEdge(city::SEOUL, city::MOSCOW, 6600);
	g.addEdge(city::SEOUL, city::SEATTLE, 8000);
	g.addEdge(city::SEOUL, city::DUBAI, 7000);
	g.addEdge(city::SEOUL, city::SYDNEY, 8000);
	g.addEdge(city::SEATTLE, city::MOSCOW, 8400);
	g.addEdge(city::SEATTLE, city::SYDNEY, 12000);
	g.addEdge(city::DUBAI, city::SYDNEY, 1200);

	g.addEdge(city::SEATTLE, city::LONDON, 8000);
	g.removeEdge(city::SEATTLE, city::LONDON);

	return 0;
}

 

출처 : 코딩 테스트를 위한 자료구조와 알고리즘 with C++ (길벗)

'Computer Science > Data Structure & Algorithm' 카테고리의 다른 글

그리디 알고리즘  (0) 2023.07.02
분할 정복  (0) 2023.07.01
해시 테이블과 블룸 필터  (0) 2023.06.29
트리/힙/그래프(1) - 트리  (0) 2023.06.27
리스트 / 스택 /큐  (0) 2023.06.26