30、双向循环链表的实现

单链表的另一个缺陷:

单向性:只能从头结点开始高效访问链表中的数据元素。

缺陷:如果需要逆向访问单链表中的数据元素将极其低效。

新的线性表:设计思路:在单链表的结点中增加一个指针pre,用于指向当前结点的前驱结点。

#ifndef DUALLINKLIST_H_
#define DUALLINKLIST_H_
#include "List.h"
#include "Exception.h"
namespace WSlib
{
template <typename T>
class DualLinkList: public List<T>
{
protected:
struct Node:public Wobject  //继承这个顶层父类的意思是继承 new/delete,在创建一个结点的时候,需要在堆空间申请内存
{
T value;

Node* next;

Node* pre;

};
//mutable Node m_header;  //mutable:const成员函数中能取地址
mutable struct:public Wobject //匿名类也必须继承顶层父类,如果不继承,就与上边的类型内存布局的不同,导致错误
   {
   char reserved[sizeof(T)]; //定义单链表对象时对成员进行构造,构造头结点对象时发现这里实现中不可能调用任何构造函数了,因为有了数组,所以就不会调用构造函数了。数组没什么用只是为了占空间
Node* next;                //头结点在内存布局上和之前是没有差异的,差异仅仅是不管T是什么都不会调用构造函数了。
Node* pre;
}m_header;
int m_length;
int m_step;  //24
Node* m_current;
Node* position(int i) const   //position(0)返回头结点的指针, position(1)返回第0个元素的地址,position(i)表示返回第i-1个元素的地址,i-2结点中的内容,注意从0开始算的    
{
Node* ret=reinterpret_cast<Node*>(&m_header);  //虽然匿名类型在内存布局上和Node是一样的,但是由于类型不同不能初始化,需要用reinterpret_cast
// SmartPointer<Node> ret=reinterpret_cast<Node*>(&m_header); 
for(int p=0;p<i;p++)
{
ret=ret->next;  
}
return ret;                          
}
virtual Node* create()
    { 
     return new Node();
    }
virtual void destroy(Node* pn)
    {
delete pn;
}
public:
DualLinkList()
{
m_header.next=NULL;
m_header.pre=NULL;
m_length=0;
m_step=1;
m_current=NULL;
}
bool insert(const T& e)    
{
return insert(m_length,e);
}
bool insert(int i,const T& e)   //o(n)
{
bool ret=((0<=i)&&(i<=m_length));  //插入操作的话是i<=m_length的
if(ret)
{
Node* node=create(); 
if(node !=NULL)
{
Node* current=position(i);
Node* next=current->next; 

/*Node* current=&m_header;

for(int p=0;p<i;p++)
{
current=current->next;
}*/
node->value=e;
node->next=current->next;
current->next=node;
if(current !=reinterpret_cast<Node*>(&m_header)) //判断current是不是头结点
    node->pre=current;
else
{
node->pre=NULL; //第一个结点的pre为NULL}
if(next!=NULL) //为空的话就是插入到最后位置
{
next->pre=node;}
m_length++;
else{
THROW_EXCEPTION(NoEnoughMemoryException,"no enough memory...");
}
}
return ret;}
bool remove(int i)  //o(n)  移除i是包括0的,第0个,第1个,第2个..i=0也可以
{
bool ret=((i>=0)&&(i<m_length));
if(ret)
{
Node* current=position(i);
///SmartPointer<Node>current=position(i);
/*
Node* current=&m_header;
for(int p=0;p<i;p++)
{
current=current->next;
}*/
Node* toDel=current->next;
//SmartPointer<Node>toDel=current->next;
//26+,30+
Node* next=toDel->next;
if(m_current==toDel)   //如果游标所指向的结点就是待删除的结点,那么游标就移动一个位置,防止错误的
{
m_current=next;
}
current->next=next;
if(next !=NULL)
{
next->pre=toDel->pre;
}
m_length--;
destroy(toDel);
return ret;

}

bool set(int i,const T& e)  //o(n)

bool ret=((0<=i)&&(i<m_length));
if(ret)
{
/*Node* current=&m_header;
for(int p=0;p<i;p++)
{
current=current->next;
position(i)->next->value=e;
T get(int i) const  //o(n)
{
T ret;
if(get(i,ret))
{
return ret;
}
else
{
THROW_EXCEPTION(IndexOutOfBoundsException,"Invalid parameter i to get element...");
}
return ret;}
virtual bool get(int i,T& e) const  //o(n)
{
bool ret=((0<=i)&&(i<m_length));
if(ret)
{
/*Node* current=&m_header;  //由于当前成员函数是const成员函数,不允许改变任何成员变量的值,&取地址符编译器认为是有可能修改成员变量的值,
for(int p=0;p<i;p++)      //解决:只需要在对应的成员变量前加mutable声明就可以了
{current=current->next;
e=position(i)->next->value;  //因为是const成员函数,不可以调用position,所以position函数得定义为const成员函数
}
return ret;}
int find(const T& e) const  //o(n)
int ret=-1;
int i=0;

Node* node=m_header.next;

while(node)

{
if(node->value==e)
{
ret=i;
break;
}
else
{
node=node->next;
i++;

int length() const  //o(1)
{
return m_length;
}
void clear()   //o(n)
{
while(m_length>0)   //
{
remove(0);
}
}
virtual bool move(int i,int step=1)  //move(0)表示position(0)头结点->next,定位到第0个元素
{
bool ret=((i>=0)&&(i<m_length)&&(step>0));
if(ret)
{
m_current=position(i)->next;  //当前结点中的指针,也就是下一个结点的地址 ,position 是从头开始的,move(0)表示头
m_step=step;
}
return ret;
}
virtual bool end()
{
return (m_current==NULL);
}
virtual T current()
{
if(m_current != NULL)  // 等价于 if(!end)
{
return m_current->value;
}
else 
{
THROW_EXCEPTION(InvalidOperationException, "No value at current position ...");}
}
virtual bool next()
{
int i=0;
while((i<m_step)&& (!end()))   //循环m_step次,并且游标不为空
{
m_current=m_current->next;
i++;
}
return(i==m_step); //表示移动成功
}
virtual bool pre()
{
int i=0;
while((i<m_step)&& (!end()))   //循环m_step次,并且游标不为空
{
m_current=m_current->pre;
i++;
}
return(i==m_step); //表示移动成功
}
~DualLinkList()  //o(n)
{
clear(); //如果单链表需要销毁,先将单链表对象中的每一个结点删除了,然后销毁单链表对象,所以在析构函数中调用clear()函数
/******************************************************************************************
#include <iostream>
#include "DualLinkList.h"
using namespace WSlib;
using namespace std;
int main()
{
DualLinkList<int> c1; //如果说int跟T不匹配,可能是拼写错误 大小写
for(int i=0;i<5;i++)
{
c1.insert(0,i);
c1.insert(0,5);
}
for(c1.move(0);!c1.end();c1.next())
{
cout<<c1.current()<<endl;
}cout<<"begin"<<endl;
c1.move(c1.length()-1);
while(!c1.end())
{
if(c1.current()==5)
{
cout<<c1.current()<<endl;
c1.remove(c1.find(c1.current())); //测试remove 和find
}
else
{
c1.pre();
}}
cout<<endl;
for(c1.move(c1.length()-1);!c1.end();c1.pre())
{
cout<<c1.current()<<endl;
}
return 0;
}
//注意insert中i=i%(this->m_length+1);表示从0到数据长度的位置都可以插入数据,mod函数中只是i%(m_length))而减1是在main中c1.move(s-1,m-1)实现的
*****************************************************************************************************************************/

小结:双向链表是为了弥补单链表的缺陷而重新设计的,在概念上,双向链表不是单链表,没有继承关系,双向链表中的游标能够直接访问当前结点的前驱和后继,双向链表是线性表概念的最终实现(更贴近理论上的线性表)。

问题:DualLinkList和LinkList中存在很多完全一样的代码,如果进行重构降低代码的冗余性?冗余代码的出现意味着DualLinkList和LinkList之间应该是继承关系?其他设计方案中双向链表作为单链表的子类。设计只有合理不合理,不影响最终功能,影响后期维护性。

双向静态链表,双向循环链表

猜你喜欢

转载自blog.csdn.net/ws857707645/article/details/80434733
今日推荐