前言
前面我们已经学习了string和vector的模拟实现和使用,相信对于容器的模拟实现和使用的能力已经上升一定的水平,今天我们要学习的是list的模拟实现,List的模拟实现和string和vector其实没有本质的区别,只是在list的模拟实现过程中,list的迭代器和string和vector有所不同,这是我们实现List的模拟实现中需要重点掌握的,今天学习的List本质就是一个带头双向循环链表。
一、关于list
1. 简介
list本质就是一个带头双向循环链表,支持在任何位置以O(1)的时间进行插入和删除。
2. 成员类型
看到上图,我们一定要知道迭代器的类型:list中的迭代器的类型是双向迭代器,其他的迭代器类型好还有:单向迭代器,随机迭代器。
- 单向迭代器:只支持单向遍历访问的迭代器,只支持++,不支持–
- 双向迭代器:支持双向访问容器的迭代器,同时支持++和–
- 随机迭代器:支持随机访问容器的迭代器,同时支持++,–,+,-
3. 结构
list的底层本质是一个带头双向循环链表,支持在任何位置以O(1)的时间复杂度进行插入和删除,因为相比于前面学习的string和vector,string和vector的底层是一个连续存储的数组,所以在插入和删除数据的时候需要挪动数据,时间复杂度为O(N),List插入和删除数据只需要改变插入和删除位置的前后结点的指针关系,不需要挪动数据,所以插入和删除的时间复杂度为O(1)。
4. 代码表示底层结构
- 代码:
namespace hjt
{
// 结点类型
template <class T>
struct ListNode
{
// 成员函数
// 构造函数:
ListNode(const T& data)
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{}
// 基本的成员变量
ListNode* _next;
ListNode* _prev;
T _data;
};
// 实现list
template <class T>
class list
{
typedef ListNode<T> Node;
public:
// 基本的成员函数
// 基本成员变量
private:
Node* _head;
};
}
list是一个带头双向循环链表,由很多的结点链接而成的,一个List中最本质的就是其头结点,所以,list的成员变量就是头结点的指针,所以我们首先需要实现结点类型,实现结点类型,结点类型中需要存储数据(数据具有不同的数据类型,所以需要模板来支撑),还需要两个指针分别指向前一个结点和后一个结点,从而实现双向的功能。
二、默认成员函数
1. 构造函数
1. list()
- 代码:
// 构造函数
list(const T& val = T())
{
_head = new Node(val);
_head->_prev = _head;
_head->_next = _head;
}
- 使用代码(std::list)
void test_list1()
{
// 无参构造函数
list<int> lt1;// 创建一个存储int的list对象
list<char> lt2;// 创建一个存储char的list对象
list<double> lt3;// 创建一个存储double的list对象
list<string> lt4;// 创建一个存储string的list对象
}
2. list(size_t n,const T& val = T())和list(InputIterator first,InputIterator last)
- 代码:构造函数:支持使用一段迭代器区间的数据进行构造
// 构造函数:支持使用一段迭代器区间的数据进行构造
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
first++;
}
}
void test_list7()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
list<int> lt1(lt.begin(), lt.end());
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
- 使用代码(std::list):
void test_list2()
{
// 用n个值来构造List
list<int> lt1(3, 6);// 用3个6来构造一个list对象
// 使用一段迭代器区间来构造
string s2("hello list::list(InputIterator first,InputIterator last)");
vector<char> v2(s2.begin(), s2.end());
list<char> lt2(v2.begin(), v2.end());
// 遍历
// 使用迭代器进行遍历
// 遍历lt1
cout << "lt1:" << endl;
list<int>::iterator lit1 = lt1.begin();
while (lit1 != lt1.end())
{
cout << *lit1 << " ";
lit1++;
}
cout << endl;
// 遍历lt2
cout << "lt2:" << endl;
list<char>::iterator lit2 = lt2.begin();
while (lit2 != lt2.end())
{
cout << *lit2 << " ";
lit2++;
}
cout << endl;
// 使用范围for进行遍历
cout << "lt1:" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl;
cout << "lt2:" << endl;
for (auto& e : lt2)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
2. 拷贝构造函数
拷贝构造函数和前面的容器样子还是差不多
- 代码:
// 拷贝构造函数
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
void test_list8()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
list<int> lt1(lt);// 调用拷贝构造函数
cout << "lt:" << endl;
for (auto& e: lt)
{
cout << e << " ";
}
cout << endl;
cout << "lt1:" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
- 使用代码(std::list)
void test_list3()
{
string s("hello list(const list<char>& lt)");
list<char> lt1(s.begin(), s.end());
list<char> lt2(lt1);
cout << "lt1:" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl;
cout << "lt2" << endl;
for (auto& e : lt2)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
3. 析构函数
- 代码:
// 析构函数
~list()
{
clear();
_head = nullptr;
}
4. 赋值运算符重载函数
- 代码:
// 赋值运算符重载函数
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
void test_list9()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
list<int> lt1;
lt1 = lt;// 调用赋值运算符重载函数
cout << "lt:" << endl;
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
cout << "lt1:" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
- 使用代码(std::list):
void test_list4()
{
string s("hello list<char>& operator=(const list<char>& lt)");
list<char> lt(s.begin(), s.end());
list<char> lt1;
lt1 = lt;// 调用赋值运算符重载函数
cout << "lt:" << endl;
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
cout << "lt1:" << endl;
for (auto& e : lt1)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
三、迭代器(重难点)
list中的迭代器的实现是本节学习的重难点,我们要深刻理解list中的迭代器的实现方法,因为list是一个链表,链表中的结点不是顺序存储的,所以不能直接通过结点的指针的++或者–就直接找到下一个或者上一个结点,所以我们可以通过将迭代器封装成一个自定义类型,从而通过运算符重载来实现其功能。
- 代码1:最原始的版本(封装迭代器)
// 迭代器
template <class T>
struct __list_iterator
{
typedef ListNode<T> Node;
// 构造函数
__list_iterator(Node* node)
:_node(node)
{
}
// 运算符重载
// * ->
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
// 前置++前置--后置++后置--
__list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
__list_iterator<T>& operator--()
{
_node = _node->_prev;
return *this;
}
__list_iterator<T> operator++(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
__list_iterator<T> operator--(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_prev;
return tmp;
}
// 比较大小
bool operator==(const __list_iterator<T>& it)
{
return _node == it._node;
}
bool operator!=(const __list_iterator<T>& it)
{
return _node == it._node;
}
// 成员变量
Node* _node;
};
// 普通迭代器
template <class T>
struct __list_const_iterator
{
typedef ListNode<T> Node;
// 构造函数
__list_const_iterator(Node* node)
:_node(node)
{
}
// 运算符重载
// * ->
const T& operator*()
{
return _node->_data;
}
const T* operator->()
{
return &_node->_data;
}
// 前置++前置--后置++后置--
__list_const_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
__list_const_iterator<T>& operator--()
{
_node = _node->_prev;
return *this;
}
__list_const_iterator<T> operator++(int)
{
__list_const_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
__list_const_iterator<T> operator--(int)
{
__list_const_iterator<T> tmp(*this);
_node = _node->_prev;
return tmp;
}
// 比较大小
bool operator==(const __list_const_iterator<T>& it)
{
return _node == it._node;
}
bool operator!=(const __list_const_iterator<T>& it)
{
return _node == it._node;
}
// 成员变量
Node* _node;
};
- 代码:在list 中定义迭代器
typedef __list_iterator<T> iterator;
typedef __list_const_iterator<T> const_iterator;
// 迭代器
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
- 代码2:迭代器实现的优化
// 迭代器实现的优化
template <class T,class Ref,class Ptr>
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> Self;
Node* _node;
// 构造函数
__list_iterator(Node* node)
:_node(node)
{
}
// 运算符重载
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self& operator--(int)
{
Self tmp(*this);
_node = _node->_prev;
}
// 比较大小
bool operator==(const Self& it)
{
return _node == it._node;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
};
- 代码:list中定义普通迭代器和const对象的迭代器
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
// 迭代器
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
- 测试代码:
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
lit++;
}
cout << endl;
}
运行结果:
1. 普通对象的正向迭代器
- 使用代码(std::list):
void test_list5()
{
string s("hello list<char>::iterator begin() and end()");
list<char> lt(s.begin(), s.end());
list<char>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
lit++;
}
cout << endl;
}
运行结果:
2. const对象的正向迭代器
- 使用代码(std::list):
void test_list6()
{
string s("hello list<char>::const_iterator begin() and end()");
const list<char> lt(s.begin(), s.end());
list<char>::const_iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
lit++;
}
cout << endl;
}
运行结果:
3. 普通对象的反向迭代器
使用代码(std::list):
void test_list7()
{
string s("hello list<char>::reverse_iterator begin() and end()");
list<char> lt(s.begin(), s.end());
list<char>::reverse_iterator lit = lt.rbegin();
while (lit != lt.rend())
{
cout << *lit << " ";
lit++;
}
cout << endl;
}
4. const对象的反向迭代器
四、容量接口
1. empty()
这个函数的功能是判断链表是否为空
- 代码:
// 判断链表是否为空
bool empty()
{
return _head->_next == _head;
}
void test_list10()
{
list<int> lt;
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
if (lt.empty())
{
cout << "链表为空" << endl;
}
else
{
cout << "链表不为空" << endl;
}
lt.clear();
if (lt.empty())
{
cout << "链表为空" << endl;
}
else
{
cout << "链表不为空" << endl;
}
}
运行结果:
2. size()
返回list结点个数
- 代码:
// 结点个数
size_t size()
{
iterator pos = begin();
int count = 0;
while (pos != end())
{
count++;
pos++;
}
return count;
}
void test_list11()
{
list<int> lt;
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
cout << "结点个数:" << lt.size() << endl;
}
运行结果:
五、元素访问接口
1. front()
2. back()
六、修改接口
1. push_front()
- 代码:
void push_front(const T& val)
{
Node* front = _head->_next;
Node* newnode = new Node(val);
// _head newnode front
_head->_next = newnode;
newnode->_prev = _head;
newnode->_next = front;
front->_prev = newnode;
}
2. pop_front()
- 代码:
void pop_front()
{
Node* front = _head->_next;
Node* frontNext = front->_next;
// _head front frontNext
_head->_next = frontNext;
frontNext->_prev = _head;
delete front;
}
3. push_back()
- 代码:
void push_back(const T& val)
{
Node* tail = _head->_prev;
// _head tail newnode
Node* newnode = new Node(val);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
4. pop_back()
- 代码:
void pop_back()
{
Node* tail = _head->_prev;
Node* tailPrev = tail->_prev;
// tailPrev tail _head
tailPrev->_next = _head;
_head->_prev = tailPrev;
delete tail;
}
综合测试尾插尾删,头插,头删
- 代码:
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
lt.push_front(1);
lt.push_front(2);
lt.push_front(3);
lt.push_front(4);
lt.push_front(5);
lt.push_front(6);
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_back();
lt.pop_back();
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_front();
lt.pop_front();
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
5. insert()![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8862be7445e936f7fff2bf6a07226cb9.png)
insert()函数主要是使用迭代器进行插入,就是通过迭代器找到对应的插入位置,注意,首先我们应该先通过迭代器找到这个迭代器对应的指针,然后通过这个指针找到插入的位置,insert(pos,val)就是在pos位置的前面插入val,插入的过程中,在插入前和插入后,pos迭代器指向的都是同一个结点,所以list的插入不会导致迭代器失效,insert函数最终返回新插入结点的迭代器。
- 代码:
iterator insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
// prev newnode cur
Node* newnode = new Node(val);
// 链接指针
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
void test_list4()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
if (*lit == 3)
{
lit = lt.insert(lit, *lit * 10);
lit++;
}
lit++;
}
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
// 在所有偶数前面插入这个偶数的10倍
list<int>::iterator lit1 = lt.begin();
while (lit1 != lt.end())
{
if (*lit1 % 2 == 0)
{
lit1 = lt.insert(lit1, *lit1 * 10);
lit1++;
}
lit1++;
}
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
6.erase()
erase()函数中会删除pos迭代器指向的那个结点,所以调用erase函数之后会产生迭代器失效的问题,所以在使用的时候我们需要注意迭代器失效的问题,erase函数调用之后会返回删除的结点的下一个结点的迭代器。
- 代码:
iterator erase(iterator pos)
{
assert(pos != end());
// end()位置是头结点,不能将头结点删除
// 找到删除的结点
Node* cur = pos._node;
// 找到前一个结点
Node* prev = cur->_prev;
// 找到下一个结点
Node* next = cur->_next;
// prev cur next
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
void test_list5()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
lt.erase(lt.begin());
lt.erase(--lt.end());
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
// 删除所有奇数
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
if (*lit % 2)
{
lit = lt.erase(lit);
}
lit++;
}
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
7. clear()
这个函数负责将list中的有效结点清空
- 代码:
void clear()
{
list<int>::iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void test_list6()
{
list<int> lt;
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
lt.clear();
for (auto& e : lt)
{
cout << e << " ";
}
cout << endl;
}
运行结果:
七、list经典题目
- reverse的使用
- 题目:
- 选项:
分析:
本题主要考察list
中的构造函数(使用一段迭代器区间进行初始化),还有reverse(),参数是一段迭代器区间,注意迭代器区间都是左闭右开的,所以一定要注意逆置的边界,第一次逆置传的参数是:(mylist.begin(),pos)
,所以实际逆置的范围是:(mylist.begin(),pos-1),所以此时会将0-4进行逆置,变成4321056789,接下来的逆置传的是(pos,mtlist.end()),所以实际逆置的范围:(pos,--mylist.end()),也就是对56789进行逆置,所以最终的结果是:4321098765,然后再反向输出:5678901234。故本题选:C
自己实现的代码:
void test_list2()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
int n = sizeof(arr) / sizeof(int);
list<int> mylist(arr, arr + n);
list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5);
if (pos != mylist.end())
{
reverse(mylist.begin(), pos);
reverse(pos, mylist.end());
}
list<int>::const_reverse_iterator crit = mylist.rbegin();
while (crit != mylist.rend())
{
cout << *crit << " ";
crit++;
}
cout << endl;
}
int main()
{
test_list2();
return 0;
}
运行结果:
- 删除list中的重复项
- 题目:
- 选项
分析:
本题的思路是:首先使用cur遍历整个链表,刚开始先保存第一个结点的值,然后使cur和p都从第二个开始,然后不断遍历后面的结点,如果此时p指向的结点和保存的值是相等,那么此时就是重复的值,接下来可能出现两种情况:cur和p指向同一个结点,cur和p指向不同的结点,如果cur和p指向同一个结点,删除当前p指向的结点,同时更新cur和p指向下一个结点。如果cur和p指向不同的结点,删除当前p指向的结点,更新p指向下一个结点。更新cue和p是通过list.erase()的返回值来实现的。
自己实现的代码:
// 删除链表中的重复项
template <typename T>
void RemoveDuplicate(list<T>& mylist)
{
// 保存每一次不重复值的变量
T value;
// 迭代器遍历链表的
typename list<T>::iterator cur, p;
cur = mylist.begin();
while (cur != mylist.end())
{
value = *cur;
p = ++cur;
while (p != mylist.end())
{
if (*p == value)
{
if (p == cur)
{
p = cur = mylist.erase(p);
}
else
{
p = mylist.erase(p);
}
}
else
{
p++;
}
}
}
}
void test_list1()
{
list<int> mylist;
mylist.push_back(1);
mylist.push_back(2);
mylist.push_back(1);
mylist.push_back(1);
mylist.push_back(3);
mylist.push_back(5);
mylist.push_back(5);
list<int>::iterator lit = mylist.begin();
while (lit != mylist.end())
{
cout << *lit << " ";
lit++;
}
cout << endl;
RemoveDuplicate(mylist);
for (auto& e : mylist)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_list1();
return 0;
}
运行结果: