• [ 목차 ]
  • 우선순위 큐 ADT
  • 우선순위 큐를 이용한 정렬
  • 제자리 정렬
  • 선택 정렬과 삽입 정렬 비교
  • 우선순위 큐 ADT
  • 우선순위 큐를 이용한 정렬
  • 제자리 정렬
  • 선택 정렬과 삽입 정렬 비교
  • 우선순위 큐란 무엇일까 ?
  • 우선순위 큐는 각 항목에 대해 ( 키, 원소 ) 쌍으로 저장되는 자료구조다.
  • 일반적인 큐와 달리 들어오는 순서 (FIFO)가 아닌 일정한 키 값의 기준에따라서 큐 안에 데이터가 정렬되서 사용한다.
  • 우선순위 큐 ADT 메쏘드
  • 주요메쏘드
    • InsertItem(k,e) : 키 k인 원소 e를 큐에 삽입
    • element removeMin( ) : 큐로부터 최소 키를 가진 원소를 삭제해서 반환
      ( removeMax( ) 로 하냐 removeMin( )으로 하냐에 따라 우선순위의
      내부적인 정렬 상태가 내림차순 or 오름차순으로 결정된다. )
  • 일반 메쏘드
    • integer size( ) : 큐의 항목수를 반환
    • boolean isEmpty( ) : 큐가 비어 있는지 여부를 반환
      ( 비어있으면 0(false), 그렇지 않으면 1(true) 리턴 ( 사용자 구현에 따라 이는 달라 질수 있다. )
  •  접근 메쏘드
    • element minElement( ) : 큐에 최소 키를 가진 원소를 반환
    • element minKey( ) : 큐에서 최소 키를 반환
  • 예외처리
    • emptyQueueException( ) : 비어 있는 큐에 대해 삭제나 원소 접근을 시도 할 경우 발생
    • fullQueueException( ) : 가득 찬 큐에 대해 삽입을 시도할 경우 발생
      ( 주로, 배열을 통한 우선순위 큐를 구현했을 경우 자주 발생한다. )
  • 일반적(generic)인 우선순위 큐를 이용한 정렬
[ pseudocode ]
---------------------------------
Alg PQ-Sort(L)
	input list L
    Output sorted list L
    
P <- empty priority queue
while(!L.isEmpty())
	e <- L.removeFirst()
    P.insertItem(e)		// ★
while(!P.isEmpty())
	e<- P.removeMin()	// ★
    L.addLast(e)
return
----------------------------------
★로 표시한 부분, 외부 리스트 P 를 하나두고, 기존의 리스트 L의 요소들을 하나씩 빼내어 insertItem과
P에 들어간 요소들을 삭제하면서, 다시 원본리스트 L에 최소값을 넣어주는식의 방식 
( 이는 오름차순 정렬인 상황, 만약 내림차순으로 우선순위큐를 정렬시에는 removeMax()라는 함수가 필요할 것 )
  • 여기서 일반적(generic)함수란 ?
  • 함수의 인자타입,반환타입 상관없이 여려가지 타입에도 작동하는 함수
  • 리스트에 기초한 우선순위 큐
  • 무순리스트로 구현
    • 우선순위 큐의 항목들을 리스트에 임의 순서로 저장
    • [ 성능 ]
      • InsertItem : O(1) 시간 소요.
        => 항목을 리스트의 맨 앞 또는 맨뒤에 삽입할 수 있기 때문이다.
      • removeMin, minKey, minElement ( Max도 해당 ) : O(n) 시간 소요
        => 최소(최대) 키를 찾기 위해 전체 리스트를 순회해야 하기때문.
      • 비유하면, 시험지를 겉을 때는 막 쌓으면서 걷음.
        후에 시험지를 정리할 때 순서(ex.학번)에 맞게 정리
  • 순서리스트로 구현
    • 우선순위 큐에 항목들을 리스트에 키 정렬 순서로 저장
    • [ 성능 ]
      • InsertItem : O(n) 시간 소요.
        => 항목을 삽입할 곳을 찾고 넣어야하기 때문.
      • removeMin,minKey, minElement ( Max도 해당 ) : O(1) 시간 소요
        => 최소(최대) 키가 리스트의 맨앞에 이미 정렬되어 있으므로.
      • 비유하면, 시험지를 걷을 때부터 (ex.학번)순서대로 정리하고
        후에는 이미 정리했으므로 순설대로 쓰기만 하면 된다.
  • 선택 정렬 ( Selection-Sort )
  • PQ-Sort의 일종
  • 우선순위 큐를 "무순리스트"로 구현
  • [ 실행시간 ]
    • n회 InsertItem 작업을 사용해서 원소들을 외부에 우선순위 큐에 삽입하는데 O(n) 시간 소요
    • n회의 removeMIn(Max) 작업을 사용하여 원소들을 우선순위 큐로 부터 정렬 순서로 삭제하면서 원래의 리스트에 다시 넣는다.
      n + (n-1) + (n-2) + ... + 2 + 1
    • Total : O(n^2)
  • 삽입정렬 ( Inserttion Sort )
  • PQ-Sort의 일종
  • 우선순위 큐를 "순서리스트"로 구현
  • [ 실행시간 ]
    • n회의 InsertItem 작업을 사용하여 원소들을 우선순위 큐에 삽입하는데 다음 비례하는 시간 소요. 애초에 기존 리스트에서 우선순위 큐에 넣을 때 우선순위에 맞게 넣어야하기 떄문이다.
      1 + 2 + ... + (n-2) + (n-1) + n
    • n회의 removeMin(Max) 작업을 사용하여 원소들을 우선순위 큐로부터 정렬 순서로 삭제하는데 O(n)시간 소요
    • Total : O(n^2)

 

  • 제자리 정렬 ( In-Place Sort )
  • 선택정렬,삽입정렬 모두 O(n) 공간을 차지하는 "외부의" 우선순위 큐를 사용하여 리스트를 정렬했다.
  • 만약, 원래 리스트 자체를 위한 공간 이외에 O(1) 공간만을 사용한다면
    이를 "제자리(In-Place)"에서 수행한다고 말한다.
    = ★ 정렬 ( 리스트 내 ) 영역을 조정해 간다 생각하자.
  • 일반적으로, 어떤 정렬 알고리즘이 정렬 대상 개체를 위해 필요한 메모리에 추가하여 오직 "상수 메모리"만을 사용한다면, 해당 정렬 알고리즘이 "제자리"에서 수행한다고 말한다.
  • 제자리 선택 정렬 ( In-Place Selection-Sort )
  • Selection-Sort가 외부 데이터구조를 사용하는 대신, "제자리"에서 수행하도록 구현 가능
  • ★ 우선순위 큐 : 입력 리스트의 일부 ( 초록색 점선부분 )
  • 제자리 선택정렬을 위해서는
    • 이 설명에 경우, 리스트의 앞부분을 정렬 상태로 유지
      ( 사용자 기준에 따라, 뒷부분을 정렬 상태로 유지해도 된다.
    • 리스트를 변경하는 대신에 SwapElements를 사용한다.
  • 제자리 삽입 정렬 ( In-Place Insertion-Sort )
  • Insertion-Sort가 외부 데이터구조를 사용하는 대신, 제자리에서 수행하도록 구현 가능
  • ★ 우선순위 큐 : 입력 리스트의 일부 ( 초록색 점수부분 )

  • 제자리 삽입정렬을 위해서는
    • 이 설명에 경우, 리스트의 앞부분을 정렬 상태로 유지
      ( 사용자 기준에 따라, 뒷부분을 정렬 상태로 유지해도 된다.
    • 리스트를 변경하는 대신 SwapElements를 사용한다.
  • 제자리 선택정렬 vs 제자리 삽입정렬
[ pseudocode ]
---------------------------

Alg InPlaceSelectionSort(A)
	input array A of n Keys
    output sorted array A

for pass <- 0 to n - 2
	minLoc <- pass
    for j <- (pass + 1) to n -1
    	if(A[j] < A[minLoc]
        	minLoc <- j
        A[pass] <-> A[minLoc]
    A[pass] <-> A[minLoc]
return

---------------------------

Alg InPlaceInsertionSort(A)
	input array A of n Keys
    output sorted array A

for pass <- to n - 1
	save <- A[pass]
    j <- pass - 1
    while((j>=0) & (A[j] > save))
    	A[j+1] <- A[j]
        j <- j - 1
    A[j+1] <- save
return

---------------------------
  • C Code
// clock()을 통해 시간 측정할때 현재 코드에 쓴 QueryPerformanceFrequency() => ms 단위로측정
// 외에도, nanosec단위로도 셀수있고, window()함수에서가 아닌 다른 환경에서 측정할수있는 방법도
// 임있을 상기하자.
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>

int* InPlaceSelectionSort(int* arr, int N);
int* InPlaceInsertionSort(int* arr, int N);
void SwapElement(int* a, int* b);
int* RegularArrangement(int* arr, int N);
int* InverseArrangement(int* arr, int N);

int main()
{
	int N, elem;
	int* arr1 = NULL, * arr2 = NULL;
	LARGE_INTEGER ticksPerSec;
	LARGE_INTEGER start, end, diff;

	srand(time(NULL));
	scanf("%d", &N);
	arr1 = (int*)malloc(sizeof(int) * N);
	arr2 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; i++)
	{
		elem = rand() % N + 1;	// 그냥 이렇게되면 ,N이 32767이하에 수에서만 랜덤으로
		arr1[i] = elem;			// 입력하기때문에 중복이 많이 발생할 수 있다.
		arr2[i] = elem;		
	}

	// 정배열 , 역배열로 전환
	//arr1 = RegularArrangement(arr1,N);
	//arr2 = RegularArrangement(arr2,N);
	//arr1 = InverseArrangement(arr1, N);
	//arr2 = InverseArrangement(arr2, N);

	// 정렬 되었는지 확인용 출력
	/*for (int i = 0; i < N; i++)
		printf(" %d", arr1[i]);
	printf("\n");
	for (int i = 0; i < N; i++)
		printf(" %d", arr2[i]);
	printf("\n");*/

	QueryPerformanceFrequency(&ticksPerSec);
	QueryPerformanceCounter(&start);
	arr1 = InPlaceSelectionSort(arr1, N);
	QueryPerformanceCounter(&end);

	// 측정 값으로부터 실행시간 계산
	diff.QuadPart = end.QuadPart - start.QuadPart;
	printf("선택정렬 : %.9f ms\n", ((double)diff.QuadPart / (double)ticksPerSec.QuadPart) * 1000);

	QueryPerformanceFrequency(&ticksPerSec);
	QueryPerformanceCounter(&start);
	arr2 = InPlaceInsertionSort(arr2, N);
	QueryPerformanceCounter(&end);

	diff.QuadPart = end.QuadPart - start.QuadPart;
	printf("삽입정렬 : %.9f ms\n", ((double)diff.QuadPart / (double)ticksPerSec.QuadPart) * 1000);

	// 선택,삽입정렬결과 출력부분
	/*for (int i = 0; i < N; i++)
		printf(" %d", arr1[i]);
	printf("\n");
	for (int i = 0; i < N; i++)
		printf(" %d", arr2[i]);*/

	free(arr1);
	free(arr2);
	return 0;
}
int* InPlaceSelectionSort(int* arr, int N)	// 제자리 선택정렬
{
	int minLoc, save;
	for (int i = N - 1; i > 0; i--)
	{
		minLoc = i;
		for (int j = i - 1; j >= 0; j--)
		{
			if (arr[j] > arr[minLoc])
				minLoc = j;
		}
		SwapElement(&arr[i], &arr[minLoc]); // 쿼리 퍼포먼스사이 시간측정에 함수를 넣지않고
	}										// 원래의 형태를 넣어주는것이 시간측정에 더 정확
	return arr;
}
int* InPlaceInsertionSort(int* arr, int N)	// 제자리 삽입정렬
{
	int save, j;

	for (int i = 1; i < N; i++)
	{
		save = arr[i];
		j = i - 1;
		while (j >= 0 && arr[j] > save)
		{
			arr[j + 1] = arr[j];
			j--;
		}
		arr[j + 1] = save;
	}
	return arr;
}
void SwapElement(int* a, int* b)
{
	int tmp;

	tmp = *a;
	*a = *b;
	*b = tmp;
}
int* RegularArrangement(int* arr, int N)	// 제자리정렬전에 정배열리스트 만들기 ( by.버블정렬 )
{
	int tmp;
	for (int i = 0; i < N - 1; i++)
	{
		for (int j = 0; j < N - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
	return arr;
}
int* InverseArrangement(int* arr, int N)	// 제자리정렬전에 역배열리스트 만들기 ( by.버블정렬 )
{
	int tmp;
	for (int i = 0; i < N - 1; i++)
	{
		for (int j = 0; j < N - 1; j++)
		{
			if (arr[j] < arr[j + 1])
			{
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
	return arr;
}
  • 선택정렬 vs 삽입정렬
  • 공통점
    • 전체적으로 O(n^2) 시간
      => 내부 반복문 : O(n) 선형탐색
      => 외부 반복문 : O(n) 패스
    • 제자리 버전은 O(1) 공간소요
    • 구현이 단순
    • 작은 n에 대해 유용 ( n이 클수록 삽입정렬에 관심을 보여야한다. )
  • 초기 리스트가 완전히 또는 거의 "정배열"로 정렬된 겨우
    • ★ "제자리 삽입정렬" 이  더 빠르다.
    • 내부 반복문이 O(1)시간 소요
      => 따라서 전체적으로 O(n)시간에 수행된다.
    • 기존 리스트가 "역배열"로 정렬된 경우
      => swapElements 작업이 많이 소요되는경우
    • "제자리 선택정렬" 이 더 빠르다.
      => swapElements 작업이 패스마다 O(1)시간 수행되는데 반해,
      제자리 삽입정렬은 동일한 작업이 패스마다 최악의 경우 O(n)시간
      수행되기 떄문이다. 
  • 실제 실행결과 ( 삽입정렬 vs 선택정렬 )

  • 분석 결과

  • 참고사항
  • 실행시간을 측정하는 과정이 코드안에 있다.
    아래는 이에 대한 참고사항을 정리한 내용이다.


 

728x90
반응형

'Algorithm Theory' 카테고리의 다른 글

해시테이블(Hash Table)  (0) 2020.12.13
이진탐색트리(Binary Search Tree)  (0) 2020.12.12
사전(Dictionary)  (0) 2020.12.12
힙과 힙정렬 ( Heap & Heap Sort )  (0) 2020.09.26
[ OT ] 들어가기전에..  (0) 2020.09.24

+ Recent posts