알고리즘/개념

[Algo] @정렬 알고리즘 ( Sort Algorithm )

코딩하는상후니 2022. 8. 19. 18:13

 

 


 

 

* 정렬 알고리즘 ( Sort Algorithm )

 
 
정렬 알고리즘은 굉장히 다양하고
상황 마다 효율적인 알고리즘이 다를 수 있다.
 
 
우선 간단한 것부터 개념 정리를 할 것이고
 
정렬에 관한 내용을 갱신해가며 업데이트 할 예정이다.
 
 
 
( 아래에 구현된 코드는 검증되지 않은 코드이다. )
알고리즘을 만족하지 못하는 케이스가 있을 수 있다.
 
 
 

 

 

 

*선택 정렬 ( Selection Sort )  /  O( N2 )

 

 
=> 처음부터 끝까지 탐색 후 데이터를 첫 순서 놓기 -> 2번째 idx 부터 끝까지 찾고 옮기기...
 
{ 1, 5, 10, 7, 2 }
{ 1, 5, 10, 7, 2 }
{ 1, 2, 10, 7, 5 }
{ 1, 2, 5, 7, 10 }
{ 1, 2, 5, 7, 10 }

 

 

void SelectSort(std::vector<int>& vec)
{
	int size = vec.size();
	for (int j = 0; j < size; ++j)
	{
		int Min = INT_MAX, idx = 0;
		for (int i = j; i < size; ++i)
		{
			if (Min > vec[i])
			{
				Min = vec[i];
				idx = i;
			}
		}
		Swap(vec[j], vec[idx]);
	}
}

 

 

 

 


 

 

*버블 정렬 ( Bubble Sort )  /  O( N2 )

 
=> 앞 뒤 비교 -> j (맨 뒤) 는 확정적으로 순서가 정해짐.  -> j - 1  까지 앞 뒤 비교....
 
 
{ 8, 3, 5, 4, 1 }
{ 3, 5, 6, 1, 8 }
{ 3, 5, 1, 6, 8 }
{ 3, 1, 5, 6, 8 }
{ 1, 3, 5, 6, 8 }

 

 

 

void BubbleSort(std::vector<int>& vec) 
{ 
	int size = vec.size(); 
	for (int j = 0; j < size; ++j) 
	{ 
		for (int i = 0; i < size - j - 1; ++i) 
		{ 
			if (vec[i] > vec[i + 1]) 
			{ 
				Swap(vec[i], vec[i + 1]); 
			} 
		} 
	} 
}

 

 


 

 

 

*삽입 정렬 ( Insertion Sort )  /  O( N2 )

 
 
=> 계속 범위를 늘려간다. -> 범위만큼 계속 데이터를 삽입해가며 정렬해간다.
 
 
{ 1, 5, 3, 4, 2 }
{ 1, 5 } { 3, 4, 2 }
{ 1, 3, 5 } { 4, 2 }
{ 1, 3, 4, 5 } { 2 }
{ 1, 2, 3, 4, 5 }
 
 
=> 작은 정렬 기준으로
계속 옆으로 추가해주는 방식으로 행한다.
 
{ 1, 5 } <- 3 투입.
{ 1, 3, 5 } <- 4 투입.

 

 

void InsertSort(std::vector<int>& vec) 
{ 
	int size = vec.size(); 
	for (int j = 1; j < size; ++j) 
	{ 
		int InsertValue = vec[j], Pos = 0; 
		for (int i = j; i > 0; --i) 
		{ 
			if (InsertValue < vec[i - 1]) 
			{ 
				vec[i] = vec[i - 1]; 
				continue; 
			} 
			else 
			{ 
				Pos = i; 
				break; 
			} 
		} 
		vec[Pos] = InsertValue; 
	} 
}

 

 

 


 

 
 

*힙 정렬 ( Heap Sort )  /  O( NlogN )

 
 
=> 힙 트리로 구현된 '우선순위 큐' 활용.
 
 
 
* 힙트리
 
 

 

 
void HeapSort(std::vector<int>& vec) 
{ 
	std::priority_queue<int> pq; 
	int size = vec.size(); 
	for (int i = 0; i < size; ++i) 
	{ 
		int in = (-1) * (vec[i]); 
		pq.push(in); 
	} 
	int idx = 0; 
	while (!pq.empty()) 
	{ 
		int out = (-1) * pq.top(); 
		pq.pop(); 
		vec[idx] = out; 
		++idx; 
	} 
}
 

 


 

 

 

*병합 정렬 ( Merge Sort )  /  O( NlogN )

 

 

 

 

 

 

 
=> 대표적인 분할 정복 예시
 
 
분할 ( Divide )         :  문제를 더 단순하게 분할.
정복 ( Conquer )     :  분할된 문제를 해결.
결합 ( Conbine )     :  결과를 취합해 마무리.
 

 

 

 
=> 이진 탐색과 유사한 개념.
재귀적으로 나누고 합친다.
 

 

=> 합치는 경우
{ 1, 9 }  { 4, 5 } 일 때,
{ 1 }
{ 1, 4 }
{ 1, 4, 5 }
{ 1, 4, 5, 9 }

 

 

void MergeSort(std::vector<int>& vec,int Start, int End) 
{ 
	if (Start >= End) return; 
	int Mid = (Start + End) / 2;

	MergeSort(vec, Start, Mid); 
	MergeSort(vec, Mid + 1, End); 
	Merge(vec, Start, Mid, End); 
}

void Merge(std::vector<int>& vec, int Start, int Mid, int End) 
{ 
	std::vector<int> temp; 
	int P1 = Start, P2 = Mid + 1; 
	while (P1 <= Mid && P2 <= End) 
	{ 
		if (vec[P1] <= vec[P2]) 
		{ 
			temp.push_back(vec[P1]); 
			P1++; 
		} 
		else if (vec[P1] > vec[P2]) 
		{ 
			temp.push_back(vec[P2]); 
			P2++; 
		} 
	} 
	while (P1 <= Mid) 
	{ 
		temp.push_back(vec[P1]); 
		P1++; 
	} 
	while (P2 <= End) 
	{ 
		temp.push_back(vec[P2]); 
		P2++; 
	} 
	for (int i = 0; i < temp.size(); ++i) 
	{ 
		vec[i + Start] = temp[i]; 
	} 
}

 

 
 
 

 
 
 

* 퀵 소트 ( Quick Sort ) 

 
 
 
 
조건 최악일 때, O( N2 )
 
* 최악의 상황은 언제인가 ??
 
=> 한번의 과정에서,
pivot 값이 최댓값, 최솟값일 때 그 과정은 최악이다.
 
또한,
pivot 값이 최댓값, 최솟값이 연속적인 상황은
오름차순, 내림차순 으로 정렬되어있을 때이다.
 
 
 
 
*그렇다면 최악의 상황이 O( N2 ) 인 퀵소트는 왜 빠르다고 하는것일까 ??
 
 
1. 어떤 임의로 추가된 데이터들이 정렬되어있을 확률은 거의 없다.
( 물론, 이전에 정렬을 적용하지 않은 상황. )
 
 
만약,
1,2,3,4,5,6,7,8,9 범위의 데이터가 임의로 추가될 때,
오름차순 or 내림차순으로 될 확률은 거의 없다. ( 1 / 210  )
 
 
 
2. 불필요한 데이터 복사가 필요없다.
 
앞서 본 힙, 병합 정렬 같은 경우
어떤 컨테이너를 거쳐서 그 컨테이너를 복사하는 과정이 필요한데,
퀵소트는 이런 부가적인 연산이 필요없다.
 
 
 
3. 메모리가 연속적이므로 캐쉬 Hit 율이 높다. ( 빠르다. )
 
 
 
 
 
 
 
 
=> 결론적으로,
 
퀵소트의 최악의 상황을 면하기 위해,
pivot 선정 시 최대, 최소인 값을 선택하지 않는 알고리즘을 적용한다면
'어떤 상황에서든 평균적인 속도' 가 나올 것이다.
 
 
 
 
 
*동작과정
 

 

 

 

void QuickSort(std::vector<int>& vec, int Start, int End) 
{ 
	if (Start >= End) return; 
	int Mid = (Start + End) / 2; 
	int Pivot = Start, low = 1, high = End; 
	while (low < high) 
	{ 
		while (vec[Pivot] >= vec[low] && low < End) 
		{ 
			low++; 
		} 
		while (vec[Pivot] < vec[high] && high > Start) 
		{ 
			--high; 
		} 
		if(low < high) 
			Swap(vec[low], vec[high]); 
	}

	Swap(vec[high], vec[Pivot]); 
	Pivot = high; 
	QuickSort(vec, Start, Pivot - 1); 
	QuickSort(vec, Pivot + 1, End); 
}

 

 

 


 
 
참고 링크
 
 
 

 

 

 

 

 
 

 

 

'알고리즘 > 개념' 카테고리의 다른 글

[Algo]@Disjoint Set ( 상호 배타적 집합 )  (0) 2022.08.09