꼭 알아두어야 할 자료구조: 링크드 리스트(Linked List)

45823 단어 algorithmalgorithm

대표적인 데이터 구조 - 링크드 리스트(Linked List)

링크드 리스트는 스택, 큐와 다르게 복잡함. 구조 자체는 간단하지만 이 구조를 구현을 할 때 복잡함.

1. 링크드 리스트(Linked List)구조

  • 연결 리스트라고도 함
  • 배열은 순차적으로 연결된 공간에 데이터를 나열하는 데이터 구조
  • 링크드 리스트는 떨어진 곳에 존재하는 데이터를 화살표로 연결해서 관리하는 데이터 구조
  • 본래 C언어에서는 주요한 데이터 구조이지만, 파이썬은 리스트 타입이 링크드 리스트의 기능을 모두 지원!

링크드 리스트는 배열과 달리 특정한 데이터를 저장을 할 때 해당 데이터를 저장하는 공간과 함께 뒤에 다음 데이터가 저장되어있는 주소공간을 하나의 데이터로 관리함.
👉 즉, 배열은 데이터만 저장, 링크드 리스트는 데이터+ (다음데이터)주소 저장

1-1. 링크드 리스트 기본 구조와 용어

  • 노드(Node): 데이터 저장 단위(데이터 값, 포인터)로 구성
  • 포인터(Pointer): 각 노드 안에서, 다음이나 이전의 노드와의 연결 정보를 가지고 있는 공간

2. 간단한 링크드 리스트 예

링크드 리스트를 파이썬으로 구현하려면 객체지향 문법을 알고 있어야 함

[1] Node 구현

예시 1)

class Node
	def _init_(self, data):
            self.data = data
            self.next = None

예시 2)

class Node
	def _init_(self, data, next=Node):
            self.data = data
            self.next = next

[2] Node와 Node 연결하기(포인터 활용)

node1 = Node(1)
node2 = Node(2)
node1.next = node2
head = node1
# 가장 먼저 있는 node를 head로 만듬

[3] 링크드 리스트로 데이터 추가하기

class Node:
	def _init_(self, data, next=Node):
    	self.data = data
        self.next = next

def add(data):
	node = head
    # head라는 변수에다가 맨 앞에 있는 node의 값을 저장하기로 위에서 약속함
    # 그래서 add라는 함수에는 항상 그 head의 값을 가지고 온다
    # 링크드 리스트에서 데이터가 저장될 때에는 맨 끝에 저장되므로 
    # 맨끝에 연결되어있는 노드를 찾아가기 위해서 node.next가 있는지 확인
    while node.next:
    	# node.next가 있다는 건 다음 노드를 가리키는 주소가 있다는 것
        node = node.next
   node.next = Node(data)
   
# --- test ---
# 맨 처음 Linked List의 노드를 만들어놓고, 
node1 = Node(1)
# 그 노드를 head 즉, 맨 앞에 변수에 저장을 함
head = node1
# 2부터 9까지 총 10개 마지막 숫자는 -1
for index in range(2, 10):
# 2부터 9까지 add함수 호출
	add(index)

[4] 링크드 리스트 데이터 출력하기(검색하기)

node = head
while node.next:
	print(node.data)
    node = node.next
print(node.data)

# --- result ---
# 1 -> node1 = Node(1)이였기에 1출력
# 2 -> 2~9까지는 add(index)를 통해 연결
# 3
# 4
# 5
# 6
# 7
# 8
# 9

3. 링크드 리스트의 장단점(전통적인 C언어에서의 배열과 링크드 리스트)

✦ 장점

  • 미리 데이터 공간을 할당하지 않아도 됨
  • 배열은 미리 공간을 할당해야함

✦ 단점

  • 연결을 위한 별도 데이터 공간이 필요하므로, 저장공간 효율이 높지 않음
  • 연결 정보를 찾는 시간이 필요하므로 접근 속도가 느림
  • 중간 데이터 삭제 시, 앞 뒤 데이터의 연결을 재구성하는 부가적인 작업 필요

4. 링크드 리스트의 복잡한 기능 1 (링크드 리스트 데이터 사이에 데이터를 추가)

  • 링크드 리스트는 유지 관리에 부가적인 구현이 필요함
node = head
while node.next:
	print(node.data)
    node = node.next
print(node.data)

# --- result ---
# 1 
# 2 
# 3
# 4
# 5
# 6
# 7
# 8
# 9
node3 = Node(1.5)
# 1과 2사이에 넣기
node = head
search = True
while search:
	if node.data == 1:
    	   search = False
    	else:
    	   node = node.next

node_next = node.next
node.next = node3
node3.next = node_next
# --------- 출력--------- 
node = head
while node.next:
	print(node.data)
    node = node.next
print(node.data)

# --- result ---
# 1 
# 1.5
# 2 
# 3
# 4
# 5
# 6
# 7
# 8
# 9

5. 파이썬 객체지향 프로그래밍으로 링크드 리스트 구현하기

# 각각의 노드 생성 클래스
class Node:
	def __init__(self, data, next=None):
    	self.data = data
        self.next = next
        
# 연결된 노드 관리 클래스    
class NodeMgmt:
	def __init__(self, data):
    	self.head = Node(data)
        
	# 항상 링크드 리스트 맨 끝에 추가로 노드를 추가하는 함수
	def add(self, data):
          if self.data == ":
              self.head = Node(data)
          else:
          	node = self.head
            while node.next:
            	node = node.next
            node.next = Node(data)
            
    
    # 해당 링크드 리스트 전체 데이터를 출력할 수 있는 함수
    def desc(self):
    	node = self.head
        while node:
        	print(node.data)
            node = node.next

구현된 리스트를 기반으로 링크드 리스트 코드 만들어보기

linkedlist1 = NodeMgmt(0)
linkedlist1.desc()
# 0
for data in range(1,10):
# 1부터 9(10-1)까지
	linkedlist1.add(data)
linkedlist1.desc()
# 0
# 1 
# 2 
# 3
# 4
# 5
# 6
# 7
# 8
# 9

링크드 리스트의 복잡한 기능2(특정 노드를 삭제)

  1. head 삭제
  2. 마지막 노드 삭제
  3. 중간 노드 삭제
  • 삭제는 delete함수
# 각각의 노드 생성 클래스
class Node:
	def __init__(self, data, next=None):
    	self.data = data
        self.next = next
        
# 연결된 노드 관리 클래스    
class NodeMgmt:
	def __init__(self, data):
    	self.head = Node(data)
        
	# 항상 링크드 리스트 맨 끝에 추가로 노드를 추가하는 함수
	def add(self, data):
          if self.data == ":
              self.head = Node(data)
          else:
          	node = self.head
            while node.next:
            	node = node.next
            node.next = Node(data)
            
    
    # 해당 링크드 리스트 전체 데이터를 출력할 수 있는 함수
    def desc(self):
    	node = self.head
        while node:
        	print(node.data)
            node = node.next
   # 삭제하는 함수
   def delete(self, data):
   	if self.head == ":
          print("해당 값을 가진 노드가 없습니다")
          return
        # 1. head 삭제
      	if self.head.data == data:
        	temp = self.head
            	self.head = self.head.next
                del temp
        # 2. 마지막 노드 삭제 & 3. 중간 노드 삭제 
        else:
        	node = self.head
            	while node.next:
            		if node.next.data == data:
                    		temp = node.next
                            	node.next = node.next.next
                            	del temp

테스트를 위해 1개 노드 만들어 보기

linkedlist1 = NodeMgmt(0)
linkedlist.desc()
# -> 0

head가 살아있는지 확인

linkedlist1.head
# -> <__main__.Node at 0x1099fc6a0>

head를 삭제(위에서 언급한 경우의 수 첫번째)

linkedlist1.delete(0)

코드 실행 시 값이 나오지 않는다면 head가 정상적으로 삭제됨

linkedlist1.head
# -> 

다시 하나의 노드를 만들어 보기

linkedlist1 = NodeMgmt(0)
linkedlist.desc()
# -> 0

여러 노드를 더 추가

for data in range(1,10):
	linkedlist1.add(data)
    	linkedlist1.desc()
# 0
# 1 
# 2 
# 3
# 4
# 5
# 6
# 7
# 8
# 9

노드 중 한개를 삭제함(중간 노드 삭제)

linkedlist1.delete(4)

특정 노드 삭제 결과 확인

linkedlist1.desc()
# 0
# 1 
# 2 
# 3
# 5
# 6
# 7
# 8
# 9

7. 다양한 링크드 리스트 구조

  • 더블 링크드 리스트(Doubly linked list)기본 구조
  • 이중 연결 리스트라고도 함
  • 장점) 양방향으로 연결되어 있어서 노드 탐색이 양쪽으로 모두 가능
class Node:
	def __init__(self, data, prev=None, next=None):
              self.prev = prev
              self.data = data
              self.next = next
            
class NodeMgmt:
	def __init__(self, data):
    	self.head = Node(data)
        self.tail = self.head
        
   def insert(self, data):
   		if self.head == None:
        		self.head = Node(data)
            		self.tail = self.head
        	else:
            		node = self.head
                    	while node.next:
                        	# 노드의 끝을 찾아가기 위해서
                    		node = node.next
                        new = Node(data)
                        node.next = new
                        # 앞의 데이터 주소를 넣기
                        new.prev = node
                        # 새로운 데이터 값 주소 넣기
                        self.tail = new

	def desc(self):
          node = self.head
          while node:
            print(node.data)
            node = node.next
            
 # ----- 실행 ------
 double_linked_list = NodeMgmt(0)
 for data in range(1,10):
 	double_linked_list.insert(data)
 double_linked_list.desc()
# 0
# 1 
# 2 
# 3
# 5
# 6
# 7
# 8
# 9

8. 문제 풀기

연습 3 : 위 코드에서 노드 데이터가 특정 숫자인 노드 앞에 데이터를 추가하는 함수를 만들고, 테스트해보기
☑️ 더블 링크드 리스트의 tail에서부터 뒤로 이동하며, 특정 숫자인 노드를 찾는 방식으로 함수를 구현하기
☑️ 테스트: 임의로 0~9 데이터를 링크드 리스트에 넣어보고, 데이터 값이 2인 노드 앞에 1.5 데이터 값을 가진 노드를 추가해보기

class Node:
	def __init__(self, data, prev=None, next=None):
              self.prev = prev
              self.data = data
              self.next = next
            
class NodeMgmt:
	def __init__(self, data):
    	self.head = Node(data)
        self.tail = self.head
        
   def insert(self, data):
   		if self.head == None:
        		self.head = Node(data)
            		self.tail = self.head
        	else:
            		node = self.head
                    	while node.next:
                        	# 노드의 끝을 찾아가기 위해서
                    		node = node.next
                        new = Node(data)
                        node.next = new
                        # 앞의 데이터 주소를 넣기
                        new.prev = node
                        # 새로운 데이터 값 주소 넣기
                        self.tail = new

    def desc(self):
        node = self.head
        while node:
            print(node.data)
            node = node.next
            
    # 검색 코드 만들기
    # 앞에서 검색
    def search_from_head(self, data):
    	if self.node = None:
        	return False
            
    	node = self.head
        while node:
        	if node.data == data:
            		return node
            	else:
                	node = node.next
        return False
        
     # 뒤에서 검색
    def search_from_tail(self, data):
    	if self.node = None:
        	return False
            
    	node = self.tail
        while node:
        	if node.data == data:
            		return node
            	else:
                	node = node.prev
        return False
        
     # 노드 데이터가 특정 숫자인 노드 앞에 데이터를 추가하는 함수
     def insert_before(self, data, before_data):
     	if self.head == None:
        	self.head = Node(data)
            	return True # 데이터가 넣어진지 아닌지 판별
        else:
        	node = self.tail
            while node.data != before_data:
            	node = node.prev
                if node == None:
                	return False
            new = Node(data)
            before_new = node.prev
            before_new.next = new
            new.prev = before_new
            new.next = node
            return True
 # ----- 테스트 ------
 double_linked_list = NodeMgmt(0)
 for data in range(1,10):
 	double_linked_list.insert(data)
 double_linked_list.desc()
# 0
# 1 
# 2 
# 3
# 5
# 6
# 7
# 8
# 9

node_3 = double_linked_list.search_from_head(3)
node_3.data
# 3

double_linked_list.insert_before(1.5, 2)
double_linked_list.desc()
# 0
# 1 
# 1.5
# 2 
# 3
# 5
# 6
# 7
# 8
# 9

좋은 웹페이지 즐겨찾기