C++实现可共享的双向循环链表
可共享的含义
在linux内核中,有大量的数据结构需要用到双向链表,例如进程、文件、模块、页面等。若采用双向链表的传统实现方式,需要为这些数据结构维护各自的双向链表,并且为每个链表都设计插入、删除等操作函数。
因此,为了实现在同一个项目中不同类型数据用到同一种数据结构来存储,必须将存储数据的载体与数据分离开来。这种独立于具体需存储的数据类型,可供任意数据类型使用的双向链表称为可共享的双向链表。
例如,我们可以定义一个双向链表类型,这个双向链表是一个独立的载体,不与任何其他数据类型相关联,那么如何让这样的链表能够用来承载任意自定义类型的数据呢?这就需要让具体的自定义数据类型与该链表类型产生关联,而不是让链表类型与自定义数据类型产生关联,在实现这样的双向链表中,我们采用的方法时在定义自定义数据类型时,让该数据类型包含一个双向链表的节点成员,即让每个自定义数据类型绑定一个节点类型,我们只需要将自定义数据类型对象中的节点链入链表,就相当于将自定义数据类型链入了一个链表一样。
当然,在面向对象编程方法中,我们可以用类模板的方式实现可共享的数据存储结构类型。
双向循环链表的优缺点
双向循环链表结合了双向链表与单向循环链表的优点,既可以快速访问节点的前驱节点也可以解决类似约瑟夫环的循环问题,此外,双向循环链表不仅能正向遍历链表也能逆向遍历链表。缺点是每个节点需要两个指针,消耗了更多额外的内存。
算法实现
头文件:
#pragma once
#include<iostream>
using namespace std;
//定义一个双向链表的节点类型。
typedef struct LinkNode {
LinkNode* prev;
LinkNode* next;
}Node;
using DbCirLink = Node*; //定义双向链表类型。
//自定义一个任意的数据类型,每个该类型的对象绑定着一个双向链表的
//节点,将对象的节点元素插入链表,就相当于将对象插入链表一样。
struct Test {
int a; //自定义类型的成员变量。
Node node; //绑定一个链表类型节点。
};
//向双向链表头部插入元素[时间复杂度o(1)]:
bool push_front(DbCirLink& head, Node* node);
//删除双向链表首元素[时间复杂度o(1)]:
bool pop_front(DbCirLink& head);
//向双向链表尾部插入元素[时间复杂度o(1)]:
bool push_back(DbCirLink& head, Node* node);
//删除双向链表尾元素[时间复杂度o(1)]:
bool pop_back(DbCirLink& head);
//向指定位置插入指定节点[时间复杂度o(n)]:
bool insert(DbCirLink& head, const unsigned& pos, Node* node);
//擦除指定位置的节点[时间复杂度o(n)]:
bool erase(DbCirLink& head, const unsigned& pos);
//获取双向循环链表中指定位置节点的地址[时间复杂度o(n)]:
Node* getAddr(DbCirLink& head, const unsigned& pos);
//显示双向链表中的元素[时间复杂度o(n)]:
void display(const DbCirLink& head);
CPP文件:
#include "DbCirLink.h"
bool push_front(DbCirLink& head, Node* node)
{
if (!node) return false; //若传入的节点的地址无效。
//若传入的节点地址node实参所在的节点非自由节点(即节点指针域非空,此时该节点
//可能已经被链入某个双向链表,甚至链入的是我们当前要操作的链表,这样就出现了
//将链表中自身的节点插入该链表的行为,将导致未知的后果)。
if (node->next != nullptr || node->prev != nullptr) return false;
//若双向循环链表为空。
if (!head) {
head = node;
//插入后只有一个节点,它既是首节点也是尾节点。
node->next = node->prev = node;
}
else {
Node* tmp = head;
head = node; //让头指针指向新节点。
tmp->prev->next = node; //让尾节点的next指向新首节点。
node->prev = tmp->prev; //让新首节点的prev指向尾节点。
tmp->prev = node; //让原首节点的prev指向新首节点。
node->next = tmp; //让新首节点的next指向原首节点。
}
return true;
}
bool pop_front(DbCirLink& head)
{
//若链表为空,无法执行擦除操作。
if (!head) return false;
//若链表中只有一个元素。
if (head->next == head) {
//断开该节点与链表的联系,并置空链表头指针。
head->next = head->prev = nullptr;
head = nullptr;
}
else {
Node* tmp = head;
//让尾节点的next指针指向新的首节点(原来的第二个节点)。
head->prev->next = head->next;
//让新的首节点的prev指针指向尾节点。
head->next->prev = head->prev;
//让头指针head指向新的首节点。
head = head->next;
//将被删除的节点状态置空,使其成为自由节点。
tmp->prev = tmp->next = nullptr;
}
return true;
}
bool push_back(DbCirLink& head, Node* node)
{
if (!node) return false;
if (node->next != nullptr || node->prev != nullptr) return false;
if (!head) {
head = node;
//插入后只有一个节点,它既是首节点也是尾节点。
node->next = node->prev = node;
}
else {
//让原尾节点的next指针指向待插入的新节点。
head->prev->next = node;
//让待插入的新节点的prev指针指向原尾节点。
node->prev = head->prev;
//让首节点的prev指针指向待插入的新节点。
head->prev = node;
//让待插入的新节点的next指针指向首节点。
node->next = head;
}
return true;
}
bool pop_back(DbCirLink& head)
{
if (!head) return false;
if (head->next == head) {
head->next = head->prev = nullptr;
head = nullptr;
}
else {
Node* tmp = head->prev; //让tmp指向尾节点。
//让倒数第二个节点(即删除原尾节点后的新尾节点)的next指针指向首元素。
tmp->prev->next = head;
//让首节点的prev指针指向新的尾节点。
head->prev = tmp->prev;
//指控被删除节点的指针域,使其成为自由节点。
tmp->next = tmp->prev = nullptr;
}
return true;
}
bool insert(DbCirLink& head, const unsigned& pos, Node* node)
{
if (!pos) return push_front(head, node); //若插入位置为0,即头部插入。
Node* tmp1 = head, * tmp2 = nullptr;
//index索引指示tmp1指针指向的位置。
for (unsigned index = 0; index != pos - 1; ++index) {
//若tmp1指针尚未达到pos-1位置就已到达链表尾部。
if (tmp1->next == head) return false;
tmp1 = tmp1->next;
}
//循环完整遍历完毕,则此时tmp1指针必然指向了pos-1位置。
//若指定插入位置pos为尾后位置,调用尾插法函数处理。
if (tmp1->next == head) return push_back(head, node);
if (!node) return false;
if (node->next != nullptr || node->prev != nullptr) return false;
tmp2 = tmp1->next; //让tmp2指向待插入位置的节点。
tmp1->next = node;
tmp2->prev = node;
node->prev = tmp1;
node->next = tmp2;
return true;
}
bool erase(DbCirLink& head, const unsigned& pos)
{
if (!pos) return pop_front(head); //若指定擦除位置为0。
if (!head) return false; //若链表为空。
Node* tmp1 = head;
//index索引指示tmp1指针指向的位置。
for (unsigned index = 0; index != pos - 1; ++index) {
//若tmp1指针尚未达到pos-1位置就已到达链表尾部。
if (tmp1->next == head) return false;
tmp1 = tmp1->next;
}
//若指定的pos超过链表长度(刚好超过一个节点长度)。
if (tmp1->next == head) return false;
tmp1 = tmp1->next; //让tmp1指向指定节点。
tmp1->prev->next = tmp1->next;
tmp1->next->prev = tmp1->prev;
tmp1->next = tmp1->prev = nullptr;
return true;
}
Node* getAddr(DbCirLink& head, const unsigned& pos)
{
if (!head) return nullptr;
Node* tmp = head;
//让tmp指针迭代后移,index指示此时tmp指针的位置,当
//循环正常结束,tmp将指向pos位置。
for (unsigned index = 0; index != pos; ++index) {
//若tmp还未到达pos位置就已经指向尾节点了。
if (tmp->next == head) return nullptr;
tmp = tmp->next;
}
return tmp;
}
void display(const DbCirLink& head)
{
if (!head) return; //若链表为空。
Node* tmp = head;
//利用链表中节点的地址,可以找到该节点绑定的数据对象的地址。
//链表不为空,先打印第一个节点绑定的对象,然后tmp指针后移。
Test* obj = (Test*)((decltype(offsetof(Test, node)))tmp - offsetof(Test, node));
cout << obj->a << " ";
tmp = tmp->next;
//当tmp等于head时,表明tmp已遍历完整个双向循环链表。
while (tmp != head) {
Test* obj = (Test*)((decltype(offsetof(Test, node)))tmp - offsetof(Test, node));
cout << obj->a << " ";
tmp = tmp->next;
}
cout << endl;
}
使用方法示例:
#include<iostream>
#include<string>
#include"DbCirLink.h"
using namespace std;
#define NUMBER 10
int main()
{
DbCirLink head = nullptr; //定义一个双向链表。
//利用Test指针数组动态分配NUMBER个Test对象。
Test* arr[NUMBER] = {
nullptr };
for (int i = 0; i < NUMBER; ++i) {
arr[i] = new Test;
arr[i]->a = i;
//置空Test对象中的节点成员,使其成为自由节点。
arr[i]->node.next = arr[i]->node.prev = nullptr;
}
for (int i = 0; i < NUMBER; ++i) {
insert(head, i, &(arr[i]->node));
}
display(head);
if (!getAddr(head, 10)) {
cout << "getAddr()函数返回值为null" << endl;
}
else {
cout << (((Test*)((size_t)getAddr(head, 10) - offsetof(Test, node)))->a) << endl;
}
for (unsigned i = 0; i < NUMBER; ++i) {
delete arr[i];
}
return 0;
}