【数据结构】线性表2——线性表的链式表示和实现(单链表)


线性表的链式表示

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

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

  • 逻辑结构:(a,b,…,z)
  • 链式存储结构:
    在这里插入图片描述

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

链式存储有关的术语

1、结点:数据元素的存储映像。由数据域和指针域两部分组成。

各结点由两个域组成:
数据域:存储元素数值数据 ;
指针域:存储直接后继结点的存储位置。

在这里插入图片描述

2、链表: n个结点由指针链组成链表。
它是线性表的链式存储映像,称为线性表的链式存储结构。
在这里插入图片描述

3、单链表、双链表、循环链表

  • 单链表: 结点只有一个指针域的链表,称为单链表或线性链表。

在这里插入图片描述

  • 双链表:结点有两个指针域的链表,称为双链表。

在这里插入图片描述

  • 循环链表:首尾相接的链表称为循环链表。

在这里插入图片描述
4、头指针、头结点和首元结点

头指针:是指向链表中第一个结点的指针;
首元结点:是指链表中存储第一个数据元素a1的结点;
头结点:是在链表的首元结点之前附设的一个结点。

在这里插入图片描述
5、不带头结点和带头结点

  • 不带头结点

在这里插入图片描述

  • 带头结点

在这里插入图片描述

讨论1:如何表示空表?

  • 无头结点时,头指针为空时表示空表。

在这里插入图片描述

  • 有头结点时,当头结点的指针域为空时表示空表。

在这里插入图片描述

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

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

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

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

在这里插入图片描述

链表(链式存储结构)的特点

1、结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻。
2、访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,这种存取元素的方法被称为顺序存取法,所以寻找第一个结点和最后一 个结点所花费的时间不等。

存取方式区别:
顺序表——随机存取
链表——顺序存取

单链表的定义和表示

在这里插入图片描述

typedef struct Lnode{  //声明结点的类型和指向结点的指针类型
	ElemType data;  //结点的数据域
	struct Lnode *next;  //结点的指针域
}Lnode,*LinkList;  //给新建的结构类型起了个名字叫Lnode,LinkList为指向结构体Lnode的指针类型

//定义链表L
LinkList L;//建议这种方式
或
LNode *L;
//定义结点指针p
LNode *p;//建议这种方式
或
LinkList p;
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;//或L=(LinkList)malloc(sizeof(LNode));
	L->next=NULL;
	return 0;
}

2、判断链表是否为空
空表:链表中无元素称为空链表(头指针和头结点仍然在)

int ListEmpty(LinkList L){//若L为空表,则返回1,否则返回0
	if(L->next)  //非空
		return 0;
	else
		return 1;
}

3、单链表的销毁
链表销毁后不存在。
【算法思路】从头指针开始,依次释放所有结点。
在这里插入图片描述

Status DestroyList_L(LinkList &L){//销毁单链表L
	Lnode *p;  //或LinkList p;
	while(L){
		p=L;
		L=L->next;
		delete p;
	}
}

4、清空链表
链表仍存在,但链表中无元素,成为空链表(头指针和头结点仍然在)
【算法思路】依次释放所有结点,并将头结点指针域设置为空

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

Status ClearList(LinkList &L){  //将L重置为空表
	Lnode *p,*q;  //或LinkList p,q;
	p=L->next;
	while(p){  //p没有指向空,p!=null
		q=p->next;
		delete p;
		p=q;
	}
	L->next=null;  //将头结点指针域设置为空
	returb ok;
}

5、求单链表的表长
【算法思路】从首元结点开始,依次计数所有结点。

在这里插入图片描述

int ListLength_L(LinkList L){  //返回L中数据元素个数
	LinkList p;
	p=L->next;  //p指向第一个结点
	i=0;
	while(p){  //遍历单链表,统计结点数
		i++;
		p=p->next;
	}
	return i;
}
类型定义:
typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

变量定义:
LinkList L;
LNode *p,*s;

重要操作:
p->L;  //p指向头结点
s=L->next;  //s指向首元结点
p=p->next;  //p指向下一结点

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){//获取线性表L中的第i个数据元素的内容,通过变量e返回
	p=L->next;  //初始化
	j=1;  
	while(p&&j<i){  //向后扫描,直到p指向第i个元素或p为空
		p=p->next;
		++j;
	}
	if(!p||j>i)  //第i个元素不存在
		return 0;
	e=p->data;  //取第i个元素
	return ok;
}  //GetElem_L

7、按值查找
【算法步骤】
1、从第- -个结点起,依次和e相比较。
2、如果找到一个其值与e相等的数据元素,则返回其在链表中的“位置”或地址;
3、如果查遍整个链表都没有找到其值和e相等的元素,则返回0或"NUL L”。

在这里插入图片描述
(1)根据指定数据获取该数据所在的位置(地址)

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

时间复杂度:O(n)

(2)根据指定数据获取该数据位置序号

//在线性表L中查找值为e的数据元素的位置序号
int LocateElem_L(LinkList L,Elemtype e){
//返回L中值为e的数据元素的位置序号,查找失败返回0
	p=L->next;
	j=1;
	while(p&&p->data!=e)
		p=p->next;
		j++;
	if(p)
		return j;
	else
		return 0;
}

8、插入结点——在第i个结点前插入值为e的新结点
【算法步骤】
1、首先找到a;-1的存储位置p。
2、生成一一个数据域为e的新结点s。
3、插入新结点:
①新结点的指针域指向结点ai

s->next=p->next

②结点ai-1的指针域指向新结点

p->next=s

在这里插入图片描述

在这里插入图片描述

//在L中第i个元素之前插入数据元素e
Status ListInsert_L(LinkList &L,int i,ElemType e){
	p=L;
	j=0;
	while(p&&j<i-1)  //寻找第i-1个结点,p指向i-1结点
		p=p->next;  
		++j;
	if(!p||j>i-1)  //i大于表长+1或者小于1,插入位置非法
		return error;
	s=new LNode;  //生成新结点s
	s->data=e;  //将结点s的数据域置为e
	s->next=p->next;  //将结点s插入L中
	p->next=s;
	return ok;
}  //ListInsert_L

时间复杂度:O(n)

9、删除——删除第i个结点
【算法步骤】
1、首先找到ai-1的存储位置p,保存要删除的a的值。
2、令p->next指向ai+1。
3、释放结点ai的空间。
在这里插入图片描述

//将线性表L中第i个数据元素删除
Status ListDelete_L(LinkList &L,int i,ElemType &e){
	p=L;
	j=0;
	while(p->next&&j<i-1)  //寻找第i个结点,并令p指向其前驱
		p=p->next;
		++j;
	if(!(p->next)||j>i-1)  //删除位置不合理
		return error;
	q=p->next;  //临时保存被删结点的地址以备释放
	p->next=q->next;  //改变删除结点前驱结点的指针域
	e=q->data;  //保存删除结点的数据域
	delete q;  //释放删除结点的空间
	return ok;
}  //ListDelete_L

时间复杂度:O(n)

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;  //生成新结点p=(LNode*)malloc(sizeof(LNode));
		cin>>p->data;  //输入元素值scanf(&p->data);
		p->next=L->next;  //插入到表头
		L->next=p;
	}
}  //CreateList_H

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

在这里插入图片描述

//正位序输入n个元素的值,建立带表头结点的单链表L
void CreateList_R(LinkList &L,int n){
	L=new LNode;
	L->next=NULL;
	r-L;  //尾指针r指向头结点
	for(i=0;i<n;++i){
		p=new LNode;
		cin>>p->data;  //生成新结点,输入元素值
		p->next=NULL;
		r->next=p;  //插入到表尾
		r=p;  //r指向新的尾结点
	}
}  //CreateList_R

ending~~~

有什么问题欢迎留言噢

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值