• 启迪思维:循环链表


    一:概念

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

    二:链表名词解释

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

     

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

  • 相关阅读:
    [树莓派]wifi在面板看不到,但是可以scan到的解决方案
    关于GCD的几个结论
    Ubuntu配置vncserver
    树莓派更改vnc分辨率
    玩转树莓派
    两个树莓派(或香蕉派)之间的音频直播测试
    使用树莓派录音——USB声卡
    树莓派自定义命令(给命令起别名)
    gitlab搭建
    linux下的缓存机制及清理buffer/cache/swap的方法梳理 (转)
  • 原文地址:https://www.cnblogs.com/sunysen/p/3051813.html
Copyright © 2020-2023  润新知