今天总结线性表中的双向循环链表。
什么是双向循环链表?
看名字估计也就知道了,首相他是一个循环链表,也就是最后一个结点的指针域不为空,而是指向头结点,其次与单向循环链表相比,它是双向的。所谓双向,就是给每个结点再增加一个指针域,这个指针域指向前一个结点。
即是下面这样(来自百度图片):
为什么要用双向循环链表?
无论单链表还是单向循环链表,都只有一个指针域,它们都是直接指向后继结点的,如果要查找当前结点的后继结点,会很方便。但是如果给定一个结点,要得到它的前继结点,就会很麻烦,必须从第一个元素开始遍历,然后判断当某个结点的指针域指向的是当前结点时,就判断该结点为当前结点的前继结点,时间复杂度为O(n)。而如果给每个结点增加一个指针域,指向其前继结点,那么给定一个结点时,直接就可以得到它的前继结点。
如何实现双向循环链表?
<span style="font-family:Courier New;font-size:14px;">#include <iostream>
using namespace std;
template <class T>
struct Node {
T data; //数据域
struct Node<T> *pre; //指向前一个结点
struct Node<T> *next; //指向后一个结点
};
template <class T>
//双向循环链表 模板类
class DoubleLinkList {
private:
Node<T> *front; //头指针
public:
//空的循环链表
DoubleLinkList() {
front = new Node<T>; //头结点
front->next = front;
front->pre = front;
}
~DoubleLinkList(); //析构函数 释放节点空间
DoubleLinkList(T a[],int n); //用数组a初始化链表
void Insert(int i,T x); //插入元毒到指定位置
T Delete(int i); //删除指定位置的元素
Node<T> *GetNode(int i); //返回指定位置的结点
int GetLength(); //获取链表的长度
void PrintLinkList(); //遍历链表
};
template <class T>
//尾插法
DoubleLinkList<T>::DoubleLinkList(T a[],int n) {
front = new Node<T>;
front->next = front->pre = front; //初始化空表
for(int i=0;i<n;i++) {
Node<T> *s = new Node<T>;
s->next = front->next; //让当前结点指向头结点
s->data = a[i];
s->pre = front; //当前结点pre指针指向前一个结点
front->next = s; //前一个结点指向当前结点
front->pre = s;
front = front->next; //指针后移
}
}
template <class T>
DoubleLinkList<T>::~DoubleLinkList() {
Node<T> *p = front->next; //获取头结点
while(p!=front->next) {
Node<T> *q = p; //保存待删结点地址
p=p->next;
delete q;
}
}
template <class T>
int DoubleLinkList<T>::GetLength() {
Node<T> *p = front->next;
int count = 1;
while(p!=front->pre) {
count++;
p = p->next;
}
return count;
}
template <class T>
void DoubleLinkList<T>::PrintLinkList() {
Node<T> *p = front->next->next; //得到第一个结点
for(int i=0;i<GetLength();i++) {
cout<<p->data<<" ";
p = p->next;
}
cout<<endl;
}
template <class T>
Node<T> * DoubleLinkList<T>::GetNode(int i) {
Node<T> * p = front->next;
for(int j=0;j<i;j++) {
p = p->next;
}
return p;
}
template <class T>
T DoubleLinkList<T>::Delete(int i) {
Node<T> *p = front;
if(i!=1)
p = GetNode(i-1); //获取待删结点的前一个结点
if(p) {
Node<T> *q = p->next; //保存待删结点
T x = q->data; //待删元素
p->next->next->pre = p; //使待删结点的后继结点的pre指针域指向前一个结点
p->next = p->next->next; //前一个结点next指针域指向待删结点后面的那个元素
delete q;
return x;
}
return 0;
}
/**
插入操作实现
思路:
1.获取待插位置的前一个结点
2.新建一个结点,将待插结点元素保存到其数据域中
3.将待插结点指向插入位置后面的那个结点,其pre指向前面的那个结点
4.将后面结点的pre指向待插结点,将前面结点指向待插结点
技巧:这里可能结点指向操作的顺序容易弄混,导致出错.
我的办法是,只要先把当前结点先分别指向后继结点和前继结点,然后再使前继结点和后继结点指向当前结点
就一句话,记住先处理当前结点的指针域,顺序基本就不会错了。
*/
template <class T>
void DoubleLinkList<T>::Insert(int i,T x) {
Node<T> *p = front;
if(i!=1)
p = GetNode(i-1);
if(p) {
Node<T> *s = new Node<T>;
s->data = x;
s->next = p->next; //指向后继结点
s->pre = p; //pre指针域指向前一结点
p->next->pre = s; //后继结点pre指向待插结点
p->next = s; //前一结点指向待插结点
}
}
int main()
{
int a[6] = {1,3,6,7,4,8};
DoubleLinkList<int> list(a,6);
cout<<"双向循环链表的长度为:"<<endl;
cout<<list.GetLength()<<endl;
cout<<"遍历双向循环链表"<<endl;
list.PrintLinkList();
cout<<"删除位置3的元素:"<<endl;
cout<<"删除的元素为:"<<list.Delete(3)<<endl;
list.PrintLinkList();
cout<<"在位置3插入元素5"<<endl;
list.Insert(3,5);
list.PrintLinkList();
return 0;
}
</span>
运行结果: