数据结构笔记一:线性表--链表

单链表 线性表的链式存储表示

特点:
逻辑上相邻的两个数据元素其存储的物理位置不要求紧邻。
顺序存取。

定义

链式存储存储结构实现了线性结构。
一个结点存储一个数据元素。
单链表:链表的每个结点只包括一个指针域。
单链表的存取由头指针开始,头指针指向链表中第一个结点的存储位置。最后一个数据元素无直接后继,指向NULL

//单链表的存储结构
typedef struct LNode{
	ElemType data;		//数据域
	struct LNode *next;	//指针域
}LNode, *LinkList;

结点包括数据域和指针域。数据域存储数据元素信息,指针域存储直接后继结点的存储位置。–>各个结点间的先后关系用一个指针表示。

LNode、LinkList
LNode* 和LinkList本质上是等价的。
LinkLIst定义单链表,强调定义的是某个单链表的头指针。
LNode* 定义指向单链表中任意结点的指针变量。

单链表可以用头指针唯一确定。

//使用LNode、LinkList定义变量的含义。
LinkList L; 	//L为单链表的头指针
LNode *p;		//p为指针。指向单链表某个结点;*p为该结点。

头结点,附设在单链表的第一个结点之前。在这里插入图片描述首元结点、头结点、头指针
首元结点:链表中存储第一个数据元素的结点
头结点:指针域指向首元结点,头结点的数据域可以不存储信息也可存储附加信息。
头指针:指向链表中第一个结点。若有头结点则指向头结点,无头结点则指向首元结点。

空表判断:

//设头结点 头结点指针域置空
L->next==NULL;

//无头结点 头指针指向空
L==NULL;

初始化 InitList

//构造一个空的有头结点的单链表L
Status InitList(LinkList &L){
	//生成新节点为头结点,头指针L指向头结点
	L= new LNode;

	//头结点的指针域置空
	L->next = NULL;
	return OK;
}

插入 ListInsert

按位插入

在带头结点的单链表L中第i个位置插入值为e的新结点。需要生成一个数据域为e的结点插入到单链表中。
①找到第i-1个结点,p指向该结点
②生成一个新节点* s
③* s的数据域存为e
④* s的指针域指向原来的第i个结点
⑤* p的指针域指向新节点* s
(可以看做对第i-1个结点进行了后插操作)
在这里插入图片描述

Status ListInsert(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;
	s= new LNode;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return OK;
}

时间复杂度:O(n)
p=L:L指向头结点,头结点为第0个结点
j:用来表示当前p指向的为第几个结点

指定结点的后插操作

Status InsertNextNode(LNode *p , ElemType e)
{
	if(!p) return ERROR;	//即p==NULL
	
	s= new LNode;
	if(!s) return ERROR;	//内存分配失败
	
	s->data = e;
	s->next = p->next;
	p->next = s;
	
	return OK;
}

指定结点的前插操作

方法一:
定义链表的头结点,循环查找指定结点的直接前驱结点,对指定节点的前驱结点进行后插操作。
时间复杂度为O(n)
方法二:
申请新节点s , 利用后插法s成为p的后继节点,将p的数据域信息存入s,将所要插入的数据信息存入原来的结点p。
时间复杂度为O(1)

Status InsertPriorNode(LNode *p , ElemType e)
{
	if(!p) return ERROR;	//即p==NULL
	
	s= new LNode;
	if(!s) return ERROR;	//内存分配失败
	
	s->next = p->next;
	p->next = s;
	s->data = p->data;
	p->data = e;
	
	return OK;
}

删除 ListDelete

按位删除

删除单链表的第i个结点
①查找第i个结点,指针p指向该结点
②临时保存待删除结点的地址于p,在后续释放存储空间
③将结点*p的指针域指向待删除结点的直接后继结点
④释放待删除结点的空间

Status ListDelete(LinkList &L , int i )
{
	p=L;
	j=0;
	
	//找到要删除的结点
	while ((p->next)&& (j<i-1)){
		p=p->next;
		j++;
	}
	
	//删除结点、释放存储空间	
	if(!(p->next) || j>i-1)
		return ERROR;
	q = p->next;
	p->next = q->next;
	delete q;
	
	return OK;
}

循环条件
插入算法中循环条件为(p&&(j<i-1)),也可写为((p!=NULL)&&(j<i-1))

删除算法中循环条件为((p->next)&&(j<i-1)),也可写为(((p->next)!=NULL)&&(j<i-1))

当单链表有n个结点时,合法的插入位置有n+1个,自头结点后至尾结点后都可以插入,1<=i<=n+1(头结点为第0个结点)
合法的删除位置有n个,即存储数据元素的n个结点,1<=i<=n

指定结点的删除

待删除结点为*p,指定结点时,*p的前驱结点不明,类比指定节点的插入可知有两种方法,一为查找链表得到前驱结点,时间复杂度为O(n);一为替换结点,用待删除结点的后继结点代替待删除结点被删除、释放,时间复杂度为O(1)。
下实现第二种方法。待删除结点 * p,用 *p的后继节点代替它被删除、释放,那么新的待删除结点 *q的前驱结点就是 *p ,将p->next的数据域存入 *p,改变相应的指针,释放 *q即可。

Status DeleteNode(LNode *p)
{
	if(!p) return ERROR;	//即p==NULL
	
	//q指向待删除结点p的后继
	LNode *q = p->next;	
	
	//待删除结点p与后继结点交换数据域	
	p->data = p->next->data;	
	
	//将*q结点断开
	p->next = q->next;			
	
	//释放后继节点的存储空间
	delete q;		
			
	return OK;
}

值得注意的是,该方法不具有普适性,当要删除最后一个结点,即指定节点为链表的尾结点,指针域为空时, “p->data = p->next->data”无法顺利实现。

按位查找/取值

给定一个结点位置序号i,找到单链表对应的结点,用参数e保存当前节点的数据域。
单链表不是随机存取结构,无法像顺序表一样随机访问,从首元结点出发,顺着next向下访问。
–>指针指向首元结点,顺着链域next依次访存;需要计数器j
①指针p指向首元结点,计数器j赋值为1
②从首元结点开始向下访存,只要p!=null(链表未结束)且没到达序号为i的结点,循环执行: p指向下一个节点,计数器+1
③退出循环时,若p为空或者计数器j>1,说明指定的i不合法。否则取之成功,此时j=i,p指向要找的第i个结点,参数e保存当前节点的数据域。
i合法取值:1<=i<=n

//带有头结点
status GetElem(LinkList L, int i, ElemType &e){
	p=L->next;
	j=1;

	//找到第i个结点
	while(p&&j<i){
		p=p->next;
		j++;
	}
	if(!p||j>i) return ERROR;

	//取值 此时j=i,p指向第i个结点,不为空
	e=p->data;	
	return OK;
}

时间复杂度:O(n)

按值查找

从链表的首元结点出发,依次将结点数据域的值与给定数值e进行比较,返回查找到的结点。
①指针p指向首元结点
②从首元结点开始依次顺着链域next向下查找,只要指向当前结点的指针p不为空,且p所指结点数据域不等于定值e,循环执行:p指向下一个结点
③返回p。查找成功,p值为找到的结点,查找失败,p值为null

//不带头结点
LNode *LocateElem(LinkList L, ElemType e){
	p=L->next;
	while (p&&p->data!=e){
		p=p->next;
	}
	return p;
}

时间复杂度与e的值有关,可能第一个结点的数据域就与e相等,也可能尾结点才相等,平均时间复杂度为O(n)
LNode* 强调返回的为某个结点。

求表长

指针p依次遍历链表的各个结点,不为空时记为一个结点,计数器加一。当p为空时表示单链表结束,返回计数值。

//有头结点
int ListLength(LinkList L){
	LinkList p;		//注意p的类型
	p=L->next;		//p指向首元结点
	i=0;
	while(p!=NULL){
		p=p->next;
		i++;
	}
	return i;
}

销毁

指针p暂存L的首元结点,并在后续释放,头指针L沿链域后移,随着释放p的存储空间,单链表逐渐缩短,当头指针为空时表示单链表完全销毁。

//无头结点
Status DestoryList(LinkList &L){
	LinkList p;
	while (L){
		p=L;
		L=L->next;
		delete p ;
	}
	return OK;
}

创建单链表

前插法

将新节点逐个插入链表的头部、头结点之后来创建链表。每次申请一个新节点存入数据元素值,将新节点插入到头结点之后。
最后生成的单链表中,数据存储是逆序的。

//有头结点,输入n个元素的值,创建有头结点的单链表L
void CreatList_H(LinkList &L, int n){
	//先建立一个有头结点的空链表 
	L = new LNode; 
	L->next = NULL;
	
	for(i=0;i<n;i++){
		p = new LNode;
		cin >> p->data;
		p->next = L->next;
		L->next = p;
	}
}

使用之前的函数,封装

void CreatList_H(LinkList &L, int n){
	//先建立一个有头结点的空链表 
	InitList(L) 
	//根据元素个数进行循环操作,在头结点后使用后插操作,创建单链表
	for(i=0;i<n;i++){
		cin>>e;
		InsertNextNode(L, e)
	}
}

后插法

将新节点逐个插入到链表的尾部。每申请一个新节点,存入相应的数据。增加一个尾指针指向链表的尾结点。
生成的单链表的数据顺序相对输入数据为顺序的。

void CreateList_R( LinkList &L, int n){
	L = new LNode; 
	L->next = NULL;				//初始化得到空表L
	r=L;						//尾指针指向头结点
	
	for(i=0;i<n;i++){
		p = new LNode;
		cin >> p->data;
		p->next = L->next;
		r->next = p;
		r=p;					//r指向新的尾结点*p
	}

时间复杂度为O(n)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值