数据结构-线性结构-单链表

单链表的读取

在线性表的顺序存储结构中,我们要知道任意一个元素的存储位置是很容易的,但是在单链表中,实现获取第i个元素的数据操作GetElem,就需要点想法。

思路:

  1. 声明一个结点 p 指向链表第一个结点,初始化 j 从1开始
  2. 当 j < i 时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j  累加1
  3. 若到链表末尾p为空,则说明第 i 个元素不存在
  4. 否则查找成功,返回结点 p 的数据

实现代码:

/*初始条件:顺序线性表L存在,l ≤i ≤ListLength(L)*/ 
/*操作结果:用e返回L中第i个数据元素的值*/
Status GetElem(LinkList L, int i, ElemType *e)
{
	int j;
	LinkList p;        /*声明一个结点*/
	p = L ->next;	   /*让p指向链表L的第一个结点*/	
	j = 1;		  	   /*j为计数器*/
	while(p && j<i)	   /* p不为空或者计数器还没有等于i时,循环继续*/ 
	{
		p = p ->next;  /* 让p指向下一个结点*/ 
		++j;
	}
	if (!p || j>i)
		return ERROR;  /* 第i个元素不存在*/ 
	*e = p ->data;	   /* 取第i个元素的数据*/	
	return OK;
 } 

这个读取确实不如顺序存储结构,因为顺序存储结构可以使用for循环来读取,但是有差的地方当然也有优势,单链表的插入和删除就比较方便。

单链表的插入

假设存储元素e的结点为 s ,要实现结点p、p->next 和 s 之间逻辑关系的变化,只需将结点 s 插入到结点 p 和 p->next之间即可。不用动其他的结点,只需要将s->next 和 p->next 的指针做一点改变即可。

即 s->next = p->next     p->next = s

也就是说让p的后继结点改成s的后继结点,再把结点s变成p的后继结点。

但是这两个语句的顺序一定不能交换。

思路:

  1. 声明一个结点p指向链表第一个结点,初始化 j 从 1 开始
  2. 当 j < i 时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j 累加 1
  3. 若到链表末尾 p 为空,则说明第 i 个元素不存在
  4. 否则查找成功,在系统中生成一个空结点s
  5. 将数据元素e赋值给s->data
  6. 单链表的插入标准语句s->next = p ->next; p->next = s
  7. 返回成功

实现代码:

/*初始条件:顺序线性表L存在,l ≤i ≤ListLength(L)*/ 
/*操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1*/
Status ListInsert(LinkList *L, int i, ElemType e)
{
	int j;
	LinkList p, s;
	p = *L;
	j = 1;
	while(p && j < i)	/*寻找第i个结点*/ 
	{
		p = p->next;
		++j;
	}
	if(!p || j > i)
		return ERROR;
	s = (LinkList)malloc(sizeof(Node));	/*生成新结点(C标准函数)*/ 
	s->data = e;
	s->next = p->next;	/*将p的后继结点赋值给s的后继结点*/ 
	p->next = s;		/*将s赋值给p的后继*/ 
	return Ok;
 } 

malloc函数作用:生成一个新的结点,其类型与Node是一样的,其实质就是在内存中找了一小块空地,准备用来存放 e 数据 s 结点。 

单链表的删除

设存储元素a_{i} 的结点为q,要实现将结点q删除单链表的操作,其实就是将它的前继结点的指针绕过,指向它的后继结点即可。

如下图所示:

实际上只需要一步即可,p->next = p->next->next 也就是说让p的后继结点改成p的后继结点。

思路:

  1. 声明一个结点 p 指向链表第一个结点,初始化 j 从1开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点,j 累加1
  3. 若到链表末尾 p 为空,则说明第 i 个元素不存在
  4. 否则查找成功,将预删除的结点 p->next 赋值给 q
  5. 单链表的的删除标准语句 p->next = q->next
  6. 将 q 结点中的数据元素赋值给 e ,作为返回
  7. 释放 q 结点
  8. 返回成功 

实现代码:

/*初始条件:顺序线性表L存在,l ≤i ≤ListLength(L)*/ 
/*操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1*/
Status ListDelete(LinkList *L, int i, ElemType *e)
{
	int j;
	LinkList p, q;
	p = *L;
	j = 1;
	while(p->next && j < i)	/*遍历寻找第i个元素*/ 
	{
		p = p->next;
		++j; 
	}
	if (!(p->next) || j > i)
		return ERROR;		/*第i个元素不存在*/ 
	q = p->next;			
	p-next = q->next;		/*将q的后继结点赋值给p后继*/ 
	*e = q->data;			/*将q结点中的数据赋值给e*/ 
	free(q);				/*让系统回收此结点,释放内存*/ 
	return OK;
 } 

 free函数作用:让系统回收一个Node结点,释放内存。

 分析:

单链表的插入和删除算法,其实就是由连两部分组成,第一部分就是遍历查找第 i 个元素,第二部分就是插入和删除元素。

它们的时间复杂度都是O(n)。

单链表的整表创建

创建单链表的过程是一个动态生成链表的过程。即从“空表”的初始状态起,依次建立各元素结点,并逐个插入链表。

思路:

  1. 声明一个结点 p 和计数器变量 i 
  2. 初始化一空链表L
  3. 让L的头结点的指针指向NULL,即建立一个带头结点的单链表
  4. 循环
  •          生成一个新结点赋值给 p
  •          随机生成一数字赋值给 p 的数据域 p ->data
  •          将 p 插入到头结点与前一新结点之间

实现代码:          

/*随机产生n个元素的值,建立带头结点的单链线性表L(头插法)*/
void GreateListHead(LinkList *L, int n)
{
	LinkList p;
	int i;
	srand(time(0));							/*初始化随机数种子*/ 
	*L = (LinkList)malloc(sizeof(Node));	
	(*L)->next = NULL;						/*先建立一个带头结点的单链表*/		
	for (i=0; i<n; i++)
	{
		p = (LinkList)malloc(sizeof(Node));	/*生成新结点*/ 
		p->data = rand()%100 + 1;			/*随机生成100以内的数字*/ 
		p->next = (*L)->next;				
		(*L)->next = p;						/*插入到表头*/ 
	}						
 } 

这种算法简称头插法,因为始终让新的结点在第一的位置。

我们当然也可以把新结点都插在终端结点的后面,这种算法称之为尾插法。

实现代码:

/*随机产生n个元素的值,建立带头结点的单链线性表L(尾插法)*/
void GreateListTail(LinkList *L, int n)
{
	LinkList p, r;
	int i;
	srand(time(0));							/*初始化随机数种子*/ 
	*L = (LinkList)malloc(sizeof(Node));	/*为整个线性表*/ 
	r = *L;									/*r为指向尾部的结点*/ 
	for (i=0; i<n; i++)
	{
		p = (Node *)malloc(sizeof(Node));	/*生成新结点*/ 
		p->data = rand()%100 + 1;			/*随机生成100以内的数字*/ 
		r->next = p;						/*将表尾部终端结点的指针指向新结点*/ 
		r = p; 								/*将当前的新结点定义为表尾终端结点*/ 
	}
	r ->next = NULL;						/*表示当前链表结束*/ 
 } 

单链表的整表删除

思路:

  1. 声明一个结点 p 和 q
  2. 将第一个结点赋值给 p 
  3. 循环:
  •          将下一个结点赋值给 q
  •          释放 p
  •         将 q 赋值给 p

实现代码:

/*初始条件:顺序线性表L已存在,操作结果:将L重置为空表*/
Status ClearList(LinkList *L) 
{
	LinkList p;
	p = (*L)->next;		/*p指向第一个结点*/ 
	while(p)
	{
		q = p->next;
		free(p);		
		p = q;
	}
	(*L)->next = NULL;	/*头结点指针域为空*/ 
	return OK;
 } 

单链表结构与顺序存储结构优缺点

从三个方面来简单对比一下

存储分配方式

  • 顺序存储结构用一段连续的存储单元一次存储线性表的数据元素
  • 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素

时间性能

  • 查找
  1. 顺序存储结构 O(1)
  2. 单链表 0(n)
  • 插入和删除
  1. 顺序存储结构需要平均移动表长一半的元素,时间为 O(n)
  2. 单链表在算出某位置的指针后,插入和删除时间仅为 O(1)
  • 空间性能
  1. 顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢
  2. 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制

 

 

 

 

 

 

  • 38
    点赞
  • 156
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

suxiaorui

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

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

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

打赏作者

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

抵扣说明:

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

余额充值