문제를 해결하기 위한 명확한 절차나 방법
입력(n)에 비례해 시간이 얼마나 걸리는지
입력(n)에 비례해 공간이 얼마나 사용되는지
정렬하는 알고리즘
static void Swap(int[] arr, int i, int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void SelectionSort(int[] arr)
{
for (int i = 0; i < arr.Length; i++)
{
int minValueIndex = i;
for (int j = i + 1; j < arr.Length; j++)
{
if (arr[j] < arr[minValueIndex])
minValueIndex = j;
}
}
Swap(arr, i, j);
}
static void Main(string[] args)
{
//그냥 랜덤 배열 생성기다. 심심해서 만들어봤다.
Random random = new Random();
int arrLength = random.Next(10, 20);
int[] intArr = new int[arrLength];
for (int i = 0; i < arrLength; i++)
{
intArr[i] = random.Next(100);
}
//랜덤 생성된 배열 확인
foreach (int i in intArr)
Console.Write(i + " ");
//Sort진행 후 배열 확인
SelectionSort(intArr);
Console.WriteLine();
foreach (int i in intArr)
Console.Write(i + " ");
}
//메커니즘은 두 번째 요소부터 마지막 요소까지 한 번씩 key로 설정하면서,
//key값을 왼쪽 요소들과 비교하면서 한칸씩 왼쪽으로 옮기는 구조이다.
static void InsertionSort(int[] arr)
{
int n = arr.Length;
// 두 번째 요소부터 시작해서 배열을 탐색
for (int i = 1; i < n; i++)
{
int key = arr[i];
int j = i - 1;
// key 값보다 큰 요소들은 한 칸씩 오른쪽으로 이동
while (j >= 0 && arr[j] > key)
{
arr[j + 1] = arr[j];
j--;
}
// 적절한 위치에 key 값을 삽입
arr[j + 1] = key;
}
}
static void Main(string[] args)
{
int[] arr = { 12, 11, 13, 5, 6 };
Console.WriteLine("정렬 전 배열:");
Console.WriteLine(string.Join(" ", arr));
InsertionSort(arr);
Console.WriteLine("정렬 후 배열:");
Console.WriteLine(string.Join(" ", arr));
}
static void QuickSort(int[] arr, int low, int high)
{
if (low < high)
{
// 분할하고 피벗의 위치를 받아옴
int pivotIndex = Partition(arr, low, high);
// 피벗을 기준으로 왼쪽과 오른쪽 부분 배열에 대해 재귀적으로 퀵 정렬을 수행
QuickSort(arr, low, pivotIndex - 1); // 피벗보다 작은 부분 정렬
QuickSort(arr, pivotIndex + 1, high); // 피벗보다 큰 부분 정렬
}
}
static int Partition(int[] arr, int low, int high)
{
// 피벗을 배열의 마지막 요소로 설정
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++)
{
// 현재 요소가 피벗보다 작거나 같으면 i를 증가시키고 교환
if (arr[j] <= pivot)
{
i++;
Swap(arr, i, j);
}
}
// i+1 위치와 피벗(현재 arr[high])을 교환하여 피벗을 제자리로 이동
Swap(arr, i + 1, high);
return i + 1;
}
static void Swap(int[] arr, int a, int b)
{
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
static void Main(string[] args)
{
int[] arr = { 10, 7, 8, 9, 1, 5 };
Console.WriteLine("정렬 전 배열:");
Console.WriteLine(string.Join(" ", arr));
QuickSort(arr, 0, arr.Length - 1);
Console.WriteLine("정렬 후 배열:");
Console.WriteLine(string.Join(" ", arr));
}
근데 우리는 사실 그냥 C#에서 제공하는 Sort를 쓰면된다.
따로 정렬 알고리즘이 필요할 때 만들어서 사용.
// 병합 정렬 함수
static void MergeSort(int[] arr, int left, int right)
{
if (left < right)
{
// 중간점 계산
int mid = left + (right - left) / 2;
// 왼쪽과 오른쪽을 각각 병합 정렬
MergeSort(arr, left, mid);
MergeSort(arr, mid + 1, right);
// 정렬된 두 부분을 병합
Merge(arr, left, mid, right);
}
}
// 병합 함수: 두 정렬된 부분을 병합
static void Merge(int[] arr, int left, int mid, int right)
{
int n1 = mid - left + 1; // 왼쪽 배열의 크기
int n2 = right - mid; // 오른쪽 배열의 크기
// 임시 배열 생성
int[] leftArray = new int[n1];
int[] rightArray = new int[n2];
// 원본 배열의 값을 임시 배열에 복사
Array.Copy(arr, left, leftArray, 0, n1);
Array.Copy(arr, mid + 1, rightArray, 0, n2);
// 병합 작업
int i = 0, j = 0, k = left;
// 두 배열에서 작은 값을 선택해서 원본 배열에 넣음
while (i < n1 && j < n2)
{
if (leftArray[i] <= rightArray[j])
{
arr[k] = leftArray[i];
i++;
}
else
{
arr[k] = rightArray[j];
j++;
}
k++;
}
// 남은 요소들을 원본 배열에 복사
while (i < n1)
{
arr[k] = leftArray[i];
i++;
k++;
}
while (j < n2)
{
arr[k] = rightArray[j];
j++;
k++;
}
}
static void Main(string[] args)
{
int[] arr = { 12, 11, 13, 5, 6, 7 };
Console.WriteLine("정렬 전 배열:");
Console.WriteLine(string.Join(" ", arr));
// 병합 정렬 호출
MergeSort(arr, 0, arr.Length - 1);
Console.WriteLine("정렬 후 배열:");
Console.WriteLine(string.Join(" ", arr));
}
데이터 집합 내에서 원하는 데이터를 탐색하는 알고리즘
순차 탐색(Sequential Search)이라고도 함.
// 선형 탐색 함수
int LinearSearch(int[] arr, int target)
{
// 배열을 처음부터 끝까지 순회하며 목표 값 탐색
for (int i = 0; i < arr.Length; i++)
{
// 현재 값이 목표 값과 일치하면 해당 인덱스 반환
if (arr[i] == target)
{
return i;
}
}
// 목표 값을 찾지 못한 경우 -1 반환
return -1;
}
int BinarySearch(int[] arr, int left, int right, int target)
{
if (left <= right)
{
// 중간 인덱스 계산
int mid = left + (right - left) / 2;
// 중간값이 목표 값과 같은 경우 인덱스 반환
if (arr[mid] == target)
return mid;
// 목표 값이 중간값보다 작으면 왼쪽 절반 탐색
if (arr[mid] > target)
return BinarySearch(arr, left, mid - 1, target);
// 목표 값이 중간값보다 크면 오른쪽 절반 탐색
return BinarySearch(arr, mid + 1, right, target);
}
// 목표 값을 찾지 못한 경우 -1 반환
return -1;
}
깊은 곳을 먼저 탐색하는 알고리즘(깊이 우선)
깊이를 순차적으로 탐색하는 알고리즘(너비 우선)
using System;
using System.Collections.Generic;
public class Graph
{
private int V; // 그래프의 정점 개수
private List<int>[] adj; // 인접 리스트 (간선 리스트) // List<int>를 원소로 가지는 배열
//정점 개수 v만큼 배열 내부에 List<int> 초기화
//List는 각 정점에 연결되어있는 정점을 저장해주는 역할
public Graph(int v)
{
V = v;
adj = new List<int>[V];
for (int i = 0; i < V; i++)
{
adj[i] = new List<int>();
}
}
//인접 리스트에 Edge(간선)추가.
public void AddEdge(int v, int w)
{
adj[v].Add(w); //v번째 리스트(정점)에 w(정점)추가
}
//v 정점에서 DFS 시작
public void DFS(int v)
{
//방문했음?에 해당하는 bool값을 가지는, (정점개수와 길이가 같은) visited 배열 선언
bool[] visited = new bool[V];
DFSUtil(v, visited);
}
//DFS방식으로 정점확인하는 재귀
private void DFSUtil(int v, bool[] visited)
{
//v를 방문했으니 visited true로 변경
visited[v] = true;
Console.Write($"{v} ");
//v번째 정점에 연결된 정점을 가본적이 없다면 DFSUtill 실행
//들어간 DFSUtill에서 또다시 정점을 확인하기 때문에, 계속 깊은 곳으로 먼저 가게 된다.
foreach (int n in adj[v])
{
if (!visited[n])
{
DFSUtil(n, visited);
}
}
}
//v 정점에서 BFS 시작
public void BFS(int v)
{
//DFS와 마찬가지로 방문했는지 물어보는 visited배열 작성
//시작 정점을 Queue에 넣고, 나올 때 연결된 정점을 Queue에 넣는다.
bool[] visited = new bool[V];
Queue<int> queue = new Queue<int>();
//시작 정점 true & Queue에 넣음
visited[v] = true;
queue.Enqueue(v);
//queue가 비어있지 않으면 계속
while (queue.Count > 0)
{
// 가장 최근 넣은 정점을 뺌
int n = queue.Dequeue();
Console.Write($"{n} ");
//뺀 정점(n)에 연결된 정점(adj[n])을 Queue에 넣어줌(방문표시-true표시-도 하면서)
foreach (int m in adj[n])
{
if (!visited[m])
{
visited[m] = true;
queue.Enqueue(m);
}
}
}
}
}
public class Program
{
public static void Main()
{
//정점이 6개인 그래프 선언
Graph graph = new Graph(6);
//정점끼리 연결해줌. >>간선들 모음
graph.AddEdge(0, 1);
graph.AddEdge(0, 2);
graph.AddEdge(1, 3);
graph.AddEdge(2, 3);
graph.AddEdge(2, 4);
graph.AddEdge(3, 4);
graph.AddEdge(3, 5);
graph.AddEdge(4, 5);
Console.WriteLine("DFS traversal:");
graph.DFS(0);
Console.WriteLine();
Console.WriteLine("BFS traversal:");
graph.BFS(0);
Console.WriteLine();
}
}
주석을 너무 친절하게 달아놨으니, 코드 구현을 한 번 해보도록 하자.
하나의 시작 정점에서 다른 모든 정점들에 대한 최단 거리를 알 수 있는 알고리즘. 단, 음의 가중치를 가진 간선을 갖는 그래프에선 쓸 수 없다.
시간이 남는다면 다익스트라 알고리즘(Dijkstra Algorithm)에 대해 공부해 보는 것도 좋다.
using System;
class DijkstraExample
{
static int V = 6; // 정점의 수
// 주어진 그래프의 최단 경로를 찾는 다익스트라 알고리즘
static void Dijkstra(int[,] graph, int start)
{
int[] distance = new int[V]; // 시작 정점으로부터의 거리 배열
bool[] visited = new bool[V]; // 방문 여부 배열
// 거리 배열 초기화
for (int i = 0; i < V; i++)
{
distance[i] = int.MaxValue;
}
distance[start] = 0; // 시작 정점의 거리는 0
// 모든 정점을 방문할 때까지 반복
for (int count = 0; count < V - 1; count++)
{
// 현재 방문하지 않은 정점들 중에서 최소 거리를 가진 정점을 찾음
int minDistance = int.MaxValue;
int minIndex = -1;
for (int v = 0; v < V; v++)
{
if (!visited[v] && distance[v] <= minDistance)
{
minDistance = distance[v];
minIndex = v;
}
}
// 최소 거리를 가진 정점을 방문 처리
visited[minIndex] = true;
// 최소 거리를 가진 정점과 인접한 정점들의 거리 업데이트
for (int v = 0; v < V; v++)
{
if (!visited[v] && graph[minIndex, v] != 0 &&
distance[minIndex] != int.MaxValue &&
distance[minIndex] + graph[minIndex, v] < distance[v])
{
distance[v] = distance[minIndex] + graph[minIndex, v];
}
}
}
// 최단 경로 출력
Console.WriteLine("정점\t거리");
for (int i = 0; i < V; i++)
{
Console.WriteLine($"{i}\t{distance[i]}");
}
}
static void Main(string[] args)
{
int[,] graph = {
{ 0, 4, 0, 0, 0, 0 },
{ 4, 0, 8, 0, 0, 0 },
{ 0, 8, 0, 7, 0, 4 },
{ 0, 0, 7, 0, 9, 14 },
{ 0, 0, 0, 9, 0, 10 },
{ 0, 0, 4, 14, 10, 0 }
};
int start = 0; // 시작 정점
Dijkstra(graph, start);
}
}
음의 간선을 가진 그래프에서도 사용가능한 알고리즘
특정 목적지까지 최단 경로를 찾는 알고리즘.
휴리스틱 함수를 이용하여 각 정점까지의 예상 비용을 계산하고 가장 낮은 비용을 가진 정점들을 선택하여 탐색.