본문 바로가기
내일배움캠프/알고리즘

알고리즘 2주차

by 노믹 2022. 11. 23.

배열(Array)

같은 타입의 변수들로 이루어진 유한 집합

 

링크드 리스트(Linked List)

- Node : 데이터와 다음 데이터를 가리키는 주소(포인터)로 이루어져 있다.

- Pointer : 각 노드에서 다음 데이터를 가리키는 주소값을 가진다.

- Head : 링크드리스트에서 가장 시작점인 데이터를 의미한다.

- Tail : 링크드리스트에서 가장 마지막 데이터를 의미

- Next=None(또는 Null) : 다음 데이터가 없을 경우 포인터의 주소값은 None(또는 Null)이다.

 

  • 링크드 리스트의 장단점

1) 장점

 

- 배열은 미리 데이터 공간을 할당해야 하지만 링크드리스트는 미리 할당할 필요가 없다.(유동적으로 데이터 추가,삭제 가능)

-  링크드리스트 수정시 시간복잡도 O(1)을 갖는다. 

     : 항상 일정한 속도

 

2) 단점

 

- 위 구조에서 보듯이 다음 데이터를 연결하기 위해선 별도의 주소 공간을 가져야 한다.

    : 저장공간 효율이 높지 않음

- 배열은 인덱스를 통해 데이터에 접근하므로 시간복잡도 O(1)을 갖지만 링크드리스트의 경우 O(n)을 갖는다. 즉, 연결 된 정보를

찾기 위해 주소를 확인하고 다음 데이터를 탐색하는 시간이 있기 때문에 접근 속도가 느리다.

 -  중간데이터를 삭제시, 앞뒤 데이터를 연결하고 재구성하는 코드가 추가로 필요하다.

 

링크드리스트 파이썬 구현

  • Node 선언
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

# 3을 가진 Node 를 만드려면 아래와 같이 하면 됩니다
node = Node(3) # 현재는 next 가 없이 하나의 노드만 있습니다 [3]
  • Node 연결하기
class LinkedList:
    def __init__(self, value):
        self.head = Node(value)  # head 에 시작하는 Node 를 연결합니다.


linked_list = LinkedList(5)
print(linked_list.head.data) # 5가 출력됩니다!

# 현재 LinkedList 는 (5) 만 존재합니다!
  • Node 삭제
# Node에 데이터 할당 및 next에 None
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


class LinkedList:
    def __init__(self, value):
        self.head = Node(value)

    def append(self, value):
        # cur 에 self.head 할당
        cur = self.head
        # cur.next 뒤에 None이 없을때 cur에 cur.next를 할당하고 cur.next에 Node(value) 할당
        # cur.next에 있던 데이터를 cur에 옮기고 그 cur의 next에 새로운 Node를 할당하는 것
        while cur.next is not None:
            cur = cur.next
        cur.next = Node(value)

    def print_all(self):
        cur = self.head
        # cur이 None이 아닐때 cur.data를 출력하고 cur을 cur.next에 할당
        while cur is not None:
            print(cur.data)
            cur = cur.next

    def get_node(self, index):
        # node 에 head를 할당하고 인덱스보다 count가 작으면 node 다음번으로 넘어가고 count +1
        node = self.head
        count = 0
        while count < index:
            node = node.next
            count += 1
        return node

    def add_node(self, index, data):
        new_node = Node(data)   # [6]
        if index == 0:
            new_node.next = self.head   # [6]
            self.head = new_node        # head -> [6] -> [5]
            return

        node = self.get_node(index - 1) # [5]
        next_node = node.next           # [12]
        node.next = new_node            # [5] -> [6]
        new_node.next = next_node       # [6] -> [12]

    def delete_node(self, index):
        # index가 0이면 self.head에 다음 노드를 할당하고 리턴
        if index == 0:
            self.head = self.head.next
            return
        # node에 get_node(index - 1)을 할당 (기존 node는 지워짐)
        # node.next에 node.next.next를 할당
        node = self.get_node(index - 1)
        node.next = node.next.next


linked_list = LinkedList(5)
linked_list.append(12)
linked_list.append(8)
print(linked_list.get_node(0).data)
linked_list.add_node(1, 6)
linked_list.delete_node(0)
linked_list.print_all()

 

링크드 리스트 문제

  • 두 링크드 리스트 합 계산
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


class LinkedList:
    def __init__(self, value):
        self.head = Node(value)

    def append(self, value):
        cur = self.head
        while cur.next is not None:
            cur = cur.next
        cur.next = Node(value)


def get_linked_list_sum(linked_list_1, linked_list_2):
    sum_1 = _get_linked_list_sum(linked_list_1)
    sum_2 = _get_linked_list_sum(linked_list_2)

    print(sum_1)
    print(sum_2)
    return sum_1 + sum_2


def _get_linked_list_sum(linked_list):
    sum = 0
    head = linked_list.head
    # head가 None이 아닐때까지 sum에 sum *10 + head.data를 하고 head에 다음 Node를 할당
    while head is not None:
        sum = sum * 10 + head.data
        head = head.next
    return sum


linked_list_1 = LinkedList(6)
linked_list_1.append(7)
linked_list_1.append(8)

linked_list_2 = LinkedList(3)
linked_list_2.append(5)
linked_list_2.append(4)

print(get_linked_list_sum(linked_list_1, linked_list_2))

 

이진 탐색

이진 탐색이란 데이터가 정렬돼 있는 배열에서 특정한 값을 찾아내는 알고리즘이다. 배열의 중간에 있는 임의의 값을 선택하여 찾고자 하는 값 X와 비교한다. X가 중간 값보다 작으면 중간 값을 기준으로 좌측의 데이터들을 대상으로, X가 중간값보다 크면 배열의 우측을 대상으로 다시 탐색한다. 동일한 방법으로 다시 중간의 값을 임의로 선택하고 비교한다. 해당 값을 찾을 때까지 이 과정을 반복한다.

 

finding_target = 14
finding_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]


def is_existing_target_number_binary(target, array):
    min = 0
    # array의 길이 - 1
    max = len(array) - 1
    # finding numbers를 반으로 나누기 위해 (min + max) 나누기 2를 하고 소수점 이하의 수를 모두 버린다
    # 시도값 == guess
    guess = (min + max) // 2
    # min이 max 이하일때
    while min <= max:
        # array[guess]가 target과 일치한다면 True
        if array[guess] == target:
            return True
        # array[guess]가 target보다 작다면
        elif array[guess] < target:
            # min에 guess + 1을하고
            min = guess + 1
        else:
            # array[guess]가 target보다 크다면 max에 guess -1을 할당한다
            max = guess - 1
        # 그 후 guess 에 (max+min)의 2를 나누고 소수점 이하를 버린다
        guess = (max + min) // 2
    return False


result = is_existing_target_number_binary(finding_target, finding_numbers)
print(result)

 

재귀 함수

함수 안에서 함수 자기자신을 호출하는 방식을 재귀호출(recursive call)이라고 합니다. 재귀호출은 일반적인 상황에서는 잘 사용하지 않지만 알고리즘을 구현할 때 매우 유용합니다(구현은 만들다와 같은 뜻입니다). 보통 알고리즘에 따라서 반복문으로 구현한 코드보다 재귀호출로 구현한 코드가 좀 더 직관적이고 이해하기 쉬운 경우가 많습니다.

 

  • Q. 60에서 0까지 숫자를 출력하시오.
def count_down(number):
    if number < 0:
        return
    # number를 출력하고
    print(number)          
    # count_down 함수를 number - 1 인자를 주고 다시 호출한다!
    count_down(number - 1) 

count_down(60)
  • 팩토리얼

팩토리얼은 1부터 어떤 양의 정수 n까지의 정수를 모두 곱한 것을 의미한다

def factorial(n):
    # 팩토리얼은 1부터 어떤 양의 정수 n까지의 정수를 모두 곱한 것을 의미한다
    # n 이 1이라면 1을 리턴하고 
    if n == 1:
        return 1
    # 5 * 4 * 3 * 2 * 1
    return n * factorial(n - 1)


print(factorial(5))
  • 회문 검사

똑바로 읽으나 거꾸로 읽으나 똑같은 단어나 문장

  • Q. 다음과 같이 문자열이 입력되었을 때, 회문이라면 True 아니라면 False 를 반환하시오
input = "abcba"


def is_palindrome(string):
    # n에 string의 길이를 할당한다
    n = len(string)
    # n의 길이만큼 i에 할당하고 for문을 돌린다
    for i in range(n):
        # string[i] 가 string[n - 1 - i](끝글자 - i)와 같지 않다면 False를 반환한다
        if string[i] != string[n - 1 - i]:
            return False
    # 그것이 아니라면 True를 반환한다
    return True


print(is_palindrome(input))
  • 회문 검사 - 재귀함수
input = "abcba"


def is_palindrome(string):
    # string의 길이가 1 이하라면 True를 반환한다
    if len(string) <= 1:
        return True
    # string의 0번째 배열이 string의 마지막 배열과 같지않다면 False를 반환한다
    if string[0] != string[-1]:
        return False
    # 반환이 다 되었기 때문에 다시 함수를 불러서 string의 첫번째 배열과 마지막 배열을 비교한다 
    return is_palindrome(string[1:-1])


print(is_palindrome(input))

 

2주차 숙제

  • 링크드 리스트 끝에서 K 번째 값 출력하기
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None


class LinkedList:
    def __init__(self, value):
        self.head = Node(value)

    def append(self, value):
        cur = self.head
        while cur.next is not None:
            cur = cur.next
        cur.next = Node(value)

    def get_kth_node_from_last(self, k):
        length = 1
        cur = self.head

        # cur의 length 를 구하기 위한 함수
        # cur.next 가 None이 아니라면 cur에 cur.next를 할당하고 length에 +1을 한다 그리고 end_length에 length - k를 할당한다
        while cur.next is not None:
            cur = cur.next
            length += 1
        end_length = length - k

        # 그 후 cur 을 end_length 만큼 돌린 후 cur을 리턴한다.
        cur = self.head
        for i in range(end_length):
            cur = cur.next

        return cur

linked_list = LinkedList(6)
linked_list.append(7)
linked_list.append(8)
linked_list.append(9)

print(linked_list.get_kth_node_from_last(1).data)  # 7이 나와야 합니다!
  • 배달의 민족 배달 가능 여부
shop_menus = ["만두", "떡볶이", "오뎅", "사이다", "콜라"]
shop_orders = ["오뎅", "콜라", "만두"]


def is_available_to_order(menus, orders):
    # menus를 집합 자료형으로 변환하여 menus_set에 할당
    menus_set = set(menus)
    
    # order가 menus_set에 없으면 False 다 있다면 True를 반환한다
    for order in orders:
        if order not in menus_set:
            return False
        return True


result = is_available_to_order(shop_menus, shop_orders)
print(result)
  • 더하거나 빼거나
numbers = [1, 1, 1, 1, 1]
target_number = 3
result_count = 0  # target 을 달성할 수 있는 모든 방법의 수를 담기 위한 변수


def get_count_of_ways_to_target_by_doing_plus_or_minus(array, target, current_index, current_sum):
    # curr index가 array의 길이와 같다면
    if current_index == len(array):
        # curr sum이 target과 같다면
        if current_sum == target:
            # result count를 글로벌하게 영향을 줄 수 있게 하고 +1을 한다
            global result_count
            result_count += 1 
        return
    # 다시 함수를 출하되 curr sum + array[curr index]한 함수와 curr sum - array[curr index]한 함수를 호출한다
    get_count_of_ways_to_target_by_doing_plus_or_minus(array, target, current_index + 1,
                                                        current_sum + array[current_index])
    get_count_of_ways_to_target_by_doing_plus_or_minus(array, target, current_index + 1,
                                                        current_sum - array[current_index])


get_count_of_ways_to_target_by_doing_plus_or_minus(numbers, target_number, 0, 0)
# current_index 와 current_sum 에 0, 0을 넣은 이유는 시작하는 총액이 0, 시작 인덱스도 0이니까 그렇습니다!
print(result_count)

'내일배움캠프 > 알고리즘' 카테고리의 다른 글

알고리즘 4주차  (0) 2022.11.28
알고리즘 3주차  (0) 2022.11.24
알고리즘 1주차  (0) 2022.11.23