这里主要介绍链表的基本知识,加深对链表的了解,以及关于链表的常见的面试题。最后介绍STL中的迭代器失效的问题
一、链表基础知识
1.概念
链表是一种物理存储结构上非连续/非顺序的存储结构。链表的每个结点里面存储着下一个结点的指针,把存储数据元素的数据串链起来。
2.结点组成
数据域:存储数据元素
指针域:存储下一个结点地址的指针域
3.分类
单链表:是一种逻辑上的线性结构,只有一条链,首尾不相连,方向不会改变。
双向链表:指针域有两个指针,分别指向它的前驱结点和后继个结点。
循环链表:尾结点的指针域的指针指向头结点(头尾相连形成一个环形结构)
4.优缺点
优点:
1.创建结点:克服预先知道数据大小的缺点,充分利用计算机空间,实现灵活的内存动态管理。
2.删除结点:删除结点时只需要修改结点的指针域,不需要将其他数据向前移动。
注:优缺点一般是相对于顺序存储。
缺点:
1.访问结点:通过循环或递归访问到链表的数据,访问效率低于线性数据结构。
2、存储方面:增加结点的指针域,空间开销比较大。
二、链表的实现
1.单链表
单链表结构
插入结点
Node* BuyNode(DataType x)
{
Node* node = (Node* )malloc(sizeof(Node));
node->data = x;
node->next = NULL;
return node;
}
void PushFront(Node** pphead, DataType x)
{
if(*pphead == NULL)
{
*pphead = BuyNode (x);
}
else
{
Node* node = BuyNode(x);
node->next = *pphead;
*pphead = node;
}
}
void Insert(Node** pphead,Node* pos,DataType x)
{
assert(pos);
if(*pphead == pos)//头插
{
PushFront(pphead,x);
}
else
{
Node* prev = *pphead ;
Node* tmp = BuyNode(x);
while(prev->next != pos)
{
prev = prev->next ;
}
tmp ->next = pos;
prev->next = tmp;
}
}
删除结点
void Erase(Node** pphead,Node* pos)
{
assert(pos);
if(*pphead == pos)
{
PopFront (pphead);
}
else
{
Node* cur = *pphead;
while(cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
}
}
2.双向链表
插入结点
void List::Insert(Node* pos,const DataType& x)
{
//空链表、一个结点、一般情况
Node* NewNode = new Node(x);
if(_head == pos)
{
if(_head == NULL)
{
_head = _tail = NewNode;
}
else
{
NewNode->_next = _head;
_head->_prev = NewNode;
_head = NewNode;
}
}
else
{
Node* prev = pos->_prev ;
prev->_next = NewNode;
NewNode->_prev = prev;
NewNode->_next = pos;
pos->_prev = NewNode;
}
}
删除结点
void List::Erase(Node* pos)
{
//头删、尾删、一般情况、NULL、一个结点
if(_head == _tail)//一个结点
{
assert(_head);
_head = _tail = NULL;
}
else if(pos == _head)//头删
{
_head = _head ->_next ;
_head ->_prev = NULL;
}
else if(pos == _tail)//尾删
{
Node* tmp = _tail->_prev ;
tmp->_next = NULL;
_tail = tmp;
}
else //一般情况
{
Node* prev = pos->_prev ;
Node* next = pos->_next ;
prev->_next = next;
next->_prev = prev;
}
delete pos;
}
3.双向循环链表
插入结点&删除结点
List()
:_head(new Node(T()))
{
_head->_prev = _head;
_head->_next = _head;
}
template<class T>
void List<T>::Insert(Node* pos, const T& x)
{
assert(pos);
Node* newNode = new Node(x);
Node* prev = pos->_prev;
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = pos;
pos->_prev = newNode;
}
template<class T>
void List<T>::Erase(Node* pos)
{
assert(pos && pos != _head);
Node* prev = pos->_prev ;
Node* next = pos->_next ;
delete pos;
prev->_next = next;
next->_prev = prev;
}
三、STL中List
1.各接口
2.迭代器失效问题
3.实现(迭代器实现)
template<class T, class Ref, class Ptr> //通过实例化的类型不同,实现不同的迭代器
struct _ListIterator
{
typedef _ListIterator<T,Ref, Ptr> Self;
typedef ListNode<T> Node;
Node* _node;
_ListIterator(Node* node) //构造函数
:_node(node)
{}
Ref operator* () //*操作符重载
{
return _node->_data;
}
Self& operator++() //后置++
{
_node = _node->_next;
return *this;
}
Self operator++(int)//前置++
{
Self tmp(*this);
_node = _node->_next;
return tmp; //返回的是临时变量的值,所以用Self
}
Self& operator--() //后置--
{
_node = _node->_prev;
return *this;
}
Self operator--(int) //前置--
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator != (const Self&s )
{
return _node != s._node;
}
bool operator == (const Self&s)
{
return _node == s->_node;
}
Ptr operator->()
{
return &(_node->_data); //当指向结构体时,具有 -> 作用
}
};
总结:从单链表到双向循环链表,可以发现双向循环链表简化增加和删除操作,具体使用哪种链表还依情况而定。对于STL中的list可以说极大的方面了用户操作,迭代器也很好的实现了封装。
本文只是对链表的简单介绍,有关错误欢迎指出。
有关于链表的具体实现详见:https://github.com/zwjuan/List