不带哨兵节点的双向链表即一般的双向链表,有一个头指针指向第一个节点,每个节点有key值和两个指针next和pre,分别指向前后相邻的节点,头结点的pre=NULL,尾节点的next=NULL,比较明了,但是也有麻烦的地方:在做查找删除节点等操作的时候,免不了要判断边界条件,比如node==NULL等。先来看看这种链表的代码:
/*
* 实现没有哨兵节点的双向链表,需要自己判断边界条件
*/
#include <iostream>
using namespace std;
class list {
struct node {
int key;
node* next;
node* pre;
node(int k) :
key(k), next(NULL), pre(NULL) {
}
};
public:
node* head;
int len;
list() :
head(NULL), len(0) {
}
~list() {
node* tmp1 = head, *tmp2;
while (tmp1 != NULL) {
tmp2 = tmp1->next;
delete tmp1;
len--;
cout << "len=" << len << endl;
tmp1 = tmp2;
}
}
/*
* 在头处插入节点
*/
void insertHead(int k) {
node* n = new node(k);
n->next = head;
head = n;
if (n->next != NULL) {
n->next->pre = n;
}
len++;
}
/*
* 删除指针指向的节点
*/
int del(node* n) {
if (n == NULL) {
return -1;
}
if (n->pre != NULL) {
n->pre->next = n->next;
} else {
head = n->next;
}
if (n->next != NULL) {
n->next->pre = n->pre;
}
delete n;
len--;
return n->key;
}
/*
* 查找具有某个key值的节点
*/
node* searchList(int k) {
if (len == 0) {
return NULL;
}
node* tmp = head;
while (tmp != NULL) {
if (tmp->key == k) {
break;
}
tmp = tmp->next;
}
return tmp;
}
/*
* 遍历list
*/
void travelList() {
node* tmp = head;
while (tmp != NULL) {
cout << tmp->key << ' ';
tmp = tmp->next;
}
cout << endl;
}
};
int main() {
list* l = new list();
l->insertHead(5);
l->insertHead(4);
l->insertHead(3);
l->travelList();
l->del(l->head->next);
l->travelList();
delete l;
return 0;
}
每次判断边界条件,虽然不会从根本上增加时间复杂度,但是对其常数项还是有影响的;而如果使用带哨兵节点构成的双向循环链表,则可以省去这些问题。我们使用一个“哑的”NIL节点来代替之前的head头指针,NIL节点的key值没有实际的意义,主要关注它的next和pre,初始的时候,链表只有一个NIL节点,NIL.next指向自己,NIL.pre也指向自己。当添加了若干个节点之后,NIL.next指向头节点,而NIL.pre则指向尾节点;而同样的,这时头节点的pre不再是NULL而是指向NIL,尾节点的next也不再是NULL,也是指向NIL。
这样的好处在于,我们判断边界条件的时候,不需要再判断是否为空,尤其在删除节点的时候,只需要写两句即可。但是这样也带来一些问题,就是要额外分配空间来存储NIL节点,如果对于多个比较短的链表而言,这样可能会代码比较大的冗余空间。
代码如下:
/*
* 实现带哨兵是双向循环链表
*/
#include <iostream>
using namespace std;
class list {
struct node {
int key;
node* next;
node* pre;
node(int k) :
key(k), next(NULL), pre(NULL) {
}
};
// node* head;
public:
node* nil; //哨兵节点
int len;
list() :
nil(), len(0) {
nil = new node(0); //初始化哨兵节点
//让链表循环起来
nil->next = nil;
nil->pre = nil;
}
~list() {
//析构的时候要delete掉还存在于list中的节点
if (len == 0) {
delete nil;
return;
}
node* n1 = nil->next;
node* n2 = NULL;
while (n1 != NULL && n1 != nil) {
n2 = n1->next;
delete n1;
len--;
n1 = n2;
}
delete nil;
}
/*
* 在头部插入节点
*/
void insertHead(int k) {
node* n = new node(k);
n->next = nil->next;
n->pre = nil;
nil->next = n;
//这一句不要丢了
n->next->pre = n;
len++;
}
/*
* 删除节点,这里删除的操作只需要写两句,比不带哨兵的链表操作要简洁的多
*/
void del(node* n) {
n->pre->next = n->next;
n->next->pre = n->pre;
delete n;
len--;
}
node* searchList(int k) {
node* tmp = nil->next;
//让nil的key值永远不可能等于k
// nil->key = k + 1;
// while (tmp->key != k) {
while (tmp != nil && tmp->key != k) {
tmp = tmp->next;
}
if (tmp != nil) {
return tmp;
} else {
return NULL;
}
}
/*
* 从next指针方向遍历链表
*/
void travelClockwise() {
node* tmp = nil->next;
while (tmp != nil) {
cout << tmp->key << ' ';
tmp = tmp->next;
}
cout << endl;
}
/*
* 从pre指针方向遍历链表
*/
void travelAnticlockwise() {
node* tmp = nil->pre;
while (tmp != nil) {
cout << tmp->key << ' ';
tmp = tmp->pre;
}
cout << endl;
}
};
int main() {
list* l = new list();
l->insertHead(5);
l->insertHead(4);
l->insertHead(3);
l->travelClockwise();
l->del(l->nil->pre);
// l->travelClockwise();
l->travelAnticlockwise();
return 0;
}