• 线性表的链式存储(C代码实现)


    线性表的链式存储结构

    线性表的实现分顺序存储结构链式存储结构。

    线性表的链式存储结构又称单链表。

    上一节我们学习了线性表的顺序存储结构,并实现解顺序存储的基本操作。

    这一节我们来学习线性表链式存储结构,那我们再想象一下我为什么我们要引入链式存储结构,万物存在必有其道理

    主要还是因为线性存储结构存在着这样一个问题:当我们需要插入和删除元素时,就必须挪动大量与之无关的元素,因为线性存储结构结点与节点之间的关系是相邻关系,一个节点挨着一个节点

    如为了插入或者删除一个元素移动大量的元素,这样就降低了程序运行效率。

    当我们引入

    顾名思义链式存储结构,数据与数据之间是以链式关系来产生连接的的,我们可以脑补一下锁链的样子,不扯淡了,进入正题--

    我们如何定义一个链式存储结构的节点呢?

    /*Node表示一个节点*/
    typedef struct Node{
        int data;   //数据域
        struct Node* next;   //存放下一个节点的指针
    }Node;
    typedef struct Node* LinkList; /*取了一个别名,定义LinkList = Node*,用于存放节点的指针*/

    一个节点包括一个数据域和指针域

    我们将这种只带有一个指针域的线性表称为单链表

    链表中第一个结点的存储位置叫做头指针。

    单链表的第一个结点前附设一个结点,称为头结点

    注意可以没有头节点,但是要是链表,就一定存在头指针。

     那么问题来了,我们如何区分头节点和头指针呢?

    头指针:是指向链表的指针,如果不存在头节点,那么头指针就会指向链表的第一个节点。

    头节点:实际上是不存在的,只不过是为了链表的一些操作方便而设置的,头节点与第一个节点以链式关系相连,并且头节点的数据域没有意义,指针域存放第一个节点的地址。

     

    单链表的插入

    实现代码:

    /*插入元素 n是位置,c是数*/
    void InsertElemList(LinkList L,int n,int c){
    
        int count=0;
        LinkList p,s;
        p =L;   //注意这里的p不在指向第一个节点了
        count =1; 
        while(p->next && count<n){   9         p =p->next;
            ++count;
        }
        s =(Node*)malloc(sizeof(Node));
        s->data=c;
        s->next =p->next;
        p->next=s;
    }

    主要思路:

    1、创建一个节点指针,用于存放头节点地址,注意这里不是第一个节点。

    2、将这个节点移动要插入节点的位置上(插在第几个位置上就移动几个位置)

    3、然后创建一个要插入的节点并分配内存空间 

    4、执行s->next =p->next;    p->next=s;    //注意前后顺序不能调

    单链表的删除

    主要代码:

    /*删除元素*/
    void DeleteElem(LinkList L,int n){
        int count=0;
        LinkList p ,q;
        p =L;   //注意这里的p不在指向第一个节点了
        count =1; 
        while(p->next && count<n){  //
            p =p->next;
            ++count;
        }
        if(!(p->next) || count>n)
            printf("没有找到可删除的元素");
        q= p->next;
        p->next = q->next;
         free(q);
    }

    主要思路:

    1、定义一个计数器(用于确定删除的位置)

    2、创建一个指针p,指向头节点

    3、创建一个指针q(用于释放内存)

    4、当指针移动到要删除元素的前一个,停止 

    5,执行q= p->next;  p->next = q->next;  free(q);

    单链表的建表(头插法)

    顾名思义直接插在第一位,就是头节点的后面。

    void CreateListHead(LinkList *L,int n){
    
        LinkList p;
        *L = (Node*)malloc(sizeof(Node));  //生成的新节点节点要初始化
        (*L)->next=NULL;    //并指向空
    
        for(int i=0;i<n;i++){
            p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化
            p->data=i;
            p->next=(*L)->next;  //这里不能指向NULL
            (*L)->next =p;   //两级指针
        }
    }

    主要思路:

    1、首先创建头节点,并初始化指向空

    2、然后再头节点和NULL之间插入第一个元素,然后再头节点和第一个元素插入第二个元素,以此类推...

    3、这里要注意的是这里节点的地址要使用二级来存储,因为我们在使用的malloc为链表初始化分配的内存空间在第一次函数调用后内存就失效了

    也可以不使用二级指针,直接把初始化链表的代码另外写在外面即可

    4、所以用可以使用二级指针来避免内存泄漏

    单链表的建表(尾插法)

    void CreateListTail(LinkList *L ,int n){
    
        LinkList p,r;   //生成节点p,存放Node地址
        *L = (Node*)malloc(sizeof(Node));  //生成的头节点节点要初始化
        (*L)->next=NULL;
        r = *L;  //用于遍历
    
        for(int i=0;i<n;i++){
            p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化
            p->data=i;
            r->next=p;
            r=p;  //r指针移动到p上r ,以便
        }
        r->next=NULL; //最后一个指向空
    }

    主要思路:

    1、定义两个指针,一个r,一个q

    2、p用于创建新的节点,r用于确定尾部,这样每一次插入的时候只要确定r的位置,就确定了尾部的位置

    3、二级指针的使用同头插法

    4、核心代码执行 r->next=p;   r=p;   r->next=NULL;

    单链表的遍历

    void TraverseList(LinkList L){
        LinkList p;
        p = L->next;
        while(p){
            printf("%d ",p->data);
             p=p->next;
        }
    }

    清空单链表

    /*清空链表*/
    void ClearList(LinkList L){
        LinkList p ,q;
        p = L->next;     //指向第一个元素
        while(p){
            q=p->next;  //q指向了p的下一个
            free(p);   //释放内存
            p =q;
        }
        L->next =NULL;     //头节点指向空
    }

    主要思路:

    1、定义一个指针指向第一个节点

    2、在定义一个指针用于间接存放即将释放的内存,用q表示

    3、当p移动到要释放节点内存时,将它赋值给q,然后移动到下一位,依次类推...

     单链表的初始化

    主要代码

    /*初始化链表,传的是二级指针,操作的是一级指针的地址*/
    int InitList(LinkList *L){    
        *L=(LinkList)malloc(sizeof(Node));  //这里的*L就是Node节点的指针对象
        if(!(*L))   //申请内存失败
            return 0;
        (*L)->next=NULL;
        return 1;
    }

    也没有什么,一定要注意在malloc分配的内存在函数调用完成后就会失效,如果想继续使用,要么借助C++里面的引用,或者二级指针(可能还有其它办法)

    因为内存泄漏问题被搞了一个晚上了 - -

    罪过--罪过--

     指针作为参数传值可以参考大佬资料:https://www.cnblogs.com/WeyneChen/p/6672045.html

    其它的一下操作见下面代码吧

    #include <stdio.h>
    #include <stdlib.h>
    #include "time.h"
    
    /*Node表示一个节点*/
    typedef struct Node{
        int data;
        struct Node* next;
    }Node;
    typedef struct Node* LinkList; /*定义LinkList*/
    
    /*初始化链表*/
    int InitList(LinkList *L){    
        *L=(LinkList)malloc(sizeof(Node));  //这里的*L就是Node节点的指针对象
        if(!(*L))   //申请内存失败
            return 0;
        (*L)->next=NULL;
        return 1;
    }
    
    //头插法,n表示插入的个数
    void CreateListHead(LinkList *L,int n){
    
        LinkList p;
        *L = (Node*)malloc(sizeof(Node));  //生成的新节点节点要初始化
        (*L)->next=NULL;    //并指向空
    
        for(int i=0;i<n;i++){
            p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化
            p->data=i;
            p->next=(*L)->next;  //这里不能指向NULL
            (*L)->next =p;   //两级指针
        }
    }
    //尾插法,n表示插入的个数
    void CreateListTail(LinkList *L ,int n){
    
        LinkList p,r;   //生成节点p,存放Node地址
        *L = (Node*)malloc(sizeof(Node));  //生成的头节点节点要初始化
        (*L)->next=NULL;
        r = *L;  //用于遍历
    
        for(int i=0;i<n;i++){
            p =(Node*)malloc(sizeof(Node)); //新生成的节点节点要初始化
            p->data=i;
            r->next=p;
            r=p;  //r指针移动到p上r ,以便
        }
        r->next=NULL; //最后一个指向空
    }
    
    /*遍历链表*/
    void TraverseList(LinkList L){
        LinkList p;
        p = L->next;
        while(p){
            printf("%d ",p->data);
             p=p->next;
        }
    }
    
    /*清空链表*/
    void ClearList(LinkList L){
        LinkList p ,q;
        p = L->next;     //指向第一个元素
        while(p){
            q=p->next;  //q指向了p的下一个
            free(p);   //释放内存
            p =q;
        }
        L->next =NULL;     //头节点指向空
    }
    
    /*获取链表长度*/
    int GetLengthList(LinkList L){
        LinkList p;
        p=L->next;
        int count=0;   //计数器
        while(p){
            count++;
            p= p ->next;    
        }
        return count;
    
    }
    
    /*删除元素*/
    void DeleteElem(LinkList L,int n){
        int count=0;
        LinkList p ,q;
        p =L;   //注意这里的p不在指向第一个节点了
        count =1; 
        while(p->next && count<n){  //
            p =p->next;
            ++count;
        }
        if(!(p->next) || count>n)
            printf("没有找到可删除的元素");
        q= p->next;
        p->next = q->next;
         free(q);
    }
    
    /*插入元素 n是位置,c是数*/
    void InsertElemList(LinkList L,int n,int c){
    
        int count=0;
        LinkList p,s;
        p =L;   //注意这里的p不在指向第一个节点了
        count =1; 
        while(p->next && count<n){  //
            p =p->next;
            ++count;
        }
        s =(Node*)malloc(sizeof(Node));
        s->data=c;
        s->next =p->next;
        p->next=s;
    
    }
    
    /* 初始条件:顺序线性表L已存在 */
    /* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
    /* 若这样的数据元素不存在,则返回值为0 */
    int LocateElem(LinkList L,int e){
    
        LinkList p;
        p =L->next;
        while(p){
            if(p->data == e)
                return 1;
            p =p->next;
        }
        return 0;
    }
    
    /*获取元素,n表示第几个,并返回查到的值*/
    int GetElem(LinkList L,int n){
    
        LinkList p;   //生成节点p,存放Node地址
        p = L->next;   //p指向第一个元素
        int count=1;   //计数器
    
        while(p && count<n){ //查找
            p = p->next;
            count++;
        }
        if(!p || count>n){ //没找到
            return 0;
        }    
        int ret = p->data;    //找到
        return ret;
    }
    
    /*判断链表是否为空*/
    int ListElmpty(LinkList L){
    
        if(L->next){
            return 0;
        }else{
            return 1;
        }
    }
    
    int main(){
        LinkList L1;   //创建一个节点,用于头插法
        LinkList L2;   //创建一个节点,用于尾插法
    
        printf("......头插法......
    ");
        InitList(&L1);  //初始化
        CreateListHead(&L1,5);
        TraverseList(L1);
        printf("
    ");
    
        printf("......尾插法......
    ");
        InitList(&L2);  //初始化
        CreateListTail(&L2,5);
        TraverseList(L2);
        printf("
    ");
    
        //获取元素的值
        int getElem= GetElem(L2,3);
        printf("%d 
    ",getElem);
    
        //获取长度
        int GetLength=GetLengthList(L2);
        printf("L1链表的长度:%d",GetLength);
        printf("
    ");
    
        //删除L1中2号元素
        printf("删除L1中2号元素:");
        DeleteElem(L1,2);
        TraverseList(L1);
        printf("
    ");
    
        //在第三个位置插入11
        printf("在第三个位置插入11元素:");
        InsertElemList(L1,3,11);
        TraverseList(L1);
        printf("
    ");
    
        int localFind=LocateElem(L1,11);
        printf("找到了吗: %d
    d",localFind);
    
        //判断L1是否为空
        int lstElempty=ListElmpty(L1);
        printf("L1为空吗: %d
    ",lstElempty);
        //清空L1
        ClearList(L1);
        //在判断L1是否为空
        lstElempty=ListElmpty(L1);
        printf("L1为空吗: %d
    ",lstElempty);
    
        return 0;
    }

    写完插入和删除操作,我们便可以看出,链式存储结构对于插入和删除的优势是明显的,不需要进行大量的元素的移动。

    当然单链表这么个优秀,也是存在缺点的

    缺点就是其不便于进行查找和修改,每查找或者修改一个元素就要开始从头开始遍历  - -这么坑爹的吗 ?没错 就是这么坑爹 - -

    所以当我们应用的场合不同 ,就用不同的存储结构。

  • 相关阅读:
    概率图模型 ——(6)团树传播算法
    概率图模型 ——(5)变量消元法求边缘概率
    Catkin workspace `/home/qian` is already initialized. No action taken.
    安装TensorRT
    vscode教程
    概率图模型 ——(4)因子图
    概率图模型 ——(3)马尔科夫随机场
    概率图模型 ——(2)贝叶斯网络
    概率图模型 ——(1)概率论与图论基础
    Kubernetes 是怎么实现服务发现的?
  • 原文地址:https://www.cnblogs.com/liuzeyu12a/p/10300110.html
Copyright © 2020-2023  润新知