栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

最全数据结构(二)——单链表篇

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

最全数据结构(二)——单链表篇

一.链式存储结构的概述

        1.个人人为,链表和顺序表最大的差别,即是链表可以描述各元素间的逻辑关系

        链表有一个个结点构成,每个结点有两部分:数据域+指针域

        2.一般的,可以理解为【链表=头结点+首元结点+其它结点】构成,或者

        【链表=首元结点+其它结点】构成(链表不一定有头节点)

如下图所示:

        3.链表的分类:单链表,双链表,循环链表

二.单链表操作的引入

        1.首先我们定义一个带头节点的指针

2.1 【带头节点的链表定义】

typedef Struct Lnode{           //声明结点
    ElemType data;              //结点的数据域
    Struct Lnode *next;         //结点的中指针域
}Lnode,*LinkList;               //LinkList为指向结构体Lnode的指针类型

        对于这个结构体的理解有点容易把人搞昏,我用自己的话表达一下。在结构体的末尾有Lnode,*LinkList,均是代表“类型”,而这里的类型可以理解为int型,float型,只是这里类型里面的,是代表一个结构体,且是一个指针类型。把这里搞清楚以后,这个结构体typedef的是struct Lnode作为结构体名,那么在结构体的末尾又出现了一个Lnode,这里用到了嵌套,那么指针*LinkList即会指向下一个仍含有data和next的结构体。

        注:之前文章有同学私信我问typedef加到这里的意思,其实这里用typedef之后,就可以用Lnode来代替结构体。

        2.操作链表L:

        2.1定义链表L: LinkList L;   //定义一个头指针,指向某一个链表

        2.2定义结点指针p:Lnode *p;或LinkList p;  //虽然两者意义相同,但当用于指向结点时,个人认为用Lnode *p会更好

        3.实例

        我们用一个存储学生学号,姓名,成绩的单链表的例子,可以让大家初步认识链表并理解之前讲到的关于结构体的内容

2.2【存储学生学号,姓名,成绩的单链表】

typedef Struct student{
    char num[8];             //数据域
    char name[8];            //数据域
    int score;               //数据域
    Struct student *next     //指针域
}Lnode,*LinkList;            //*LinkList指针就会指向跟Lnode一个类型包含这四项的结构

        另一种表示,也是更常用的操作,就是先定义一个结构体存储数据项,然后再定义一个结构体有data,next形成链表的结构。

typedef Struct{
    char num[8];            //数据域
    char name[8];           //数据域
    int score;              //数据域
}ElemType;

typedef Struct Lnode{
    ElemType data;          //数据域
    Struct Lnode *next;     //指针域
}Lnode,*LinkList

        上一篇文章讲到,ElemType不是具体的哪一种类型,而是要靠你自己定义一个名称,这个你自己定义的类型,就是代表含有char num,char name,int score这三个数据项的一种结构体类型

三.单链表操作的实现

        1.初始化

        算法:生成新的结点作为头节点,用头指针L指向头节点,将头节点的指针域置空(即next域)

2.3【单链表的初始化】

Status InitList_L(LinkList &L){  //首先构造一个函数
    L=(LinkList)malloc(sizeof(Lnode));     //动态分配内存
    L->next=NULL;                            //next域指针置为空(相当于初始化)
    return OK;
}
    

        这里就不得不提到malloc函数;(LinkList)malloc(sizeof(Lnode)) 

        分配内存的大小:sizeof(Lnode)   

        (*LinkList)的含义:强制类型转化,将分配的内存用于该结构

        2.判断链表是否为空

        算法:判断头节点的指针域是否为空

2.4【判断链表是否为空】

int ListEmpty(LinkList L){  //判断是否为空表,如果是,返回值为1
    if(L->next==NULL)
    return 1;
    else
    return 0;
}

        3.单链表的销毁(注意:链表销毁后不存在)

        算法:从头指针开始,依次释放所有结点(要让指针指向哪一结点,即将那一结点的地址赋给他)即让L指向下一结点:将next域赋值给L(即L=L->next;),并且这里引入新的指针接受L的地址(防止地址丢失后找不到下一个结点)     循环条件:L!=NULL

2.5【单链表的销毁】

Status DestroyList_L(LinkList,&L){
    Lnode *p;                        //或LinkList p;这个的意思是定义结点指针p
    while(L!=NULL){
        p=L;                         //用P保存L指向位置的地址(从头节点开始)
        L=L->next;                   //L指针指向下一个结点
        delete p;                    //删除p所处位置的结点
    }
return OK;}

        4.清空链表(链表任然存在)

        算法:依次释放所有结点,并将结点指针域设置为空

        与销毁最大的不同:p指针指向的开始位置不同! 这里是p=L->next,是从首元结点开始的,而删除是p=L,是从头节点开始的引入q指针指向p后面的一个结点,防止清空p后找不到下一个结点,q=p->next;然后再删p即可。

2.6【清空单链表】

Status ClearList(LinkList &L){
    Lnode *p,*q;        //定义结点指针
    p=L->next;          //让p指向首元结点
    while(p!=NULL){
        q=p->next;      //q指向p的下一个结点
        delete p;       //删除p指向的结点
        p=q;            //删除后p,q再次指向同一结点
}
    L->next=NULL;       //将头节点的指针域也置为空
    return OK;
}

        5.求链表的表长

        算法:从首元结点开始,依次计数所有结点(不是从头节点开始)

        p=p->next 并判断是否为空    p==NULL

2.7【求链表的表长】

int ListLength_L(ListList &L){
    Lnode *p;          //先创建一个结点指针p
    p=L->next;         //让p指向首元结点(注意不是头节点)
    i=0
    while(p==NULL){
    i++;
    p=p->next;         //p指向下一个结点
}
return i;}
四.重要操作

        1.取值操作:取单链表中第i个元素的内容

        算法:从头指针开始延链表一个个到第i个结点为止(因此这里可以说明,链表不是随机存取)

        初始:p=L->next      还需要引入变量j作为计数器

2.8【单链表的取值操作】

Status Getelem_L(LinkList L,int i,Elemtype &e){//获取线性表L中第i个位置的内容,并通过变量e返回
    p=L->next;                     //初始化,令p从首元结点开始
    j=1;
    while(p!=NULL&&jnext;                 //p向下一个结点扫描,直到p指向第i个元素或P为空
        j++;
}
    if(p==NULL||j>i) return ERROR;//代表第i个元素不存在
    e=p->data;                    //取第i个元素(第i个结点的data域)
    return OK;
}//Getelem_L

        2.按值查找:根据指定数据获取该数据(设所找的数据值为e)所在位置

        算法:从第一个元素开始,p=L->next    比较:p->data!=e 再移动指针的位置p=p->next

没有找到(结束条件)p==NULL

2.9【单链表按值查找】

Lnode *LocateElem_L(LinkList L,Elemtype e){
//在线性表L中查找值为e的元素
//找到了,则返回L中值为e的数据元素的地址,查找失败则返回NULL
    p=L->next;
    while(p!=NULL&&p->data!=e){
        p=p->next;
}
    return p;        //找到了p本身就是值,没找到它就变成了NULL
}

        3.插入操作:在第i个结点前插入值为e的新结点

        那么如果要插i这个地方,首先要找到i-1这个结点,再在后面生成新结点,并令data域为e

        现在p指针指向ai-1的位置,那么p->next域则代表ai这个结点,那么插入语结点后,则s结点s->next即为ai,所以可以通过赋值得:s->next=p->next,好的,现在让 ai-1的后面不是ai而是s指向的新插入的结点,所以第二步关键操作时p->next=s。  

2.10【单链表的插入操作】

Status ListInsert_L(LinkList &L,int i,ElemType e){
    p=L;j=0;
    while(p!=NULL&&jnext;
        j++;
}
    if(p==NULL||j>i-1) return ERROR;     //插入位置非法
    s=new Lnode;                         //生成新结点s(这里偷懒没有用malloc
    s->data=e                            //设s的数据域为e
    s->next=p->next;
    P->next=s;                           //将s插入L中
    return OK;
}//ListInsert_L

        4.删除第i个结点

        算法:找到第i-1个结点(用p指针指向),令ai-1结点的后一个结点是ai+1(因为要删除ai),故有:p->next=p->next->next(因为p指向的是ai-1位置,因此p->next->next代表ai+1),现在引入q来保存ai的地址并释放,上述式子亦可改为:p->next=q->next;

2.11【删除第i个结点】

Status ListDelete_L(LinkList &L,int i,ElemType &e){//删除i位置的结点,并用e保存删除结点的数据域
    p=L,j=0,i,q;                                   //这里没有详细定义了
    while(p->next!=NULL&&jnext;                                 //p指针指向i-1这个位置
        j++;
}
    if((p->next)==NULL||j>i-1) return ERROR;       //删除位置不合法
    q=p->next;                                     //q指向i位置
    P->next=q->next;                               //让ai-1的下一个结点是a1+1
    e=q->data;                                     //用e保存i位置的data
    delete q;                                      //删除结点
    return OK;}
五.单链表的建立

        1.头插法:从一个空表开始,重复读入数据,生成新结点,将读入的数据存到新结点的数据域中,从最后一个结点开始,依次将各结点插入到链表的前端(头节点)(这里可能有点抽象,下面给大家具体讲解一下)

算法:

设要插入的元素是:a1,a2,a3......an         思路:头结点-新节点-老结点(所有的老结点)

插入顺序是:an,an-1......a1

step 1:建立一个头节点

 L=(LinkList)malloc(sizeof(Lnode));     L->netx=NULL; //创立头节点并将头节点指针域置为空

step 2:从最后一个元素开始创建新结点 

p=(LinkList)malloc(sizeof(Lnode));      p->data=an;    //创立结点指针并将数据域保存最后一个元素

step 3:将新结点(an)放在头节点后

 p->next=L->next; L->next=p;        这一步有很多同学都难以理解,首先我们要明白,无论是第一个还是后面的结点,我们插入新节点时,永远都是放在头节点的后面,老结点的前面(老节点就是L->next),因此这里把L头结点的next域赋值给p指向结点的next域使得插入结点在老节点前,再有:L->next=p使得新节点在头节点后。

step 4:同理就轮到an-1插入了

 p=(LinkList)malloc(sizeof(Lnode));     p->data=an-1;     p->next=L->next; L->next=p;

然后循环step 3和step 4

2.12【头插法创建单链表】

void CreatList_L(LinkList $L,int n){
    L=new Lnode;                 //用malloc也可以,反正就创建结点指针嘛
    L->next=NULL;                //这两步是创建头节点
    for(i=n;i>0;i--){            //从最后一项开始插入
        p=new Lnode;             //创建结点指针
        scanf(&p->data)          //接受输入的元素值
        p->next=L->next;         //让新结点插入到以前的结点之前
        L->next=p;               //让新结点插入到头节点之后
    }
}//CreatList_L

        2.尾插法

        算法:从空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的尾结点,初始时,r同L共同指向头节点,每读入一个元素,则申请一个新结点,将新结点插入到尾结点后,r指向新节点(这时新结点所处位置在表尾),然后循环下去。

2.13【尾插法创建单链表】

void CreatList_L(LinkList &L,int n){
    L=(LNode*)malloc(sizeof(LNode));               //这里用new用malloc全凭个人
    L->next==NULL;                                 //这两步还是创建头节点
    r=L;                                           //尾指针r指向头节点
    for(i=0;i<=n,i++){
        p=(LNode*)malloc(sizeof(LNode));           //创建结点指针
        scanf(&p->data);                           //接受输入元素值
        p-next==NULL;                              //结点指针域置空(理解为初始化)
        r->next=p                                  //r结点的next域就是新插入的结点
        r=p;                                       //再把r指针的位置指向表尾
    }
}//CreatList_L

好了,这就是单链表最基本的所有操作了,下一期会为大家介绍循环链表以及双向链表

感谢各位阅读!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/889494.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号