双向链表定义
双向链表一般有三个域,为一个数据域和两个指针域,两个指针域分别指向其前一个节点和后一个节点。结构图解如下:
可以看到,第一个节点没有上一个结点,我们将其last指针域指向NULL,最后一个节点没有下一个结点,我们将其next指针域指向NULL,剩下的节点都是last指针域指向上一个节点,而next指针域指向下一个结点。
双向链表的C++描述
节点类的描述:
template<class T>//声明链表类
class Link;
template<class T>//描述节点(节点类的定义)
class Node{
friend class Link<T>;
private:
Node<T>* last=NULL; //左指针域
Node<T>* next=NULL; //右指针域
T data;//数据域
Node(){}; //构造方法
};
链表类的描述:
template<class T>
class Link{
public:
Link(){}
void Insert(const int k,const T& x);
void Browser()const;//浏览所有数据
void Delete(const T& x);//删除数据
int Length()const;//获取链表长度
private:
Node<T>* first=NULL;//指向第一个节点
Node<T>* ends=NULL;//指向最后一个节点
};
插入方法的实现
插入分三种情况,如果再首位插入结点,那么需要改变first指针,而如果在末尾插入节点,则要改变end指针,剩下的在中间插入的则只需要改变其前后两个节点的信息即可。
首先,在首位插入结点
继续在尾部插入结点
由上面可以总结出,当一个节点的右指针域为NULL时,end指针一定指向这个节点,而如果左指针域为空时,那么first指针一定指向这个节点。
代码实现示例:
template<class T>
void Insert(const int k,T& x){
int length = Length();
if(k<1 || k>(length+1)){ //参数不合法
cerr<<"the k is out of bound"<<endl;
throw *this;
}
Node<T>* pr=first;//用来指向要插入位置的前一个位置(当k为1时指向第一个元素)
for(int i=2;i<k;i++){//将pr指针指向要插入的位置的前一个位置
pr = pr->next;
}
Node<T>* pt = new Node<T>(); //创建节点,封装x
pt->data = x;
if(k==1){
if(length==0){//将first和end都指向此节点
first=pt;
ends = pt;
}else{ //将新节点的下一个结点设为原来的first节点,然后再将原来的first节点的上一个结点设为新节点,使first指针指向新的表首节点
pt->next=pr;
pr->last=pt;
first=pt;
}
}else{
Node<T>* pn = pr->next;
pr->next = pt;
pt->last = pr;
pt->next = pn;
if(pn==NULL){
ends = pt;
}else{
pn-last=pt;
}
}
}
浏览所有数据方法的实现
此方法是用来测试链表的构建及一些操作方法的,所以会对双向链表从正反两个方向分别遍历,以验证节点之间双向的联系。
template<class T>
void Link<T>::Browser(){
int length = Length();
if(length==0) throw "this link is empty";
Node<T>* p = first;
while(p!=NULL){
cout<<p->data;
p = p->next;
if(p!=NULL){
cout<<" -> ";
}
}
cout<<endl;
p = ends;
while(p!=NULL){
cout<<p->data;
p = p->last;
if(p!=NULL){
cout<<" <- ";
}
}
cout<<endl;
}
获取数组长度方法的实现
这个和单向链表一样,直到下一个节点为空时停止循环即可。
template<class T>
int Link<T>::Length(){
int length = 0;
Node<T>* p = first;
while(p){
length++;
p=p->next;
}
return length;
}
删除某个元素的方法的实现
和插入元素一样,在这里,如果删除的是表头节点或者最后一个节点,那么相应的,first和end指针的值就需要做相应的改变,具体图解如下:
删除最后一个节点的过程如下:
代码实现如下:
template<class T>
void Link<T>::Delete(const T& x){
Node<T>* p = first;
Node<T>* q = ends;
Node<T>* temp=NULL; //用来暂时保存要删除的节点
int length = Length();
for(int i=0;i<((length/2)+1);i++){
if(p->data==x){ //找到退出
temp = p;
break;
}
if(q->data==x){ //找到退出
temp = q;
break;
}
q = q->last;
p = p->next;
}
//此时,temp保存的是将要删除的节点
if(temp==first){ //删除首节点
first = first->next;
first->last = NULL;
delete temp;
}else if(temp==ends){ //删除尾节点
ends = ends->last;
ends->next = NULL;
delete temp;
}else if(temp!=NULL){ //删除中间的节点
Node<T>* pr = temp->next; //temp的下一个结点
Node<T>* pl = temp->last; //temp的上一个结点
pl->next = pr;
pr->last = pl;
delete temp;
}
}
最后需要注意的是最后一个else中temp!=NULL必须添加,不能只写else,这是为了防止传入的x在链表中原本就没有。