一,目的。
实现无头双链表,并完成其基本的操作,包括插入,删除等。
二,为什么有单链表了还要有双向链表?
双向链表,顾名思义,相对于单链表,每个节点不仅要有指向其后继的指针,还有指向其前驱的指针。首先我们来看节点的定义:
template<class T>
struct Node
{
T data; //节点的数据
Node<T>* _prev; //其前驱
Node<T>* _next; //其后继
Node(const T& x) //构造函数
:
data(x),
_prev(NULL),
_next(NULL)
{}
};
回到正题为什么有了单链表还要提出双向链表的概念呢?
单链表在对一个元素访问的时候,只能把链表遍历一遍,而且只能获得其后继,如果想要知道它前驱的信息,便只有再遍历一遍链表。而双向链表在对元素访问的时候可以很容易的获得某个元素前驱以及其后继,可进可退,也很容易实现链表的逆置等操作。
三,双向链表的实现
template<class T>
class LinkList
{
public:
typedef struct Node<T> Node;
LinkList() //默认构造函数
{
_head = _tail = NULL;
}
LinkList(const LinkList<T>& x) //拷贝构造函数
{
Node* cur = x._head;
while (cur)
{
push_back(cur->data);
cur = cur->_next;
}
}
LinkList<T>& operator=(const LinkList<T>& other) //等号运算符重载
{
if (&other == this)
return *this;
destory(_head);
Node* cur = other._head;
while (cur)
{
push_back(cur->data);
cur = cur->_next;
}
return *this;
}
void push_back(const T& x) //插入操作,在尾节点插入
{
if (_head == NULL) //当链表为空时,使头结点,和尾结点指向该节点
{
Node* temp = new Node(x);
_head = temp;
_tail = temp;
}
else //头节点不为空,此时只需要改变尾节点
{
Node* temp = new Node(x);
_tail->_next = temp;
temp->_prev =_tail;
_tail = temp;
}
}
void pop_back() //从尾部pop元素
{
if (_head == NULL) //判断链表是否为空
return;
if (_head == _tail) //只有一个元素的时候
{
delete _head;
_head = _tail = NULL;
}
else //删除可以直接从尾部删除
{
Node* temp = _tail->_prev;
delete _tail;
temp->_next = NULL;
_tail = temp;
}
}
~LinkList() //析构函数
{
destory(_head);
}
void Print() const
{
Node* cur = _head;
while (cur)
{
cout << cur->data << " ";
cur = cur->_next;
}
cout << endl;
}
size_t Size() const //获得链表的长度
{
Node* cur = _head;
size_t num = 0;
while (cur)
{
++num;
cur = cur->_next;
}
return num;
}
void Insert(int pos,const T& x) //指定位置处插入一个新的节点,从1开始
{
size_t len = Size();
if(pos<1 || pos>len) //插入位置不合法
return;
Node* cur = _head;
if (pos == 1 || pos ==len) //在头尾插,需要调整head和tail
{
if (len == 0) //当链表为空时
{
push_back(x);
}
else
{
if (pos == 1) //在头部插入新元素,需要更新head
{
Node* cur = new Node(x);
cur->_next = _head;
_head->_prev = cur;
_head = cur;
}
else //在尾部插入,需要更新tail的值
{
Node* cur = _tail;
Node* temp = new Node(x);
cur->_next = temp;
temp->_prev = _tail;
_tail = temp;
}
}
}
else //不在尾也不在头进行插入
{
Node* cur = _head;
for (size_t i = 0; i < pos-1; ++i)
{
cur = cur->_next;
}
Node* prev = cur->_prev;
Node* newNode = new Node(x);
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev=newNode;
}
}
void Swap(LinkList<T>& other) //交换两个链表
{
swap(this->_head, other._head); //只需要改变头尾节点信息即可
swap(this->_tail, other._tail);
}
Node* getHead() //获得头结点
{
if (_head)
return _head;
return NULL;
}
private:
Node* _head;
Node* _tail;
void destory(Node* head) //元素的销毁
{
while (head)
{
Node* temp = head->_next;
delete head;
head = temp;
}
_head = _tail = NULL;
}
};
四,总结
1,带头节点与不带头节点的不同在于:当链表不带头时,进行插入或删除时若正好是头结点则需要对头结点进行改变;而带头节点的链表。由于一开始就有一个节点在占位,所以不存在那样的问题。
2,在插入的时候需要考虑多种情况。