数据结构:循环链表

目录

0基础概念

1链表结构

1.1链表的基本内容(链表结点类型结构体):

1.2头结点内容:

1.3用户数据结构(插入元素的数据结构):

2初始化和销毁

3插入

4遍历

5删除

6链表拼接

验证代码:

附录(全部代码)


 

0基础概念

单向链表中,只有后驱指针,没有前驱指针

先将单向链表终端结点的指针端由空指针改为向头结点,使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked list)

循环链表解决了一个问题:从任意一个结点开始,就可以访问所有的结点

单链表和循环链表的区别在于,判断链表是否结束的方式不一样

  • 单链表判断链表结束的标志:next指针指向nullptr
  • 循环链表判断链表结束的标志:next指针指向头指针

 

将头指针置换为尾指针

头结点初始化时候一定要注意 

 

(图源:大话数据结构)

循环链表也是单向链表的衍生,所以也是遵循单向链表的基本特征的:

下图是单向链表的原理图:

为了更方便操作,链表都有头结点,且头结点不存储插入元素

只保存链表相关的性质变量,如链表长度,尾指针(队),或者前驱指针(双向链表)

而循环链表的数据关系如下:

收尾对接,从任意一个结点出发,都能够完成一周的循环遍历

链表的末尾结点的next指针,指向头结点。

具体操作代码如下:

1链表结构

1.1链表的基本内容(链表结点类型结构体):

//链表
struct CricularLink
{
    CricularLink * next;
};

1.2头结点内容:

//链表性质
struct CricularLinkProperty
{
    CricularLink header;
    int size;
    CricularLink * rear;//增加尾部指针
};

除了头结点中的头指针外(hearer.next) ,还有尾部指针和链表大小。

注意: 头结点,最好是CricularLink类型,因为头结点不存放任何的插入元素,只是在内存中占用一块地址就行

让整个链表有起始点,方便进行各类操作的产物,如果定义为CricularLink * 类型,这个指针无法指向任何内容,失去了指针的意义。

1.3用户数据结构(插入元素的数据结构):

typedef struct CricularLinkData
{
    CricularLink node;
    QString name;
    int age;
}CLD;

用户自定义类型中,要求第一个成员必须是CricularLink类型(链表结点类型结构体变量

为了更好的开发和应用分离,就是面对对象编程

在开发过程中不知道用户会传递什么样的数据进来,为了让用户传递任何类型的数据进来,程序都能应对,采用如此办法。

下面通过一个例子对上述数据结构的设置做出解释:

.h

//链表节点数据类型
struct LinkContent
{
    struct LinkContent * next;
};
//链表数据类型
struct LinkProperty
{
    struct LinkContent Content;
    int size;
};
typedef void * LinkListTwo;//为了更好的让用户理解函数
typedef struct Test
{
    LinkContent node;
    QString name;
    int age;
}T;

数据结构和上述相同(只是部分变量名称进行了替换)

 .cpp 具体操作如下:

    T t1 = {nullptr,"1",10};
    T t2 = {nullptr,"2",20};
    T t3 = {nullptr,"3",30};
    T t4 = {nullptr,"4",40};
    T t5 = {nullptr,"5",50};

    qDebug()<<"初始化结果:"<<t1.node.next;
    qDebug()<<"初始化结果:"<<t2.node.next;
    qDebug()<<"初始化结果:"<<t3.node.next;
    qDebug()<<"初始化结果:"<<t4.node.next;
    qDebug()<<"初始化结果:"<<t5.node.next;

首先是初始化,我们可以看见,链表中的next指针目前全部都被初始化为空了

    qDebug()<<"t1首地址:"<<&t1;
    qDebug()<<"t2首地址:"<<&t2;
    qDebug()<<"t3首地址:"<<&t3;
    qDebug()<<"t4首地址:"<<&t4;
    qDebug()<<"t5首地址:"<<&t5;

    LinkContent *myContent1 = (LinkContent*)(&t1);
    LinkContent *myContent2 = (LinkContent*)(&t2);
    LinkContent *myContent3 = (LinkContent*)(&t3);
    LinkContent *myContent4 = (LinkContent*)(&t4);
    LinkContent *myContent5 = (LinkContent*)(&t5);

    myContent1->next = myContent2;
    myContent2->next = myContent3;
    myContent3->next = myContent4;
    myContent4->next = myContent5;
    myContent5->next = nullptr;

之后我们取出插入元素的首地址

并将其强制转换类型,转换为结构体第一个成员的类型

t1是T类型,取地址(&t1),变成了指针,指向结构体的第一个成员,也就是LinkContent类型的指针

这也就是为什么一定要强调把 链表结点类型结构体变量 定义在用户自定义结构体中第一位的原因

强转后,就可以访问到每个要插入数据的next指针了,然后直接进行赋值操作

之后进行输出,并对比输出内容

    qDebug()<<"强转类型后t1.node.next:"<<t1.node.next;
    qDebug()<<"强转类型后t2.node.next:"<<t2.node.next;
    qDebug()<<"强转类型后t3.node.next:"<<t3.node.next;
    qDebug()<<"强转类型后t4.node.next:"<<t4.node.next;
    qDebug()<<"强转类型后t5.node.next:"<<t5.node.next;

可见,要插入元素之间的连接已经完成,那么以上只是演示

言外之意是,在开发者眼里,不管用户自定义类型有多少个成员变量,只要第一个成员变量是链表结点类型结构体变量,在开发者写各类接口的时候,直接将传入接口的参数强制转换成链表结点类型结构体变量即可,然后就可以完成链表元素之间的连接。

 

到时候只需要给用户提供: 

  1. 链表结点类型结构体名称
  2. 头结点结构体名称(初始化和调用接口的时候用)
  3. 各个接口名称和输入要求

即可

做到了面向对象编程的基本要求

2初始化和销毁

typedef void * CLHeader;

 为了让用户更好理解,增加可读性。

初始化过程中,需要先堆上要一部分内存空间

在new和malloc之间,选择原则很简单,需要扩容,用malloc,就像动态数组,及线性表的顺序存储,用malloc显然更好操作:

数据结构:线性表-动态数组:https://blog.csdn.net/qq_41605114/article/details/104315027

C/C++:细说new与malloc的10点区别 :https://blog.csdn.net/qq_41605114/article/details/104342587

//初始化
CLHeader Inite_CL()
{
    //初始化
    CricularLinkProperty * MyCl = new CricularLinkProperty;
    MyCl->size = 0;
    MyCl->header.next = &(MyCl->header);//循环链表 目前没有元素,所以自己指向自己
    MyCl->rear = &(MyCl->header);//尾指针指向链表尾部,也就是目前的首地址
    return MyCl;
}

进行初始化后,头结点中包含有头结点中的头指针外(hearer.next) ,还有尾部指针和链表大小。

大小自然为零,因为只有一个头结点,所以尾部指针指向头结点(MyCl->rear = &(MyCl->header);)

因为目前没有元素,所以头结点的头指针也指向自己(MyCl->header.next = &(MyCl->header);)

因为循环链表从如此,在单链表中,头结点的头指针指向nullptr

销毁也是一样的

//销毁
void Destroy_Cl(CLHeader Cl)
{
    if(nullptr == Cl)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    delete MyCl;

}

new和delete对应,malloc和free对应,不用少释放,也不用多释放。彼此对应 

3插入

链表的插入,整体上的思路都是找到要插入位置的上一个位置,进行插入操作,在循环链表和双向链表中,

因为有尾指针和前驱指针的存在,要考虑在尾部插入结点和在链表中插入结点两种情况

//插入
void Insert_Cl(CLHeader Cl,int place,void * data)
{
    if(nullptr == Cl)
        return;
    if(nullptr == data)
        return;
    if(place <=0)
        return;

    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * pCurrent = &(MyCl->header);
    CricularLink * InsertData = (CricularLink *)data;
    if(place > MyCl->size)//在链表尾部插入
    {
        qDebug()<<"在队尾插入";

        MyCl->rear->next = InsertData;//1更新原来尾部原型的指向
        InsertData->next = &(MyCl->header);//2断开原来尾部元素next的指向,将新插入元素的next指向头结点
        MyCl->rear = InsertData;//3更新尾指针的指向
        MyCl->size++;

    }
    else//在中间部分插入
    {
        for(int i = 0;i<place-1;++i)
        {
            pCurrent = pCurrent->next;
        }
        InsertData->next = pCurrent->next;
        pCurrent->next = InsertData;
        MyCl->size++;
    }
}

对其中尾部插入新结点进行说明:

具体程序如下:

    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * pCurrent = &(MyCl->header);//头结点
    CricularLink * InsertData = (CricularLink *)data;
    if(place > MyCl->size)//在链表尾部插入
    {
        qDebug()<<"在队尾插入";

        MyCl->rear->next = InsertData;//1更新原来尾部原型的指向
        InsertData->next = &(MyCl->header);//2断开原来尾部元素next的指向,将新插入元素的next指向头结点
        MyCl->rear = InsertData;//3更新尾指针的指向
        MyCl->size++;

    }

因为尾部指针一直指向尾部,所以直接调用即可,不用像 链表中插入结点那样,再去找位置。

 在链表中插入结点

 其他部分都是保护程序,插入部分的主要原理如上两图所示。

    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * pCurrent = &(MyCl->header);//头结点
    CricularLink * InsertData = (CricularLink *)data;
    if(place > MyCl->size)//在链表尾部插入
    {
         .....

    }
    else//在中间部分插入
    {
        for(int i = 0;i<place-1;++i)
        {
            pCurrent = pCurrent->next;
        }
        InsertData->next = pCurrent->next;
        pCurrent->next = InsertData;
        MyCl->size++;
    }

 链表的插入,整体上的思路都是找到要插入位置的上一个位置,PCurrent指向头结点,循环次数=插入位置-1

其中插入位置是这个元素插入后,在链表中的位置,所以是大于0的。

4遍历

 普通遍历:没有什么好说的,单纯的循环输出即可

//遍历
void Foreach_Cl(CLHeader Cl,int SizeOFLoop,void(*Print)(void *))//第二个参数是遍历的遍数
{
    if(nullptr == Cl)
        return;
    if(SizeOFLoop <= 0)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;//只创建了一次性质,之后性质的MyCl.header就是指向链表的首地址,所以也可以把header改成hearder
    CricularLink * pCurrent = MyCl->header.next;//第一个元素
    for(int i = 0;i<SizeOFLoop;++i)
    {
        while(pCurrent!=&(MyCl->header))//next指针等于头指针时停止
        {
            Print(pCurrent);
            pCurrent = pCurrent->next;
        }
    }
}

但是因为是循环链表,非常特殊,所以多了一种遍历方式,就是从任意位置开始,遍历一周

//从指定位置开始循环
void Arbitrary_Cl(CLHeader  Cl,int place,int SizeOFLoop,void(*Print)(void *))
{
    if(nullptr == Cl)
        return;
    if(place <= 0)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * pCurrent = MyCl->header.next;//第一个元素

    if(place > MyCl->size)
        return;
    //先找出place的元素
    for(int i = 0;i<place-1;++i)
    {
        pCurrent = pCurrent->next;
    }
    CricularLink * pStopFlag = pCurrent;//遍历的前一个元素

    //再循环
    for(int i = 0;i<SizeOFLoop;++i)
    {
        do
        {
            Print(pCurrent);
            pCurrent = pCurrent->next;
            if(pCurrent == &(MyCl->header))//头结点是空的,所以要跳过
            {
                pCurrent = pCurrent->next;
            }

        }while(pCurrent != pStopFlag);

    }
}

 唯一要注意的是,在路过头结点的时候,要跳过头结点,因为头结点什么都没有,不跳过的话会发生内存泄露的。

(上述遍历是从第一个结点开始的,所以算法和普通遍历不大一样)

因为开发过程中不知道用户输入的数据类型,所以输出部分需要用户写一个回调函数:

void PrintDATA(void * data)
{
    if(nullptr == data)
        return;
    CLD * DATA = (CLD *)data;
    qDebug()<<"DATA->age:"<<DATA->age<<"DATA->name:"<<DATA->name;
}

5删除

和插入一样,找到要删除元素的上一个位置 

//按照位置删除
void Delete_Cl(CLHeader Cl,int place)
{
    if(nullptr == Cl)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    if(place<=0)
        return;
    if(place>MyCl->size)
        return;
    CricularLink * pCurrent = &(MyCl->header);//从头开始
    for(int i = 0;i<place-1;++i)
    {
        pCurrent = pCurrent->next;
    }
    pCurrent->next = pCurrent->next->next;
    MyCl->size--;//数量减去1

}

这时不用考虑链表尾部结点,删除对谁来说都一样,原理如下:

 

6链表拼接

拼接的原理也很简答,如下图所示,主要是操作尾指针

//合并
void Merge_Cl(CLHeader Cl1,CLHeader Cl2)
{
    if(nullptr == Cl1)
        return;
    if(nullptr == Cl2)
        return;
    CricularLinkProperty * MyCl1 = (CricularLinkProperty * )Cl1;
    CricularLinkProperty * MyCl2 = (CricularLinkProperty * )Cl2;
    CricularLink * Cl2FristItem = MyCl2->header.next;
//    将Cl2拼接在Cl1后面
    //1将cl1的末尾指针指向cl2的第一个元素
    MyCl1->rear->next = Cl2FristItem;
    //2将cl2的尾部最后一个元素的next指向cl1的头部
    MyCl2->rear->next = &(MyCl1->header);
    //最后,将cl1的指针指向cl2的尾部,也就是拼接后新链表的尾部
    MyCl1->rear = MyCl2->rear;
    //4个数的同一
    MyCl1->size =MyCl1->size + MyCl2->size;

}

验证代码:

    qDebug()<<"/*****************************进入循环链表部分*****************************/";
    qDebug()<<"初始化";
    CricularLinkProperty * MyCriclarLink = (CricularLinkProperty * )Inite_CL();

    CLD cld1 = {nullptr,"111",1};
    CLD cld2 = {nullptr,"222",2};
    CLD cld3 = {nullptr,"333",3};
    CLD cld4 = {nullptr,"444",4};
    CLD cld5 = {nullptr,"555",5};
    CLD cld6 = {nullptr,"666",6};


    Insert_Cl(MyCriclarLink,1,&cld1);
    Insert_Cl(MyCriclarLink,2,&cld2);
    Insert_Cl(MyCriclarLink,3,&cld3);
    Insert_Cl(MyCriclarLink,4,&cld4);
    Insert_Cl(MyCriclarLink,5,&cld5);
    Insert_Cl(MyCriclarLink,6,&cld6);

    qDebug()<<"&cld1.node:"<<&cld1.node;
    qDebug()<<"&cld2.node"<<&cld2.node<<"cld1.node.next:"<<cld1.node.next;
    qDebug()<<"&cld3.node"<<&cld3.node<<"cld2.node.next"<<cld2.node.next;
    qDebug()<<"&cld4.node"<<&cld4.node<<"cld3.node.next"<<cld3.node.next;
    qDebug()<<"&cld5.node"<<&cld5.node<<"cld4.node.next"<<cld4.node.next;
    qDebug()<<"&cld6.node"<<&cld6.node<<"cld5.node.next"<<cld5.node.next;
    qDebug()<<"cld6.node.next"<<cld6.node.next<<"MyCriclarLink:"<<MyCriclarLink;

通过输出,可以看出插入操作是没有问题的

    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"从4开始遍历";
    Arbitrary_Cl(MyCriclarLink,4,1,PrintDATA);

    qDebug()<<"删除第3个元素";
    Delete_Cl(MyCriclarLink,3);
    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);

    qDebug()<<"删除第4个元素(通过按值查询)";
    DeleteItem_Cl(MyCriclarLink,&cld4);
    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);

    qDebug()<<"将删除的3和4重新插入,插入到1,2的位置";
    Insert_Cl(MyCriclarLink,1,&cld3);
    Insert_Cl(MyCriclarLink,2,&cld4);
    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);

    qDebug()<<"从第4个元素开始遍历";
    Arbitrary_Cl(MyCriclarLink,4,1,PrintDATA);

 此部分程序验证了删除,按元素删除,和在不同位置插入

    //链表2
    CLD cld7 = {nullptr,"777",7};
    CLD cld8 = {nullptr,"888",8};
    CLD cld9 = {nullptr,"999",9};
    CLD cld10 = {nullptr,"aaa",10};
    CLD cld11= {nullptr,"bbb",11};
    CLD cld12 = {nullptr,"ccc",12};

    CricularLinkProperty * NewMyCriclarLink = (CricularLinkProperty * )Inite_CL();
    Insert_Cl(NewMyCriclarLink,1,&cld7);
    Insert_Cl(NewMyCriclarLink,2,&cld8);
    Insert_Cl(NewMyCriclarLink,3,&cld9);
    Insert_Cl(NewMyCriclarLink,4,&cld10);
    Insert_Cl(NewMyCriclarLink,5,&cld11);
    Insert_Cl(NewMyCriclarLink,6,&cld12);


    qDebug()<<"NEW从头遍历";
    Foreach_Cl(NewMyCriclarLink,1,PrintDATA);

 创建链表2

    qDebug()<<"链表拼接";
    Merge_Cl(MyCriclarLink,NewMyCriclarLink);
    qDebug()<<"从头遍历合并后的链表";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"合成后的个数"<<Size_Cl(MyCriclarLink);
    qDebug()<<"从第4个元素开始遍历";
    Arbitrary_Cl(MyCriclarLink,4,1,PrintDATA);

查看链表是否合并成功

    qDebug()<<"清空";
    Clear_Cl(MyCriclarLink);
    qDebug()<<"&cld1.node:"<<&cld1.node;
    qDebug()<<"&cld2.node"<<&cld2.node<<"cld1.node.next:"<<cld1.node.next;
    qDebug()<<"&cld3.node"<<&cld3.node<<"cld2.node.next"<<cld2.node.next;
    qDebug()<<"&cld4.node"<<&cld4.node<<"cld3.node.next"<<cld3.node.next;
    qDebug()<<"&cld5.node"<<&cld5.node<<"cld4.node.next"<<cld4.node.next;
    qDebug()<<"&cld6.node"<<&cld6.node<<"cld5.node.next"<<cld5.node.next;
    qDebug()<<"cld6.node.next"<<cld6.node.next<<"MyCriclarLink:"<<MyCriclarLink;
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);
    qDebug()<<"销毁";
    Destroy_Cl(MyCriclarLink);
    Destroy_Cl(NewMyCriclarLink);

验证清空功能

附录(全部代码)

.h

#ifndef CIRCULARLINK_H
#define CIRCULARLINK_H

#include <QWidget>
#include<QDebug>
class circularlink : public QWidget
{
    Q_OBJECT
public:
    explicit circularlink(QWidget *parent = nullptr);

signals:

public slots:
};
//链表
struct CricularLink
{
    CricularLink * next;
};
//链表性质
struct CricularLinkProperty
{
    CricularLink header;
    //不能是指针,链表的第一个位置是空,不放任何元素,如果变成指针的话,指针无法指向头文件,因为头文件还没有被创建,逻辑上不合理
    int size;
    CricularLink * rear;//增加尾部指针
};
//包含数据的内容
typedef struct CricularLinkData
{
    CricularLink node;
    QString name;
    int age;
}CLD;
typedef void * CLHeader;
//初始化
CLHeader Inite_CL();
//插入
void Insert_Cl(CLHeader Cl,int place,void * data);
//遍历
void Foreach_Cl(CLHeader Cl,int SizeOFLoop,void(*Print)(void *));//第二个参数是遍历的遍数
//任意位置循环遍历
void Arbitrary_Cl(CLHeader  Cl,int place,int SizeOFLoop,void(*Print)(void *));
//打印函数
void PrintDATA(void * data);
//按照位置删除
void Delete_Cl(CLHeader Cl,int place);
//按照元素删除
void DeleteItem_Cl(CLHeader Cl,void * data);
//销毁
void Destroy_Cl(CLHeader Cl);
//清空
void Clear_Cl(CLHeader Cl);
//个数
int Size_Cl(CLHeader  Cl);
//合并
void Merge_Cl(CLHeader Cl1,CLHeader Cl2);

#endif // CIRCULARLINK_H

 

.cpp 

#include "circularlink.h"

circularlink::circularlink(QWidget *parent) : QWidget(parent)
{
    qDebug()<<"/*****************************进入循环链表部分*****************************/";
    qDebug()<<"初始化";
    CricularLinkProperty * MyCriclarLink = (CricularLinkProperty * )Inite_CL();

    CLD cld1 = {nullptr,"111",1};
    CLD cld2 = {nullptr,"222",2};
    CLD cld3 = {nullptr,"333",3};
    CLD cld4 = {nullptr,"444",4};
    CLD cld5 = {nullptr,"555",5};
    CLD cld6 = {nullptr,"666",6};


    Insert_Cl(MyCriclarLink,1,&cld1);
    Insert_Cl(MyCriclarLink,2,&cld2);
    Insert_Cl(MyCriclarLink,3,&cld3);
    Insert_Cl(MyCriclarLink,4,&cld4);
    Insert_Cl(MyCriclarLink,5,&cld5);
    Insert_Cl(MyCriclarLink,6,&cld6);

    qDebug()<<"&cld1.node:"<<&cld1.node;
    qDebug()<<"&cld2.node"<<&cld2.node<<"cld1.node.next:"<<cld1.node.next;
    qDebug()<<"&cld3.node"<<&cld3.node<<"cld2.node.next"<<cld2.node.next;
    qDebug()<<"&cld4.node"<<&cld4.node<<"cld3.node.next"<<cld3.node.next;
    qDebug()<<"&cld5.node"<<&cld5.node<<"cld4.node.next"<<cld4.node.next;
    qDebug()<<"&cld6.node"<<&cld6.node<<"cld5.node.next"<<cld5.node.next;
    qDebug()<<"cld6.node.next"<<cld6.node.next<<"MyCriclarLink:"<<MyCriclarLink;

    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"从4开始遍历";
    Arbitrary_Cl(MyCriclarLink,4,1,PrintDATA);

    qDebug()<<"删除第3个元素";
    Delete_Cl(MyCriclarLink,3);
    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);

    qDebug()<<"删除第4个元素(通过按值查询)";
    DeleteItem_Cl(MyCriclarLink,&cld4);
    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);

    qDebug()<<"将删除的3和4重新插入,插入到1,2的位置";
    Insert_Cl(MyCriclarLink,1,&cld3);
    Insert_Cl(MyCriclarLink,2,&cld4);
    qDebug()<<"从头遍历";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);

    qDebug()<<"从第4个元素开始遍历";
    Arbitrary_Cl(MyCriclarLink,4,1,PrintDATA);

    //链表2
    CLD cld7 = {nullptr,"777",7};
    CLD cld8 = {nullptr,"888",8};
    CLD cld9 = {nullptr,"999",9};
    CLD cld10 = {nullptr,"aaa",10};
    CLD cld11= {nullptr,"bbb",11};
    CLD cld12 = {nullptr,"ccc",12};

    CricularLinkProperty * NewMyCriclarLink = (CricularLinkProperty * )Inite_CL();
    Insert_Cl(NewMyCriclarLink,1,&cld7);
    Insert_Cl(NewMyCriclarLink,2,&cld8);
    Insert_Cl(NewMyCriclarLink,3,&cld9);
    Insert_Cl(NewMyCriclarLink,4,&cld10);
    Insert_Cl(NewMyCriclarLink,5,&cld11);
    Insert_Cl(NewMyCriclarLink,6,&cld12);


    qDebug()<<"NEW从头遍历";
    Foreach_Cl(NewMyCriclarLink,1,PrintDATA);

    qDebug()<<"链表拼接";
    Merge_Cl(MyCriclarLink,NewMyCriclarLink);
    qDebug()<<"从头遍历合并后的链表";
    Foreach_Cl(MyCriclarLink,1,PrintDATA);
    qDebug()<<"合成后的个数"<<Size_Cl(MyCriclarLink);
    qDebug()<<"从第4个元素开始遍历";
    Arbitrary_Cl(MyCriclarLink,4,1,PrintDATA);

    qDebug()<<"清空";
    Clear_Cl(MyCriclarLink);
    qDebug()<<"&cld1.node:"<<&cld1.node;
    qDebug()<<"&cld2.node"<<&cld2.node<<"cld1.node.next:"<<cld1.node.next;
    qDebug()<<"&cld3.node"<<&cld3.node<<"cld2.node.next"<<cld2.node.next;
    qDebug()<<"&cld4.node"<<&cld4.node<<"cld3.node.next"<<cld3.node.next;
    qDebug()<<"&cld5.node"<<&cld5.node<<"cld4.node.next"<<cld4.node.next;
    qDebug()<<"&cld6.node"<<&cld6.node<<"cld5.node.next"<<cld5.node.next;
    qDebug()<<"cld6.node.next"<<cld6.node.next<<"MyCriclarLink:"<<MyCriclarLink;
    qDebug()<<"个数"<<Size_Cl(MyCriclarLink);




    qDebug()<<"销毁";
    Destroy_Cl(MyCriclarLink);
    Destroy_Cl(NewMyCriclarLink);


}
//初始化
CLHeader Inite_CL()
{
    //初始化
    CricularLinkProperty * MyCl = new CricularLinkProperty;
    MyCl->size = 0;
    MyCl->header.next = &(MyCl->header);//循环链表 目前没有元素,所以自己指向自己
    MyCl->rear = &(MyCl->header);//尾指针指向链表尾部,也就是目前的首地址
    return MyCl;
}
//插入
void Insert_Cl(CLHeader Cl,int place,void * data)
{
    if(nullptr == Cl)
        return;
    if(nullptr == data)
        return;
    if(place <=0)
        return;

    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * pCurrent = &(MyCl->header);//头结点
    CricularLink * InsertData = (CricularLink *)data;
    if(place > MyCl->size)//在链表尾部插入
    {
        qDebug()<<"在队尾插入";

        MyCl->rear->next = InsertData;//1更新原来尾部原型的指向
        InsertData->next = &(MyCl->header);//2断开原来尾部元素next的指向,将新插入元素的next指向头结点
        MyCl->rear = InsertData;//3更新尾指针的指向
        MyCl->size++;

    }
    else//在中间部分插入
    {
        for(int i = 0;i<place-1;++i)
        {
            pCurrent = pCurrent->next;
        }
        InsertData->next = pCurrent->next;
        pCurrent->next = InsertData;
        MyCl->size++;
    }
}
//遍历
void Foreach_Cl(CLHeader Cl,int SizeOFLoop,void(*Print)(void *))//第二个参数是遍历的遍数
{
    if(nullptr == Cl)
        return;
    if(SizeOFLoop <= 0)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;//只创建了一次性质,之后性质的MyCl.header就是指向链表的首地址,所以也可以把header改成hearder
    CricularLink * pCurrent = MyCl->header.next;//第一个元素
    for(int i = 0;i<SizeOFLoop;++i)
    {
        while(pCurrent!=&(MyCl->header))//next指针等于头指针时停止
        {
            Print(pCurrent);
            pCurrent = pCurrent->next;
        }
    }
}
//从指定位置开始循环
void Arbitrary_Cl(CLHeader  Cl,int place,int SizeOFLoop,void(*Print)(void *))
{
    if(nullptr == Cl)
        return;
    if(place <= 0)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * pCurrent = MyCl->header.next;//第一个元素

    if(place > MyCl->size)
        return;
    //先找出place的元素
    for(int i = 0;i<place-1;++i)
    {
        pCurrent = pCurrent->next;
    }
    CricularLink * pStopFlag = pCurrent;//第一个元素

    //再循环
    for(int i = 0;i<SizeOFLoop;++i)
    {
        do
        {
            Print(pCurrent);
            pCurrent = pCurrent->next;
            if(pCurrent == &(MyCl->header))//第一个元素是空的,所以要跳过第一个元素
            {
                pCurrent = pCurrent->next;
            }

        }while(pCurrent != pStopFlag);

    }
}
void PrintDATA(void * data)
{
    if(nullptr == data)
        return;
    CLD * DATA = (CLD *)data;
    qDebug()<<"DATA->age:"<<DATA->age<<"DATA->name:"<<DATA->name;
}
//按照位置删除
void Delete_Cl(CLHeader Cl,int place)
{
    if(nullptr == Cl)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    if(place<=0)
        return;
    if(place>MyCl->size)
        return;
    CricularLink * pCurrent = &(MyCl->header);//从头开始
    for(int i = 0;i<place-1;++i)
    {
        pCurrent = pCurrent->next;
    }
    pCurrent->next = pCurrent->next->next;
    MyCl->size--;//数量减去1

}
//按照元素删除
void DeleteItem_Cl(CLHeader Cl,void * data)
{
    if(nullptr == Cl)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * InsertItem = (CricularLink *)data;
    CricularLink * pCurrent = MyCl->header.next;//从第一个元素开始
    int place = 1;//注意起始点
    for(int i = 0;i<MyCl->size;++i)
    {
        if(pCurrent == InsertItem)
        {
            Delete_Cl(Cl,place);//按照位置删除
            break;
        }
        pCurrent = pCurrent->next;
        place++;
    }
}
//销毁
void Destroy_Cl(CLHeader Cl)
{
    if(nullptr == Cl)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    delete MyCl;

}
//清空
void Clear_Cl(CLHeader Cl)
{
    if(nullptr == Cl)
        return;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    CricularLink * pCurrent = MyCl->header.next;//第一个元素
    CricularLink * pDel = nullptr;//
    while(pCurrent!= &(MyCl->header))//指向头文件说明循环结束
    {
        pDel = pCurrent;
        pCurrent = pCurrent->next;
        pDel->next = nullptr;//指向空
    }//顺序不能错
    MyCl->rear = &(MyCl->header);//因为清空,尾指针自然指向头指针
    MyCl->header.next =  &(MyCl->header);//头指针的next指向自己,因为只剩自己一个了
    MyCl->size = 0;//清空,数据只剩0了
}
//个数
int Size_Cl(CLHeader Cl)
{
    if(nullptr == Cl)
        return 0;
    CricularLinkProperty * MyCl = (CricularLinkProperty * )Cl;
    return MyCl->size;

}
//合并
void Merge_Cl(CLHeader Cl1,CLHeader Cl2)
{
    if(nullptr == Cl1)
        return;
    if(nullptr == Cl2)
        return;
    CricularLinkProperty * MyCl1 = (CricularLinkProperty * )Cl1;
    CricularLinkProperty * MyCl2 = (CricularLinkProperty * )Cl2;
    CricularLink * Cl2FristItem = MyCl2->header.next;
//    将Cl2拼接在Cl1后面
    //1将cl1的末尾指针指向cl2的第一个元素
    MyCl1->rear->next = Cl2FristItem;
    //2将cl2的尾部最后一个元素的next指向cl1的头部
    MyCl2->rear->next = &(MyCl1->header);
    //最后,将cl1的指针指向cl2的尾部,也就是拼接后新链表的尾部
    MyCl1->rear = MyCl2->rear;
    //4个数的同一
    MyCl1->size =MyCl1->size + MyCl2->size;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值