线性表的链式存储——双向链表的实现
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,双向链表是线性表概念的最终实现(更贴近理论上的线性表);
此文为作者学习唐佐林老师的学习笔记,仅为交流共享之用,由此带来的后果,与作者无关;转载请注明转载出处;难免有错,欢迎指正,联系方式qunchao24@sina.com。