본문 바로가기

Python/알고리즘 (Algorithm)

7-2-2. 큐, 우선순위 큐, 힙, 연결 리스트 예제 (Other Types Examples): 파이썬 자료구조와 알고리즘

데크와 회문

데크를 사용하면 문장이 회문인지 쉽게 확인할 수 있다.

 

내 정답:

 

from deque import Deque

def check_palindrome(string):
    d = Deque()
    for i in string:
        d.enqueue_back(i)
    
    h_leng = d.size() // 2
    for i in range(h_leng):
        if d.dequeue() == d.dequeue_front():
            continue
        else:
            return False
    return True

palindrome = '다시합창합시다'
not_palindrome = '안녕하세요'
print(check_palindrome(palindrome))
print(check_palindrome(not_palindrome))

 

책의 정답:

 

STRIP = string.whitespace + string.punctuation + "\"'"
def check_palindrome_answer_book(str1):
    d = Deque()

    for s in str1.lower():
        if s not in STRIP:
            d.enqueue(s)
    
    eq = True
    while d.size() > 1 and eq:
        if d.dequeue_front() != d.dequeue():
            eq = False

    return eq

 

책은 띄어쓰기와 따옴표 모두 신경써 주는 듯 하다. 따라서 내 코드도 다음과 같이 바꿔주었다.

 

def check_palindrome(string):
    temp = string.lower()
    d = Deque()
    for i in temp:
        if not i in " '\"":
            d.enqueue_back(i)
        else:
            continue
    
    h_leng = d.size() // 2
    for i in range(h_leng):
        if d.dequeue() == d.dequeue_front():
            continue
        else:
            return False
    return True

 

결과

 

PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/7. 추상 데이터 타입/eg_queue_palindrome_check.py"
True
False

 

큐와 동물 보호소

개와 고양이를 입양했다가 다시 출양하는 동물 보호소를 큐로 구현해 보자. 동물 보호소는 개와 고양이를 지정하여 입출양할 수 있어야 한다.

 

from queue import Queue

class AnimalShelter(object):
    def __init__(self):
        DogQ = Queue()
        CatQ = Queue()
        self.set = [DogQ, CatQ]
    
    def size(self):
        return (self.set[0].size(), self.set[1].size())

    def enqueue_dog(self, name):
        self.set[0].enqueue(name)
    def dequeue_dog(self):
        value = self.set[0].dequeue()
        if value is not None:
            return value
        else:
            print("Got no Dog...")
    
    def enqueue_cat(self, name):
        self.set[1].enqueue(name)
    def dequeue_cat(self):
        value = self.set[1].dequeue()
        if value is not None:
            return value
        else:
            print("Got no Cat...")

    def peek_dog(self):
        value = self.set[0].peek()
        if value is not None:
            return value
        else:
            print("Got no Dog...")
    def peek_cat(self):
        value = self.set[1].peek()
        if value is not None:
            return value
        else:
            print("Got no Cat...")

    def __repr__(self):
        return repr(self.set)

s = AnimalShelter()
s.enqueue_cat('Kitty')
s.enqueue_cat('Nabi')
s.dequeue_cat()
s.enqueue_dog('Dangdang')
s.dequeue_dog()
s.dequeue_dog()
print(s)

 

결과

 

PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/7. 추상 데이터 타입/eg_queue_animal_shelter.py"
Queue is empty.
Got no Dog...
[[], ['Nabi']]

 

연결 리스트

끝에서 k번째 항목 찾기

연결 리스트의 끝에서 k번째 항목을 찾아보자.

 

책의 정답:

 

from linkedListFIFO import LinkedListFIFO
from node import Node

class KthFromLast(LinkedListFIFO):
    def find_kth_to_last(self, k):
        p1, p2 = self.head, self.head
        i = 0
        while p1:
            if i > k-1:
                try:
                    p2 = p2.pointer
                except AttributeError:
                    break
            p1 = p1.pointer
            i += 1
        return p2.value

k = KthFromLast()
for i in range(1, 11):
    k.addNode(i)
k._printList()

k_from_last = k.find_kth_to_last(3)

print(k_from_last)

 

결과

 

PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/7. 추상 데이터 타입/eg_find_kth_from_the_end.py"
a
b
1 2 3 4 5 6 7 8 9 10
8

 

연결 리스트 분할하기

숫자가 담긴 연결 리스트에서 한 항목을 선택했을 때, 그 항목 값의 왼쪽에는 작은 숫자 항목만 나오고 오른쪽에는 큰 숫자 항목만 나오도록 연결 리스트를 분할해보자. 즉, 예를 들어 연결리스트가 다음과 같고 6을 기준으로 나눈다면,

 

분할 전

6 7 3 4 9 5 1 2 8

 

분할 후

3 4 5 1 2 6 2 9 8

 

이와 같이 되야한다.

 

내 정답:

 

from linkedListFIFO import LinkedListFIFO

def part_linked_list(ll, n):
    result_ll = LinkedListFIFO()
    ll_temp = ll
    
    node = ll_temp.head
    i = 0
    while node:
        if node.value < n:
            result_ll.addNode(node.value)
            ll_temp.deleteNode(i)
        elif node.value == n:
            ll_temp.deleteNode(i)
        else:
            i += 1
        node = node.pointer
    
    result_ll.addNode(n)

    node = ll_temp.head
    while node:
        result_ll.addNode(node.value)
        node = node.pointer
    
    return result_ll


ll_test = LinkedListFIFO()
for i in [4,7,2,9,8,5,2,4,1]:
    ll_test.addNode(i)

part_linked_list(ll_test, 5)._printList()

 

책의 정답:

 

def part_linked_list_answer_book(ll, n):
    more = LinkedListFIFO()
    less = LinkedListFIFO()

    node = ll.head
    while node:
        item = node.value
        if item < n:
            less.addNode(item)
        elif item > n:
            more.addNode(item)
        
        node = node.pointer
    
    less.addNode(n)
    nodemore = mode.head
    while nodemore:
        less.addNode(nodemore.value)
        nodemore = nodemore.pointer
    return less

 

결과

 

PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/7. 추상 데이터 타입/eg_ll_part_linked_list.py"
4 2 2 4 1 5 7 9 8 

 

기본적으로 코드를 들여다 봤을 때 메커니즘 자체는 비슷하지만 내 풀이 방식은 인덱스를 더했기 때문에 좀 더 보기 어렵고 난해할 수 있다. 책의 코드가 훨씬 직관적이고 알아보기 쉽다.

 

이중 연결 리스트와 FIFO

linkedListFIFO 코드를 사용하여 이중 연결 리스트를 구현해보자. 우리가 배운 연결 리스트는 단일 연결 리스트 (singly linked list) 로써, 각 노드의 포인터 하나가 다음 노드를 가리키지만, 이중 연결 리스트 (doubly linked list) 에서는 포인터가 두 개 있어 하나는 앞 노드를, 하나는 뒤 노드를 가리킨다. Node가 단일 포인터 노드를 쓰는 대신, 앞 노드 (previous) 와 뒤 노드 (pointer) 모두를 가리키도록 만들면 된다.

 

회문 확인하기

앞에서는 데크를 사용하여 회문 여부를 확인했다. 이번에는 연결 리스트가 회문인지 확인해보는 코드를 작성해보자.

 

내 정답:

 

from linkedListFIFO import LinkedListFIFO

def ll_check_palindrome(string):
    str_temp = ''
    for i in string.lower():
        if not i in ' \'"':
            str_temp += i
        else:
            continue
    
    mid_i = len(str_temp) // 2
    from_fwd_ll = LinkedListFIFO()
    from_back_ll = LinkedListFIFO()

    for i in range(mid_i):
        from_fwd_ll.addNode(str_temp[i])
        from_back_ll.addNode(str_temp[-1-i])
    
    fwd_node = from_fwd_ll.head
    back_node = from_back_ll.head
    while fwd_node and back_node:
        if fwd_node.value == back_node.value:
            fwd_node = fwd_node.pointer
            back_node = back_node.pointer
        else:
            return False
    return True

palindrome = 'Madam I\'m Adam'
not_palindrome = 'Hello World!'

print(ll_check_palindrome(palindrome))
print(ll_check_palindrome(not_palindrome))

 

책의 정답

 

def isPal(l1):
    if len(l1) < 2:
        return True
    if l1[0] != l1[-1]:
        return False
    return isPal(l1[1:-1])

def checkllPal(ll):
    node = ll.head
    l = []
    while node is not None:
        l.append(node.value)
        node = node.pointer
    return isPal(l)

 

결과

 

PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/7. 추상 데이터 타입/eg_ll_check_palindrome.py"
True
False

 

책에서는 위와 같이 리스트로 취급을 하여 리스트의 첫 아이템과 마지막 아이템이 일치하는지, 일치 한다면 1과 -1 인덱스 안에 있는 리스트를 사용해 재귀함수를 돌리는 방식으로 하였다. 그런데 개인적으로 이 경우 굳이 연결 리스트를 써야하는지 의문이 있다. 그냥 리스트 그대로 넣어도 동작이 될텐데, 아무튼 책에선 이렇게 풀었다.

 

두 연결 리스트의 숫자 더하기

연결 리스트의 각 항목은 양의 정수라고 가정하자. 한 연결 리스트의 항목으로 예를 들어 1, 7, 6, 2가 추가되었다면 이 연결 리스트의 숫자는 2671이다. 두 연결 리스트의 숫자를 더하여 숫자 결과를 출력하는 코드를 작성해보자. 

 

내 정답:

 

from linkedListFIFO import LinkedListFIFO

def sum_linked_list(ll1, ll2):
    # 연결 리스트: 숫자 2671의 경우 1 7 6 2 순으로 되어있다.
    ll_result = LinkedListFIFO()

    node_1 = ll1.head
    node_2 = ll2.head

    def node_val_or_zero(node):
        if not node is None and node.value:
            return node.value
        else:
            return 0
    def node_pointer_or_None(node):
        if not node is None and node.pointer:
            return node.pointer
        else:
            return None

    pending = 0
    while node_1 or node_2:
        sum_val = node_val_or_zero(node_1) + node_val_or_zero(node_2) + pending
        pending = 0
        if sum_val < 10:
            ll_result.addNode(sum_val)
        else:
            ll_result.addNode(sum_val - 10)
            pending += 1
        node_1 = node_pointer_or_None(node_1)
        node_2 = node_pointer_or_None(node_2)
    
    return ll_result

list_2671 = [1, 7, 6, 2]
list_355 = [5, 5, 3]

ll_2671 = LinkedListFIFO()
ll_355 = LinkedListFIFO()
for i in list_2671:
    ll_2671.addNode(i)
for i in list_355:
    ll_355.addNode(i)

ll_result = sum_linked_list(ll_2671, ll_355)
ll_result._printList()

 

결과:

 

PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/7. 추상 데이터 타입/eg_ll_sum_linked_list.py"
6 2 0 3 

 

2671 + 355 = 3026이고, 결과값 연결리스트는 6 2 0 3 (3026) 이 나왔으니 일단은 성공이다. 다만 살짝 지저분해졌다. 책의 정답을 보자.

 

class LinkedListFIFOYield(LinkedListFIFO):
    def _printList(self):
        node = self.head
        while node:
            yield node.value
            node = node.pointer

def sumlls(l1, l2):
    lsum = LinkedListFIFOYield()
    dig1 = l1.head
    dig2 = l2.head
    pointer = 0

    while dig1 and dig2:
        d1 = dig1.value
        d2 = dig2.value
        sum_d = d1 + d2 + pointer
        if sum_d > 9:
            pointer = sum_d // 10
            lsum.addNode(sum_d % 10)
        else:
            lsum.addNode(sum_d)
            pointer = 0
        dig1 = dig1.pointer
        dig2 = dig2.pointer
    
    if dig1:
        sum_d = pointer + dig1.value
        if sum_d > 9:
            lsum.addNode(sum_d % 10)
        else:
            lsum.addNode(sum_d)
        dig1 = dig1.pointer
    
    if dig2:
        sum_d = pointer + dig2.value
        if sum_d > 9:
            lsum.addNode(sum_d % 10)
        else:
            lsum.addNode(sum_d)
        dig2 = dig2.pointer
    
    return lsum

l1 = LinkedListFIFOYield()
l2 = LinkedListFIFOYield()

l1.addNode(1)
l1.addNode(7)
l1.addNode(6)
l1.addNode(2)

l2.addNode(5)
l2.addNode(5)
l2.addNode(3)

lsum = sumlls(l1, l2)
l = list(lsum._printList())
for i in reversed(l):
    print(i, end="")
print()

 

결과

 

3026

 

책의 정답은 나와 달리, 둘 중 하나라도 노드의 값이 존재하지 않게 되면 (즉 자릿수가 맞는 계산들이 끝나면) 자릿수가 남아있는 연결 리스트의 노드를 가져와 계속 계산한다. 내 계산법은 노드가 존재 하지 않으면 (즉 나는 예를 들어 455라는 수를 0455로 변환한 것처럼 계산을 했던 것이다) 그 자리를 0으로 간주하고 계산을 계속했었다. 따라서 비효율적일 수 있는 방법이다.

 

원형 연결 리스트 찾기

헤드와 테일이 연결된 연결 리스트를 원형 연결 리스트 (circular linked list) 라고 한다. 어떤 연결 리스트가 원형 연결 리스트인지 여부를 확인하는 함수를 구현해보자. 두 포인터를 사용하면 원형 연결 리스트의 여부를 확인할 수 있다. 내 짧은 지식으론 이게 어떻게 구현 가능한지 가늠이 안간다. 책의 정답을 보자.

 

from linkedListFIFO import LinkedListFIFO
from node import Node

class CircularLinkedListFIFO(LinkedListFIFO):
    def _add(self, value):
        self.length += 1
        node = Node(value, self.head)
        if self.tail:
            self.tail.pointer = node
        self.tail = node

def isCircularll(ll):
    p1 = ll.head
    p2 = ll.head

    while p2:
        try:
            p1 = p1.pointer
            p2 = p2.pointer.pointer
        except:
            break
        
        if p1 == p2:
            return True
    return False

ll = LinkedListFIFO()
for i in range(10):
    ll.addNode(i)
print(isCircularll(ll))

lcirc = CircularLinkedListFIFO()
for i in range(10):
    lcirc.addNode(i)
print(isCircularll(lcirc))

 

결과

 

PS C:\Users\bumsu\Desktop\Python\DSA> & C:/Python38/python3.exe "c:/Users/bumsu/Desktop/Python/DSA/Algorithm/7. 추상 데이터 타입/eg_ll_circular_linked_list.py"
False
True

 

원형 연결 리스트는 위와 같이 구현되었다.