线性表的链式存储——双向链表的实现

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,双向链表是线性表概念的最终实现(更贴近理论上的线性表);

posted @ 2019-05-25 16:34  子宇24  阅读(679)  评论(0编辑  收藏  举报