zoukankan      html  css  js  c++  java
  • 线性表的链式存储——双向链表的实现

    1,单链表另一个缺陷:

           1,单向性:

                  1,只能从头结点开始高效访问链表中的数据元素;

           2,缺陷:

                  1,如果需要逆向访问(最先访问倒数第一个节点)单链表中的数据元素将极其低效;

                  2,插入时效率很高,逆序访问时效率很低:

                        

                        

    2,新的线性表(解决高效访问问题):

           1,设计思路:

                  1,在单链表的结点中增加一个指针 pre,用于指向当前结点的前驱结点:

      

                 

    3,双向链表的继承层次结构:

     

           1,双向链表和单链表在内部的实现上是完全不同的,所以它们不应该是父子关系,应该是同级别兄弟关系;

          

    4,DualLinkList 的定义:

          

          

    5,插入新结点:

          

    6,删除结点:

      

    7,DualLinkList 双向链表的实现:

      1 #ifndef DUALLINKLIST_H
      2 #define DUALLINKLIST_H
      3 
      4 #include "List.h"
      5 #include "Exception.h"
      6 
      7 /* 双向链表三要素:长度、头结点、前后指针域。 */
      8 /* 双向静态链表和双向循环链表 */
      9 namespace DTLib
     10 {
     11 
     12 template <typename T>
     13 class DualLinkList : public List<T>
     14 {
     15 protected:
     16     struct Node : public Object   
     17     {
     18         T value;
     19         Node* next;
     20         Node* pre;
     21    };
     22 
     23     mutable struct : public Object   // 构造头结点的时候,设法不去调用泛指类型的函数;构造匿名类型的结构
     24     {
     25         char reserved[sizeof(T)];   // 定义数组,没有实际作用,仅仅用于占空间
     26         Node* next;
     27         Node* pre;
     28    }m_header;    //  头结点对象在内存布局上面和上面结构Node结构体没有任何差异,有差异仅在于不管T为何对象,都不会调用T的构造函数(如果有)
     29 
     30     int m_length;      // 定义链表的长度
     31     Node* m_current;     // 定义遍历函数当前游标的位置
     32    int m_step;    // 定义遍历函数遍历的步幅
     33 
     34     Node* position(int i) const    // O(n)
     35     {
     36         Node* ret = reinterpret_cast<Node*>(&m_header);
     37 
     38         for(int p=0; p<i; p++)
     39         {
     40             ret = ret->next;
     41         }
     42 
     43         return ret;
     44    }
     45 
     46     virtual Node* creat()
     47     {
     48         return new Node();
     49    }
     50 
     51     virtual void destroy(Node* pn)
     52     {
     53         delete pn;   // 只能对堆空间来释放空间,如果不是堆空间,则程序会不稳定;
     54    }
     55 
     56 public:
     57     DualLinkList()                                       
     58     {
     59         m_header.pre = NULL;
     60         m_header.next = NULL;
     61         m_length = 0;
     62         m_current = NULL;
     63         m_step = 1;
     64    }
     65 
     66     bool insert(const T& e)   // 在线性表的尾部默认的插入一个元素,所以 i 省略了;
     67     {
     68         return insert(m_length, e);
     69    }
     70 
     71     bool insert(int i, const T& e)    //(n)   
     72     {
     73         bool ret = ( (0 <= i) && (i <= m_length));
     74         if( ret )
     75         {
     76             Node* node = creat();  // 从堆空间申请一个对象出来;取决于调用的是哪个具体的对象,因为为虚函数;
     77             if( node != NULL )
     78             {
     79                 Node* current = position(i);  // O(n)
     80                 Node* next = current->next;
     81                 node->value = e;
     82                 node->next = next;   // 第一步
     83                 current->next = node;// 第二步    赋值时是节点,比较的时候是地址
     84 
     85                 /* 后向插入中由于头结点为空,已经自然的考虑了插入尾结点和首结点,而这里需要重新考虑 */
     86                 if( current != reinterpret_cast<Node*>(&m_header) ) // 第三步多的,为头结点则插入的是第 0 个结点,涉及 pre 则判断
     87                 {
     88                     node->pre = current;
     89                 }
     90                 else
     91                 {
     92                     node->pre = NULL;
     93                 }
     94 
     95                 if( next != NULL )  // 第四步 多的,向后插入已经考虑了最后的 NULL;
     96                 {
     97                     next->pre = node;
     98                 }
     99 
    100                 m_length++;
    101             }
    102             else
    103             {
    104                 THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ...");
    105             }
    106         }
    107 
    108         return ret;
    109    }
    110 
    111     bool remove(int i)     // O(n)  
    112     {
    113         bool ret = ( (0 <= i) && (i < m_length) );
    114 
    115         if( ret )
    116         {
    117             Node* current = position(i);   // O(n)
    118             Node* toDel = current->next;                            
    119             Node* next = toDel->next;
    120 
    121             if( m_current == toDel)  // 第零步
    122             {
    123                 m_current = toDel->next;
    124             }
    125 
    126             current->next = next;  // 第一步
    127 
    128             if( next != NULL // 第二步,多了此步,不要忘记判断了,涉及 pre 则判断
    129             {
    130                 next->pre = toDel->pre;
    131             }
    132 
    133             m_length--;
    134             destroy(toDel);
    135         }
    136 
    137         return ret;
    138    }
    139 
    140     bool set(int i, const T& e)
    141     {
    142         bool ret = ( (0 <= i) && (i < m_length) );
    143 
    144         if( ret )
    145         {
    146             position(i)->next->value = e;     // O(n)
    147         }
    148 
    149         return ret;
    150    }
    151 
    152     virtual T get(int i) const
    153     {
    154         T ret;
    155 
    156         if( get(i, ret) )
    157         {
    158             return ret;
    159         }
    160         else
    161         {
    162             THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
    163         }
    164 
    165         return ret;
    166    }
    167 
    168     bool get(int i, T& e) const     //  const 表明不能够修改任何成员变量的值;
    169     {
    170         bool ret = ( (0 <= i) && (i <= m_length) );
    171 
    172         if( ret )
    173         {
    174             e = position(i)->next->value;    // O(n)
    175         }
    176 
    177         return ret;
    178    }
    179 
    180     int find(const T& e) const   // 发现当前值为 e 的节点所处的链表位置 i ;
    181     {
    182         int ret = -1;
    183         int i = 0;
    184         Node* node = m_header.next;
    185 
    186         while( node )   // O(n)
    187         {
    188             if(node->value == e)
    189             {
    190                 ret = i;
    191                 break;
    192             }
    193             else
    194             {
    195                 node = node->next;
    196                 i++;
    197             }
    198         }
    199 
    200         return ret;
    201    }
    202 
    203     int length() const   // O(1)
    204     {
    205         return m_length;
    206    }
    207 
    208     void clear()   // O(n)  
    209     {
    210         while( m_length > 0 )
    211         {
    212             remove(0);
    213         }
    214    }
    215 
    216     /* 以下四个函数move(),end(),next(),current()是为了将遍历输出函数时间复杂度由O(n*n)降为O(n) */
    217     virtual bool move(int i, int step = 1)    // 从第 i 个位置移动,每次移//动 1 个位置; O(n)
    218     {
    219         bool ret = ( (0<= i) && (i<m_length) && (0<step));
    220 
    221         if( ret )
    222         {
    223             m_current = position(i)->next;   // 定位到节点i,不是第i个节点,所以要加上next
    224             m_step = step;   // 将每次要移动的值传进来
    225         }
    226 
    227         return ret;
    228    }
    229 
    230     virtual bool end()  // 判断当前的游标是否结束
    231     {
    232         return (m_current == NULL);  // 这里不可写成赋值了
    233    }
    234 
    235     virtual T current()   // 获取游标当前位置的值
    236     {
    237         if( !end() )
    238         {
    239             return m_current->value;
    240         }
    241         else
    242         {
    243             THROW_EXCEPTION(InvalidOperationException, "No value at current position ...");
    244         }
    245    }
    246 
    247     virtual bool next()   // 移动游标
    248     {
    249         int i = 0;
    250 
    251         while( (i<m_step) && (!end()) )
    252         {
    253                 m_current = m_current->next;
    254                 i++;
    255         }
    256 
    257         return (i == m_step);
    258    }
    259 
    260     virtual bool pre()  // 移动游标  新添加
    261     {
    262         int i = 0;
    263 
    264         while( (i<m_step) && (!end()) )
    265         {
    266                 m_current = m_current->pre;
    267                 i++;
    268         }
    269 
    270         return (i == m_step);
    271    }
    272 
    273     ~DualLinkList()    // O(n)
    274     {
    275         clear();  // 构造函数和析构函数中不会发生多态;不管是直接调用的虚函数,还是间接调用的虚函数,都是直接调用的当前类中的实现版本;
    276     }
    277 };
    278 
    279 }
    280 
    281 #endif // DUALLINKLIST_H

    8,DualLinkList 实现的测试代码:

     1 #include <iostream>
     2 #include "DualLinkList.h"
     3 
     4 using namespace std;
     5 using namespace DTLib;
     6 
     7 int main()
     8 {
     9 DualLinkList<int> dl;
    10 
    11     for(int i=0; i<5; i++)
    12     {
    13         dl.insert(0, i);
    14         dl.insert(0, 5);
    15    }
    16 
    17     for(int i=0; i<dl.length(); i++)      // O(n*n)
    18     {
    19         cout << dl.get(i)  << endl;
    20    }
    21 
    22    cout << "begin" << endl;
    23    dl.move(dl.length()-1);
    24 
    25     while( !dl.end() )
    26     {
    27         if( dl.current() == 5 )
    28         {
    29             cout << dl.current() << endl;
    30             dl.remove(dl.find(dl.current()));
    31         }
    32         else
    33         {
    34             dl.pre();
    35         }
    36    }
    37    cout << "end" << endl;
    38 
    39     cout << "begin" << endl;
    40     for(dl.move(dl.length()-1); !dl.end(); dl.pre())     // O(n)
    41     {
    42         cout << dl.current() << endl;
    43     }
    44    cout << "end" << endl;
    45 
    46     return 0;
    47 }

    9,小结:

           1,双向链表是为了弥补单链表的缺陷而重新设计的;

           2,在概念上,双向链表不是单链表,没有继承关系;

           3,双向链表中的游标能够直接访问当前结点的前驱和后继;

           4,双向链表是线性表概念的最终实现(更贴近理论上的线性表);

  • 相关阅读:
    css3新特性总结
    H5新特性总结
    小程序本地移除有一条数据
    字符串截取(某个指定字符前面和后面的值)(指定前几位后几位)
    uni-app 创建项目
    数组转数组对象及数组对象中的某个属性值拼成一个数组
    VUE 解决单页使用keep-alive页面返回不刷新的问题
    小程序弹窗真机不动
    js 数组去重方法
    VUE 列表页中实现分页(下拉到底部触发下一页 )
  • 原文地址:https://www.cnblogs.com/dishengAndziyu/p/10922876.html
Copyright © 2011-2022 走看看