学《数据结构》越学越聪明--第二章》》》线性表之链表(单)

线性表的链式存储结构

链表的概述
  1. 结点不仅包含本身的信息还包含元素之间逻辑关系的信息

    在链表存储结构中每个结点用于存储线性表的一个元素,每个结点不仅包含所存元素本身的信息,而且包含元素之间逻辑关系的信息,即前驱结点包含后继结点的地址信息,称为指针域
  2. 链表中数据元素可以存储在内存中被占用的任意位置

    链表由多个结点组成,这些结点在地址上可以是连续的,也可以不连续。
  3. 头指针

    链表中的一个结点的存储位置称为头指针,如果链表带有头结点,那么头指针为头结点的地址,如果链表不带头结点,头指针为开始结点的地址。通常用头指针来标识一个链表
特点:
  1. 顺序表比链表密度高

    由于每个结点带有指针域,因而在存储空间上比较比顺序表要付出较大的代价
  2. 链表操作简单

    在链表中插入或删除操作中,只需要修改相关结点的指针域即可,不需要移动结点。
单链表增加一个头结点的优点如下:
  1. 第一个结点的操作和表中其他结点的操作一致,无须进行特殊处理

  2. 无论表是否为空,都有一个头结点,因此空表和非空表的处理也统一了

在这里插入图片描述

单链表

在单链表中,假设每个结点的类型用LinkNode表示,它应包括存储元素的数据域,这里用data表示,其类型用通用类型标识符ElemType表示,还包括存储后继结点位置的指针域,这里用next表示。LinkNode类型的声明如下:
*相比较顺序表的优点
* 1、不用规定长度
* 2、存储的元素个数不受限制
* 3、插入和删除时,不用移动其他元素
*/
typedef struct LNode
{
ElemType data;  //存放元素值
struct LNode * next;  //指向后继结点
}LinkNode;  //单链表的结点类型

为了方便,假设ElemType为int类型,使用以下自定义类型语句:

typedef int ElemType;

在后面的算法设计中,如果没有特别说明,均采用带头结点的单链表。其优点有:

  1. 单链表中首结点的插入和删除操作与其他结点一致,无须进行特殊处理
  2. 无论单链表是否为空都有一个头结点,因此统一了空表和非空表的处理过程。
/**
	* 头指针:链表中的第一个结点的存储位置
	* 头结点:在单链表的第一个结点前附设的一个结点
	*/
	//结点包括指针域和数据域
	//链表是由N个结点链结成
	
/**头结点
*注意:我们在定义链表时,习惯性的会定义头结点,以便统一链表结点的插入和删除操作。
* 头结点也可以称为首元结点,最后一个结点也称为尾元结点
*/
typedef struct LinkList {
	LNode* next;//头指针(如果链表有头结点,	next就指向头节点,如果没有就指向第一个结点)
	int length;//链表的长度,初始值为0;
}LinkList;
建立单链表
1、头插法
该方法从一个空表开始依次读取数组a中的元素,生成一个新结点(由s指向它),将读取的数组元素存放到该结点的数据域中,然后将其插入到当前链表的表头上,知道数组a的所有元素读完为止。
void CreateListF(LinkNode *&L,ElemType a[],int n)
{
LinkNode *s;   //生成一个新结点s
L=(LinkNode *)malloc(sizeof(LinkNode));  //分配内存空间
L->next =NULL;//创建头结点,其指针域next置为NULL;
for(int i=0;i<n;i++)  //循环建立数据结点s
{
s=(LinkNode *)malloc(sizeof(LinkNode)); //循环分配s的空间
s->data=a[i];  //创建数据结点s
s->next=L->next; //将结点s插入到原首结点之前,头结点之后
L->next = s;
}
}
2、尾插法
该方法从一个空表开始依次读取数组a中的元素,生成一个新结点s,将读取的数组元素存放到该结点的数据域中,然后将其插入到当前链表的表尾上,知道数组a的所有元素读完为止。为此需要增加一个尾指针r,使其始终指向当前链表的尾结点,每插入一个新结点后让r指向这个新结点,最后还需要将r所指结点的next域置为空。
void CreateListR(LinkNode *&L,ElemType a[],int n)
{
LinkNode *s,*r;   //生成新结点
L=(LinkNode *)malloc(sizeof(LinkNode));  //分配内存空间,创建头结点
r=L;//r始终指向尾结点,初始时指向头节点
for(int i=0;i<n;i++)  //循环建立数据结点
{
s=(LinkNode *)malloc(sizeof(LinkNode)); //循环分配s的空间
s->data=a[i];  //创建数据结点s
r->next=s; //将结点s插入到结点r之后
r = s;
}
r->next=NULL;  //尾结点的next域置为空NULL;
}

线性表基本运算在单链表中的实现

1)初始化线性表InitList(&L)
该运算建立一个空的单链表,即创建一个头结点并将其next域置为NULL;
void InitList(LinkNode *&L)
{
L=(LinkNode *)malloc(sizeof(LinkNode));
L->next = NULL;   //创建头结点,其next域置为NULL
}
2)销毁线性表DestroyList(&L)
该运算释放单链表L占用的内存空间,即逐一释放全部结点的空间。其过程是让pre,p指向两个相邻的结点(初始时pre指向头结点,p指向首结点)。当p不为空时循环;释放结点pre,让后pre、p同步后移一个结点。循环结束后,pre指向尾结点,在将其释放。
void DestroyList(LinkNode *&L)
{
LinkNode *pre = L,*p=L->next;   //pre指向结点p的前驱结点
while(p!=NULL)//扫描单链表L
{
free(pre);  //释放pre结点
pre=p;  //pre、p同步后移
p=pre->next;
}
free(pre);  //循环结束时p为NULL,pre指向尾结点,释放它
}
3)判断线性表是否为空表ListEmpty(L)
该运算在单链表L中没有数据结点时返回真,否则返回假。
bool ListEmpty(LinkNode *L)
{
return (L->next==NULL);
}
4)求线性表的长度ListLength(L)
该运算返回单链表L中数据结点的个数。由于单链表没用存放数据结点个数的信息,需要通过遍历来统计。其过程是让P指向头结点,n用来累计数据结点个数(初始值为0),当p不为空时循环:n增1,p指向下一个结点。循环结束后返回n
int ListLength(LinkNode *L)
{
int n=0;
LinkNode * p=L;  //p指向头结点,n置为0(0即为头结点的序号)
while(P->next!=NULL)
{
n++;  
p=p->next;  
}
return n; //循环结束,p指向尾结点,其序号n为结点个数
}
5)输出线性表DispList(L)
该运算逐一扫描单链表L的每个数据结点,并显示个结点的data域值。
int 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");
}
6)求线性表中某个数据元素值GetElem(L,i,&e)
该运算在单链表L中从头开始找到第i个结点,若存在第i个数据结点,则将其data域值赋给变量e。
void GetElem(LinkNode *L,int i,ElemType &e)
{
int j=0;  
LinkNode * p = L;  //p指向头结点,j置为0即头结点的序号
if(i<=0)retrun;   //如果i错误则停止
while(j<i&&p!=NULL)  //找到第i个结点p
{
j++;
p=p->next;
}
if(p==NULL)return  //不存在第i给数据结点,停止
else
e=p->data;   //存在则赋值给引用型参数e
}
7)按元素值查找LocateElem(L,e)
该运算在单链表L中从头开始找第一个值域与e相等的结点,若存在这样的结点,则返回逻辑序号,否则返回0
int LocateElem(LinkNode *L,ElemType e)
{
int i=1;
LinkNode * p=L->next;  //p指向首结点,i置为1(即首结点的序号为1)
while(p!=NULL&&p->data!=e)  //查找data值为e的结点,其序号为i
{
p=p->next;
i++;
}
if(p==NULL)return 0;  //不存在值为e的结点,返回0
else  return i; //存在值为e的结点 ,返回逻辑序号i
}
8)插入数据元素ListInsert(&L,i,e)
该运算的实现过程是先在单链表L中找到第i-1个结点,由p指向它。若存在这样的结点,将值为e的结点(s指向它)插入到p所指结点的后面。
void ListInsert(LinkNode *&L,int i,ElemType e)
{
int j=0;
LinkNode * p= L,*s;  //p指向头结点,j置为0(即头结点的序号为0)
if(i<=0)return;  //i错误
while(j<i-1&&p!=NULL) //查找第i-1个结点p
{
j++;
p=p->next;
}
if(p==NULL)return;  //没找到i-1个结点
else
{//找到第i-1个结点p,插入新结点
s=(LinkNode *)malloc(sizeof(LinkNode));  //分配空间
s-data=e;  //创建新结点s,其data域置为e
s->next=p->next; //将结点s插入到结点p之后
p->next=s;
}
}
9)删除数据元素ListDelete(&L,i,&`e)
该运算的实现过程是先在单链表L中找到第i-1个结点,由p指向它。若存在这样的结点,且也存在后继结点(由q指向它),则删除q所指向的结点
void ListDelete(LinkNode *&L,int i,ElemType &e)
{
int j=0;
LinkNode * p=L,*q;//p指向头结点,j置为0(即头结点的序号为0)
if(i<=0)return;//i错误
while(j<i-1&&p!=NULL)//查找第i-1个结点p
{
j++;
p=p->next;
}
if(p==NULL)return;
else
{//找到i-1个结点p
q = p->next;//q指向第i个结点
if(q==NULL)return//若不存在第i个结点
e=q->data;//将q的data域赋值给引用型参数e
p->next=q-next;//从单链表中删除q结点
free(q);//释放q结点
}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雷军的小宝贝

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

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

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

打赏作者

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

抵扣说明:

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

余额充值