這裏主要介紹鏈表的基本知識,加深對鏈表的瞭解,以及關於鏈表的常見的面試題。最後介紹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