启迪思维:循环链表

一:概念

循环链表是一种链式存储结构,它的最后一个结点指向头结点,形成一个环。因此,从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同。

二:链表名词解释

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、鼓励自己能坚持把更多数据结构方面的知识写出来,让自己掌握更深刻,也顺便冒充下"小牛"

 

 欢迎继续阅读“启迪思维:数据结构和算法”系列

posted @ 2013-04-30 14:09  sunysen  阅读(563)  评论(2编辑  收藏  举报