공부/Algorithm

병합 정렬(Merge sort)

sudo 2021. 8. 10. 07:14

 

// Mergesort.h
#pragma once

template <typename T>
class CMergesort
{

public:
	CMergesort()
	{
		m_Size = 0;
		m_Capacity = 10;
		m_Array = new T[m_Capacity];
		m_CopyArray = new T[m_Capacity];

		m_Func = SortFunc;
	}

	~CMergesort()
	{
		if (m_Array)
		{
			delete[] m_Array;
		}

		if (m_CopyArray)
		{
			delete[] m_CopyArray;
		}
	}

private:
	T* m_Array;
	T* m_CopyArray;
	int m_Size;
	int m_Capacity;
	bool(*m_Func)(const T&, const T&);

public:
	void push(const T& Data)
	{
		if (m_Size == m_Capacity)
		{
			m_Capacity *= 2;

			T* tmp = new T[m_Capacity];

			memcpy(tmp, m_Array, sizeof(T) * m_Size);

			delete[] m_Array;

			delete[] m_CopyArray;

			m_CopyArray = new T[m_Capacity];

			m_Array = tmp;
		}
		m_Array[m_Size] = Data;
		++m_Size;
	}

	void push(const T* Array, int Count)
	{
		if (m_Capacity < Count)
		{
			m_Capacity = Count;
			delete[]	m_Array;
			delete[]	m_CopyArray;
			m_Array = new T[m_Capacity];
			m_CopyArray = new T[m_Capacity];
		}

		for (int i = 0; i < Count; ++i)
		{
			m_Array[i] = Array[i];
		}

		m_Size = Count;
	}

	int	size()	const
	{
		return m_Size;
	}

	bool empty()	const
	{
		return m_Size == 0;
	}

	// 멤버인 m_Array를 정렬하는 함수 
	void Sort()
	{
		MergeSort(0, m_Size - 1, m_Array);
	}

	// 외부 배열을 정렬하는 함수
	void Sort(T* Array, int Count)
	{
		if (m_Capacity < Count)
		{
			m_Capacity = Count;
			delete[]	m_Array;
			delete[]	m_CopyArray;
			m_Array = new T[m_Capacity];
			m_CopyArray = new T[m_Capacity];
		}

		MergeSort(0, Count - 1, Array);
	}

private:
	static bool SortFunc(const T& Data1, const T& Data2)
	{
		return Data1 < Data2;
	}


	void MergeSort(int Left, int Right, T* Array)
	{
		int Mid = (Left + Right) / 2;

		if (Left < Right)
		{
			MergeSort(Left, Mid, Array);
			MergeSort(Mid + 1, Right, Array);

			Merge(Left, Mid, Right, Array);
		}
	}

	void Merge(int Left, int Mid, int Right, T* Array)
	{
		int Low = Left;
		int High = Mid + 1;
		int Index = Left;

		while (Low <= Mid && High <= Right)
		{
			if(m_Func(Array[Low], Array[High]))
			{
				m_CopyArray[Index] = Array[Low];
				++Index;
				++Low;
			}
			else
			{
				m_CopyArray[Index] = Array[High];
				++Index;
				++High;
			}
		}

		if (Low > Mid)
		{
			for (int i = High; i <= Right; ++i)
			{
				m_CopyArray[Index] = Array[i];
				++Index;
			}
		}

		else
		{
			for (int i = Low; i <= Mid; ++i)
			{
				m_CopyArray[Index] = Array[i];
				++Index;
			}
		}

		for (int i = Left; i <= Right; ++i)
		{
			Array[i] = m_CopyArray[i];
		}
	}
};

 

// main.cpp
#include <iostream>
#include <time.h>
#include "Mergesort.h"

int main()
{
	srand((unsigned int)time(0));
	rand();

	int Arr[100] = {};

	CMergesort<int> msort;

	for (int i = 0; i < 10; ++i)
	{
		Arr[i] = rand();
		std::cout << Arr[i] << std::endl;
	}

	msort.Sort(Arr, 10);

	std::cout << "===================== After sort =====================" << std::endl;

	for (int i = 0; i < 10; ++i)
	{
		std::cout << Arr[i] << std::endl;
	}

	return 0;
}

 

병합 정렬은 최악의 경우, 평균의 경우 모두 theta(nlogn)의 시간 복잡도를 보인다. 하지만 같은 분할 정복 알고리즘을 이용하면서 평균적인 경우에는 같은 시간 복잡도를 보이는 퀵 정렬과 비교했을 때, 좀 더 느리다고 한다. 그 이유는 merge sort에만 필요한 복사와, 메모리 접근 패턴에 따른 locality와 관련 있다.

 

Merger sort는 위의 구현에서도 볼 수 있듯이, in-place 정렬이 아니라 따로 같은 크기의 배열에 정렬을 하고 정렬이 다 되면 원하는 container나 메모리로 옮긴다. 그에 반해 quicksort는 in-place 정렬이라서 따로 복사가 필요 없다. 

 

그리고 Merge sort는 정렬이 안된 데이터를 갖고 있는 공간과 정렬을 완료해서 정렬된 데이터를 갖고 있는 공간이 다르다. 이 두 공간을 왔다 갔다 하면서 정렬이 완성되어 간다. 반면에 Quick sort는 정렬이 안된 데이터를 갖고 있는 공간과 정렬을 완료해서 정렬된 데이터를 갖고 있는 공간이 같은 공간이다. 이 말은 quick sort로 정렬 할 때는 이전에 접근한 메모리에 다시 접근(temporal locality)하거나 contiguous memory에 접근(spatial locality)할 가능성이 높다는 의미이기도 하며 이것은 cache에 adapt된 페이지에 대해 hit가 일어날 가능성이 높다는 것을 의미한다. 

 

위와 같은 이유로 평균적으로 quick sort가 merge sort보다 빠르다고 한다.

 

Reference

https://medium.com/pocs/locality%EC%9D%98-%EA%B4%80%EC%A0%90%EC%97%90%EC%84%9C-quick-sort%EA%B0%80-merge-sort%EB%B3%B4%EB%8B%A4-%EB%B9%A0%EB%A5%B8-%EC%9D%B4%EC%9C%A0-824798181693