정렬 알고리즘(sorting algorithm)은 수열을 정렬하는 알고리즘입니다.
O(1) > O(logN) > O(N) > O(NlogN) > O(N^2) > O(2^N) > O(N!)

① 0번과 1번 인덱스(6, 3)의 데이터를 비교합니다.
② 큰 데이터(6)는 뒤로 배치합니다.
③ 1번과 2번 인덱스(6, 1)의 데이터를 비교합니다.
④ 큰 데이터(6)를 다시 뒤로 배치합니다.
⑤ 반복하면 데이터가 가장 큰 인덱스(9)가 배열의 마지막으로 이동합니다.
9는 정렬을 끝낸 것으로 간주하고 처음부터 다시 실행합니다.
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int[] array = new int[] { 6, 3, 1, 7, 9, 2, 4, 5, 8 };
int temp;
for (int i = 0; i < array.Length - 1; i++)
{
for (int j = 0; j < array.Length - 1 - i; j++)
{
if (array[j] > array[j + 1])
{
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
}
}
}
장점
단점
① 배열을 순차적으로 탐색합니다.
② 최소값(1)을 찾습니다.
③ 최소값(1)을 0번 인덱스(6)의 데이터와 교체하고 0번 인덱스(1)는 정렬을 마친 것으로 간주합니다.
④ 처음부터 다시 실행합니다.
using System;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int[] array = new int[] { 6, 3, 1, 7, 9, 2, 4, 5, 8 };
int temp;
int min;
for (int i = 0; i < array.Length - 1; i++)
{
min = i;
for (int j = i + 1; j < array.Length; j++)
{
if (array[j] < array[min])
{
min = j;
}
}
temp = array[min];
array[min] = array[i];
array[i] = temp;
}
for (int i = 0; i < array.Length; i++)
{
Console.WriteLine(array[i]);
}
}
}
}
장점
단점
불안정 정렬은 정렬의 방법이 아닌 정렬의 특성입니다.
의미는 무엇일까요. 한가지 예를 들어보겠습니다.
1,2,3,1 배열이 있습니다. 이 배열을 선택 정렬하면 아래와 같이 두가지의 결과가 존재합니다.


빨간색 1과 초록색 1은 데이터가 같지만 위치가 다릅니다. 그렇기에 어느 것이 맞는 정렬인지 보장되지 않습니다.
이것이 불안정 정렬입니다. 오직 같은 숫자만으로 정렬되기 때문에 실행을 할때마다 위치가 뒤바낄 수 있습니다.

반대로, 빨간색 1이 초록색 1의 인덱스를 침범하지 못하게 설계된 정렬을 안정 정렬이라고 합니다.
(플레잉카드의 안정 정렬 예시)
이 특성을 모를 경우 정렬을 했을 때 원하는 결과를 얻지 못할 수 있습니다.
그렇기에 정렬의 안정적 특성을 고려하여 알고리즘을 선택하는 것이 중요합니다.
① 두 번째에 위치한 데이터(3)부터 탐색을 시작합니다. 이 때 기준점을 잡는 인덱스를 키(key)라고 정정하겠습니다.
② key(3)와 key앞의 데이터(6)를 비교하여, key(3)의 크기가 작다면 서로의 위치를 변경을 진행합니다.
key가 배열의 가장 앞에 도착할 때까지 이 작업을 반복하는데, 만약 key보다 데이터가 작으면 변경하지 않고 다음 인덱스를 기준으로 다시 정렬을 실행합니다.
지금은 key(3) 보다 더 이상 앞의 인덱스를 찾을 수 없으니 다음 인덱스로 넘어갑니다.
③ 2번 인덱스(1)가 key가 됩니다.
key와 정렬이 완료된 앞의 인덱스(6)를 비교합니다.
④ 앞에 아직 인덱스가 존재하니 이번에는 다시 0번 인덱스(3)와 교체합니다.
동일한 작업을 모든 원소가 정렬을 마칠 때까지 반복합니다.
using System;
namespace ConsoleApp1
{
class Program
{
static int[] array = new int[] { 6, 3, 1, 7, 9, 2, 4, 5, 8 };
//결과 출력 함수
static void Print()
{
Console.Write(string.Join(", ", array));
}
static void Main(string[] args)
{
int key;
int j;
for (int i = 1; i < array.Length; i++)
{
//외부 for문이 작동할 때마다 key값을 갱신합니다.
key = array[i];
for (j = i - 1; j >= 0; j--)
{
if (array[j] < key) break; //배열의 인덱스[j]가 key 데이터의 크기보다 작으면 for문 멈춥니다.
array[j + 1] = array[j]; //그렇지 않다면 key 앞의 인덱스를 key 자리에 삽입합니다.
}
array[j + 1] = key; //그리고 키 값을 교체합니다.
}
Print();
}
}
}
장점
단점


① 루트(root-node) : 부모 노드가 없는 최상위 노드 (A)
② 리프(leaf-node) : 자식 노드가 없는 노드 (C,D,E)
③ 부모(parent-node) : 자신과 간선으로 이어진 하위 노드가 존재할 때 본인의 노드 (B)
④ 자식(child-node) : 부모 노드가 같은 노드 (D,E). 서로 형제(sibling)라고 부릅니다.

1부터 7까지 숫자가 섞여있는 배열 {4,6,1,7,5,3,2}로 최대 힙과 최소 힙을 만들어 보겠습니다.
① 첫번째 값인 4가 빠져나와 뿌리 노드가 됩니다.

② 그다음 두번째 값이 빠져나와 자식 노드가 됩니다.

③ 최대 힙 정렬은 높은 숫자가 뿌리 노드에 와야합니다.
자식 노드의 값이 부모 노드보다 크다면 교체를 해줍니다.
④ 1은 부모 노드보다 값이 크지 않으므로 변경하지 않고 넘어갑니다.
⑤ 마침 큰 값의 자식 노드가 생겼습니다.
⑥ 규칙에 맞게 노드를 교체해주면 이와 같이 교체가 됩니다.
남은 값들도 채워준 {7,6,3,4,5,1,2} 배열이 최대 힙입니다.
반대로 작은 수가 뿌리 노드에 오게 하는 것은 최소 힙이라 합니다.


① 처음 시작은 늦게 들어온 값부터 뺄 수 있기 때문에 뿌리 노드(7)의 데이터를 2와 7의 위치를 변경하는 것 입니다.
② 7은 정렬되었다고 간주하고 노드에서 사용을 하지 않습니다.
③ 뿌리 노드의 자식 노드 중 왼쪽 자식 노드와 오른쪽 자식 노드의 크기를 비교합니다.
④ 변경이 완료되었으나 2 밑에 아직 자식 노드가 존재합니다.
다시 자식 노드끼리의 크기 비교를 하여 큰 값을 부모 노드(2)와 위치를 변경합니다.
완료됐습니다. 여기까지가 표의 6행까지 진행 순서입니다.
⑤ 남은 노드들은 다시 최대 힙 정렬을 시행하여 모든 요소가 정렬을 마칠 때까지 반복합니다.
힙 정렬은 꺼낸 숫자를 역순으로 나열하면 최대 힙 정렬이 완료됩니다.
최대 힙 정렬 과정입니다.
using System;
namespace ConsoleApp1
{
class Program
{
static int[] array = new int[] { 4, 6, 1, 7, 5, 3, 2 };
static void Print()
{
// 출력 함수
Console.Write(string.Join(", ", array));
}
static void heapSort(int[] array)
{
// 최대 힙 초기화
for (int i = array.Length / 2 - 1; i >= 0; i--)
{
heapify(array, array.Length, i);
}
for (int i = array.Length - 1; i > 0; i--)
{
swap(array, 0, i);
heapify(array, i, 0);
}
}
static void heapify(int[] array, int arrayLength, int i)
{
int parent = i;
int left = i * 2 + 1;
int right = i * 2 + 2;
// 왼쪽 자식노드
if (left < arrayLength && array[parent] < array[left])
{
parent = left;
}
// 오른쪽 자식노드
if (right < arrayLength && array[parent] < array[right])
{
parent = right;
}
// 부모노드 < 자식노드
if (i != parent)
{
swap(array, parent, i);
heapify(array, arrayLength, parent);
}
}
static void swap(int[] array, int a, int b)
{
int temp;
temp = array[a];
array[a] = array[b];
array[b] = temp;
}
static void Main(string[] args)
{
heapSort(array);
Print();
}
}
}
static void heapSort(int[] array)
{
// 최대 힙 초기화
for (int i = array.Length / 2 - 1; i >= 0; i--)
{
heapify(array, array.Length, i);
}
for (int i = array.Length - 1; i > 0; i--)
{
swap(array, 0, i);
heapify(array, i, 0);
}
}
static void heapify(int[] array, int arrayLength, int i)
{
int parent = i;
int left = i * 2 + 1;
int right = i * 2 + 2;
// 왼쪽 자식노드
if (left < arrayLength && array[parent] < array[left])
{
parent = left;
}
// 오른쪽 자식노드
if (right < arrayLength && array[parent] < array[right])
{
parent = right;
}
// 부모노드 < 자식노드
if (i != parent)
{
swap(array, parent, i);
heapify(array, arrayLength, parent);
}
}
static void swap(int[] array, int a, int b)
{
int temp;
temp = array[a];
array[a] = array[b];
array[b] = temp;
}
그런데 메소드에서는 왜 i, 2i+1, 2i+2로 선언했을까요?
이는 heapSort 함수 첫 번째 for문 조건식을 0까지 허용했기 때문인데요,
장점
단점
유익한 글이었습니다.