자료구조

[2/24 출근 전 공부하는 파이썬 기초 자료구조, 알고리즘] 배열

여러가지 공부를 하고 있습니다. 2025. 2. 24. 22:40

 

 

배열 Array

 

- 삽입/삭제 : O(N)

- 탐색: O(1)

- C++에서는 size 변경 불가

- Python은 리스트를 사용 -> 담는 요소를 다양하게 담을 수도 있고 사이즈도 다양하게 변경이 가능함 

 

 

c++
int arr[4] = (10, 11, 12, 13);
arr[2] = 5; 


#Python
arr = [10, 11, 12, 13]
arr[2] = 5

 

 

 

 

arr[2] 라는 코드를 쓰면 arr 의 시작 인덱스가 있을거고 + (2 * 4byte) = 메모리주소값으로 계산해서 점프하게 됨 

 

배열의 주소 계산 공식 (주소 = 배열 시작 주소 + (인덱스 * 자료형 크기)

 

 

한번에 메모리 주소값이 어디있는지 한번에 계산할 수 있기 때문에 탐색 속도가 굉장히 빠름 그래서 O(1) 임 이걸 임의 접근 이라고 함 

 

📌 💡 왜 O(1)인가?배열의 시작 주소와 인덱스만 알면, 연산 한 번으로 바로 해당 요소에 접근 가능!
반복적인 탐색이 필요하지 않음 → 탐색 속도가 일정함 (O(1))

 

배열은 메모리 공간에 연속적으로 할당되므로 임의 접근이 가능해서 그래서 탐색 속도가 빠름 

 

arr[0] 10 0x1000
arr[1] 20 0x1004
arr[2] 30 0x1008
arr[3] 40 0x100C
arr[4] 50 0x1010

 

 

➡️ 각 요소가 연속된 메모리 공간을 차지하고 있음
➡️ 배열의 시작 주소(arr = 0x1000)를 알면, 원하는 위치를 바로 계산할 수 있음

 

✅ 만약 데이터를 삽입하려면?

 

 

1. 배열에서 삽입 연산의 시간 복잡도 (O(N))

만약 배열의 중간에 값을 삽입하면, 기존 요소들을 오른쪽으로 한 칸씩 이동해야 함.
즉, 최악의 경우 모든 요소를 이동해야 하므로 O(N)의 시간 복잡도를 가짐

 

예제: 인덱스 2에 6 삽입 (O(N))

arr = [1, 2, 3, 4, 5]

arr.insert(2, 6)  # 2번 인덱스에 6 삽입
print(arr)  # [1, 2, 6, 3, 4, 5]

 

동작 과정

  1. 3 → 3을 arr[3]으로 이동
  2. 4 → 4를 arr[4]으로 이동
  3. 5 → 5를 arr[5]으로 이동
  4. arr[2]에 6 삽입

📌 💡 최악의 경우 (맨 앞에 삽입하면)
✔ 모든 요소를 오른쪽으로 이동해야 하므로 O(N)

 

2. 배열에서 삭제 연산의 시간 복잡도 (O(N))

만약 배열의 중간 요소를 삭제하면, 나머지 요소들을 왼쪽으로 한 칸씩 당겨야 함
즉, 최악의 경우 모든 요소를 이동해야 하므로 O(N)의 시간 복잡도를 가짐

예제: 인덱스 2의 요소 삭제 (O(N))

arr = [1, 2, 6, 3, 4, 5]

arr.pop(2)  # 2번 인덱스 삭제
print(arr)  # [1, 2, 3, 4, 5]

 

동작 과정

  1. arr[3] → arr[2]으로 이동 (3이 arr[2]로 옴)
  2. arr[4] → arr[3]으로 이동
  3. arr[5] → arr[4]으로 이동

📌 💡 최악의 경우 (맨 앞 요소를 삭제하면)
✔ 모든 요소를 왼쪽으로 이동해야 하므로 O(N)

더보기

 

만약에 2번인덱스에 6을 넣고싶다면 모든 현재 인덱스에 있는 값들을 전부 오른쪽으로 이동시켜줘야함 

 

✔ Python의 list 는 동적 배열이라 크기 변경이 가능하지만, 내부적으로 새로운 배열을 만들고 데이터를 복사하는 방식

 

따라서 0개부터 ~ N 개 미뤄줄 수 있음 시간복잡도를 따질 때는 최악일 때로 따지기 때문에 O(N)의 시간 복잡도를 갖게된다. 

 

삭제도 마찬가지임 최악의 경우 N 개를 다 당겨야할 수도 있기 때문에 O(N)의 시간복잡도를 갖게 된다. 

 

 

이러한 삽입 삭제를 N번만큼 하게 되면 결국 N의 제곱에 해당하는 시간복잡도를 갖게됨 

 

3. 여러 번 삽입/삭제 연산을 수행할 경우 (O(N^2))

만약 삽입/삭제 연산을 N번 수행하면, 각 연산이 O(N)이므로 최종적으로 O(N^2)의 시간 복잡도가 됨.

예제: N개의 삽입 연산 (O(N^2))

 
arr = []

for i in range(1000):  # 1000번 삽입
    arr.insert(0, i)  # 매번 맨 앞에 삽입 (최악의 경우 O(N))

 

✔ O(N)의 삽입을 N번 반복하므로 O(N^2)

 

예제: N개의 삭제 연산 (O(N^2))

arr = list(range(1000))

for _ in range(1000):  # 1000번 삭제
    arr.pop(0)  # 매번 맨 앞 요소 삭제 (최악의 경우 O(N))

 

✔ O(N)의 삭제를 N번 반복하므로 O(N^2)

 

따라서 탐색을 많이 하는 경우는 배열을 사용하는 것이 좋은데 삽입과 삭제를 하는 경우에는 다른 자료구조를 사용하는 것이 유리하다. 

 

v = []

 

v.append((123, 456))

v.append((789, 987))

print("size:", len(v))

for p in v : 

print(p)

 

위와 같은 경우에는 무엇이 출력될까?

 

1. 튜플(Tuple)이란?

➡️ 튜플(Tuple)여러 개의 값을 저장할 수 있는 자료구조이며, 한 번 생성하면 수정할 수 없는(immutable) 데이터 타입
➡️ 리스트(list)와 비슷하지만, 튜플은 변경이 불가능(Immutable)한 점이 차이점

📌 💡 쉽게 말하면?
✔ 리스트(list)는 수정 가능 (mutable) → 요소를 추가/삭제 가능
✔ 튜플(tuple)은 수정 불가능 (immutable) → 요소를 변경할 수 없음

 

 

튜플 예제 (소괄호 () 사용)

 
my_tuple = (1, 2, 3)
 
print(my_tuple) # (1, 2, 3)
 

튜플과 리스트의 차이

 
my_list = [1, 2, 3] # 리스트 my_tuple = (1, 2, 3)
# 튜플 my_list[0] = 10 # 가능
# my_tuple[0] = 10 # 오류 발생 (튜플은 수정 불가)

2. 튜플의 특징

🔹 (1) 순서가 있는 자료형 (Ordered)

✔ 리스트처럼 튜플도 순서(order)를 유지
인덱스를 통해 요소에 접근 가능

예제: 인덱스로 접근

 
numbers = (10, 20, 30)
 
print(numbers[0]) # 10
print(numbers[1]) # 20

 


🔹 (2) 변경이 불가능 (Immutable)

한 번 생성된 튜플의 요소를 수정할 수 없음
✔ 요소를 변경하려면 새로운 튜플을 생성해야 함

예제: 튜플 수정 불가능

 
my_tuple = (1, 2, 3)
# my_tuple[0] = 10 # TypeError 발생 (튜플은 수정 불가)

튜플을 수정하려면 새로운 튜플을 만들어야 함

 
my_tuple = (1, 2, 3) new_tuple = (10,) + my_tuple[1:] # (10, 2, 3) print(new_tuple)

 


🔹 (3) 여러 데이터 타입 저장 가능

✔ 튜플은 서로 다른 자료형을 함께 저장할 수 있음

예제: 다양한 데이터 타입 저장

 
person = ("Alice", 25, True)
print(person) # ('Alice', 25, True)

🔹 (4) 패킹(Packing)과 언패킹(Unpacking) 가능

패킹(Packing): 여러 개의 값을 하나의 튜플로 묶기
언패킹(Unpacking): 튜플을 여러 개의 변수로 분해

예제: 패킹

info = ("Bob", 30, "Developer") # 패킹

예제: 언패킹

 
name, age, job = info # 언패킹
print(name) # Bob
print(age) # 30
print(job) # Developer

3. 튜플 vs 리스트 비교

비교 항목 리스트(list) 튜플(tuple)
 
변경 가능 여부 가능 (mutable) 불가능 (immutable)
요소 추가/삭제 가능 (append(), remove()) 불가능
인덱싱 지원 지원
반복문 사용 가능 가능
메모리 사용량 작음 (최적화됨)
실행 속도 느림 빠름
사용 예시 동적인 데이터 관리 고정된 데이터 저장

📌 💡 리스트와 튜플을 선택하는 기준

  • 데이터를 변경할 필요가 있으면 list 사용
  • 변경할 필요가 없고, 빠른 실행이 필요하면 tuple 사용

튜플이 더 유리한 경우

 

months = ("January", "February", "March") # 변경할 필요 없는 데이터
 

리스트가 더 유리한 경우

 
tasks = ["Task1", "Task2"] tasks.append("Task3") # 변경 가능

 

연결 리스트 Linked List

 

- 삽입/삭제: O(1)

- 탐색: O(N)

 

다른 자료구조들을 구현할 때 많이 쓰인다.

 

연결리스트는 배열과 반대임 왜 그럴까?

 

메모리 상에 어디에 존재하는지 확정 지을 수 없음 

 

1이 어디있는지는 0이 알고 있고 2가 어디있는지는 1이 알고 있음 그래서 한단계 한단계 노드를 거쳐가야함 

 

그래서 O(N)으로 느리게 됨 

 

그러면 삽입 삭제는? 

 

삽입하려는 위치 이전 이후에서만 관계를 맺어주면 됨 

 
 
 

'자료구조' 카테고리의 다른 글

[자료구조] 첫번째 공부  (0) 2024.11.23