同单向链表比较,双向链表的优势在于查找的方便性;
单向循环链表仅对于该节点及后继结点的查找便利,但当查找结点前驱节
点时就遇到最高时间复杂度O(n),遍历一周;
双向循环链表则只需要O(1),详细代码如下;
-
定义链表结点
给出了两个指针prior 和 next,一个指向前驱结点,一个指向后继结点;
struct DulNode{
string data;
DulNode* prior;
DulNode* next;
DulNode():data(""),prior(nullptr),next(nullptr){}
};
-
初始头结点
这里使用一个头结点,不存储数据!! 至于为啥有个头结点,我认为这应该是作为终止信号的标志,方便后续操作;
DulNode* CreateDulNode(){
//头结点
DulNode* head = new DulNode();
head->next = head;
head->prior = head;
//
return head;
}
-
添加结点
同单向链表操作稍微不同的是,这里需要多操作一个指针prior;
void AddDulNode(DulNode* head, string s) {
DulNode* p = head->next;
while (p->next != head) { //即当p->next == head 停止循环,此时 p 指向尾结点
p = p->next;
}
//New Node
DulNode* tar = new DulNode();
tar->data = s; //数据
//指针操作
tar->prior = p; //前驱
tar->next = p->next; //后继 p->next 即 head
p->next = tar; //原尾结点后继连上
head->prior = tar; //头结点的前驱结点更新***
}
上述需要注意的只有指针改变的顺序,以免“认错指针”;(改之前看原指针有没有改变)
-
插入结点
插入节点和单向链表相同,先找到插入结点位置的前驱结点,再进行指针操作;
//在第 i 个数据前插入
void InsertDulNode(string s, DulNode* head, int i) {
DulNode* p = head;
for (int k = 1; k < i; ++k) {
p = p->next; //此时将 p 定位到第 i - 1 个结点
}
//准备空结点
DulNode* tar = new DulNode();
tar->data = s;
//插入
tar->prior = p;
tar->next = p->next;
p->next->prior = tar;
p->next = tar;
}
/*
由于链表的循环特性,实际上插入的是第 (i-1) % N 个结点, N 为有效结点个数, i - 1 是因为有一个 头结点 不存数据
*/
顺序顺序!注意指针变化的顺序即可;
-
删除结点
步骤无太大差别,会插入结点删除应该问题不大了;
//删除第 i 个数据
void DeleteDulNode(DulNode* head, int i) {
DulNode* p = head;
for (int k = 1; k < i; ++k) p = p->next;
DulNode* tar = p->next;
p->next = tar->next; //跳过 tar 结点
tar->next->prior = p;
delete tar; //release
}
- 剩下一些必要操作直接给代码了
//列举结点数据
void ShowDulNode(DulNode* head) {
DulNode* p = head->next; //p 指向 开始结点
while (p != head) {
cout << p->data << '\n';
p = p->next;
} //当 p 指向头结点 结束循环
/*
这里正向循环,若要反向,只需要将其中的 next 都修改为 prior 即可!
*/
}
//获取有效长度
int GetLengthDN(DulNode* head) {
int ret(0);
DulNode* p = head->prior;
while (p != head) {
++ret;
p = p->prior;
}
return ret;
}
共勉!