这里主要介绍链表的基本知识,加深对链表的了解,以及关于链表的常见的面试题。
一、链表基础知识
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