算法(10) 链表
来源:互联网 发布:数据驱动下的网红营销 编辑:程序博客网 时间:2024/05/21 09:42
这篇的代码不是自己写的是参考来的,虽然原博主代码写得不是很规范,但是基本思路是正确的,可作为学习的材料。原博地址 http://blog.csdn.net/cyp331203/article/details/42388065
———————————————————————————————————————————————————
不带哨兵节点的双向链表即一般的双向链表,有一个头指针指向第一个节点,每个节点有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;
- }
- 算法(10) 链表
- 每日算法(链表)
- 银行家算法(链表)
- 算法-->查找 (链表)
- 算法、排序(10)
- dsa算法(10)
- 算法导论(10)
- java算法(10)
- c++ primer (第10章 泛型算法) 算法
- 《算法导论》笔记(10)贪心算法 部分习题
- 经典排序算法(10)——基数排序算法详解
- Luhn算法(模10算法)检验银行卡号正确性
- 算法的一些小栗子10(回溯算法)
- 算法导论习题(10)
- (10)边填充算法
- 算法导论读书笔记(10)
- Java与算法(10)
- EM算法(算法原理+算法收敛性)
- 基于ZigBee的物联网家居系统学习(硬件尾声到android端)
- 解决Win10家庭版无法使用TeamViewer 13
- c++ concurrency in action中的 SpinLockMutex
- 程序员,这样面试,你的成功率能达到 90%!
- 实验(加权图)
- 算法(10) 链表
- pyqt学习
- 哈希表-开放地址法之线性探测
- Build Qt for android 遇到的一些坑
- 小程序应该如何入门,初学者应该掌握哪些基本知识?
- generatorConfig.xml详解
- zookeeper一键启动脚本配置
- Coursera deeplearning.ai 深度学习笔记2-1-Practical aspects of deep learning-神经网络实际问题分析(初始化&正则化&训练效率)与代码实现
- js工具与开发库