7.线性表的链式表示和实现

目录

一. 一些术语

二. 单链表的定义与表示

三. 单链表基本操作的实现

(1)单链表的初始化

(2)判断链表是否为空

(3)销毁链表

(4)清空链表(注意和销毁链表的区别)

(5)求单链表的表长

(6)取值-取单链表第i个元素的内容

(7)按值查找-根据指定数据获取该数据位置序号

(8)插入-在第i个结点前插入新结点e

(9)删除结点

(10)单链表的建立-头插法

(11)单链表的建立-尾插法


链式存储与链表:用一组物理位置任意的存储单元来存放线性表的数据元素。这组存储单元既可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。链表中元素的逻辑次序和物理次序不一定相同。

一. 一些术语

结点:数据元素的存储映像,由两部分组成:数据域+指针域;

头指针H:记录第一个元素的地址;链表由n个结点和指针链组成,它是线性表的链式存储映像,称为线性表的链式存储结构。

单链表由头指针唯一确定,因此单链表可以用头指针的名字来命名。

单链表,双链表,循环链表:结点只有一个指针域的链表,称为单链表或线性链表;结点有两个指针域的链表,称为双链表;首尾相接的链表称为循环链表;

头指针,头结点和首元结点:头指针:是指向链表中第一个结点的指针;首元结点:是指链表中存储第一个数据元素a1的结点;头结点:是在链表的首元结点之前附设的一个结点;(因此,链表的存储结构可以带头结点,也可以不带头结点)

例:26个英文字母的链式存储结构

讨论1:如何表示空表?

无头结点时,头指针为空时表示空表;有头结点时,当头结点的指针域为空时表示空表;

讨论2:在链表中设置头结点有什么好处?

1.便于首元结点的处理:首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置上的操作和其它位置一致,无须进行特殊处理;
2.便于空表和非空表的统一处理:无论链表是否为空,头指针都是指向头结点的非空指针,因此空表和非空表的处理也就统一了;

讨论3:头结点的数据域装的是什么?

头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表长度值。

讨论4:链表(链式存储结构)的特点?

(1)结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。

(2)访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等(顺序存取);

顺序表->随机存取;链表->顺序存取;

二. 单链表的定义与表示

单链表由表头唯一确定,因此单链表可以用头指针的名字来命名,若头指针是L,则把此链表称为表L。

typedef struct Lnode //声明结点类型和指向结点的指针类型
{
    ElemType data;  //结点的数据域
    struct Lnode *next;  //结点的指针域,指向定义的结构体,嵌套定义
}Lnode,*LinkList;  //LinkList为指向结构体Lnode的指针类型

注意:在C++中,结构体的定义需要使用struct关键字,struct Lnode表示定义了一个名为Lnode的结构体。而typedef关键字可以用来为数据类型创建别名,将struct Lnode与一个新的名称关联起来,使得使用该结构体更加方便。通过这段代码,我们可以使用Lnode作为结构体类型的名称(Lnode就相当于struct Lnode了),以及LinkList作为指向Lnode结构体的指针类型的名称。这里Lnode和LinkList都是类型。通过使用typedef关键字定义结构体别名,可以简化代码的书写,并提高代码的可读性和可维护性。

定义链表L:LinkList L(这里定义了一个头指针L指向了单链表);Lnode *L(一般不用,也是定义指针指向头结点);

定义结点指针p:Lnode *p(指针加*,Lnode这里意思是指向Lnode类型的指针)或LinkList p(LinkList本身就是指针型,不用加*);

例如,定义一个学生信息管理系统。可以用以下定义:

typedef struct Student; 
{
    char num[8];
    char name[8];
    int score;  
    struct Student *next;  
}Lnode,*LinkList;

实际上为了统一一般采用以下定义方法:

typedef struct; 
{
    char num[8];
    char name[8];
    int score;   
}ElemType;  

typedef struct Lnode 
{
    ElemType data;  
    struct Lnode *next;  
}Lnode,*LinkList;

三. 单链表基本操作的实现

(1)单链表的初始化

这里要求初始化一个带头结点的单链表,算法步骤:(1)生成新结点作为头结点,用头指针L指向头结点;(2)将头结点的指针域悬空;

Status InitList_L(LinkList &L){
    L = new Lnode; //或C语言的写法:L = (LinkList)malloc(sizeof(Lnode));
    L->next = NULL; //如果结构体引用的成员为指针就用->,成员为一般变量就用.
    return OK;   
}

L = new Lnode创建了一个新的Lnode节点,并将其地址赋值给L。这样L就指向了链表的头节点。然后,L->next = NULL;这行代码将链表的头节点的next指针指向NULL,表示链表目前为空,没有其他节点。最后,函数返回OK,表示初始化成功。

(2)判断链表是否为空
int ListEmpty(LinkList L){ //若L为空表,则返回1,否则返回0;
    if(L->next) //非空
        return 0;
    else
        return 1;
    }
(3)销毁链表

算法思路:从头指针开始,依次释放所有结点

(1)p=L,这里头指针L里面放的是L的头结点的地址,这时p,L都指向头结点;(2)L=L->next,将L往后移一个结点,让它指向下一个结点;(3)delete p(C语言中是free(p)),删除上一个结点;(4)执行(1),这时p也指向下一个结点,依次循环往复;(5)循环条件:当p,L指向最后一个结点时,执行L=L->next,这时候L为NULL,循环结束。

Status DestroyList_L(LinkList &L){  //销毁单链表
    Lnode *p;  //或LinkList p;
    while(L){
        p=L;  //从头结点开始
        L=L->next;  //删除之前先把下一个结点找到
        delete p;  //C语言中用free(p);
    }
    return OK;
} 
(4)清空链表(注意和销毁链表的区别)

链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然在)。

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

Status ClearList(LinkList &L){
    Lnode *p,*q;  //或LinkList p,q;
    p=L->next;  //指向首元结点;
    while(p){
        q=p->next;  //q指向p当前结点的下一个结点,先保存地址
        delete p;  //销毁p
        p=q;  //p移动到下一个结点
    }
    L->next=NULL;  //把头结点的指针域置空
    //注意这里L始终指向头结点
    return OK;
}
(5)求单链表的表长

算法思路:从首元结点开始,依次遍历所有结点。

int ListLength_L(LinkList &L){  //返回L中数据元素个数
    LinkList p;
    p=L->next;  //p指向第一个结点
    i=0;
    while(p){  //遍历单链表,统计结点数
        i++;
        p=p->next;
    }
}
(6)取值-取单链表第i个元素的内容

算法思路:从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存取结构。

步骤:(1)从第1个结点(L->next)顺链扫描,用指针p指向当前扫描到的结点,p初值p = L->next;(2)j做计数器,累计当前扫描过的结点数,j初值为1;(3)当p指向扫描到的下一结点时,计数器j加1;(4)当j==i时,p所指的结点就是要找的第i个结点;

Status GetElem_L(LinkList L,int i,ElemType &e){
//获取链表中的第i个元素的内容,通过变量e返回
    p=L->next;  //此时p指向首元结点
    j=1;  //初始化
    while(p&&j<i){  //向后扫描,直到p指向第i个元素或者p为空
    p=p->next;
    ++j;
    }
    if(!p||j>i) return ERROR;  //当i取0,-1...或者p=NULL(此时i大于链表长度),报错
    e=p->data;  //把第i个结点的数据元素存到e中
    return OK;
}

注意&&表示逻辑与,||表示逻辑或。

(7)按值查找-根据指定数据获取该数据位置序号

算法步骤:(1)从第一个结点起,依次和e相比较;(2)如果找到一个其值与e相等的数据元素,则返回其在链表中的位置或地址;(3)如果查遍整个链表都没有找到其值和e相等的元素,则返回0或NULL;

//在线性表L中查找值为e的元素的序号
int LocateElem_L(LinkList L,Elemtype e){
    p=L->next;  //指向首元结点
    j=1;
    while(p&&p->data!=e){ //当p走到最后或找到e时,退出循环
        p=p->next; //移动到下一个结点
        j++; //j加1
    }
    if(p) return j;
    else return 0;
}
(8)插入-在第i个结点前插入新结点e

算法步骤:(1)首先找到a_(i-1)的地址p;(2)生成一个数据域为e的新结点s;(3)插入新结点:第一步,新结点的指针域指向结点a_i;第二步,结点a_(i-1)的指针域指向新结点;(注意此两步不能交换)

Status ListInsert_L(LinkList &L,int i,ElemType e){
    p=L;
    j=0;  //初始化
    while(p&&j<i-1){
    p=p->next;
    ++j;
    }
    if(!p||j>i-1)return ERROR;  //i取0,负值或超过表长,非法
    s=new LNode;  //建立新的s结点
    s->data=e;  //把e放入s的数据域中
    s->next=p->next;  //将结点s插入L中,此时p指向第i-1个结点,这一步表示s的指针域存入第i个结点
    p->next=s; //将第i-1个结点的指针域更改为s(这里s是s的地址)
    return OK;
}
(9)删除结点

算法步骤:(1)首先找到a_(i-1)的存储位置p,保存要删除的a_i的值;(2)令p -> next指向a_(i+1);(3)释放结点a的空间。

Status ListDelete_L(LinkList &L,int i,ElemType &e){ //将线性表第i个元素删除
    p=L;
    j=0;  //初始化
    while(p->next&&j<i-1){
    p=p->next;
    ++j;  //寻找第i个结点,并令p指向其前驱(第i-1个结点)
    }
    if(!(p->next)||j>i-1)return ERROR;  //i取0,负值或超过表长,删除位置不合理
    q=p->next;  //此时q指向第i个结点
    p->next=q->next;  //p的指针域指向第i+1个结点
    e=q->data;  //保存删除节点的数据域
    delete q; //释放删除节点的空间
    return OK;
}

单链表的查找,插入,删除操作时间复杂度分析:查找的时间复杂度为O(n),插入和删除操作本身的时间复杂度为O(1)。

(10)单链表的建立-头插法

头插法:指元素插入在链表头部,也叫前插法。倒位序,从最后一个元素开始插入。

算法步骤:(1)从一个空表开始,重复读入数据;(2)生成新结点,将读入数据存放到新结点的数据域中;(3)从最后一个结点开始,依次将各结点倒序插入到链表的前端;

void CreateList_H(LinkList &L,int n){
    L=new LNode;
    L->next=NULL; //先建立一个带头结点的单链表
    for(i=n,i>0,i--){
        p=New LNode;  //生成新结点
        cin>>p->data; //输入数据
        p->next=L->next; //将新结点与后续已有结点相连
        L->next=p; //头结点与新插入的结点相连
    }
}
(11)单链表的建立-尾插法

尾插法:指元素插入在链表尾部,也叫尾插法。正位序,从第一个元素开始插入。

算法步骤:(1)从一个空表L开始,将新结点逐个插入到链表的尾部,尾指针r指向链表的头结点;(2)初始时,r同L均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r指向新结点;

void CreateList_R(LinkList &L,int n){
    L=new LNode;
    L->next=NULL; //先建立一个带头结点的单链表
    r=L; //尾指针先指向头结点
    for(i=0,i<n,i++){
        p=New LNode;  //生成新结点
        cin>>p->data; //输入数据
        p->next=NULL; //新结点是最后一个结点,所以指针域为NULL
        r->next=p; //此时r指向上一个结点,它的指针域指向新结点p
        r=p; //尾指针+1,指向新的最后一个结点
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北京地铁1号线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值