2021-02-19

C ++は、共有可能な双方向の循環リンクリストを実現します

共有の意味
Linuxカーネルには、プロセス、ファイル、モジュール、ページなど、二重にリンクされたリストを使用する必要のあるデータ構造が多数あります。二重リンクリストの従来の実装を採用する場合、これらのデータ構造に対してそれぞれの二重リンクリストを維持し、各リンクリストの挿入や削除などの設計操作を行う必要があります。

したがって、同じデータ構造を使用して同じプロジェクトに異なるタイプのデータを格納するには、データを格納するためのキャリアをデータから分離する必要があります。保存する必要のあるデータ型に依存せず、任意のデータ型で使用できるこの種の二重リンクリストは、共有可能な二重リンクリストと呼ばれます。

たとえば、二重リンクリストタイプを定義できます。この二重リンクリストは独立したキャリアであり、他のデータタイプには関連付けられていません。では、このようなリンクリストを使用してカスタムタイプのデータを伝送するにはどうすればよいでしょうか。これには、カスタムデータ型に関連付けられたリンクリストタイプではなく、特定のカスタムデータ型がリンクリストタイプに関連付けられている必要があります。このような二重リンクリストを実装する場合、使用する方法は、カスタムデータ型を定義するときです。データ型には二重リンクリストのノードメンバーが含まれます。つまり、各カスタムデータ型にノードタイプをバインドさせる場合、カスタムデータ型オブジェクトのノードをリンクリストにリンクするだけで済みます。これは、カスタムデータタイプはリンクリストにリンクされています。

もちろん、オブジェクト指向プログラミングの方法では、クラステンプレートを使用して、共有できるデータストレージ構造のタイプを実現できます。二重循環リンクリストの長所と
短所
二重循環リンクリストは、二重リンクリストと単方向循環リンクリストの利点を組み合わせたもので、ノードの先行ノードにすばやくアクセスできるだけでなく、ジョセフリングのような循環問題も解決できます。さらに、二重に循環リンクリストは、リンクリストを正の方向にトラバースできるだけでなく、リンクリストを逆方向にトラバースすることもできます。欠点は、各ノードに2つのポインタが必要であり、追加のメモリを消費することです。

アルゴリズム実装
ヘッダーファイル:

#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;
}

おすすめ

転載: blog.csdn.net/weixin_48343353/article/details/113867173