数据结构与算法(三)链表

链表是由一系列结点组成的,每个节点包含两域,一个是数据域,用来保存用户数据,另外一个是指针域,保存下一个节点的地址且在内存中是非连续的存储模式。链表在指定位置进行插和删除时不需要移动元素,只需要修改指针。

一、单链表:

是一种常见的动态存储线性表的实现方式,分为单向,双向,单循环,双循环等基本结构:

1.结点:每个节点包含两部分:数据部分和指针部分,数据部分存储元素的值,指针部分指向下一个结点。

①首元结点:链表中第一个有效结点(数据域存储有效数据)。

②头结点(可以不保存任何数据):是链表中的附着在首元结点之前的节点数据域存储当前链表特性(长度),指向链表的第一个结点(这里我们可以理解为数组的首地址元素,但不能混淆),链表中的最后一个结点的指针部分为NULL,表示链表的结束。

N个节点(ai)链接成一个链表,即是线性表(a1,a2,⋯,an)的链式存储结构,因此链表的每个结点中只包含一个指针域,所以叫单链表。

2.头指针:对于线性表来说,我们把链表中第一个结点的存储位置叫做头指针。

 ①链表的入口点:头指针是链表的起点,它使得链表可以从第一个结点开始遍历。没有头指针,无法访问链表的内容或对链表进行操作。

②链表操作管理的核心作用:它使得访问,插入,删除和遍历链表中的结点成为可能,正确管理头指针是确保链表功能正常和避免出错的关键。

为了更加方便的对链表进行操作,会在单链表的额第一个结点前附设一个 结点,称为头结点,头结点的数据域可以不存储任何信息。最后一个结点必须指向空(NULL),否则最后一个结点的指针指向未知位置,变为野指针。

当然了,头指针和头结点区别还是很大的

3.链表的优势:

①链表具有很好的动态性(非顺序存储结构),单线联系。

②在进行插入和删除数据时,仅仅需要改变指针的指向,提升数据的修改效率。

③结点的个数可以无限增加(硬件允许),自由扩充。

4.链表的缺点:

①查找元素效率太低,需要遍历链表,从时间复杂度的角度来说,处理时间过长,不算是一个好的算法。

②降低了磁盘数据的存储密度,容易产生碎片(磁盘碎片是指文件在磁盘上的不连续存储,这会导致空闲空间的浪费和访问性能的下降),碎片会影响磁盘的存储效率和性能。

下面我们来看看具体的定义过程:

//结点结构定义
typedef struct LN
{
	ElemType data;//数据域
	struct LN *next;//指针域 
} LNode;

//链表结构定义
typedef struct
{
	LNode head;//头结点
	int count;//链表中的有效长度 
} LinkList;

在这里写几个式子给大家看看:

LNode *p,*q;
p=q;//表示p和q 指向q指向的结点
q=p->next;//表示p指向q
//next 是指针类型,存储下一个结点的地址 
p = p->next;//表示将p移动到下一个结点
q->next = p; //表示q指向p
q->next = p->next;// p的下一个结点位置是q的下一个结点 

初始化链表并构造结点:

/*
	初始化链表
	参数1:传入带初始化的链表指针
	返回值:成功返回0 
*/ 
int InitList(LinkList *L)
{
	L->head.next = NULL;
	L->count = 0;
	return 0;
}

/*
	构造结点
	参数1:结点数据域的值
	返回值:成功返回一个结点的指针,失败返回NULL 
*/
LNode *NewNode(ElemType dat)
{
	LNode *new;
	new = (LNode *)malloc(1 * sizeof(LNode));
	if(new == NULL)
		return NULL;
	else
	{
		new->data = dat;
		new->next = NULL;
		return new; 
	}
}

销毁-置空链表:

/*
	销毁链表:逐个释放结点,遍历
	参数1:待销毁的链表指针
	返回值:成功返回0,失败返回-1 
*/
int DestoryList(LinkList *L)
{
	LNode *p,*q;//p指向释放位置,q指向下一个待释放的位置
	p=L->head.next;
	while(p != NULL)
	{
		q=p->next;
		free(p);
		p=q;
	}
	L->head.next = NULL;
	L->count = 0;
}

在这里释放p的原因是:链表结点在使用时也就在堆区分配了内存,必须要显式释放这些内存,防止内存泄漏。释放p相当于同时释放了p的数据域和指针域。

获取链表的长度(可以用来判断链表是否为空):

/*
	判断链表长度
	参数1:链表指针
	返回值:成功返回链表长度,失败返回-1 
*/
int ListLength(Link *L)
{
	LNode *p;
	int i = 0;
	p=L->head.next;//head.next代表头结点的指针域 
	while(p !=NULL)
	{
		p=p->next;
		i++;
	}
	return L->count;//或者用return i来代替 
}

打印链表:

/*
	打印链表
	参数:链表指针 
*/
void ListPrint(Link *L)
{
	LNode *p;
	p=L->head.next;//head.next代表头结点的指针域
	while(p != NULL)
	{
		printf("%d\n",p->data);
		p=p->next;
	}
}

初始化部分函数测试:

int main()
{
	LinkList list;
	LNode *node;
	InitList(&list);
	node=NewNode(55);
	
	if(ListEmpty(&list) == 0)
		printf("list链表为空\n");
	else
		printf("list长度为%d\n",ListLength(&list));
	
	return 0;
}

取值操作(这里我们给出两种方法):

①通过位置查找该位置上的值,成功,通过形参返回

/*
	取值操作:通过位置查找该位置上的值,成功,通过形参返回
	参数1:链表指针
	参数2:数据的位置
	参数3:使用指针返回取到的数据
	返回值:成功返回0,失败返回-1 
*/
int GetElemList(LinkList *L,int i,ElemType *e)
{
	LNode *p;
	int j=1;
	
	//初始化
	p=L->head.next;//p指向首元结点
	while(j<i && p)
	{
		p=p->next;
		j++; 
	}
	if(!p)//!表示取反操作,相当于取值失败
		return -1;
	*e=p->data;
	return 0;
}

②通过传入一个值,判断该值是否在链表中,如果有,返回结点

/*
	取值操作:通过传入一个值,判断该值是否在链表中,如果有,返回结点
	参数1:链表指针
	参数2:值
	返回值:成功返回结点,失败返回NULL 
*/
LNode *GetLocateElemList(Link *L,ElemType e)
{
	LNode *p;
	p=L->head.next;
	while(p->data != e && p)
	{
		p=p->next;
	}
	return p;
}

插入操作:前插,中插,尾插

前插(从头部插入数据):

/*
	从头部插入数据
	参数1:链表指针
	参数2:新结点数据域值
	返回值:成功返回0,失败返回-1 
*/ 
int ListHeadInsert(LinkList *L,ELemType e)
{
	LNode *new;
	new=NewNode(e);//创建孤立的结点 
	if(new == NULL)
		return -1;//创建新的结点失败,返回
	new->next = L->head.next;//new指向原来的首元结点 
	L->head.next = new;//将连表头结点指向new 
	L->count++;
	
	return 0;
}

尾插(从尾部插入数据):

/*
	从尾部插入插入数据
	参数1:链表指针
	参数2:新结点数据域值
	返回值: 成功返回0,失败返回-1 
*/
int ListTailInsert(LinkList *L,ElemType e)
{
	LNode *p=L->head.next;//p指向首元结点
	int length = L->count;//链表有效数据个数
	LNode *new;
	new=NewNode(e);
	if(new == NULL)
		return -1;
	while(--length)//跳出时,p指向最后一个有效的数据
		p=p->next;
	p->next = new;
	L->count++;
		
	return 0; 
}

中插(从中间插入数据):

/*
	从尾部插入插入数据
	参数1:链表指针
	参数2:数据的位置 
	参数3:新结点数据域值
	返回值: 成功返回0,失败返回-1 
*/
int ListMidInsert(LinkList *L,int i,ElemType e)
{
	LNode *p;
	int j=;
	LNode *new;
	new=NewNode(e);//创建新结点 
	if(new == NULL)
		return -1;
	//初始化
	p=L->head.next;//p指向首元结点
	while(j<i && p)
	{
		p=p->next;
		j++;
	}
	if(!p)//p == NULL;
		return -1;//位置非法
	new->next=p->next;
	p->next=new;
	L->count++;
	
	return 0;
}

这篇博客就暂时写到这吧,因为Leo也只写到这,后续还会继续更新的,大家谅解一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值