• 数据结构之单链表


    数据结构之单链表

    一、线性表的链式存储结构——链表

    线性表中每个结点都有唯一的前驱结点和后继结点。
    在这里插入图片描述
    设计链式存储结构时,每个逻辑点存储单独存储,为了表示逻辑关系,增加指针域。

    上图能清晰表示顺序表和链表之间的不同,之后还会详细介绍两者的区别与联系。

    • 每个物理结点增加一个指向后继结点的指针域 ⇒ \Rightarrow 单链表

    • 每个物理节点增加一个指向后继节点的指针域和一个指向前驱结点的指针域 ⇒ \Rightarrow 双链表

    • 若一个节点中某哦个指针域不需要指向其他任何节点,则它的位置将为空,用常量NULL表示。

    • 通常每个链表带有一个头节点,并通过头结点的指针唯一标识该链表,称之为头指针,相应地指向首结点或开始结点的指针称为首指针,指向尾结点的指针称为尾指针

    二、存储密度

    存储密度是指结点数据本身所占的存储量和整个结点结构中所占的存储量之比。
    在这里插入图片描述
    一般地,存储密度越大,存储空间利用率就越高。由此可得,顺序表的存储空间为1,链表的存储空间小于1。

    三、链表与顺序表的区别与联系

    1.在顺序表中,逻辑上相邻的元素对应的存储位置也相邻,所以在进行插入或删除操作时,平均需要移动半个表的元素。而在链表中,逻辑上相邻的元素的存储位置是通过指针建立的,因而每个结点的存储位置可以任意安排,插入和删除相关结点时只需修改相关结点的指针域即可,相较于顺序表,这样更加方便。
    2.顺序表具有随机选取的特性,而链表则不具有。
    3.顺序表的存储密度为1,而链表则不为1。

    四、单链表

    在这里插入图片描述
    在单链表中,每个结点(LinkNode)类型包括存储元素的数据域(data),还包括存储后继结点的指针域(next)。
    下面将用代码定义一个单链表:

    typedef struct LNode{
    	Elemtype data;	//存放元素值
    	struct LNode 8next;	//指向后继结点
    	}LinkNode;	//单链表结点类型
    
    • 1
    • 2
    • 3
    • 4

    使用typedef的原因(C语言):
    这样在后续写代码时就不用再带struct了。在C++中可不必这样操作。

    五、单链表的特点

    当访问过一个结点后,只能接着访问它的后继节点,而无法访问它的前驱结点。
    在这里插入图片描述

    六、单链表代码操作

    1.插入结点和删除结点操作

    (1).插入结点

    在表L第i个位置插入指定元素e。
    插入操作:将值为x的新结点s插入到p结点之后。
    特点:只需修改相关结点的指针域,不需要移动结点。
    在这里插入图片描述
    C/C++语句描述如下:

    s->next=p-<next;	//将x的指针域指向b结点
    p->next=s;	//将x结点的地址赋给p所指向的后继结点
    
    • 1
    • 2
    (2).删除结点

    删除操作:删除p结点后的一个结点。
    特点:只需修改相关结点的指针域,不需要移动结点。
    在这里插入图片描述
    删除操作语句:

    p->next=p->next->next;	//这相当于直接用x结点代替了b结点
    
    • 1

    但其实上面操作并没有释放b结点所占用的内存。
    因此,为释放被删除结点所占用的空间,采用如下方式:

    q=p->next;	//将临时变量q指向即将被删除的b结点
    p->next=q->next;	//将q结点的指针指向b结点,相当于挖出了b结点
    free(q);	//释放p所占用的空间
    
    • 1
    • 2
    • 3

    由上述操作可知,在单链表中插入 和删除一个结点时,需要先找到其前驱结点,在做相应的指针域修改。

    2.建立单链表

    此处为整体创建单链表。
    在这里插入图片描述

    (1).头插法建表
    • 从一个空表开始,创建一个头结点。
    • 依次读取字符数组a中的元素,生成新结点。
    • 将新结点插入到;当前链表的表头上,直到结束为止。
      在这里插入图片描述
      注意:链表的逻辑顺序与逻辑次序相反。

    算法如下:

    void CreatListF(LinkNode *L,ElemType a[],int n)
    {
    	LinkNode *s;
    	int i;
    	L=(LinkNode *)malloc(sizeof(LinkNode));
    	L-next=NULL;	//创建头结点,将其next域置为NULL
    	for(i=0;i<n;i++)	//循环建立数据结点s
    	{
    		s=(LinkNode *)malloc(sizeof(LinkNode);
    		s->data=a[i];	//创建数据结点s
    		s-next=L->next;	//将结点s插入原首结点前,头结点后
    		L-next=s;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    (2).尾插法建表
    • 从一个空表开始,创建一个头结点。
    • 依次读取数组a中的元素,生成新的结点。
    • 将新结点插入到当前链表的表尾上,直到结束为止。
      在这里插入图片描述
      注意:链表的结点顺序与逻辑次序相等。

    算法如下:

    void CreateListR(LinkNode *&L,ElemType a[],int n)
    {
    	LinkNode *s,*r;
    	int i;
    	L=(LinkNode *)malloc(sizeof(LinkNode));	//创建一个头结点
    	r=L;	//r始终指向尾结点,开始时由于链表为空所以指向头结点
    	for(i=0;i<n;i++)	//循环建立数据结点
    	{
    		s->data=a[i];	//创建数据结点s
    		t->next=s;	//将结点s插入到结点r之后
    		r=s;
    	}
    	r->next=NULL;	//将尾结点的next域置为NULL
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    3.线性表基本运算在单链表上的实现

    (1).初始化线性列表InitList(L)

    建立一个空的单链表,即创建一个头结点。

    void InitList(LinkNode *&L)
    {
    	L=(LinkNode *)malloc(sizeof(LinkNode));	//创建头结点
    	L-next=NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    (2).销毁线性表DestoryList(L)

    释放单链表L占用的内存空间。即逐一释放全部结点的空间。

    void DestoryList(L)
    {
    	LinkNode *pre=L,*p=L->next;	//双指针法使per指向头结点,p指向首结点
    	while(p!=NULL)	//遍历单链表L
    	{
    		free(pre);	//释放pre结点
    		pre=p;	//pre,p同步后移一个结点
    		p=pre->next;
    	}
    	free(pre);	//循环结束时p为NULL,pre指向尾结点
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    在这里插入图片描述
    该算法时间复杂度为O(n),其中n为单链表中数据结点的个数。

    (3).判断线性表是否为空表ListEmpty(L)

    若单链表L没有数据结点,则返回真,否则返回假。

    bool ListEmpty(LinkNode *L)
    {
    	return(L->next==NULL);
    }
    
    • 1
    • 2
    • 3
    • 4
    (4).求线性表的长度ListLenght(L)

    返回单链表L中数据结点的个数。

    void ListLenght(*&L)
    {
    	int n=0;
    	LinkNode *p=L;	//p指向头结点,n置为0
    	while(p->next!=NULL)
    	{
    		n++;
    		p=p->next;	//循环结束,p指向尾结点,其序号n为结点个数
    	}
    	return(n);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    在这里插入图片描述

    (5).输出线性表DispList(L)

    逐一扫描单链表L的每个数据结点,并显示各结点的data域值。

    void DispList(LinkNode *L)
    {
    	LinkNode *p=L->next;	//p指向开始结点
    	while(p!=NULL)	//p不为NULL,输出p结点的data域
    	{
    		printf("%d",p->data);
    		p=p->next;	//p指向下一个结点
    	}
    	printf("\n");
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    (6).求线性表L中位置i的数据元素GetElem(L,i,&e)

    在单链表L中从头结点开始找到第i个结点,若存在第i个数据结点,则将其data域值赋给变量e。

    bool GetElem(LinkNode *L,int i,ElemType &e)
    {
    	int j=0;
    	LinkNode *p=L;	//p指向头结点,j置为0
    	while(j<i&&p!=NULL)
    	{
    		j++;
    		p=p->next;
    		if(p==NULL)	//不存在第i个数据结点,返回false
    		return false;
    		else
    		{
    			e=p->data;
    			return true;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述
    在这里插入图片描述

    (7).按元素查找LocateElem(L,e)

    在单链表L中从开头找第一个值域与e相等的结点,若存在这样的结点,则返回位置,否则返回0。

    int LocateElem(LinkNode *&L,ElemType e)
    {
    	int i;
    	LinkNode *p=L->next;	//p指向开始结点,i置为0
    	while(p!=NULL&&p->data!=e)
    	{
    		p=p->next;	//查找data值为e的结点,其序号为i
    		i++;
    	}
    	if(p==NULL)	//不存在元素值为e的结点,返回0
    	return 0;
    	else
    	return i;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    (8).插入数据元素ListInsert(&L,i,e)

    先在单链表L中找到第i-1个结点p,若存在这样的结点,将其值为e的结点s插入到其后。

    bool ListInsert(LinkNode *&L,int i,Elemtype e)
    {
    	int j=0;
    	LinkNode *p=L,*s;	//p指向头结点,j置为0
    	while(j<i-1&&p!=NULL)
    	{
    		j++;
    		p=p->next;
    	}	//此过程实现查找第i-1个结点
    if(p==NULL)	//未找到第i-1个结点,返回false
    return false;	
    else
    {
    	s=(LinkNode *)malloc(sizeof(LinkNode));
    	s->data=e;	//创建新结点s,其data域置为e
    	s-next=p-next;	//将s插入到p之后
    	p->next=s;
    	return true;
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    (9).删除数据元素ListDelete(&L,i,&e)

    先在单链表L中找到第i-1个结点p,若存在这样的结点,且也存在后继节点,则删除该后继结点。

    bool ListDelete(LinkNode *&L,int i,ElemType &e)
    {
    	int j=0;
    	LikNode *p=L,*q;	//p指向头结点,j置为0
    	while(j<i-1&&p!=NULL)
    	{
    		j++;
    		p=p->next;
    	}	//查找第i-1个结点
    	if(p==NULL)
    	return false;	//未找到第i-1个结点,返回false
    	else
    	{
    		q=p->next;	//q指向第i个结点
    		if(q==NULL)	//不存在第i个结点
    		return false;
    		e=q->data;
    		p->next=q-next;	//从单链表中删除q结点
    		free(q);
    		return true;	//返回true表示成功删除第i个元素
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.单链表的算法设计方法

    a.以建表算法为基础的算法设计
    b.以查找为基础的算法设计

  • 相关阅读:
    聚L-精氨酸/纳米金/石墨烯/聚苯胺复合膜/铝粉/稀土粒子修饰多巴胺的制备
    java-net-php-python-jsp学生会人事管理信息系统计算机毕业设计程序
    分库分表真实案例,扩容10倍容量
    Linux学习之make/Makefile
    (Java)Mybatis学习笔记(三)
    【毕业设计】大数据睡眠数据分析与可视化 - python
    全志R128适配 ST7789v LCD
    DataX同步数据到StarRocks问题记录
    Kafka系列之二Docker集群安装运行
    大数据 - HBase《一》- Hbase基本概念
  • 原文地址:https://blog.csdn.net/weixin_68153081/article/details/126735868