启迪思维:循环链表
一:概念
循环链表是一种链式存储结构,它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同。
二:链表名词解释
1、链表的“每个节点”都包含一个”数据域“和”指针域“;
2、”数据域“中包含当前的数据;
3、”指针域“中包含下一个节点的指针;
4、”头指针”也就是head,指向头结点数据;
5、“末节点“作为单向链表,因为是最后一个节点,通常设置指针域为头结点;
如下示例图(空的循环链表)
如下示例图(有数据循环链表)
三:链表的优缺点
链表最大的一个缺点,就是查找一个元素,必须整个链表遍历,也可以看出这种数据结构便于插入或者删除一个接点;
四:经典问题求解
约瑟夫问题;
五:代码分析
1、插入节点
每次想到自己能搞懂数据结构一部分知识,都在心里默默感谢发明图的人(推荐大家看看<<打开餐巾纸>>),如下图
/** * 插入元素到指定位置 * 在我们实际项目开发中很少会用到链表的带有两个参数的方法,代码更常见Insert(e)或者Add(e) * 所有下面方法,正常情况下应该是私有,这样才能体现出面向对象封装 * * size_t 无符号的类型,一个好的方法(1、一个易懂的方法和参数名称; * 2、准确参数内型(插入链表不存在负数);3、友好错误提示;4、短小漂亮的代码;5、必要的注释) * * const和& 一般都是程序员装逼的利器,阅读过很多开源项目的代码,在对于基本类型(int,bool char....) * 都没有加const和&,对于自定义和string类型必加,这样提醒我们写代码的时候,不要处处都装逼 */ bool Insert(size_t i,const T &e){ //查询插入的位置 int j = 0; LNode<T> *p = tail->next; while(j < i-1){ p = p->next; j++; } //校验参数的合法性,如果把次方法私有,这个代码可删除 if(p == 0 || j > i -1){ return false; } //创建一个新的新的节点 LNode<T> *q = new LNode<T>(e); //指向插入的后继节点的下一个节点 q->next = p->next; //更新前驱节点指针域 p->next = q; if(p == tail){ tail = q; } return true; }
2、删除节点
1 /** 2 *删除循环链表中的节点 3 */ 4 bool Delete(size_t i){ 5 6 //查询删除的位置 7 int j = 0; 8 LNode<T> *p = tail->next; 9 while(p != 0 && j < i -1){ 10 p = p->next; 11 j++; 12 } 13 14 //校验参数的合法性 15 if(p == 0 && j > i -1){ 16 return false; 17 } 18 19 //智能指针(资源获取即初始化),更多知识请阅读<<More Effective C++>> 20 std::auto_ptr<LNode<T> > new_ptr(p->next); 21 p->next = new_ptr->next; 22 23 //如果尾节点,需要更新头节点 24 if(tail == new_ptr.get()){ 25 tail = p; 26 } 27 return true; 28 }
3、初始化和清空链表
1 /** 2 *构造函数 3 */ 4 LinkListCy():tail(new LNode<T>(0)){ 5 tail->next = tail; 6 } 7 /** 8 *析构函数 9 */ 10 ~LinkListCy(){ 11 Clear(); 12 delete tail; 13 } 14 15 /** 16 *请循环链表,头结点指向头结点 17 */ 18 void Clear(){ 19 LNode<T> *p,*q; 20 tail = tail->next; 21 p = tail->next; 22 23 //循环删除结点数据,并回收结点内存(这样频繁创建和回收会造成大量内存碎片,导致系统性能下降) 24 //更优秀的内存内存可以参考STL的内存分配和boost的内存分配机制 25 while(p != tail){ 26 //只能指针(资源获取即初始化),更多知识请阅读<<More Effective C++>> 27 std::auto_ptr<LNode<T> > new_ptr(p); 28 q = new_ptr->next; 29 p = q; 30 } 31 //头结点指向头结点 32 tail->next = tail; 33 }
4、判断是否为空
1 /** 2 *判断循环链表是否为空 3 */ 4 bool Empty() const{ 5 return tail->next == tail; 6 }
5、计算链表长度
1 /** 2 *计算循环链表的长度 3 */ 4 int Length() const{ 5 int i = 0; 6 LNode<T> *p = tail->next; 7 8 while(p != tail){ 9 i++; 10 p = p->next; 11 } 12 13 return i; 14 }
6、遍历链表
1 /** 2 * 遍历循环链表 3 */ 4 void Traversal(){ 5 6 //获取循环链表第一个节点 7 LNode<int> *p = tail->next->next; 8 9 //循环打印出链表的数据 10 while(p != tail->next){ 11 if(p->data){ 12 std::cout<<"p is value = "<<p->data<<std::endl; 13 } 14 p = p->next; 15 } 16 }
7、测试代码段如下
1 /** 2 *测试代码 3 */ 4 void test(){ 5 std::cout<<"-----------insert begin------------"<<std::endl; 6 for(int i = 1; i <= 5; ++i){ 7 Insert(i); 8 } 9 std::cout<<"-----------insert end------------"<<std::endl; 10 11 std::cout<<"frist list length="<<Length()<<std::endl; 12 13 std::cout<<"-----------delete begin----------"<<std::endl; 14 Delete(1); 15 std::cout<<"-----------delete end----------"<<std::endl; 16 17 std::cout<<"second list length="<<Length()<<std::endl; 18 19 std::cout<<"-----------traversal of the elemetns begin----------"<<std::endl; 20 21 Traversal(); 22 23 Clear(); 24 25 std::cout<<"third list length="<<Length()<<std::endl; 26 }
8、运行结果
9、完整代码
View Code
1 /* 2 * LinkListCy.h 3 * 4 * Created on: 2013-04-11 5 * Author: hs 6 */ 7 8 #ifndef LINKLISTCY_H_ 9 #define LINKLISTCY_H_ 10 11 12 template <class T> 13 class LinkListCy{ 14 private: 15 LNode<T> *tail; 16 public: 17 /** 18 *构造函数 19 */ 20 LinkListCy():tail(new LNode<T>(0)){ 21 tail->next = tail; 22 } 23 /** 24 *析构函数 25 */ 26 ~LinkListCy(){ 27 Clear(); 28 delete tail; 29 } 30 31 /** 32 *请循环链表,头结点指向头结点 33 */ 34 void Clear(){ 35 LNode<T> *p,*q; 36 tail = tail->next; 37 p = tail->next; 38 39 //循环删除结点数据,并回收结点内存(这样频繁创建和回收会造成大量内存碎片,导致系统性能下降) 40 //更优秀的内存内存可以参考STL的内存分配和boost的内存分配机制 41 while(p != tail){ 42 //智能指针(资源获取即初始化),更多知识请阅读<<More Effective C++>> 43 std::auto_ptr<LNode<T> > new_ptr(p); 44 q = new_ptr->next; 45 p = q; 46 } 47 //头结点指向头结点 48 tail->next = tail; 49 } 50 51 /** 52 *判断循环链表是否为空 53 */ 54 bool Empty() const{ 55 return tail->next == tail; 56 } 57 58 /** 59 *删除循环链表中的节点 60 */ 61 bool Delete(size_t i){ 62 63 //查询删除的位置 64 int j = 0; 65 LNode<T> *p = tail->next; 66 while(p != 0 && j < i -1){ 67 p = p->next; 68 j++; 69 } 70 71 //校验参数的合法性 72 if(p == 0 && j > i -1){ 73 return false; 74 } 75 76 //智能指针(资源获取即初始化),更多知识请阅读<<More Effective C++>> 77 std::auto_ptr<LNode<T> > new_ptr(p->next); 78 p->next = new_ptr->next; 79 80 //如果尾节点,需要更新头节点 81 if(tail == new_ptr.get()){ 82 tail = p; 83 } 84 return true; 85 } 86 87 /** 88 * 插入元素到指定位置 89 * 在我们实际项目开发中很少会用到链表的带有两个参数的方法,代码更常见Insert(e)或者Add(e) 90 * 所有下面方法,正常情况下应该是私有,这样才能体现出面向对象封装 91 * 92 * size_t 无符号的类型,一个好的方法(1、一个易懂的方法和参数名称; 93 * 2、准确参数内型(插入链表不存在负数);3、友好错误提示;4、短小漂亮的代码;5、必要的注释) 94 * 95 * const和& 一般都是程序员装逼的利器,阅读过很多开源项目的代码,在对于基本类型(int,bool char....) 96 * 都没有加const和&,对于自定义和string类型必加,这样提醒我们写代码的时候,不要处处都装逼 97 */ 98 bool Insert(size_t i,const T &e){ 99 100 //查询插入的位置 101 int j = 0; 102 LNode<T> *p = tail->next; 103 while(j < i-1){ 104 p = p->next; 105 j++; 106 } 107 108 //校验参数的合法性,如果把次方法私有,这个代码可删除 109 if(p == 0 || j > i -1){ 110 return false; 111 } 112 113 //创建一个新的新的节点 114 LNode<T> *q = new LNode<T>(e); 115 //指向插入的后继节点的下一个节点 116 q->next = p->next; 117 //更新前驱节点指针域 118 p->next = q; 119 120 if(p == tail){ 121 tail = q; 122 } 123 return true; 124 } 125 /** 126 *插入元素到循环链表中 127 */ 128 void Insert(const T &e){ 129 this->Insert(1,e); 130 } 131 132 /** 133 *计算循环链表的长度 134 */ 135 int Length() const{ 136 int i = 0; 137 LNode<T> *p = tail->next; 138 139 while(p != tail){ 140 i++; 141 p = p->next; 142 } 143 144 return i; 145 } 146 /** 147 * 遍历循环链表 148 */ 149 void Traversal(){ 150 151 //获取循环链表第一个节点 152 LNode<int> *p = tail->next->next; 153 154 //循环打印出链表的数据 155 while(p != tail->next){ 156 if(p->data){ 157 std::cout<<"p is value = "<<p->data<<std::endl; 158 } 159 p = p->next; 160 } 161 } 162 /** 163 *测试代码 164 */ 165 void test(){ 166 std::cout<<"-----------insert begin------------"<<std::endl; 167 for(int i = 1; i <= 5; ++i){ 168 Insert(i); 169 } 170 std::cout<<"-----------insert end------------"<<std::endl; 171 172 std::cout<<"frist list length="<<Length()<<std::endl; 173 174 std::cout<<"-----------delete begin----------"<<std::endl; 175 Delete(1); 176 std::cout<<"-----------delete end----------"<<std::endl; 177 178 std::cout<<"second list length="<<Length()<<std::endl; 179 180 std::cout<<"-----------traversal of the elemetns begin----------"<<std::endl; 181 182 Traversal(); 183 184 Clear(); 185 186 std::cout<<"third list length="<<Length()<<std::endl; 187 } 188 }; 189 190 #endif /* LINKLISTCY_H_ */
六:环境
1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3;
2、开发工具:Eclipse+make
七:题记
1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;
2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;
3、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛";
欢迎继续阅读“启迪思维:数据结构和算法”系列