알고리즘

자료구조 종류1 (연결리스트, 스택, 큐, 해시테이블)

zhelddustmq 2024. 6. 12. 23:16

알고리즘 기초 1

목차

1. 연결리스트

2.스택

3.큐

4.해시테이블

5.힙(다음 글)

 

1. 연결리스트(LINKED-LIST): 노드(값과 노드로 이루어진 데이터)와 노드를 연결해서 저장하는 방식. 주소를 통해 다음 값의 주소를 가리킴.

  • 어레이: 파이썬의 리스트. 접근 쉬움, 삽입 어려움. (파이썬의 리스트)
  • 연결리스트: 직접 구현. 접근 어려움, 삽입 쉬움.

연결리스트 구현

#노드
class ListNode:
    def __init__(self, val=0, next=None):
        #값
        self.val = val
        #주소
        self.next = next

#연결리스트
class LinkedList:
    #head는 제일 처음 넣은 노드를 가리킴
    def __init__(self):
        self.head = None
        
    #리스트에 노드 추가
    def append(self, val):
        #리스트에 아무것도 없으면 노드 삽입
        if not self.head:
            self.head = ListNode(val, None)
            return
        #리스트에 노드들이 있으면 node라는 변수를 만들어 처음 노드를 node에 저장함
        node = self.head
        #node 안의 next값이 없을때까지(마지막) 다음 노드로 찾아감
        while node.next:
            node = node.next
        #node에는 마지막 노드가 있기 때문에 이 뒤에 삽입하려는 노드를 붙여 주소를 None이 아닌 삽입하는 주소를 넣음
        node.next = ListNode(val, None)

 

2.스택: LIFO(Last in First Out)자료 구조, 데이터가 순서대로 쌓이면 찾을때는 늦게 쌓인 순서대로 데이터 접근.

             스택이 사용되는 예: 컴퓨터의 되돌리기 기능(Ctrl + Z)

 

스택 구현

#스택에 쌓을 데이터(노드)
class Node:
    def __init__(self, val = 0, next = None):
        self.val = val
        self.next = next


class Stack:
    #top은 제일 위에 뭐가 있는지 가리키는 변수
    def __init__(self):
        self.top = None

    #스택에 데이터를 넣는 함수
    def push(self, val):
        #노드를 하나 생성 후
        node = Node(val, None)
        #노드의 next를 제일 꼭대기였던 노드를 가리킴
        node.next = self.top
        #self.top을 생성한 노드로 바꾸면 쌓은것처럼 됨
        self.top = node
        #한줄로는 self.top = Node(val, self.top)

    def pop(self):
        #스택에 아무것도 없으면
        if not self.top:
            return None
        #빈 노드 만들어서 제일 위에꺼 저장
        node = self.top
        #스택의 탑을 맨 위의 노드 바로 다음꺼로 바꿈
        self.top = node.next
        #원래 탑이었던 노드의 값을 반환
        return node.val

 

 

3.큐: FIFO(First In First Out)자료구조, 데이터가 쌓이고 찾을 때 처음 들어간 데이터부터 나옴.

         큐가 사용되는 예: 주문이 들어왔을 때 먼저 들어온 순서대로 처리해야 할 때

 

큐 구현

class Node:
    def __init__(self, val = 0, next = None):
        self.val = val
        self.next = next


class Queue:
    #front는 항상 앞에있는 노드를 뜻함(스택의 top과 정반대)
    def __init__(self):
        self.front = None

    #스택에 노드를 추가하는 함수
    def push(self, val):
        #스택에 아무것도 없으면 그대로 넣기
        if not self.front:
            self.front = Node(val)
            return
        #스택에 뭔가 있다면 node라는 빈 변수에 제일 앞에걸 넣고 node에 마지막 노드를 넣을때까지 다음거를 넣음
        node = self.front
        while node.next:
            node = node.next

        #마지막에 도달하면 넣기
        node.next = Node(val)

    def pop(self):
        #비어있으면 꺼낼게 없음
        if not self.front:
            return None
        #빈노드 node에 처음꺼를 넣고 그 다음꺼를 front에 넣은 후 node값 출력
        node = self.front
        self.front = node.next
        return node.val

    def is_empty(self):
        return self.front is None

 

 

4. 해시테이블: 해시함수를 이용해 키를 값에 매핑하는 자료구조

 

- 해시 함수: 임의 크기 데이터를 고정 크기 값으로 매핑하는데 사용할 수 있는 함수, 임의의 값을 넣어도 예상 크기 내에서 결과가 나오는 함수(딕셔너리와 유사)

     해시함수 예: 나머지를 반환하는 함수

def modThree(n):
    return n % 3

print(modThree(0)) # 0
print(modThree(1)) # 1
print(modThree(2)) # 2
print(modThree(0)) # 0

위의 함수에서 n이 1일 때나 4일 때, 둘의 반환 값은 똑같이 1이고, 만약 이 반환 값을 키로 쓴다면 1일때나 4일때 구분을 못한다. 이것을 충돌이라고 한다.

 

이를 위해 체이닝(Chaining) 혹은 오픈 어드레싱(Open Addressing)을 이용해 충돌을 없앤다.

 

-체이닝(Chaining): 충돌 발생 시 ‘연결’해가는 것. 연결리스트를 사용

15가 1에 저장했는데, 8이란 값이 또 1에 저장하고 싶을 때, 15뒤에 연결리스트로 8을 연결함

 

- 오픈 어드레싱(Open Addressing): 충돌 발생 시 

John Smith를 873에 저장하고, Sandra Dee가 저장할 때, 이미 John Smith가 저장되어 있어 뒤에 빈공간을 순차적으로 찾아서 저장


 

  • 오픈 어드레싱은 값이 뭉치는 클러스터링이 발생할 수 있고, 체이닝은 메모리 오버헤드가 길어질 경우 탐색이 느려진다는 단점이 있음.
  • 각 언어 별로 해시테이블의 구현 방식: C++, Java, Go: 체이닝 / Python, Ruby: 오픈 어드레싱

해시테이블 구현(modular(10))

"""체이닝 방식의 해시테이블"""

class HashNode:
    #딕셔너리처럼 key와 value가 있고, 다음노드를 담을수 있는 next가 있음
    def __init__(self, key=None, value=None):
        self.key = key
        self.value = value
        self.next = None

#modular연산(나머지 연산) 10으로 나눈 나머지만 해싱함.
class HashTable:
    def __init__(self):
        #10으로 나눈 나머지만 저장하기 때문에 크기는 10이면 됨
        self.size = 10
        #해시테이블에 10개만큼 빈 공간을 만듬
        self.table = [None] * self.size

    #key값을 받으면 10으로 나누어 나머지 반환하는 함수
    def _hash_function(self, key):
        return key % self.size

    #해시테이블에 해시노드 추가하는 함수
    def put(self, key, value):
        #idx는 해시테이블에 인덱스를 담음
        idx = self._hash_function(key)

        #만약 해시테이블 안에서 idx번째의 공간에 아직 데이터가 없다면 그자리에 넣고 아니면 연결리스트처럼 진행함
        #연결리스트는 위에 참고
        if self.table[idx] is None:
            self.table[idx] = HashNode(key, value)
        else:
            node = self.table[idx]
            while node.next:
                node = node.next
            node.next = HashNode(key,value)

    #key로 값 찾기
    def get(self, key):
        #해시테이블의 몇번째가 idx에 담김
        idx = self._hash_function(key)
        #node라는 빈 변수에 idx번째의 맨처음 값을 넣음
        node = self.table[idx]
        #노드의 값이 None일 때까지
        while node is not None:
            #node의 키랑 찾으려는 key랑 같다면 그 노드의 value를 반환
            #첫번째값이 key값이랑 같을수 있기 때문에 아래 if문이 node = node.next문보다 위에 있어야함.
            if node.key == key:
                return node.value
            node = node.next
        #다 뒤져봤는데도 없다면 -1 반환
        return -1

    #해시테이블에서 key값을 찾아 그 노드를 제거하는 함수
    #제거하는 방법은 제거하려는 노드의 앞뒤로 연결해 고립시키면 제거된것처럼 됨
    def remove(self,key):
        #해시테이블의 몇번째가 idx에 담김
        idx = self._hash_function(key)
        #node라는 빈 변수에 idx번째의 맨처음 값을 넣음(제거하려는 노드가 될 변수)
        node = self.table[idx]
        #제거하려는 노드 앞의 노드
        prev_node = None

        while node:
            if node.key == key:
                #이전 노드가 존재하면
                if prev_node:
                   prev_node.next = node.next
                #이전 노드가 존재하지 않는다면(제거하려는 노드가 처음에 넣은 노드라면)
                else:
                    self.table[idx] = node.next
                return
            prev_node = node
            node = node.next

 

 

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

나만의 필수암기노트

 

파이썬 deque: Queue랑 Stack이 둘다 되게끔 지원해주는 라이브러리.  deque([리스트])로 구현함.

deque 사용 예제

def test_problem_queue(num):
	#deque(리스트)형식으로 만듦
    deq = deque([i for i in range(1, num + 1)])
    while len(deq) > 1:
        deq.popleft()
        deq.append(deq.popleft())
    return deq.popleft()