线性表的链式存储——单链表的遍历与优化
1,如何遍历单链表中的每一个数据元素?
1,当前单链表遍历方法:
1,插入的时间复杂度为 O(n),而遍历的时间复杂度为 O(n*n);
2,遗憾的事实:
1,不能以线性的时间复杂度完成单链表的遍历;
新的需求:
1,为单链表提供新的方法,在线性时间内完成遍历;
3,设计思路(游标):
1,在单链表内部定义一个游标(Node* m_current);
2,遍历开始前将游标指向位置为 0 的数据元素;
3,获取游标指向的数据元素;
4,通过结点中的 next 指针移动游标;
(5),从程序的角度来看,从链表的第 0 个元素一直遍历到最后一个元素,一个 next 指针就够了;
4,设计思路(游标):
1,提供一组遍历相关的函数,以线性的时间复杂度遍历链表;
1,move(),将游标定位到目标位置;
2,next(),移动游标;
3,current(),获取游标所指向的数据元素;
4,end(),获取是否达到尾部(是否为空);
5,遍历函数原型设计:
1,bool move(int i, int step = 1);
2,bool end();
3,T current();
4,bool next();
6,单链表的遍历实现:
1 /* 以下四个函数move(),end(),next(),current()是为了将遍历输出函数时间复杂度由O(n*n)降为O(n);其中 move() 函数时间复杂度为 i,其后三个函数在 for() 循环中加起来的时间复杂度为才为 O(n),很经典 */ 2 3 virtual bool move(int i, int step = 1) // 从第 i 个位置移动,每次移动 1 个//位置; O(n) 4 { 5 bool ret = ( (0<= i) && (i<m_length) && (0<step)); 6 7 if( ret ) 8 { 9 m_current = position(i)->next; // 定位到节点i,不是第 i 个节点,//所以要加上next,这里时间复杂度严格来说是 i,配合着后面的 next() 函数的移动,//则最终的时间复杂度才是 n; 10 m_step = step; // 将每次要移动的值传进来 11 } 12 return ret; 13 } 14 15 virtual bool end() // 判断当前的游标是否结束 16 { 17 return (m_current == NULL); // 这里不可写成赋值了 18 } 19 20 virtual T current() // 获取游标当前位置的值 21 { 22 if( !end() ) // m_current != NULL ==> !end(),这里是判断当前指针的有效性; 23 { 24 return m_current->value; 25 } 26 else 27 { 28 THROW_EXCEPTION(InvalidOperationException, "No value at current position ..."); 29 } 30 } 31 32 virtual bool next() // 移动游标 33 { 34 int i = 0; 35 36 while( (i<m_step) && (!end()) ) // 这里的 !end() 是为了判m_step 步幅是否//会将指针指向空,所以在 move() 不用判断 step 的小于范围; 37 { 38 m_current = m_current->next; 39 i++; 40 } 41 42 return (i == m_step); 43 }
7,单链表内部的一次封装:
1,这样的封装是因为面向对象里面的主导思想有封装,所以采纳这样的封装;
2,保护的函数,因为是内部的;
8,小结:
1,单链表的遍历需要在线性时间内完成;
2,在单链表内部定义游标变量,通过游标变量提高效率;
3,遍历相关的成员函数是相互依赖、相互配合的关系;
4,封装结点的申请和删除操作更有利于增强扩展性(见后续静态单链表的实现);