大话数据结构第二站---链式线性表

hello,大家,不好意思停更了几天大话数据结构,现在开始更新,争取隔日一更,请粉丝宝宝见谅。接下来我就要开始我们今天的大话数据结构了哦!

线性表的链式存储
他解决顺序存储的缺点,插入和删除,动态存储问题。
特点:
线性表链式存储结构的特点是一组任意的存储单位存储线性表的数据元素,存储单元可以是连续的,也可以不连续。可以被存储在任意内存未被占用的位置上。

所以前面的顺序表只需要存储数据元素信息就可以了。在链式结构中还需要一个元素存储下一个元素的地址。

为了表示每个数据元素,ai与其直接后继数据元素ai+1之间的逻辑关系,对ai来说,除了存储其本身的信息外,还需要存一个指示器直接后续的信息。把存储元素信息的域叫数据域,把存储直接后继位置的域叫指针域。这两部分信息组成数据元素ai的存储映像,叫结点(Node);以这样一个节点来存储信息,在堆上在抽象处理一个链表。各位懂了吗,就是用一个指针将各个节点连接起来。

链表的操作基本也是一样的的增删改查,对比一下线性表来看一下对比,顺序表和链表 优缺点
存储方式:
顺序表是一段连续的存储单元
链表是逻辑结构连续物理结构(在内存中的表现形式)不连续
时间性能,
查找 顺序表O(1)
 链表  O(n)
插入和删除
顺序表 O(n)
链表   O(1)

空间性能
顺序表 需要预先分配空间,大小固定
链表, 不需要预先分配,大小可变,动态分配

接下来我们通过例子来详细了解链表;(为了更好的了解我们这次采用双向链表来讲解,单向的只需要删掉一个前向指针就行)这是.h上的结构体的定义

typedef struct{
    char name[64];
    char sex;
    int age;
    int score;
}DATATYPE;

typedef struct node {
    DATATYPE data;
    struct node *next,*prev;
}DouLinkNode;

typedef struct{
    DouLinkNode *head;
    int clen;
}DouLinkList;

首先定义一个数据DATATYPE的结构体,里面有有一个学生的基本信息,名字,性别,年龄,成绩。再定义一个节点NODE节点,里面定义一个数据结构体,一个前向节点的指针一个后向节点的指针。最后在定义一个list的结构体,里面有一个节点的头指针,一个数据长度。

下面是一些需要使用的使用的函数,头文件定义出来

typedef enum{DIR_FORWARD,DIR_BACKWARD}DIRECT;
typedef int (*PFUN)(DATATYPE_L *data,void *arg);
DouLinkList* CreateDouLinkList();
int InsertHeadLinkList(DouLinkList *list, DATATYPE *data);
int ShowDouLinkList(DouLinkList *list,DIRECT direct);
int GetSizeDouLinkList(DouLinkList *list);
DouLinkNode *FindLinkList(DouLinkList *list, PFUN fun,void* arg);
DouLinkNode *Findname(DouLinkList *list,char *name);
int RevertDouLinkList(DouLinkList *list);
int DeleteLinkList(DouLinkList *list, PFUN fun,void* arg);
int IsEmptyDouLinkList(DouLinkList *list);
int ModifyDouLinkList(DouLinkList *list,PFUN fun,void* arg,DATATYPE *data);
int DestroyDouLinkList(DouLinkList **list);
int InserPosDouLinkList(DouLinkList *list,DATATYPE *data,int pos);

下面先介绍基础的几个,增删改查


DouLinkList * CreateDouLinkList()
{
    DouLinkList * dl = (DouLinkList*)malloc(sizeof(DouLinkList));
    if(NULL == dl)
    {
        fprintf(stderr,"DouLinkList malloc err");
        return NULL;
    }
    dl->head = NULL;
    dl->head = 0;
    return dl;
}
int DestroyDouLinkList(DouLinkList **list)
{
    DouLinkNode* tmp=(*list)->head;
    while(tmp)
    {
        (*list)->head=(*list)->head->next;
        free(tmp);
        tmp = (*list)->head;

    }
    free(*list);
    (*list)= NULL;
    return 0;
}

这两个创建双向链表和销毁双向链表的函数,定义一个返回值为链表指针的创建函数,不需要传参数,再堆上开辟一个大小为链表结构体大小的内存,开辟判断,初始化他的head节点为NULL,和长度为0。返回这个链表就创建好了这个链表。销毁函数,利用双星指针来指向这个要销毁的链表(为啥要双星呢,因为他是一个开在堆上的空间而且是一个指针来指向的区域所以用一个*不能代表整个链表只是一个节点,编译报错),定义一个节点的指针,来遍历链表指向实际的节点来删除。while循环free这些节点,最后再删除这个头节点就可以了。再把指针置空即可。

int GetSizeDouLinkList(DouLinkList *list)
{
    return list->clen;
}
int IsEmptyDouLinkList(DouLinkList *list)
{
    return (0 == list->clen);
}


int ShowDouLinkList(DouLinkList *list,DIRECT direct)
{
    int i = 0;
    DouLinkNode * temp = NULL;
    if(direct == DIR_FORWARD)
    {
        temp = list->head;
        int len = GetSizeDouLinkList(list);
        for(i = 0;i < len;i++)
        {
            printf("%s %c %d %d\n",temp->data.name,temp->data.sex,temp->data.age,temp->data.score);
            temp = temp->next;
        }
    }else
    {
        int len = GetSizeDouLinkList(list);
        temp = list->head;
        while(temp->next)
        {
            temp = temp->next;
        }
        for(i=0;i<len;i++)
        {
            printf("%s %c %d %d\n",temp->data.name,temp->data.sex,temp->data.age,temp->data.score);
            temp=temp->prev;
        }
    }
    return 0;
}

这是四个基础函数,第一个传入一个链表,放回他的大小len,第二个是一个判断是不是为空的函数,放回是判断链表的clen是否为0;第三个是一个是一个显示函数,第二个参数是一个带参宏,如果是前向的就从第一个开始打印数据信息,如果是后后向的就先用一个temp找到最后面的节点再从后往前打印。

int InsertHeadLinkList(DouLinkList *list, DATATYPE *data)
{
    DouLinkNode * newnode = (DouLinkNode*)malloc(sizeof(DouLinkNode));
    if(NULL == newnode)
    {
        perror("InsertHeadLinkList malloc");
        exit(1);
    }
    memcpy(&newnode->data,data,sizeof(DATATYPE));
    newnode->prev = NULL;
    newnode->next = NULL;
    if(list->clen == 0)
    {
        list->head = newnode;
    }else
    {
        newnode->next = list->head;
        list->head->prev = newnode;
        list->head = newnode;
    }
    list->clen++;
    return 0;
}
int InserPosDouLinkList(DouLinkList *list, DATATYPE *data, int pos)
{
    if(pos<0 ||pos>GetSizeDouLinkList(list))
    {
        fprintf(stderr,"InserPosDouLinkList error,index error\n");
        return 1;

    }
    if(IsEmptyDouLinkList(list) || 0 == pos)
    {
        return InsertHeadLinkList(list,data);
    }
    else
    {
        DouLinkNode* tmp = list->head;
        tmp= list->head;
        DouLinkNode* newnode = (DouLinkNode*)malloc(sizeof(DouLinkNode));
        if(NULL == newnode)
        {
            perror("InserPosDouLinkList malloc");
            return 1;
        }
        memcpy(&newnode->data,data,sizeof(DATATYPE));
        newnode->prev = NULL;
        newnode->next = NULL;
        int i = pos-1;
        while(i--)
        {
            tmp=tmp->next;
        }
        newnode ->prev = tmp;
        newnode->next = tmp->next;

        if(tmp->next)
        {
            tmp->next->prev = newnode;
        }
        tmp->next = newnode;
    }
    list->clen++;
    return 0;

}

这是两个增加函数,第一个是头插,第二个是按位置插,为啥尾插没写呢,你只需要按照我的思路改一下按位置差就行了,各位学习中的宝子要学会抢答。定义一个新的节点,再参数把数据结构体传进去,利用memcpy函数把参数的的data复制进newnode的data中,前向和后向指针置空,让后判断这个链表,如果这个链表为空,就从头插,list的head等于这个新节点,如果不是空链表newnode->next = list->head;新节点的后向指针等于头节点,list->head->prev = newnode,头节点的前向指向newnode,list->head = newnode;list的头指针直线新的节点遮掩就插入完成了。

第二个按位置插大差不差,只不过就是找到那个位置,位置小于0报错,链表为空或者位置为0报错.去过这些都不满足再合理范围内就可以按位置插入,利用一个temp的节点指针找到post-1的位置,从中断开把新节点插进去,插入方法和上面差不多。


DouLinkNode *Findname(DouLinkList *list, char *name)
{
    DouLinkNode * temp = list->head;
    int len = GetSizeDouLinkList(list);
    int i = 0;
    for(i = 0;i<len;i++)
    {
        if(0 == strcmp(name,temp->data.name))
        {
            return temp;
        }
        temp = temp->next;
    }
    return NULL;
}DouLinkNode *FindLinkList(DouLinkList *list, PFUN fun, void *arg)
{
    DouLinkNode * temp = list->head;
    int len = GetSizeDouLinkList(list);
    int i = 0;
    for(i = 0;i<len;i++)
    {
        if(fun(&temp->data,arg))
        {
            return temp;
        }
        temp = temp->next;
    }
    return NULL;
}


int DeleteLinkList(DouLinkList *list, PFUN fun, void *arg)
{
    if(NULL == list)
    {
        fprintf(stderr,"list is null\n");
        return 1;
    }
    if(IsEmptyDouLinkList(list))
    {
        fprintf(stderr,"DouLinkList is empty");
        return 1;
    }
    DouLinkNode * ret = FindLinkList(list,fun,arg);
    if(NULL == ret)
    {
        fprintf(stderr,"cant find\n");
        return 1;
    }
    if(ret == list->head)
    {
        list->head = ret->next;
        ret->next->prev = NULL;
    }else
    {
        if(ret->next)
            ret->next->prev = ret->prev;
        ret->prev->next = ret->next;
    }
    free(ret);
    list->clen--;
    return 0;
}

这个是删除节点函数,第一个是找名字的指针函数,利用名字传入需要找到的名字的节点也可以用data结构体里面的任何一个类型找节点。遍历节点利用strcmp找到节点,然后返回节点指针,第二个函数是一个利用函数指针来查找的函数,和第一个差不多只不过是灵活性和可移植性比较好。第三个是一个删除节点函数,第一个参数是链表,第二个是函数指针,第三个是函数指针的参数。利用FindLinkList(DouLinkList *list, PFUN fun, void *arg)找到想要删除的节点,返回一个节点指针如果此时ret再head的话,将头节点的下一个接到head上,把下一个节点的前向节点置空。如果不为头节点的话,就有两种情况为节点和中间节点,加入一个if判断,中间节点的话,伤处这个节点把ret的后一个街道上一个。如果ret.next为空的话就说明是为节点只需要释放掉这个节点,把他前一个节点的上一个节点的next置空就行,这里为啥只需要一个函数呢,因为ret.next为空就不用再多谢函数了。是不是有一种简介的感觉呢。

int ModifyDouLinkList(DouLinkList *list, PFUN fun, void *arg, DATATYPE *data)
{
    DouLinkNode* ret = FindLinkList(list,fun,arg);
    if(NULL == ret)
    {
        fprintf(stderr,"ModifyDouLinkList error,cant find\n");
        return 1;
    }
    memcpy(&ret->data,data,sizeof(DATATYPE));
    return 0;

}

这是一个修改函数函数,也是利用一个函数指针来找到节点,让后将新的数据结构体重新复制到里面就行,这个简单不用过多阐述。接下来介绍一下逆序。

int RevertDouLinkList(DouLinkList *list)
{
    int size = GetSizeDouLinkList(list);
    if(size<2)
    {
        return 0;
    }

    DouLinkNode* prev= NULL;
    DouLinkNode* tmp = list->head;
    DouLinkNode*next= tmp->next;
    while(1)
    {
        tmp->next = prev;
        tmp->prev = next;
        prev= tmp;
        tmp = next;
        if(NULL == tmp)
        {
            break;
        }
        next =next->next;
    }
    list->head = prev;
    return 0;
}

如果小于2只有一个节点就不存在逆序了,

  1. 初始化指针

    • prev 初始化为 NULL,用于在循环中作为前一个节点的引用。
    • tmp 初始化为链表的头节点 list->head
    • next 初始化为 tmp->next,即头节点的下一个节点。
  2. 反转过程

    • 在循环中,首先更新 tmp 的 next 指针指向 prev(这是正确的反转操作)。
    • 然后,尝试更新 tmp 的 prev 指针指向 next。然而,这实际上是错误的,因为我们需要将 tmp 的 prev 指向其原先的 prev 节点(但在反转过程中,我们不需要显式设置这个,因为 prev 已经在上一步更新为 tmp)。
    • 更新 prev 和 tmp 为当前处理的节点和下一个节点。
    • 更新 next 为 tmp->next(这实际上是多余的,因为我们已经有了 next 的值,但在此逻辑中仍然可以工作)。
    • 循环继续,直到 tmp 为 NULL,这意味着我们已经处理完了链表中的所有节点

 好的,以上就是双向链表的所有知识点,宝子们好好观看学习哦,这只是一个抛砖引玉,大家下来看一下怎么按照双向链表实现一个单线链表,喊得,那就拜拜喽,喜欢的话点赞收藏哦我接下来夜间会议通俗易懂的语言来简介接下来的数据结构内容,争取隔日一更新,绝对保证是高质量更新!!!你们的观看点赞收藏是我继续下去的动力。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值