알고리즘(코딩테스트)

정렬 및 탐색 알고리즘 정리

gbleem 2025. 3. 21. 17:20

1. 정렬 알고리즘


1 - 1. 버블정렬

  • 배열에서 두개의 원소 선택하고 비교 후, 왼쪽 원소가 오른쪽 원소보다 큰 경우 swap 한다.
  • 시간 복잡도는 O(N^2), 공간 복잡도는 O(1)
  • 만약 아래 코드에서 swap이 발생하지 않는 경우 break하는 코드를 넣으면, 정렬된 경우 시간 복잡도를 O(N)으로 줄일 수 있다.
더보기
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> vec = { 4,6,2,9,1 };

	for (int i = 0; i < vec.size(); ++i)
	{
		for (int j = 0; j < vec.size() - i - 1; ++j)
		{
			if (vec[j] > vec[j + 1])
				swap(vec[j], vec[j + 1]);
		}
	}
	for (const auto v : vec)
		cout << v << " ";
}

 

1 - 2. 선택 정렬

  • 배열의 한가지 값을 선택하고, 자신을 제외한 나머지 값들과 비교하여 가장 작은 값을 자신과 swap하여 정렬하는 방식
  • 시간 복잡도는 O(N^2), 공간 복잡도 O(1)
더보기
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> vec = { 4,6,2,9,1 };

	for (int i = 0; i < vec.size() - 1; ++i)
	{
		int idx = i;
		for (int j = i + 1; j < vec.size(); ++j)
		{
			if (vec[j] < vec[idx])
				idx = j;
		}
		
		swap(vec[i], vec[idx]);			
	}
	for (auto v : vec)
		cout << v << " ";	
}

 

1 - 3. 삽입 정렬

  • 현재 선택한 값(i)을 다음 값(i+1)과 비교해서
    • 다음 값이 작은 경우, 다음값(i+1)보다 인덱스가 작은 값(0 ~ i)들 중에서 자신 보다 작은 값 뒤로 삽입하고,
    • 다음 값이 큰 경우, 그대로 두고 다음 인덱스로 이동
  • 시간 복잡도는 평균 O(N^2) 이지만, 만약 모두 정렬된 경우 O(N), 공간 복잡도 O(1)
  • 예시) 4 6 2 9 1에서 i가 1인 경우
    • i가 가리키는 값은 6, i+1 이 가리키는 값은 2 이다. 
    • 2가 6보다 작으므로, 0 ~ i 까지 값과 비교
    • 6은 2보다 크기 때문에 swap
    • 4도 2보다 크기 때문에 swap
    • 결론적으로 2보다 작은 값은 없었으므로, 맨 앞으로 2가 이동한 것
더보기
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	vector<int> vec = { 4,6,2,9,1 };

	int value, j;
	for (int i = 1; i < vec.size(); i++) 
	{ 
		value = vec[i];
		for (j = i - 1; j >= 0; j--) 
		{
			if (vec[j] > value)
				swap(vec[j + 1], vec[j]);
			else
				break;
		}
	}
	for (auto v : vec)
		cout << v << " ";
}

 

1 - 4. 퀵 정렬

  • 피벗을 기준으로 피벗보다 작은 값은 왼쪽으로 큰 값은 오른쪽으로 나눠서 정렬하는 방식
  • 평균적으로 O(NlogN)의 시간 복잡도, 최악의 경우(이미 정렬된 경우) O(N^2)이 된다. 공간 복잡도는 재귀 호출 스택으로 인해 O(logN)
더보기
#include <iostream>
#include <vector>
using namespace std;

int Partition(vector<int>& vec, int low, int high)
{
	//중간값 찾기
	int mid = (low + high) / 2;

	//중간값을 맨 값이랑 swap
	swap(vec[mid], vec[high]);

	//중간값을 pivot으로 설정
	int pivot = vec[high];

	//시작 인덱스 설정
	int idx = low - 1;

	for (int i = low; i < high; ++i)
	{
		//피벗보다 작은 값을 왼쪽으로 몰아버리기
		if (vec[i] < pivot)
		{
			idx++;
			swap(vec[i], vec[idx]);
		}
	}
	//피벗을 정렬한 값 중앙으로 옮겨주기
	swap(vec[idx + 1], vec[high]);

	//정렬한 vec의 피벗 위치 return
	return idx + 1;
}

void QuickSort(vector<int>& vec, int low, int high)
{
	if (low < high)
	{
		//피벗보다 작은 값을 왼쪽 큰 값을 오른쪽으로 바꿔주기
		int pos = Partition(vec, low, high);

		//피벗을 기준으로 재귀함수 시작
		QuickSort(vec, low, pos - 1);
		QuickSort(vec, pos + 1, high);
	}
}


int main()
{
	vector<int> vec = { 4, 6, 2, 9, 1 };

	int low = 0;
	int high = vec.size() - 1;

	QuickSort(vec, low, high);

	for (auto v : vec)
		cout << v << " ";
}

 

1 - 5. 병합 정렬

  • 배열을 반으로 나눠, 각각을 정렬한 후 다시 합치는 방식으로 동작
  • O(NlogN)의 시간 복잡도를 보장한다. 추가적인 배열이 필요하므로 공간 복잡도는 O(N)
더보기
#include <iostream>
#include <vector>
using namespace std;

int answer[10]; //정렬된 배열 (size N)

//정렬되어 온 배열을 다시 합치는 과정
void Merge(vector<int>& vec, int left, int mid, int right)
{
	int i = left; //정렬된 왼쪽 배열 인덱스
	int j = mid + 1; // 정렬된 오른쪽 배열 인덱스
	int k = left; // 정렬된 전체 리스트 인덱스
	int l = 0;

	while (i <= mid && j <= right)
	{
		if (vec[i] <= vec[j])
			answer[k++] = vec[i++];
		else
			answer[k++] = vec[j++];
	}

	//남은 값 처리
	if (i > mid)
	{
		for (l = j; l <= right; ++l)
			answer[k++] = vec[l];
	}
	else
	{
		for (l = i; l <= mid; ++l)
			answer[k++] = vec[l];
	}

	//다시 기존 배열로 복사하기
	for (l = left; l <= right; ++l)
		vec[l] = answer[l];
}

void MergeSort(vector<int>& vec, int left, int right)
{
	int mid;

	if (left < right)
	{
		mid = (left + right) / 2;
		MergeSort(vec, left, mid);
		MergeSort(vec, mid + 1, right);
		Merge(vec, left, mid, right);
	}
}

int main()
{
	vector<int> vec = { 4,6,2,9,1 };
	MergeSort(vec, 0, vec.size() - 1);
	for (const auto& v : vec)
		cout << v << " ";
}

 

 

2. 탐색 알고리즘


2 - 1. 선형 탐색

  • 단순히 정렬되지 않은 배열을 앞에서부터 하나씩 순차적으로 체크하며 찾는 방법
  • 시간 복잡도는 O(N)

 

2 - 2. 이분 탐색

  •  정렬된 배열에서 절반씩 나눠 찾는 방식
  • 시간 복잡도 O(logN)
더보기
#include <iostream>
#include <vector>
using namespace std;

int BinarySearch(const vector<int>& vec, int target)
{
	int left = 0;
	int right = vec.size() - 1;

	while (left <= right)
	{
		int mid = (left + right) / 2;

		//찾은 경우
		if (vec[mid] == target)
			return mid;
		else if (vec[mid] < target)
			left = mid + 1;
		else
			right = mid - 1;
	}
	return -1;
}

int main()
{
	vector<int> vec = { 1,3,5,7,9,10,14 };
	int target = 10;

	int targetIdx = BinarySearch(vec, target);

	cout << targetIdx;
}
  • 참고)
    • STL에 존재하는 binary_search 함수나 upper_bound, lower_bound를 사용할 수도 있다.
    • algorithm 헤더가 필요하다.
    • binary_search 함수를 통해 target 존재 여부를 파악할 수 있다.
    • upper_bound, lower_bound 함수를 통해 target의 위치를 파악할 수 있다.
//STL
cout << binary_search(vec.begin(), vec.end(), target) << "\n";     //true (1)
cout << lower_bound(vec.begin(), vec.end(), target) - vec.begin(); //5