이중 연결 리스트(Doubly Linked List) 컨테이너이다

각 노드가 이전 노드와 다음 노드를 가리키는 포인터를 가지고 있다.
#include <iostream>
#include <list>
#include <string>
#include <vector>
// 리스트 내용을 출력하기 위한 템플릿 도우미 함수
template<typename T>
void print_list(const std::list<T>& l, const std::string& name)
{
std::cout << name << ": [ ";
for (const auto& item : l)
{
std::cout << item << " ";
}
std::cout << "]" << std::endl;
}
int main()
{
// =================================================================
// Part 1: std::list의 다양한 생성자 및 대입 연산자
// =================================================================
std::cout << "--- Part 1: 생성자 및 대입 연산자 ---" << std::endl;
// 1. 기본 생성자 : 빈 리스트 생성
std::list<int> list1;
print_list(list1, "1. 기본 생성자 list1");
// 2. 크기 지정 생성자 : 10개의 아이템을 기본값 0으로 초기화
std::list<int> list2(10);
print_list(list2, "2. 크기 지정 list2(10)");
// 3. 크기, 값 지정 생성자 : 10개의 아이템을 20으로 초기화
std::list<int> list3(10, 20);
print_list(list3, "3. 크기,값 지정 list3(10, 20)");
// 4. 초기화 리스트 생성자
std::list<int> list4 = { 1, 2, 3, 4, 5 };
print_list(list4, "4. 초기화 리스트 list4");
// 5. 범위 생성자 : 다른 컨테이너의 반복자 범위로 초기화
std::list<int> list5(list4.begin(), list4.end());
print_list(list5, "5. 범위 생성자 list5");
// 6. 복사 생성자
std::list<int> list6(list5);
print_list(list6, "6. 복사 생성자 list6");
// 7. 이동 생성자
std::list<int> list7(std::move(list6));
print_list(list7, "7. 이동 생성자 list7");
print_list(list6, " 이동 후 list6 (내용이 비워짐)");
// 8. 복사 대입 연산자
std::list<int> list8;
list8 = list7;
print_list(list8, "8. 복사 대입 list8");
// 9. 이동 대입 연산자
std::list<int> list9;
list9 = std::move(list7);
print_list(list9, "9. 이동 대입 list9");
print_list(list7, " 이동 후 list7 (내용이 비워짐)");
// 10. 초기화 리스트 대입
list9 = { 1, 2, 3, 4, 5, 6 };
print_list(list9, "10. 초기화 리스트 대입 list9");
// =================================================================
// Part 2: assign() 멤버 함수
// =================================================================
std::cout << "\n--- Part 2: assign() 멤버 함수 ---" << std::endl;
// assign() : list의 기존 내용을 지우고 새롭게 할당
std::list<std::string> str_list = { "청새치","공상어","갈치","삼치","꽁치","상어","고래" };
std::list<int> int_list1 = { 1,2,3,4,5,6,7,8,10,11,12,13,14,15,16 };
std::list<int> int_list2 = { 6,5,4,3,2,1 };
print_list(int_list1, "원본 int_list1");
// 1. void assign(size_type count, const T& value) : count개 만큼 value 값으로 채우기
int_list1.assign(10, 10);
print_list(int_list1, "1. assign(10, 10) 후");
// 2. template<class InputIt> void assign(InputIt first, InputIt last) : 반복자 범위로 할당
print_list(int_list1, "원본 int_list1");
print_list(int_list2, "복사할 int_list2");
int_list1.assign(int_list2.begin(), int_list2.end());
print_list(int_list1, "2. assign(범위) 후");
// 3. void assign(std::initializer_list<T> ilist) : 초기화 리스트로 할당
print_list(int_list2, "원본 int_list2");
int_list2.assign({ 20, 43, 423, 324, 234 });
print_list(int_list2, "3. assign(초기화 리스트) 후");
// 시간 복잡도: O(N) - N은 새로 할당되는 요소의 개수
return 0;
}
#include <list>
#include <string>
#include <iostream>
int main()
{
std::list<std::string> list1 = { "참다랑어","장어","곰장어","갈치","삼치","꽁치","상어","고래" };
std::list<int> list2 = { 1,2,3,4,5,6,7,8,10,11,12,13,14,15,16 };
// --- front() ---
// front() : list의 맨 앞(첫 번째) 요소에 접근하는 함수
//
// reference front()
// const_reference front() const
// 반환값 : 첫 번째 요소의 참조 (reference)
// 주의 : 빈 list에서 호출하면 정의되지 않은 동작(Undefined Behavior)을 일으킴
if (!list1.empty()) // 안전하게 사용하기 위해 비어있는지 확인
{
std::string s1 = list1.front();
std::cout << "list1.front() : " << s1 << std::endl;
// front()가 반환하는 것은 참조이므로, 이를 통해 원본 값을 수정할 수 있음
list1.front() = "대왕고래";
std::cout << "첫 번째 요소 수정 후: " << list1.front() << std::endl;
}
// --- back() ---
// back() : list의 맨 뒤(마지막) 요소에 접근하는 함수
//
// reference back()
// const_reference back() const
// 반환값 : 마지막 요소의 참조 (reference)
// 주의 : 빈 list에서 호출하면 정의되지 않은 동작(Undefined Behavior)을 일으킴
if (!list1.empty()) // 안전하게 사용하기 위해 비어있는지 확인
{
std::string s2 = list1.back();
std::cout << "\nlist1.back() : " << s2 << std::endl;
// back()이 반환하는 참조를 통해 원본 값을 수정
list1.back() = "상괭이";
std::cout << "마지막 요소 수정 후: " << list1.back() << std::endl;
}
return 0;
}
#include <list>
#include <string>
#include <iostream>
// 리스트 내용을 출력하기 위한 도우미 함수
void print_list(const std::list<std::string>& l, const std::string& message)
{
std::cout << "--- " << message << " ---" << std::endl;
std::cout << "[ ";
for (const auto& val : l)
{
std::cout << "\"" << val << "\" ";
}
std::cout << "]" << std::endl << std::endl;
}
int main()
{
std::list<int> list1 = { 1,3,5 };
std::list<std::string> list2; // 초기에는 비어있음
std::list<std::string> list3 = { "참다랑어","장어","곰장어" };
// --- push_front / push_back ---
// push_front(value) : 맨 앞에 요소 추가
// 반환값 : void
// 시간 복잡도: O(1)
list1.push_front(7); // list1: [ 7 1 3 5 ]
// push_back(value) : 맨 뒤에 요소 추가
// 반환값 : void
// 시간 복잡도: O(1)
list1.push_back(9); // list1: [ 7 1 3 5 9 ]
// --- emplace_front / emplace_back ---
// emplace_front() : 맨 앞에 요소를 직접 생성하여 추가
// 복사 없이 직접 객체 생성
// 반환값 : C++11: void 반환, C++17 이후: 추가된 요소에 대한 참조 반환(std::string&)
list2.emplace_front("갈치");
print_list(list2, "emplace_front(\"갈치\") 후");
// emplace_back() : 리스트의 맨 뒤에 요소를 직접 생성하여 추가
// 복사 없이 직접 객체 생성
// 반환값 : C++11: void 반환, C++17 이후: 추가된 요소에 대한 참조 반환(std::string&)
list2.emplace_back("아귀상어");
print_list(list2, "emplace_back(\"아귀상어\") 후");
// --- insert() : list의 특정 위치에 요소를 삽입 ---
// 용도: 리스트의 특정 위치에 요소 삽입
// 유연성: 단일/다중/범위 삽입 모두 가능
// 효율성: std::list는 중간 삽입이 효율적 (O(1))
// 반환값: 삽입된 요소의 iterator 반환
// 1. 단일 요소 삽입
// iterator insert(const_iterator pos, const T& value);
// iterator insert(const_iterator pos, T&& value);
list2.insert(list2.begin(), "꽁치");
print_list(list2, "1. 맨 앞에 \"꽁치\" insert 후");
// 2. 여러 개의 같은 값 삽입
// iterator insert(const_iterator pos, size_type count, const T& value);
list2.insert(list2.end(), 2, "해파리");
print_list(list2, "2. 맨 뒤에 \"해파리\" 2개 insert 후");
// 3. 범위 삽입
// template<class InputIt>
// iterator insert(const_iterator pos, InputIt first, InputIt last);
list2.insert(list2.end(), list3.begin(), list3.end());
print_list(list2, "3. 맨 뒤에 list3의 모든 요소 insert 후");
// 4. 초기화 리스트 삽입 (C++11)
// iterator insert(const_iterator pos, std::initializer_list<T> ilist);
list2.insert(list2.begin(), { "기름치","조개","새우","왕새우","가재" });
print_list(list2, "4. 맨 앞에 초기화 리스트 insert 후");
// --- emplace() ---
// template<class... Args>
// iterator emplace(const_iterator pos, Args&&... args);
// emplace() : list의 특정 위치에 객체(요소)를 직접 생성하여 삽입
// 반환값: 삽입된 요소를 가리키는 iterator
// 용도: 리스트의 특정 위치에 효율적으로 요소 삽입
// 장점: 불필요한 복사/이동 제거
// 권장: 복잡한 객체나 성능이 중요한 경우 insert() 대신 사용
list2.emplace(list2.begin(), "구피");
print_list(list2, "맨 앞에 \"구피\" emplace 후");
// --- 최종 출력 ---
std::cout << "===== 최종 list2 내용 =====" << std::endl;
for (const auto& val : list2)
{
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
#include <list>
#include <string>
#include <iostream>
#include <iterator> // std::advance를 위해 필요
// 리스트 내용을 출력하기 위한 도우미 함수
template<typename T>
void print_list(const std::list<T>& l, const std::string& message)
{
std::cout << "--- " << message << " ---" << std::endl;
std::cout << "[ ";
for (const auto& val : l)
{
std::cout << val << " ";
}
std::cout << "]" << std::endl << std::endl;
}
int main()
{
const std::list<std::string> original_list1 = { "참다랑어","장어","곰장어","갈치","삼치","꽁치","상어","고래" };
const std::list<int> original_list2 = { 1,2,3,4,5,6,7,8,10,11,12,13,14,15,16 };
// --- pop_front() / pop_back() ---
{
std::list<std::string> list1 = original_list1;
print_list(list1, "pop_front/back 전 원본 list1");
// void pop_front() : list의 맨 앞 요소를 제거
// 반환값: 없음 (void)
// 주의: 빈 list에서 호출하면 정의되지 않은 동작(Undefined Behavior)
// 성능: O(1) - 매우 효율적
list1.pop_front();
print_list(list1, "pop_front() 후");
// void pop_back() : list의 맨 뒤 요소를 제거
// 반환값: 없음 (void)
// 주의: 빈 list에서 호출하면 정의되지 않은 동작(Undefined Behavior)
// 성능: O(1) - 매우 효율적
list1.pop_back();
print_list(list1, "pop_back() 후");
}
// --- erase() : list의 특정 위치 또는 범위의 요소를 제거 ---
{
// 1. 단일 요소 제거
std::list<std::string> list1 = original_list1;
print_list(list1, "erase(단일) 전 원본 list1");
// iterator erase(const_iterator pos);
// 반환값: 제거된 요소 다음 위치의 iterator
list1.erase(list1.begin()); // 맨 앞의 "참다랑어" 제거
print_list(list1, "erase(list1.begin()) 후");
// 2. 범위 제거
list1 = original_list1; // 리스트 초기화
print_list(list1, "erase(범위) 전 원본 list1");
// iterator erase(const_iterator first, const_iterator last);
// [first, last) 범위의 요소를 제거. last는 포함되지 않음.
std::list<std::string>::iterator first = list1.begin();
std::list<std::string>::iterator last = list1.begin();
std::advance(last, 3); // last를 3칸 뒤로 이동 (begin() + 3 위치)
list1.erase(first, last); // 0, 1, 2번 인덱스 요소 제거
print_list(list1, "erase(begin, begin+3) 후");
}
// --- remove() : 특정 값과 일치하는 모든 요소를 제거 ---
{
std::list<std::string> list_with_duplicates = {"상어", "고래", "상어", "돌고래", "상어"};
print_list(list_with_duplicates, "remove() 전 원본");
// void remove(const T& value);
// 반환값: 없음 (void)
// 성능: O(n) - 리스트를 한 번 순회
list_with_duplicates.remove("상어");
print_list(list_with_duplicates, "remove(\"상어\") 후");
}
// --- remove_if() : 특정 조건을 만족하는 모든 요소를 제거 ---
{
std::list<int> list2 = original_list2;
print_list(list2, "remove_if() 전 원본 list2");
// template<class Predicate>
// void remove_if(Predicate pred);
// 매개변수: 조건을 판단하는 함수/람다/함수 객체
// 반환값: 없음 (void)
// 동작: 조건이 true인 모든 요소를 제거
int comp = 5;
// 5보다 작은 요소 삭제
list2.remove_if([comp](int a) { return a < comp; });
print_list(list2, "5보다 작은 요소를 remove_if() 후");
}
// --- clear() : list의 모든 요소를 제거 ---
{
std::list<std::string> list1 = original_list1;
print_list(list1, "clear() 전 원본 list1");
// void clear();
// 반환값: 없음 (void)
list1.clear();
print_list(list1, "clear() 후");
}
return 0;
}
#include <list>
#include <string>
#include <iostream>
// 리스트 내용을 출력하기 위한 도우미 함수
template<typename T>
void print_list_info(const std::list<T>& l, const std::string& name)
{
std::cout << "--- " << name << " 정보 ---" << std::endl;
std::cout << "내용: [ ";
for (const auto& val : l)
{
std::cout << val << " ";
}
std::cout << "]" << std::endl;
std::cout << "size(): " << l.size() << std::endl;
std::cout << "empty(): " << (l.empty() ? "true" : "false") << std::endl;
std::cout << std::endl;
}
int main()
{
std::list<std::string> list1 = { "참다랑어","장어","곰장어","갈치","삼치","꽁치","상어","고래" };
std::list<int> list2 = { 1,2,3,4,5,6,7,8,10,11,12,13,14,15,16 };
// --- size_type size() const ---
// list에 포함된 요소의 개수를 반환
// 반환값 : 요소의 개수 (size_type, 일반적으로 size_t)
// 시간복잡도 : O(1)
size_t s1 = list1.size();
std::cout << "list1.size() : " << s1 << std::endl;
// --- bool empty() const ---
// list가 비어있는지 확인
// 반환값 : 비어있으면 true, 요소가 있으면 false
// 시간복잡도 : O(1)
bool b1 = list2.empty();
std::cout << "list2.empty() : " << (b1 ? "true" : "false") << std::endl;
// --- size_type max_size() const ---
// list가 이론적으로 담을 수 있는 최대 요소 개수를 반환
// 반환값 : 최대 가능한 요소 개수 (size_type, 일반적으로 size_t)
// 시간복잡도 : O(1)
// 주의 : 실제 할당 가능한 메모리와는 다름. 시스템 아키텍처에 따른 이론적 한계치.
size_t s2 = list1.max_size();
std::cout << "list1.max_size() : " << s2 << " (이론적 최대치)" << std::endl;
std::cout << std::endl;
// --- resize() : list의 크기를 변경 ---
// void resize(size_type count)
// void resize(size_type count, const value_type& value)
// count : 새로운 크기
// value : 크기가 늘어날 경우 추가될 요소의 값 (선택적)
// 반환값 : 없음 (void)
// 시간복잡도 : O(n) - n은 추가/제거되는 요소 개수
// 용도 : 리스트를 특정 크기로 정확하게 맞춰야 할 때 매우 유용
print_list_info(list2, "resize() 전 list2");
// 1. 현재 크기(16)보다 크게 만들기
list2.resize(20, 99); // 20개로 늘리고, 추가된 요소는 99로 채움
print_list_info(list2, "resize(20, 99) 후 list2");
// 2. 현재 크기(20)보다 작게 만들기
list2.resize(10); // 뒤에서부터 요소가 제거되어 10개로 줄어듦
print_list_info(list2, "resize(10) 후 list2");
// 3. 값을 지정하지 않고 크게 만들기
list2.resize(15); // 추가된 요소들은 기본값(int의 경우 0)으로 채워짐
print_list_info(list2, "resize(15) 후 list2");
return 0;
}
#include <list>
#include <string>
#include <iostream>
int main()
{
std::list<int> list3 = { 6, 5, 4, 3, 2, 1 };
std::cout << "원본 리스트: [ 6 5 4 3 2 1 ]" << std::endl;
std::cout << "------------------------------------" << std::endl;
// ======================================================
// 1. 정방향 반복자 (Forward Iterators)
// ======================================================
// begin() : 첫 번째 요소를 가리키는 반복자
// cbegin() : 첫 번째 요소를 가리키는 읽기 전용(const) 반복자
auto it_begin = list3.begin();
auto cit_begin = list3.cbegin();
std::cout << "begin()이 가리키는 값: " << *it_begin << std::endl;
std::cout << "cbegin()이 가리키는 값: " << *cit_begin << std::endl;
// *it_begin = 60; // 가능: 일반 반복자로는 값 수정 가능
// *cit_begin = 60; // 컴파일 에러: const 반복자로는 값 수정 불가능
// end() : 마지막 요소 '다음'을 가리키는 반복자 (sentinel)
// cend() : 마지막 요소 '다음'을 가리키는 읽기 전용(const) 반복자
// 주의: end()가 가리키는 위치에는 유효한 요소가 없으므로 역참조(*it_end)하면 안됨!
std::cout << "\n[정방향 순회] (begin -> end)" << std::endl;
for (auto it = list3.begin(); it != list3.end(); ++it)
{
std::cout << *it << " ";
}
std::cout << std::endl;
// ======================================================
// 2. 역방향 반복자 (Reverse Iterators)
// ======================================================
std::cout << "\n------------------------------------" << std::endl;
// rbegin() : 역방향 순회의 시작 (즉, 원본 리스트의 '마지막' 요소)을 가리키는 반복자
// crbegin() : 역방향 순회의 시작을 가리키는 읽기 전용(const) 반복자
auto it_rbegin = list3.rbegin();
auto cit_rbegin = list3.crbegin();
std::cout << "rbegin()이 가리키는 값: " << *it_rbegin << std::endl;
std::cout << "crbegin()이 가리키는 값: " << *cit_rbegin << std::endl;
// rend() : 역방향 순회의 끝 (즉, 원본 리스트의 '첫 번째' 요소 '이전')을 가리키는 반복자
// crend() : 역방향 순회의 끝을 가리키는 읽기 전용(const) 반복자
// 주의: rend() 역시 유효한 요소를 가리키지 않으므로 역참조하면 안됨!
std::cout << "\n[역방향 순회] (rbegin -> rend)" << std::endl;
for (auto it = list3.rbegin(); it != list3.rend(); ++it)
{
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
#include <iostream>
#include <list>
#include <string>
// 리스트 내용을 간편하게 출력하기 위한 도우미 함수
template<typename T>
void print_list(const std::list<T>& l, const std::string& name)
{
std::cout << name << " (" << l.size() << "개): [ ";
for (const auto& item : l)
{
std::cout << item << " ";
}
std::cout << "]" << std::endl;
}
int main()
{
// =================================================================
// 1. sort() : list의 요소를 정렬
// =================================================================
std::cout << "--- 1. sort() ---" << std::endl;
{
std::list<int> list_to_sort = { 32, 582, 23, 421, 2, 213, 4, 334, 42, 38, 876, 7, 576 };
print_list(list_to_sort, "정렬 전");
// 기본 sort() : 오름차순 정렬 (< 연산자 사용)
list_to_sort.sort();
print_list(list_to_sort, "기본 sort() (오름차순) 후");
// 사용자 정의 비교 함수를 사용한 sort() : 내림차순 정렬
list_to_sort.sort([](const int& a, const int& b) { return a > b; });
print_list(list_to_sort, "사용자 정의 sort() (내림차순) 후");
std::cout << std::endl;
}
// =================================================================
// 2. reverse() : list의 요소 순서를 반대로 뒤집음
// =================================================================
std::cout << "--- 2. reverse() ---" << std::endl;
{
std::list<int> list_to_reverse = { 32, 582, 23, 421, 2, 213 };
print_list(list_to_reverse, "뒤집기 전");
// reverse() : 요소 복사 없이 내부 포인터만 재연결하므로 매우 빠름 (O(n))
list_to_reverse.reverse();
print_list(list_to_reverse, "reverse() 후");
std::cout << std::endl;
}
// =================================================================
// 3. unique() : 연속된 중복 요소를 제거
// =================================================================
std::cout << "--- 3. unique() ---" << std::endl;
{
// unique()는 '연속된' 중복만 제거하므로, 모든 중복을 제거하려면 먼저 정렬해야 함
std::list<int> list_to_unique = { 1, 2, 1, 2, 2, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7 };
print_list(list_to_unique, "정렬 전 원본");
list_to_unique.sort();
print_list(list_to_unique, "정렬 후");
// 기본 unique() : 바로 옆 요소와 같은지(==) 비교하여 제거
list_to_unique.unique();
print_list(list_to_unique, "기본 unique() 후");
// 사용자 정의 비교 함수를 사용한 unique()
std::list<int> list2 = { 1, 2, 4, 5, 7, 8, 10 };
print_list(list2, "\n다른 리스트 원본");
// 예: 차이가 1 이하인 연속된 요소들을 제거 (2와 1, 5와 4, 8과 7이 제거됨)
list2.unique([](const int& a, const int& b) { return std::abs(a - b) <= 1; });
print_list(list2, "차이가 1 이하인 요소 unique() 후");
std::cout << std::endl;
}
// =================================================================
// 4. merge() : 두 개의 '정렬된' 리스트를 병합
// =================================================================
std::cout << "--- 4. merge() ---" << std::endl;
{
std::list<int> list_main = { 1, 5, 9, 12 };
std::list<int> list_sub = { 2, 3, 8, 10 };
print_list(list_main, "병합 전 list_main");
print_list(list_sub, "병합 전 list_sub");
// list_main으로 list_sub의 요소들을 '이동'시켜 병합. list_sub는 비워짐.
// 두 리스트 모두 같은 기준으로 정렬되어 있어야 함 (기본: 오름차순)
list_main.merge(list_sub);
print_list(list_main, "merge() 후 list_main");
print_list(list_sub, "merge() 후 list_sub (비워짐)");
std::cout << std::endl;
}
// =================================================================
// 5. splice() : 다른 list의 요소를 복사 없이 '이동'
// =================================================================
std::cout << "--- 5. splice() ---" << std::endl;
{
std::list<int> dest_list = { 100, 200 };
std::list<int> source_list1 = { 1, 2, 3 };
std::list<int> source_list2 = { 10, 20, 30, 40, 50 };
print_list(dest_list, "초기 dest_list");
print_list(source_list1, "초기 source_list1");
print_list(source_list2, "초기 source_list2");
std::cout << std::endl;
// 1. 리스트 전체 이동: source_list1의 모든 요소를 dest_list의 끝으로 이동
dest_list.splice(dest_list.end(), source_list1);
print_list(dest_list, "1. source_list1 전체 splice 후");
print_list(source_list1, " -> source_list1 (비워짐)");
// 2. 단일 요소 이동: source_list2의 첫 번째 요소를 dest_list의 처음으로 이동
dest_list.splice(dest_list.begin(), source_list2, source_list2.begin());
print_list(dest_list, "\n2. source_list2의 첫 요소 splice 후");
print_list(source_list2, " -> source_list2");
// 3. 범위 이동: source_list2의 남은 요소들을 dest_list의 끝으로 이동
dest_list.splice(dest_list.end(), source_list2, source_list2.begin(), source_list2.end());
print_list(dest_list, "\n3. source_list2의 나머지 범위 splice 후");
print_list(source_list2, " -> source_list2 (비워짐)");
}
return 0;
}