• 启迪思维:双向循环链表


    文章目录:

    01. 博文简介:

    02. 概念:

    03. 示例图:

    04. 优缺点:

    05. 代码分析:

    06. 运行环境:

    07. 题记: 

    一:概念

    双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,一般我们都构造双向循环表,STL中实现了双链表循环表

    二:示例图

     

    三:链表的优缺点

    链表最大的一个缺点,就是查找一个元素,必须整个链表遍历,也可以看出这种数据结构便于插入或者删除一个接点。

    四:代码分析

    1、获取节点信息

     1 /**
     2  *根据位置获取相对应的节点信息
     3  *size_t 是一个无符号的类型,确定调用该函数时入参一定是正数,
     4  *这样的要求也提醒我们在写函数时,应该避免把这样函数暴露给客户
     5  *不知道为什么linux很多api函数都有size_t类型的入参(通过调试,传入负数会导致严重的bug),
     6  *有知道的朋友,请帮忙分享下
     7  */
     8 DLNode<T>* GetElemP(size_t i) const{
     9     int j = 0;
    10     DLNode<T> *p = head;
    11 
    12     do{
    13         p = p->next;
    14         j++;
    15     }while(p != head && j < i);
    16 
    17     return p;
    18 }
    19 
    20 /**
    21  *根据元素获取对应的节点信息
    22  */
    23 DLNode<T>* GetElemE(const T &e) const{
    24     DLNode<T> *p = head->next;
    25 
    26     while(p != head && e != p->data){
    27         p = p->next;
    28     }
    29 
    30     return p;
    31 }

    2、插入节点

     插入过程如下图:

    双向链表添加

     代码分析如下:

     1 /**
     2  *在指定的位置插入节点
     3  */
     4 void Insert(size_t i,const T &e){
     5     //获取指定的位置节点
     6     DLNode<T> *p = GetElemP(i);
     7     //插入新节点
     8     Insert(p,e);
     9 }
    10 /**
    11  *在指定的指针节点插入新的节点
    12  */
    13 void Insert(DLNode<T> *p,const T &e){
    14     //创建一个新的节点
    15     DLNode<T> *q = new DLNode<T>(e);
    16     //设置新节点的向后的指针域(指向图中2节点)
    17     q->next = p->next;
    18     //设置新节点的向前的指针域(指向图中1节点)
    19     q->prev = p;
    20 
    21     //设置插入节点的后继节点的向前指针域(图中3节点的上一个节点指针域指向新节点2)
    22     p->next->prev = q;
    23     //设置插入节点的向后的指针域(指向新节点2)
    24     p->next = q;
    25 }
    26 /**
    27  *从头结点的位置插入新的节点
    28  */
    29 void AddHead(const T &e){
    30     Insert(head,e);
    31 }
    32 /**
    33  *从第一个节点的位置插入新的节点
    34  */
    35 void Insert(const T &e){
    36     Insert(1,e);
    37 }

    3、删除节点

    删除节点如下图,可以从图中看到,删除节点仅仅改变前驱和后继节点相关的指针域,这个就是为什么数据删除高手还可以找回来原因;

    双向链表删除

    代码分析如下:

     1 /**
     2  *根据元素内容删除对应的节点
     3  */
     4 void Delete(const T &e){
     5     //根据元素内容获取对应的节点
     6     DLNode<T> *p = GetElemE(e);
     7     //更多关于auto_ptr知识,请阅读memory里auto_ptr源码
     8     std::auto_ptr<DLNode<T> > new_ptr(p);
     9     //设置删除节点后继节点向前的指针域为删除节点向前指针域
    10     p->next->prev = p->prev;
    11     //设置删除节点前驱节点向后的指针域为删除节点向后指针域
    12     p->prev->next = p->next;
    13 }

    4、遍历节点,在stl源码中有非常强大遍历神器(iterator),把泛型的知识运用到极致,感谢的朋友可以读读源码 

     1 /**
     2  *遍历双向链表
     3  */
     4 void Traverse(){
     5     //指向第一个节点信息
     6     DLNode<T> *p = head->next;
     7 
     8     //如果节点向后指针域等于头结点,表示链表已经遍历完成
     9     while(p != head){
    10         std::cout<<"p is value = "<<p->data;
    11         std::cout<<""<<std::endl;
    12         p = p->next;
    13     }
    14 }

    5、清空链表

     1 /**
     2  *清空整个链表节点
     3  */
     4 void Clear(){
     5     //获取第一个节点信息
     6     DLNode<T> *p = head->next;
     7 
     8     //如果节点向后指针域等于头结点,表示链表已经被清空
     9     while(p != head && p != 0){
    10         //更多关于auto_ptr知识,请阅读memory里auto_ptr源码
    11         std::auto_ptr<DLNode<T> > new_ptr(p);
    12         std::cout<<p;
    13         std::cout<<std::endl;
    14         //执行后继节点
    15         p = p->next;
    16     }
    17     //恢复出厂原状,向前和向后的指针域都指向本身,构成双向链表
    18     head->next = head->prev = head;
    19 }

    6、是否为空和计算链表长度

    /**
     *判断链表是否为空
     */
    bool Empty(){
        return head->next = head;
    }
    
    /**
     *计算链表的长度
     */
    int Length(){
    
        int i = 0;
        DLNode<T> *p = head->next;
    
        //如果节点向后指针域等于头结点,链表遍历完成,
        while(p != head){
            //执行后继节点
            p = p->next;
            //累计链表节点长度
            i++;
        }
    
        return i;
    }

    7、测试代码

     1 void test(){
     2     std::cout<<"-----------insert begin------------"<<std::endl;
     3     for(int i = 1; i <= 5; ++i){
     4         Insert(i);
     5     }
     6     AddHead(6);
     7     AddHead(7);
     8     Traverse();
     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(2);
    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     Traverse();
    22     Clear();
    23     Delete(1);
    24     Clear();
    25     std::cout<<"third list length="<<Length()<<std::endl;
    26 }

    8、运行结果

     双向链表运行结果

    9、完整代码

      1 /*
      2  * DLinkList.h
      3  *
      4  *  Created on: 2012-10-13
      5  *      Author: hs
      6  */
      7 
      8 #ifndef DLINKLIST_H_
      9 #define DLINKLIST_H_
     10 #include "core/node/DLNode.h"
     11 
     12 template<class T>
     13 class DLinkList {
     14 private:
     15 DLNode<T> *head;//头结点
     16 /**
     17  *根据位置获取相对应的节点信息
     18  *size_t 是一个无符号的类型,确定调用该函数时入参一定是正数,
     19  *这样的要求也提醒我们在写函数时,应该避免把这样函数暴露给客户
     20  *不知道为什么linux很多api函数都有size_t类型的入参(通过调试,传入负数会导致严重的bug),
     21  *有知道的朋友,请帮忙分享下
     22  */
     23 DLNode<T>* GetElemP(size_t i) const{
     24     int j = 0;
     25     DLNode<T> *p = head;
     26 
     27     do{
     28         p = p->next;
     29         j++;
     30     }while(p != head && j < i);
     31 
     32     return p;
     33 }
     34 
     35 /**
     36  *根据元素获取对应的节点信息
     37  */
     38 DLNode<T>* GetElemE(const T &e) const{
     39     DLNode<T> *p = head->next;
     40 
     41     while(p != head && e != p->data){
     42         p = p->next;
     43     }
     44 
     45     return p;
     46 }
     47 /**
     48  *在指定的位置插入节点
     49  */
     50 void Insert(size_t i,const T &e){
     51     //获取指定的位置节点
     52     DLNode<T> *p = GetElemP(i);
     53     //插入新节点
     54     Insert(p,e);
     55 }
     56 /**
     57  *在指定的指针节点插入新的节点
     58  */
     59 void Insert(DLNode<T> *p,const T &e){
     60     //创建一个新的节点
     61     DLNode<T> *q = new DLNode<T>(e);
     62     //设置新节点的向后的指针域(指向图中2节点)
     63     q->next = p->next;
     64     //设置新节点的向前的指针域(指向图中1节点)
     65     q->prev = p;
     66 
     67     //设置插入节点的后继节点的向前指针域(图中3节点的上一个节点指针域指向新节点2)
     68     p->next->prev = q;
     69     //设置插入节点的向后的指针域(指向新节点2)
     70     p->next = q;
     71 }
     72 
     73 public:
     74 /**
     75  *构造函数,初始化头结点(向前和向后都指向本身,构成双向链表)
     76  */
     77 DLinkList():head(new DLNode<T>(0)){
     78     head->next = head;
     79     head->prev = head;
     80 }
     81 
     82 /**
     83  *析构函数,释放所有新创建的节点
     84  */
     85 ~DLinkList(){
     86     Clear();
     87 }
     88 /**
     89  *清空整个链表节点
     90  */
     91 void Clear(){
     92     //获取第一个节点信息
     93     DLNode<T> *p = head->next;
     94 
     95     //如果节点向后指针域等于头结点,表示链表已经被清空
     96     while(p != head && p != 0){
     97         //更多关于auto_ptr知识,请阅读memory里auto_ptr源码
     98         std::auto_ptr<DLNode<T> > new_ptr(p);
     99         std::cout<<p;
    100         std::cout<<std::endl;
    101         //执行后继节点
    102         p = p->next;
    103     }
    104     //恢复出厂原状,向前和向后的指针域都指向本身,构成双向链表
    105     head->next = head->prev = head;
    106 }
    107 /**
    108  *判断链表是否为空
    109  */
    110 bool Empty(){
    111     return head->next = head;
    112 }
    113 
    114 /**
    115  *计算链表的长度
    116  */
    117 int Length(){
    118 
    119     int i = 0;
    120     DLNode<T> *p = head->next;
    121 
    122     //如果节点向后指针域等于头结点,链表遍历完成,
    123     while(p != head){
    124         //执行后继节点
    125         p = p->next;
    126         //累计链表节点长度
    127         i++;
    128     }
    129 
    130     return i;
    131 }
    132 /**
    133  *从头结点插入新的元素
    134  */
    135 void AddHead(const T &e){
    136     Insert(head,e);
    137 }
    138 /**
    139  *从第一个位置插入新的节点
    140  */
    141 void Insert(const T &e){
    142     Insert(1,e);
    143 }
    144 
    145 /**
    146  *根据元素内容删除对应的节点
    147  */
    148 void Delete(const T &e){
    149     //根据元素内容获取对应的节点
    150     DLNode<T> *p = GetElemE(e);
    151     //更多关于auto_ptr知识,请阅读memory里auto_ptr源码
    152     std::auto_ptr<DLNode<T> > new_ptr(p);
    153     //设置删除节点后继节点向前的指针域为删除节点向前指针域
    154     p->next->prev = p->prev;
    155     //设置删除节点前驱节点向后的指针域为删除节点向后指针域
    156     p->prev->next = p->next;
    157 }
    158 
    159 /**
    160  *遍历双向链表
    161  */
    162 void Traverse(){
    163     //指向第一个节点信息
    164     DLNode<T> *p = head->next;
    165 
    166     //如果节点向后指针域等于头结点,表示链表已经遍历完成
    167     while(p != head){
    168         std::cout<<"p is value = "<<p->data;
    169         std::cout<<""<<std::endl;
    170         p = p->next;
    171     }
    172 }
    173 
    174 void test(){
    175     std::cout<<"-----------insert begin------------"<<std::endl;
    176     for(int i = 1; i <= 5; ++i){
    177         Insert(i);
    178     }
    179     AddHead(6);
    180     AddHead(7);
    181     Traverse();
    182     std::cout<<"-----------insert end------------"<<std::endl;
    183 
    184     std::cout<<"frist list length="<<Length()<<std::endl;
    185 
    186     std::cout<<"-----------delete begin----------"<<std::endl;
    187     Delete(7);
    188     std::cout<<"-----------delete end----------"<<std::endl;
    189 
    190     std::cout<<"second list length="<<Length()<<std::endl;
    191 
    192     std::cout<<"-----------traversal of the elemetns  begin----------"<<std::endl;
    193 
    194     Traverse();
    195     Clear();
    196     Delete(1);
    197     Clear();
    198     std::cout<<"third list length="<<Length()<<std::endl;
    199 }
    200 };
    201 
    202 #endif /* DLINKLIST_H_ */
    View Code

    五:环境

    1、运行环境:Ubuntu 10.04 LTS+VMware8.0.4+gcc4.4.3

    2、开发工具:Eclipse+make

    六:题记

    1、上面的代码难免有bug,如果你发现代码写的有问题,请你帮忙指出,让我们一起进步,让代码变的更漂亮和更健壮;

    2、我自己能手动写上面代码,离不开郝斌、高一凡、侯捷、严蔚敏等老师的书籍和视频指导,在这里感谢他们;

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

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

    一根球杆,几行代码,品世间酸甜苦辣

    如果你喜欢这篇文章,欢迎推荐

    年轻人有理想、有野心,更需要脚踏实地做事情

  • 相关阅读:
    导航栏4种效果---原生js
    穿墙效果
    原生瀑布流
    拖拽碰撞--原声js(自身理解上新的方法)
    CM10 WIFI连不上解决方案
    项目报错 java lang illegalargumentexception error at 0 can t find referenced pointcut
    Apache Log4j 2 is Coming
    Struts2报错:No result defined for action xxx and result input
    运行Android程序出错:The connection to adb is down, and a severe error has occured
    DB2 和 有道词典冲突: A communication error has been detected. Communication protocol being used: Reply.fill().
  • 原文地址:https://www.cnblogs.com/sunysen/p/3054608.html
Copyright © 2020-2023  润新知