单链表
我们日常接触最多的就是单链表。 其优点很明显,增删改很方便。 但非连续存储的方式,导致了我们随机访问第i个元素的时候必须从头遍历,时间复杂度为O(N) 所以单链表比较合适于以下频繁的场景:
但不适用于随机访问频繁的场景。
list的实现——基于双向循环链表
这个我们之前也提及过: 添加链接描述 我们之前说list列表基于双向链表实现,但没说细节,我们首先来看一下node的情况:既然是双向链表,很明显会有前后两个指针和一个val值。
template <class T>
struct __list_node {
__list_node<T>* next;
__list_node<T>* prev;
T data;
};
我们之前还说指针迭代器的问题,我们来看一下迭代器具体怎么实现的:
template<typename T>
struct __list_iterator{
typedef __list_iterator<T> self;
typedef __list_node<T>* link_type;
link_type ptr;
__list_iterator(link_type p = nullptr):ptr(p){}
}
T& operator *(){return ptr->data;}
T* operator ->(){return &(operator*());}
self& operator++(){
ptr = ptr->next;
return *this;
}
self operator++(int){
self tmp = *this;
++*this;
return tmp;
}
self& operator--(){
ptr = ptr->prev;
return *this;
}
self operator--(int){
self tmp = *this;
--*this;
return tmp;
}
bool operator==(const __list_iterator& rhs){
return ptr == rhs.ptr;
}
bool operator!=(const __list_iterator& rhs){
return !(*this==rhs);
}
注意重载++和–的时候,默认是后置,如果要重载前置需要添加&符号。 后置返回的都是先保存当前节点,改变this指针后返回当前节点。 而前置只需要移动当前指针而后返回即可。 我们可以看见迭代器,主要是对ptr这个成员变量进行处理。
具体实现list:
template<typename T>
class list{
protected:
typedef __list_node<T> list_node;
typedef allocator<list_node> nodeAllocator;
public:
typedef T value_type;
typedef T& reference;
typedef value_type* pointer;
typedef list_node* link_type;
typedef const value_type* const_pointer;
typedef size_t size_type;
public:
typedef __list_iterator<value_type> iterator;
private:
link_type node;
}
我们看private里的node指针,我们知道在单链表里,我们习惯用dummy node来表示一个虚拟的头部,这里我们用虚拟节点node来标识循环链表的首位连接点,其pre为最后一个节点,其next为第一个节点。 初始化的时候,只会有一个node节点,其pre和next都指向自己。
list的初始化/插入/删除
初始化:包含一个虚拟的节点node,其首尾都指向自己
void empty_initialize() {
node = get_node(0);
node->next = node;
node->prev = node;
}
link_type get_node() { return list_node_allocator:allocate(); }
insert函数:传参需要两个,一个val和一个迭代器以表明插入位置: 这需要做到:
- 创建一个临时节点temp
- 使temp的pre&next正确指向
- 使原来pre的next指向temp,使原来next的pre指向temp
iterator insert(iterator position, const T& x) {
lik_type tmp = create_node(x);
tmp->next = position.node;
tmp->prev = position.node->prev;
(link_type(position.node->prev))->next = tmp;
position.node->prev = tmp;
return tmp;
}
这在我们刷题的时候也经常用到: 穿针引线法: 注意一定要先把新节点的指针指上去之后,再把旧节点的指针指过来
我们再看删除erase:
iterator erase(iterator position) {
link_type next_node = link_type(position.node->next);
link_type prev_node = link_type(position.node->prev);
prev_node->next = next_node;
next_node->prev = prev_node;
destroy_node(position.node);
return iterator(next_node);
}
先将前后节点连在一块儿,再释放待删除节点。
有了Insert和erase,我们可以用这两个函数以及begin&end这两个迭代器,实现pop_front/pop_back/push_back,思路是在指定位置进行insert/erase
(注意begin节点指向虚拟节点的next,即第一个node。而end指向虚拟节点,即最后一个node的next) 比如:
void pop_front() {erase(begin())};
void pop_back() {
iterator temp=end();
erase(--temp);
}
push_back(const T&x) {insert(end(),x);}
注意pop_back()要先–temp,因为end指向虚拟节点,并不是最后一个node
|