삽입정렬

삽입정렬이란?

삽입정렬은 크기가 n인 집합일 때,
2개, 3개, 4개 ... n-1개씩 비교대상이 늘어나는 것이 특징이다.

단, 사이클마다 대상 집합이 이미 정렬되어 있는 경우에는 비교가 일어나지 않는다. 그래서 무조건(정렬이 되어 있든 아니든) 비교하는 버블정렬보다 평균적으로는 성능이 좋다.

아래에서 예시로 정렬할 때 오름차순을 목표로 한다.

특징

1.정렬 대상이 처음에는 2개.
사이클을 반복할 때마다 1개씩 커집니다. 최대 n-1까지 커진다.

2.정렬 대상의 가장 오른쪽 요소가 대상 중에서 가장 큰 값인지 확인한다.
그렇지 않다면 그 것의 적절한 위치를 찾아준다. 자신보다 큰 요소를 만날 때 까지 탐색한다.

3.적절한 곳을 찾았다면, 자신보다 큰 요소들을 모두 한칸씩 앞으로 이동시킨다. 그리고 새로 생긴 곳에 위치시킨다.

예시

5 1 6 4 2 3

데이터 집합 크기는 6이다.

일단 비교 대상은 2개다. 5와 1.

가장 오른쪽 요소인 1이 가장 큰값을 가지는지 확인해본다.

1을 뽑는다. 1보다 큰 수 5 앞에 놓아야 한다.

5를 이동시켜서 오른쪽으로 밀어서 공간을 만든다.

빈 곳에 1을 삽입한다.

결과 : 1 5 6 4 2 3

비교 대상은 3개다. 1, 5, 6

가장 오른쪽 요소 6이 가장 큰 값을 가지는지 확인한다. (6을 그 앞의 숫자 5와 비교한다.)

6이 더 크다.

그 앞의 요소들은 이미 정렬을 거친 숫자들이니까 확인할 필요가 없다. (!!)

다음 단계로 넘어간다.

비교 대상은 4개다. 1, 5, 6, 4

가장 오른쪽 요소 4가 가장 큰 값을 가지는지 확인한다. (4를 그 앞의 수 6과 비교한다.)

4가 작으니까 이동이 필요하다.

4를 뽑는다.

6, 5, 1 순으로 탐색해보니까 1 앞에 오면 된다.

5와 6을 오른쪽으로 민다. 빈 공간에 4를 삽입한다.

결과 : 1 4 5 6 2 3

삽입정렬 코딩해보기 1

#include <stdio.h>
#include <string.h>

void InsertionSort(int dataset[], int length){
    int i=0, j=0, target=0;

    for(i=1; i<length; i++){

        if(dataset[i-1] < dataset[i]){
            continue; // 맨 오른쪽 요소가 가장 크다면 정렬이 필요 없다. 
        }

        target = dataset[i]; // 맨 오른쪽 요소의 적절한 위치 탐색시작

        for(j=0; j<i; j++){

            if(dataset[j] > target){
                // 데이터를 쭈욱 민다. 
                memmove(&dataset[j+1], &dataset[j], sizeof(dataset[0])*(i-j));
                dataset[j] = target;
                break;
            }
        }
    }
}

int main(void){

    int dataset[] = {5, 3, 6, 4, 1, 2};
    int length = sizeof(dataset) / sizeof(int);
    int i = 0;

    InsertionSort(dataset, length);

    for(i=0; i<length; i++){
        printf("%d ", dataset[i]);
    }

    return 0;
}

memmove() 함수

메모리에 내용을 이동시키는 함수이다. C표준함수.

배열처럼 연속된 데이터를 이동시킬 때 유용하다.

반복문으로 이 함수의 기능을 구현할 수 있지만 성능이 떨어진다.

void* memmove(void* dest, const void *src, size_tn)

dest: 복사의 대상이 되는 목적지 주소

src: 복사할 원본이 있는 주소

size_tn : 복사할 메모리 길이 (byte)

memmove() 함수는 데이터를 '이동'시키고 memcpy() 함수는 데이터를 '복사'한다.

삽입정렬 코딩해보기 2

memmove() 메모리 복사 없이, 배열의 데이터 복사하는 방식으로 정렬해본다.

i와 j로 만든 이중 for문인데,

i는 1부터 시작한다. 비교 집합의 맨 오른쪽 수가 i인데 temp 변수에 기억해둔다.

j 반복문 안에서, temp 보다 큰 수가 있다면 하나씩 오른쪽으로 당긴다.

적절한 위치를 발견하는 순간 temp에 저장된 값을 삽입한다.

input.txt 입력 설명
첫 번째 줄에 자연수 N(1<=N<=100)이 주어집니다.
두 번째 줄에 N개의 자연수가 공백을 사이에 두고 입력됩니다.
6
11 7 5 6 10 9

출력 설명
오름차순으로 정렬된 수열을 출력합니다.
5 6 7 9 10 11

#include <stdio.h> 

int main(int argc, char** argv){

    int a[100], num=0, temp=0, i=0, j=0;

    freopen("input.txt", "rt", stdin);

    scanf("%d", &num); // 데이터 개수  

    for(i=0; i<num; i++){ // 데이터 저장  
        scanf("%d", &a[i]);
    }

    for(i=1; i<num; i++){ // 오름차순으로 정렬 

        temp = a[i]; // a[i]의 적당한 위치를 탐색 시작  

        for(j=i-1; j >= 0; j--){ // 오른쪽에서 왼쪽으로 순회  

            if(a[j] > temp){  
                a[j+1] = a[j];
            }else{
                break;
            }
        }

        a[j+1] = temp; // 적당한 위치에 삽입. 
    }

    for(i=0; i<num; i++){ // 정렬 결과  
        printf("%d ", a[i]);
    }

    return 0;
}
  • 헷갈렸던 부분 정리!

데이터집합이 3 2 4 5 라고 가정한다.

i는 인덱스 1부터 temp 에는 2 가 들어가 있다.
j는 인덱스 i-1부터 시작하니까 0 이 들어가 있다.

for문의 조건을 확인한다.
j는 0보다 크거나 같으니까, 반복문 안의 코드가 실행된다.
3이 2보다 크니까 if문 조건을 만족한다.
그래서 인덱스 1 자리에 인덱스 0의 값이 복사된다.
그 결과 3 3 4 5 가 된다.
반복문 안의 코드 수행이 끝났으니까, j--가 수행된다.
j는 -1이 된다.
다시 for문의 조건을 확인한다. j>=0 을 만족하지 못하니까 반복문을 종료한다.

j는 -1인 채로 남아 있었으니까, arr[j+1] 은 arr[0]을 의미한다.
그래서 인덱스 0 자리에 temp값 2를 넣는다.
그 결과 2 3 4 5 가 된다.

728x90

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

그래프의 정의와 표현방법  (2) 2020.08.02
우선순위 큐  (0) 2020.06.03
버블 정렬  (0) 2020.05.30
균형 이진 탐색 트리 (AVL트리)  (1) 2020.03.30
이진 탐색 트리 Binary Search Tree (linked list)  (0) 2020.03.18

버블정렬

 

버블 정렬이란?

거품이 보글보글 올라오는 모양처럼 정렬이 일어난다고 해서 버블 정렬이다.

버블 정렬은 '이웃끼리' 교환을 통해 요소를 정렬한다.

한 사이클을 순회할 때 마다 '가장 큰 수'가 자리를 잡게 된다.

 

예시

5 1 6 4 2 3

제일 왼쪽에 있는 요소 부터 이웃 데이터끼리 비교하여 오름차순으로 정렬한다.

 

5와 1을 비교한다.

1 5 6 4 2 3

 

5와 6을 비교한다. 교환이 필요없으므로 넘어간다.

6과 4를 비교한다.

1 5 4 6 2 3

 

6과 2를 비교한다.

1 5 4 2 6 3

 

6과 3을 비교한다.

1 5 4 2 3 6

 

한 사이클이 끝나면, 가장 큰 수가 가장 마지막에 자리를 잡을 수 있다.

보글보글 하면서 가장 큰 수가(6) 마지막에 가 있고.

그 다음 사이클에는 또 보글보글 하면서 그 다음 큰 수(5)가 마지막에 가 있고.

그 다음 사이클에는 보글보글 하면서 그 다음 큰 수(4)가 마지막에 가 있고. ... 를 반복하게 된다.

 


얼마나 빠를까

데이터를 순회할 때 마다 순회할 데이터의 개수가 1개씩 줄어든다.

데이터 요소가 N개라면, n-1회, n-2회, n-3회 ... 1회.

따라서 n-1 부터 1까지의 합을 구하면 버블 정렬의 비교연산 횟수가 된다.

오래걸리지만 구현은 간단하다는 장점이 있다.

 

선택정렬의 경우, 한 사이클을 돌면서 교환(Swap)을 딱 1번만 한다. 

교환 할 요소의 인덱스를 기억해 두었다가 내부 for문이 종료되면 교환을 1번 하기 때문이다. 

반면 버블정렬은 가장 큰수를 가장 마지막 자리로 보내는데에 1번 이상이 필요하다.

버블 정렬은 시간 복잡도가 매우 큰 정렬 방식이다. 


버블정렬 코드

 

#include <stdio.h>

void BubbleSort(int dataset[], int num){ // 배열과 배열길이를 입력받음

    int i = 0, j=0, temp = 0;
    
    for(i=0; i<num-1; i++){ // num-1 만큼 순회  
        
        for(j=0; j<num-(i+1); j++){ // 정렬할 요소가 1개씩 줄어든다 
            
            if(dataset[j] > dataset[j+1]){ // 교환 조건
                temp = dataset[j];
                dataset[j] = dadtaset[j+1];
                dataset[j+1] = temp;
            }
        }
    }
}

int main(){
    
    int dataset[] = {10, 5, 1, 15, 4, 8, 9};
    int num = sizeof(dataset) / sizeof(dataset[0]);
    int i = 0;
    
    BubbleSort(dataset, num);
    
    for(i=0; i<num; i++){
        printf("%d ", dataset[i]);
    }
    
    return 0;
}

 

 

 

728x90

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

우선순위 큐  (0) 2020.06.03
삽입 정렬 (중요)  (0) 2020.06.02
균형 이진 탐색 트리 (AVL트리)  (1) 2020.03.30
이진 탐색 트리 Binary Search Tree (linked list)  (0) 2020.03.18
이진 탐색  (0) 2020.03.18

문제

N명의 수학성적이 주어지면 그 중 3등을 한 수학성적을 출력하는 프로그램을 작성하세요.

만약 학생의 점수가 100점이 3명, 99점이 2명, 98점이 5명, 97점이 3명 이런식으로 점수가 분포되면 1등은 3명이며, 2등은 2명이며 3등은 5명이 되어 98점이 3등을 한 점수가 됩니다.

입력

첫 번째 줄에 자연수 N(1<=N<=100)이 주어집니다.
두 번째 줄에 N개의 수학성적 점수가 공백을 사이에 두고 입력됩니다. 수학성적 점수는 100점 만점 기준으로 입력됩니다.

출력

3등을 한 점수를 출력합니다.

입출력 예시

입력
7
80 96 75 82 96 92 100

 

출력
92

 

리뷰

선택정렬을 하고 나서, 정렬된 배열을 순회하되, 앞의 수와 뒤의 수가 차이가 나면 rank를 하나씩 증가시켜서 rank가 3이 되는 순간의 숫자를 출력하도록 풀었다.
근데 테스트케이스를 통과하지 못해서 선생님해설을 봤더니 내가 생각한 것과 풀이법은 똑같았다.
내 코드의 어디가 틀린건지 혼자 찾아내고 싶었는데 잘 안됬다.
내코드와 선생님 코드의 차이점을 보니까 나는 3등이 아니라 4등을 출력하고 있었고, 3등을 찾아내는 반복문의 인덱스를 하나 빼먹고 있었다.
마지막까지 꼼꼼하게 확인하는 습관을 들여야겠다.

 

#include <stdio.h>  
#include  
#include  
using namespace std;

int main(int argc, char\*\* argv) {

    int num=0, arr\[101\], i=0, j=0, idx=0, tmp=0, rank=0, res=0;

    //freopen("input.txt", "rt", stdin);

    scanf("%d", &num);

    // 숫자들 읽기  
    for(i=0; i<num; i++){  
        scanf("%d", &arr\[i\]);  
    }

    for(i=0; i<num-1; i++){ // 선택정렬

        idx = i; // i를 기준으로.  

    	for(j = i+1; j<num; j++){

        	if(arr[j] > arr[idx]) idx = j;  // 작은 값의 '인덱스'를 기억해둔다  
        }
        
        tmp = arr[i];
        arr[i] = arr[idx];
        arr[idx] = tmp;

    }

    for(i=1; i<num; i++){ // 3등 찾기

        if(arr[i-1] > arr[i]){
            rank++;
        }

        if(rank == 2){
            printf("%d", arr[i]);
            break; 
        }
    }

	return 0;  
}

 

728x90

문제 

자연수 N이 입력되면 1부터 N까지의 각 숫자들의 약수의 개수를 출력하는 프로그램을 작성하세요.

만약 N이 8이 입력된다면 1(1개), 2(2개), 3(2개), 4(3개), 5(2개), 6(4개), 7(2개), 8(4 개) 와 같이 각 숫자의 약수의 개수가 구해집니다.

출력은 다음과 같이 1부터 차례대로 약수의 개수만 출력하면 됩니다.

1 2 2 3 2 4 2 4 와 같이 출력한다.

 

입력설명 

첫 번째 줄에 자연수 N(5<=N<=50,000)가 주어진다.

 

출력설명

첫 번째 줄에 1부터 N까지 약수의 개수를 순서대로 출력한다

 

 

 

풀이 

 

이중 반복문을 이용해서 숫자 마다의 약수를 구하려고 했었다. 

하지만 그렇게 하면 시간초과가 난다. 

그래서 약수의 개수를 for문을 순회할 때마다 +1 해주는 방식이 필요하다. 

 

"1을 약수로 갖는 숫자들은 어떤 숫자들일까?"
"1의 배수들이다."

"2을 약수로 갖는 숫자들은 어떤 숫자들일까?"
"2의 배수들이다."

2의 배수들은 2를 약수로 갖는다. 

이 점을 이용한 것이다. 

 

1은 모든 수의 약수이므로 배열 모든 칸에 +1을 해준다. 

2는 모든 짝수 들의 약수이므로 2의 배수에 해당하는 칸에 +1을 해준다. 

. . . 

내부 for문을 이렇게 구현한다. 즉, j는 i로 시작하되, i의 배수이니까 j는 j+i만큼 커지면서 배열을 건너뛰어가면서 배수를 인덱싱한다. 

 

 

 

궁금한 점 

 

50001 크기의 int 배열 cnt는 지역변수 인 경우에 테스트케이스에서 fail이 난다. 

그런데 cnt가 전역변수 인 경우에는 테스트케이스를 전부 통과했다. 

그 차이가 뭔지 궁금했다. 

 

#include <stdio.h> 

int cnt[50001];

int main(int argc, char** argv) {
	
	//freopen("input.txt", "rt", stdin);
	int input=0, i=0, j=0;
	scanf("%d", &input);
	
	for(i=1; i<=input; i++){
		
		// i의 배수들을 +1 처리해준다. 
		// i를 약수로 갖는 숫자들을 찾는 것이다.  
		for(j=i; j<=input; j=j+i){
			cnt[j]++;
		}
	}
	
	for(i=1; i<=input; i++){
		printf("%d ", cnt[i]);
	} 

	return 0;
}

 

선생님이 답변을 달아주셨다. 

더보기

배열선언에서 지역변수와 전역변수의 차이는 대략 2가지 정도입니다.

1. 배열을 전역으로 선언하면 기본값이 0으로 초기화 됩니다. 하지만 지역변수는 0으로 초기화 된다는 보장이 없습니다. 지역변수로 선언할 때 0으로 확실이 초기화 하고 싶으면 int cnt[50001]={0, } 이렇게 선언하면 됩니다.

 

2. 지역변수로 선언하면 메모리의 스택영역에 할당됩니다. 스택을 메모리가 작어 크기가 큰 배열을 선언하기에는 적당치 않습니다. 전역변수는 메모리의 데어터 역영에 할당되며, 크기가 큰 배열을 선언해도 문제없이 할당이 됩니다. 즉 문제풀이 코드에서는 크기가 10만 이상 크기의 배열은 전역으로 선언하는게 좋습니다.

 

위 코드의 문제는 지역변수로 선언해서 0으로 초기화가 되지 않아서 생기는 에러같습니다.

 

22번 문제와 24번 문제에서 벡터를 사용해 배열을 동적으로 선언하는 것을 배웁니다. 벡터을 사용하는게 제일 좋습니다.

 

728x90

균형 이진 탐색 트리란?

 

오른쪽 서브트리의 높이와 왼쪽 서브 트리의 높이 차이가 1 이하인 이진 탐색 트리를 말합니다.

이러한 높이 차이를 균형인수(Balance Factor) 라고 합니다. (BF)

AVL 트리는 삽입 또는 삭제로 트리가 균형을 잃었을 때 스스로 노드를 재배치하여 위의 조건을 다시 맞춥니다. 

( BF가 1 이하:   1, 0, 1 중에서 값을 가집니다. )

 

 

[장점]

BF가 1 이하이므로(높이 차이가 거의 비슷하기 때문에) 탐색, 삽입, 삭제 모두 O(log2n) 시간 안에 가능합니다. 

균형 없는 트리라면, 한쪽으로만 자라날 수 있기 때문에, 최악의 경우 시간 복잡도가 O(n)이 됩니다. 

 

[취약점]

자료가 많아지면 트리의 높이가 커지는 문제가 있습니다.

자료 삽입삭제가 자주 일어나면 균형을 유지하느라 재배치에 쏟는 시간이 많아질 수 있습니다. 

 

AVL TREE 라는 이름은 만든 사람 2명의 앞글자를 딴 것입니다. 아델슨 벨스키(Abelson-Velskii)와 라딘스(Landis)

 

 

균형을 잃은 4가지  경우

 

(전제)

root 노드를 기준으로 왼쪽, 오른쪽을 구분한다. 

root : 초기 부모

pivot : root의 자리를 대신할 자식 

 

LL과 RR은 한 번만 회전하는 단순회전이고, LR, RL은 두 번 회전하는 이중회전 입니다.

어떤 방향에 따라 부모와 자식 원소의 위치를 바꾸면 됩니다. 

 

 

 

1. LL (Left-Left) : 오른쪽으로 회전

 

노드 '3'을 오른쪽으로 회전한다. 노드 '3'이 노드 '2'의 자식이 된다. 

균형이 왼쪽 서브 트리의 왼쪽 서브 트리에 의해 깨진 경우. 오른쪽 방향으로 회전한다.

다시 말하면, 루트 노드의 왼쪽 자식노드의 왼쪽 서브트리에 의해 균형이 깨진 경우를 말한다. 

 

 

 

2. RR (Right-Right) : 왼쪽으로 회전

 

'1' 노드가 '2'노드의 자식이 된다.

루트노드 1을 기준으로 생각한다.

1의 오른쪽 서브 트리(2)의 오른쪽 서브 트리(3)에 의해 균형이 깨진 경우. 왼쪽 방향으로 회전한다. 

루트 노드의 오른쪽 자식노드의 오른쪽 서브 트리에 의해 균형이 깨진 경우를 말한다. 

 

 

 

3. LR ( Left-Right): LL회전 후에 RR회전

 

루트 노드 3을 기준으로 보자.  

왼쪽 서브 트리의 오른쪽 노드인 1과 2를 먼저 왼쪽으로 회전한다.

왼쪽 서브트리를 오른쪽으로 회전하여 3이 2의 자식이 되도록 한다. 

루트 노드 3을 기준으로, 루트의 왼쪽 서브트리(1)의 오른쪽 서브 트리(2)가 균형을 잃은 경우.

왼쪽으로 회전 후 오른쪽으로 회전한다. 

( 루트의 왼쪽 서브트리의 오른쪽 서브 트리를 왼쪽으로 회전하고, 루트와 루트의 왼쪽 서브트리를 오른쪽으로 회전한다. )

 

 

 

4. RL (Right-Left) : RR 회전 후에 LL회전

 

루트 노드 1을 기준으로 보자. 

오른쪽 서브트리가 균형을 잃었다. 

오른쪽 서브트리의 왼쪽 자식을 오른쪽으로 회전한다. 2와 3을 오른쪽으로 회전.

그리고 루트노드의 오른쪽 자식 '2'를 기준으로 왼쪽으로 회전한다. 

 

 

 루트 노드의 오른쪽 서브트리의 왼쪽 서브트리가 균형을  잃은 경우.

오른쪽으로 회전 후, 왼쪽으로 회전한다. 

( 왼쪽 서브 트리를 오른쪽으로 회전 후. 루트 노드와 오른쪽 서브 트리를 왼쪽으로 회전한다. )

 

 


탐색

 

이진 탐색 트리의 연산과 같습니다. 

 

 

삽입 

 

회전을 이해해야 한다. 

오른쪽 회전, 왼쪽 회전 두 가지를 기반으로 기본 연산을 한다. 

 

LeftRotate() : 왼쪽방향으로 회전 

 

RightRotate() :오른쪽방향으로 회전 

 

leftRightRotate() : 왼쪽, 오른쪽 방향 회전 

 

rightLeftRotate() : 오른쪽, 왼쪽 방향 회전 

 

 

 

 


[ 참고 포스팅 ]

 

https://edu-coding.tistory.com/43

 

균형 이진 탐색트리

AVL트리(Abelson-Velskii Tree)는 아델슨 벨스키(Abelson-Velskii)와 라딘스(Landis)가 제안한 대표적인 균형 이진 탐색트리이다. AVL 트리는 각 노드에서 왼쪽 서브 트리의 높이 hL과 오른쪽 서브 트리의 높이 hR..

edu-coding.tistory.com

http://egloos.zum.com/printf/v/913998

 

자료구조 :: 탐색(3) "균형 이진 탐색 트리 - AVL 트리"

# 균형 이진 탐색 트리이진 탐색 트리는 만약 트리가 균형 트리라면 탐색 연산은 O(log₂n)의 시간 복잡도를 가지고 있다.일반 이진 트리는 이진 트리 상태를 유지하기는 하지만 균형 트리를 보장하지는 않는다. 만약 이진 탐색 트리가균형 트리가 아닐 경우에는 트리가 경사 트리가 될 경우 시간 복잡도가 O(n)으로 높아지게 된다.스스로 균형 트리를 만드는 트

egloos.zum.com

 

728x90

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

삽입 정렬 (중요)  (0) 2020.06.02
버블 정렬  (0) 2020.05.30
이진 탐색 트리 Binary Search Tree (linked list)  (0) 2020.03.18
이진 탐색  (0) 2020.03.18
분리 집합 (유니온파인드) c++  (0) 2020.03.18

이진탐색트리

이진 탐색 트리란?

이진 트리는 자식 노드가 최대 2개인 트리 입니다.

이진 탐색 트리는 이진 탐색을 위한 이진 트리 입니다.

이진 탐색은 집합이 '배열'인 경우에만 가능합니다.

왜냐하면 처음과 끝을 알아야 하고, 집합의 전체 길이를 알아야 합니다.

가장 중요한 것은 인덱스를 이용하여 바로 중앙 요소에 접근할 수 있어야 합니다.

그러나, 링크드 리스트 처럼 동적으로 집합의 크기가 달라지는 경우에는 사용할 수 없습니다.

부모 노드('나')를 기준으로, 왼쪽 자식노드는 나보다 작고, 오른쪽 자식 노드는 나보다 큽니다.

이 규칙을 기반하여 탐색합니다.

탐색

Node* SearchNode(Node* tree, int target){
    if(tree == NULL){
        return NULL;
    }

    if(tree->data == target){ // 탐색 성공

    }else if(tree->data < target){ // 현재 데이터가 타겟보다 작다면, 오른쪽으로
        return SearchNode(tree->right, target);
    }else{
        return SearchNode(tree->left, target); // 타겟보다 크다면, 왼쪽으로 가본다
    }
}

삽입

새 노드를 어디에 삽입할지 찾아야 합니다.

루트 노드와 새 노드를 인자로 받습니다.

void InsertNode(Node** tree, Node* child){

    if((*tree)->data < child->data){ // 새 노드가 현재 노드보다 큰 경우, 

        if((*tree)->right == NULL){
            (*tree)->right = child;
        }else{
            InsertNode(&(*tree)->right, child);
        }

    }else if((*tree)->data > child->data){ // 새 노드가 현재 노드보다 작은 경우,
        if((*tree)->left == NULL){
            (*tree)->left = child;
        }else{
            InsertNode(&(*tree)->left, child);
        }
    }
}

삭제

삭제할 노드가 트리의 맨 끝 리프노드라면 쉽습니다.

하지만 리프노드가 아닌 경우의 삭제 처리가 복잡합니다.

삭제할 노드가 자식을 양쪽 다 가진 경우.

삭제할 노드가 자식을 한 쪽만 가진 경우.

양쪽 자식을 가진 경우

삭제되는 노드의 오른쪽 하위 트리중에서 가장 작은 값을 삭제되는 노드의 부모에게 연결해줍니다.

왜 삭제되는 노드의 오른쪽 하위 트리가 필요한 걸까?

삭제되는 노드의 오른쪽 하위에 있는 노드들은 삭제된 노드보다 전부 큰 수를 가지고 있습니다.

그 하위 트리 중에서 가장 작은 수를 삭제된 자리에 집어넣는 것입니다.

하위 트리 중에서 가장 작은 수는 그 트리에서 왼쪽 가장 끝에 있을 것 입니다.

외자식을 가진 경우

삭제되는 노드의 자식 트리를 부모에게 연결해줍니다.

삭제 코드
  • 삭제할 타겟을 탐색합니다.
  • 타겟을 찾고 나서, 아래를 수행합니다.
  • 잎 노드인 경우, 삭제하고 끝납니다.
  • 양쪽 자식을 가진 경우, 최소값을 가진 노드를 찾아내서 삭제한 노드 자리에 갖다 놓습니다.
  • 외자식을 가진 경우, 삭제한 노드의 자식을 부모에게 이어줍니다.
Node* RemoveNode(Node* tree, Node* parent, int target){

    // 삭제할 타겟 노드 
    Node* remove_node = NULL;

    // 타겟을 탐색
    if(tree == NULL){
        return NULL;
    }

    if(tree->data < target){
        remove_node = RemoveNode(tree->right, tree, target);
    }else if(tree->data > target){
        remove_node = RemoveNode(tree->left, tree, target);

    }else{ // 타겟 찾음 (3가지 노드 종류에 따라 삭제 수행)

        if(tree->left == NULL && tree->right == NULL){  // 1. 타겟이 잎노드인 경우,

            if(parent->right == tree){
                parent->right = NULL;
            }else{
                parent->left = NULL;
            }

        }else if(tree->left != NULL && tree->right != NULL){ // 2. 양쪽 자식 있는 경우,

            // 오른쪽 자식트리에서 '최소값'가진 노드를 탐색
            Node* minNode = SearchMinNode(tree->right);
            remove_node = RemoveNode(tree, NULL, minNode->data); // 최소값 노드를 제거 
               tree->data = minNode->data; //타겟 자리에 최소값을 갖다놓음    

        }else{ // 3. 외자식 있는 경우, 

            Node* tempTree = NULL;

            if(tree->right != NULL){ // 어느 쪽 자식트리인지 확인 
                tempTree = tree->right;
            }else{
                tempTree = tree->left;
            }

            // 타겟의 자식트리를 자신의 부모에게 이어준다.
            if(parent->right == tree){
                parent->right = tempTree;
            }else{
                parent->left = tempTree;
            }
        }
    }

    return remove_node;
}

양쪽 자식이 있는 경우에서 최소값 노드 찾는 SearchMinNode() 함수

해당 트리 내에 최소값을 찾습니다.

따라서 계속 왼쪽으로만 탐색해서 발견된 값을 리턴하면 됩니다.

Node* SearchMinNode(Node* tree){

    if(tree == NULL){
        return NULL;
    }

    if(tree->left == NULL){ // 자신이 마지막 왼쪽노드면 자신을 리턴!
        return tree;
    }else{
        return SearchMinNode(tree->left);  // 더 왼쪽으로 가본다.
    }

}
728x90

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

버블 정렬  (0) 2020.05.30
균형 이진 탐색 트리 (AVL트리)  (1) 2020.03.30
이진 탐색  (0) 2020.03.18
분리 집합 (유니온파인드) c++  (0) 2020.03.18
수식 이진 트리 (Expression Binary Tree)  (0) 2020.03.11

이진 탐색이란?

 

탐색의 범위를 반으로 줄여나가며 탐색하는 방식입니다. 

이미 정렬된 집합에서 빠르게 탐색할 수 있습니다. 

 

아래는 탐색 과정입니다. 

 

1. 가운데 요소를 고릅니다. (center)

2. 타깃이 가운데 요소보다 작은지, 큰지 비교합니다. 

3. 타깃이 가운데 요소보다 크다면, 가운데 요소 기준으로 오른쪽 집합으로 가서 재탐색합니다.

   (반대 쪽 집합은 탐색에서 제외되는 것입니다.)

   작다면 가운데 요소 기준으로 왼쪽 집합으로 가서 재탐색합니다. 

4. 타깃을 찾을 때 까지 위의 과정을 반복합니다. 

 

2번의 비교과정 후에, 어느 쪽 집합에서 재탐색할 지 정해지면, 탐색 범위가 반씩 줄어드므로 빠르게 탐색할 수 있습니다. 

 

 

이진 탐색의 구현 

 

매개 변수는 3개를 받습니다. 

데이터 집합(배열), 데이터 집합의 크기, 타겟값 입니다. 

 

int BinarySearch(int dataset[], int size, int target) {
	int left, right, center = 0;

	left = 0;
	right = size - 1;

	while (left <= right) { // 탐색 범위가 0 될 때 까지, 
		center = (left + right) / 2;

		if (dataset[center] == target) {
			return dataset[center];
		}
		else if(dataset[center] < target){
			left = center + 1;
		}
		else {
			right = center - 1;
		}
	}

	return NULL;

}

 

728x90

+ Recent posts