一、什么是双向链表
1、定义
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
双向链表的结构如图(图片来源于网络):
2、时空复杂度
双向链表的空间复杂度是 O ( n ) O(n) O(n) 的,其时间复杂度如下:
操作 | 时间复杂度 |
---|---|
遍历 | O ( n ) O(n) O(n) |
访问指定节点 | O ( 1 ) O(1) O(1) |
删除指定编号节点 | O ( n ) O(n) O(n) |
删除指定位置节点 | O ( 1 ) O(1) O(1) |
在指定编号的节点后插入节点 | O ( n ) O(n) O(n) |
在指定位置的节点后插入节点 | O ( 1 ) O(1) O(1) |
查询前驱、后继 | O ( 1 ) O(1) O(1) |
修改指定编号节点的值 | O ( n ) O(n) O(n) |
修改指定位置节点的值 | O ( 1 ) O(1) O(1) |
交换两个 list 容器 | O ( 1 ) O(1) O(1) |
二、双向链表的基本操作
1. 定义双向链表节点
每个节点有三个值:
-
val
:存储每个节点的权值; -
last
:指向每个节点的前面的第一个节点; -
next
:指向每个节点的后面的第一个节点;
代码如下:
template<typename T>
struct ListNode{
T value;
ListNode<T>* last;
ListNode<T>* next;
ListNode():value(0){
last=NULL,next=NULL;
}
ListNode(const T &x):value(x){
last=NULL,next=NULL;
}
~ListNode(){
value=0;
delete last;
delete next;
}
};
2. 创建双向链表类
类里面包含两个节点和一个变量:
-
headnode
:头节点,初始时前驱后继均为空,值为 − 1 -1 −1; -
endnode
:尾节点,初始时前驱后继均为空,值为 − 1 -1 −1; -
listsize
:记录双向链表的节点个数,不包含头尾节点;
代码如下:
template<typename T>
class list{
private:
unsigned listsize;
ListNode<T>* headnode;
ListNode<T>* endnode;
};
3. 初始化双向链表类
共有四种初始化方式:
-
list<类型名> a;
:此时创建一个空的双向链表; -
list<类型名> a(n);
:此时创建一个大小为 n n n 的双向链表,并将所有点的初始值赋为 0 0 0; -
list<类型名> a(n,m)
:此时创建一个大小为 n n n 的双向链表,并将所有点的初始值赋为 m m m; -
list<类型名> a={a1,a2,a3,...,an};
:此时创建一个大小为 n n n 的双向链表,并将第 i i i 个节点的初始值赋为 a i a_i ai;
第一种初始化方式代码如下:
list():listsize(0){
headnode=new ListNode<T>(-1);
endnode=new ListNode<T>(-1);
}
第二种初始化方式代码如下:
list(const int &size_t):
listsize(size_t) {
headnode=new ListNode<T>(-1);
endnode=new ListNode<T>(-1);
ListNode<T>* now=headnode;
for(int i=0;i<listsize;++i){
ListNode<T>* newnode=new ListNode<T>(0);
endnode->last=newnode;newnode->next=endnode;
newnode->last=now;now->next=newnode;
now=now->next;
}
}
第三种初始化方式代码如下:
list(
const int &size_t,
const int &val
):listsize(size_t){
headnode=new ListNode<T>(-1);
endnode=new ListNode<T>(-1);
ListNode<T>* now=headnode;
for(int i=0;i<listsize;++i){
ListNode<T>* newnode=new ListNode<T>(val);
endnode->last=newnode;newnode->next=endnode;
newnode->last=now;now->next=newnode;
now=now->next;
}
}
第四种初始化方式代码如下:
typedef std::initializer_list<T> lisval;
list(lisval vals){
listsize=0;
headnode=new ListNode<T>(-1);
endnode=new ListNode<T>(-1);
ListNode<T>* now=headnode;
for(auto val:vals){
ListNode<T>* newnode=new ListNode<T>(val);
endnode->last=newnode;newnode->next=endnode;
newnode->last=now;now->next=newnode;
now=now->next; ++listsize;
}
}
3. 一些基础的函数
这些函数是除了加点删点之外最常见的几个函数。
-
size()
:获取链表的大小,返回一个 unsigned 值,表示当前链表中普通节点(非头尾节点)的个数。代码如下:
unsigned size() const { return listsize; }
-
empty()
:返回当前链表是否为空,如果是,返回 true,否则返回 false。代码如下:
bool empty() const { return listsize==0; }
-
begin()
:返回第一个普通节点。代码如下:
ListNode<T>* begin( ) const { return headnode->next; }
-
end()
:返回尾指针。代码如下:
ListNode<T>* end( ) const { return endnode; }
-
rbegin()
:返回最后一个普通节点。代码如下:
ListNode<T>* rbegin( ) const { return endnode->last; }
-
rend()
:返回头指针。代码如下:
ListNode<T>* rend( ) const { return headnode; }
-
front()
:返回第一个普通节点的值。代码如下:
T front() const { return begin()->value; }
-
back()
:返回最后一个普通节点的值。代码如下:
T back() const { return rbegin()->value; }
-
print()
:遍历并输出链表中每个普通节点的值,结尾换行。代码如下:
void print( ) const { if(empty()) return; ListNode<T>* now=headnode->next; while(now->next!=NULL){ printf("%d ",now->value); now=now->next; } putchar('\n'); }
-
swap(list<类型名> &b)
:交换两个 list 容器,实际上是交换头尾指针和 l i s t s i z e listsize listsize。代码如下:
void swap(list<T> &b){ ListNode<T>* temp; temp=headnode; headnode=b.headnode; b.headnode=temp; temp=endnode; endnode=b.endnode; b.endnode=temp; unsigned size_t=listsize; listsize=b.listsize; b.listsize=size_t; }
5. 插入节点
共四种方法,代码如下:
void push_back(
const T &val
){ ++listsize;
if(endnode->last==NULL){
ListNode<T>* newnode=new ListNode<T>(val);
endnode->last=newnode;newnode->next=endnode;
headnode->next=newnode;newnode->last=headnode;
return;
}
ListNode<T>* pre=endnode->last;
ListNode<T>* newnode=new ListNode<T>(val);
pre->next=newnode;newnode->last=pre;
newnode->next=endnode;endnode->last=newnode;
}
void push_front(
const T &val
){ ++listsize;
if(headnode->next==NULL){
ListNode<T>* newnode=new ListNode<T>(val);
endnode->last=newnode;newnode->next=endnode;
headnode->next=newnode;newnode->last=headnode;
return;
}
ListNode<T>* suf=headnode->next;
ListNode<T>* newnode=new ListNode<T>(val);
headnode->next=newnode;newnode->last=headnode;
newnode->next=suf;suf->last=newnode;
}
void insert(
const T &pos,
const T &val
){
int nowpos=0;
if(pos==0){
push_front(val);
++listsize; return;
} else if(pos>=listsize){
push_back(val);
++listsize; return;
}
ListNode<T>* now=headnode->next;
while(now->next!=NULL){
++nowpos;
if(nowpos==pos){
ListNode<T>* newnode=new ListNode<T>(val);
ListNode<T>* suf=now->next;
newnode->next=suf;suf->last=newnode;
newnode->last=now;now->next=newnode;
++listsize; return;
}
now=now->next;
}
}
void insert(
ListNode<T>* now,
const T &val
){
if(now==endnode){push_back(val); return;}
ListNode<T>* newnode=new ListNode<T>(val);
ListNode<T>* suf=now->next;
newnode->next=suf;suf->last=newnode;
newnode->last=now;now->next=newnode;
++listsize; return;
}
6. 修改指定位置的值
两种方法,代码如下:
void reassign(
const T &pos,
const T &val
){
if(pos>listsize) return;
if(empty()||!pos) return;
ListNode<T>* now=headnode->next;
int nowpos=0;
while(now->next!=NULL){
++nowpos;
if(nowpos==pos){
now->value=val;
return;
} now=now->next;
}
}
void reassign(
ListNode<T>* now,
const int &val
) const {
now->value=val;
}
7.删除节点
和插入一样,共有四种,代码如下:
void pop_back(){
if(empty()) return;
ListNode<T>* now=endnode->last;
ListNode<T>* pre=now->last;
if(pre==headnode){
endnode->last=NULL;
headnode->last=NULL;
--listsize; return;
}
endnode->last=pre;
pre->next=endnode;
--listsize;
}
void pop_front(){
if(empty()) return;
ListNode<T>* now=headnode->next;
ListNode<T>* suf=now->next;
if(suf==endnode){
endnode->last=NULL;
headnode->last=NULL;
--listsize; return;
}
headnode->next=suf;
suf->last=headnode;
--listsize;
}
void erase(
const int &pos
) {
if(pos>listsize) return;
if(empty()||!pos) return;
ListNode<T>* now=headnode->next;
int nowpos=0;
while(now!=endnode){
++nowpos;
if(nowpos==pos){
ListNode<T>* pre=now->last;
ListNode<T>* suf=now->next;
if(pre