不带哨兵节点的双向链表即一般的双向链表,有一个头指针指向第一个节点,每个节点有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; }
时间: 2024-10-09 00:13:44