数据结构学习记录03——线性表之链式存储结构

前言

  前面讲了线性表的顺序存储结构,并在最后总结出了顺序存储结构的缺点,那就是在插入、删除元素时,需要移动大量的元素,易造成存储空间的浪费,为此,我们可以通过线性表的链式存储结构来解决。

1.定义

  为了表示每个数据元素与其直接后继元素之间的逻辑关系,每个元素除了存储本身的信息外,还需要存储指示其直接后继的信息。其示意图如下:
在这里插入图片描述
  n个这样的结点链结成一个链表,由于此链表的每个结点中只包含一个指针域,故称为线性链表或单链表。

2.基本概念

  表头结点:链表中的第一个结点,包含指向第一个数据元素的指针以及链表自身的一些信息。
  数据结点:链表中代表数据元素的结点,包含指向下一个数据元素的指针和数据元素的信息。
  尾结点:链表中的最后一个数据结点,其下一个元素指针为空,表示无后继。

3.单链表的常用操作

  同线性表顺序存储结构一样,链式存储结构的常用操作有创建、销毁、清空、返回单链表的长度、插入、获取元素位置、删除。
  在C语言中,可以用结构体来定义链表中的指针域,链表中的表头结点也可以用结构体来实现。

//结点指针域定义,这里相当于一条地址链,一个地址里存着下一个地址,下一个地址存着下下个地址,以此类推。
typedef struct _tag_LinkListNode LinkListNode;
struct _tag_LinkListNode
{
	LinkListNode* next;
};
typedef struct _tag_LinkList							//单链表的头结点 
{
	LinkListNode header;								//指向下一元素的指针
	int length;											//该单链表的长度 
} TLinkList;

3.1创建

  创建单链表时,我们先动态地申请表头结点,令链表的长度为0,表头结点的指针域为空。

typedef void LinkList;
LinkList* LinkList_Create()							
{
	TLinkList* ret = (TLinkList*)malloc(sizeof(TLinkList));			//动态地申请表头节点
	
	if (ret != NULL)
	{
		ret -> length = 0;
		ret -> header.next = NULL;	
	} 
	
	return ret;
}

3.2销毁

  同顺序存储结构一样,单链表的销毁直接free掉就行。

void LinkList_Destroy(LinkList* list)					
{
	free(list);
}

3.3清空

  清空单链表就只需让它回到初始状态即可,也就是长度为0,表头结点的指针域为空。

void LinkList_Clear(LinkList* list)						
{
	TLinkList* sList = (TLinkList*)list;
	
	if (sList != NULL)											//对单链表的合法性检测 
	{
		sList -> length = 0;
		sList -> header.next = NULL;
	}
}

3.4获取长度

  获取单链表的长度时,首先对其合法性进行检测,接着返回长度即可。

int LinkList_Length(LinkList* list)					
{
	TLinkList* sList = (TLinkList*)list;
	int ret = -1;
	
	if (sList != NULL)											//对单链表的合法性检测
	{
		ret = sList -> length;
	}
	
	return ret;
}

3.5插入元素

  在单链表中插入一个元素时,我们首先确定出插入的位置,然后先让插入元素的next指针指向插入的位置,再将原来的next指针断开,让其指向插入元素,其示意图为:
在这里插入图片描述
  完整的操作步骤为:
  (1)判断线性表是否合法;
  (2)判断插入位置是否合法;
  (3)由表头开始通过next指针移动pos次后,当前元素的next指针指向要插入的位置;
  (4)将新元素插入;
  (5)线性表长度加1。

int LinkList_Insert(LinkList* list, LinkListNode* node, int pos) 
{
	TLinkList* sList = (TLinkList*)list;
	int ret = (sList != NULL) && (pos >= 0) && (node != NULL);
	int i = 0;
	
	if (ret)
	{
		LinkListNode* current = (LinkListNode*)sList;			//让current指针指向表头 
		
		for (i = 0; (i < pos) && (current -> next != NULL); i++)
		{
			current = current -> next;
		}
		
		node -> next = current -> next;
		current -> next = node;
		
		sList -> length++;
	}
	
	return ret;
}

3.6获取元素

  获取单链表中的一个元素的操作为:
  (1)判断线性表是否合法;
  (2)判断位置是否合法;
  (3)由表头开始通过next指针移动pos次后,当前元素的next指针指向要获取的元素。

LinkListNode* LinkList_Get(LinkList* list, int pos)				
{
	TLinkList* sList = (TLinkList*)list;
	LinkListNode* ret = NULL;
	int i = 0;
	
	if ((sList != NULL) && (pos >= 0) && (pos < sList -> length))
	{
		LinkListNode* current = (LinkListNode*)sList;			//让current指针指向表头
		
		for (i = 0; i < pos; i++)
		{
			current = current -> next;
		}
		
		ret = current -> next;									//返回指向元素的指针 
	}
	
	return ret;
}

3.7删除元素

  在单链表中删除一个元素时,我们首先找到要删除元素的位置,然后设一个变量等于当前元素的next指针指向的元素,接着让当前元素的next指针指向变量的next指针指向的元素,其示意图为:
在这里插入图片描述
  其操作步骤如下:
  (1)判断线性表是否合法;
  (2)判断删除位置是否合法;
  (3)获取第pos个元素;
  (4)将第pos个元素从链表中删除;
  (5)线性表长度减1.

LinkListNode* LinkList_Delete(LinkList* list, int pos)			
{
	TLinkList* sList = (TLinkList*)list;
	LinkListNode* ret = NULL;
	int i = 0;
	
	if ((sList != NULL) && (pos >= 0) && (pos < sList -> length))
	{
		LinkListNode* current = (LinkListNode*)sList;			//让current指针指向表头
		
		for (i = 0; i < pos; i++)
		{
			current = current -> next;
		}
		
		ret = current -> next;									//指向元素的指针 
		current -> next = ret -> next;
		
		sList -> length--;
	}
	
	return ret;
}

4.测试

  在对单链表进行测试前,我们还要对数据元素进行结构体定义。

struct Value											//数据元素的定义 
{
	LinkListNode header;
	int val;
};

4.1头插法

  头插法是通过将新结点逐个插入链表的表头结点来创建链表,每次申请一个新结点,写入相应的数据元素值,然后将新结点插入到表头结点之后。

#include <stdio.h>
#include <stdlib.h>
#include "LinkList.h" 

struct Value											//数据元素的定义 
{
	LinkListNode header;
	int val;
};

int main(int argc, char *argv[])
{
	LinkList* list = LinkList_Create();					//创建空链表 
	int i = 0; 
	
	struct Value v1;
	struct Value v2;
	struct Value v3;
	struct Value v4;
	struct Value v5;
	
	v1.val = 1;
	v2.val = 2;
	v3.val = 3;
	v4.val = 4;
	v5.val = 5;

	LinkList_Insert(list, (LinkListNode*)&v1, 0);		//头插法,插入链表 
	LinkList_Insert(list, (LinkListNode*)&v2, 0);
	LinkList_Insert(list, (LinkListNode*)&v3, 0);
	LinkList_Insert(list, (LinkListNode*)&v4, 0);
	LinkList_Insert(list, (LinkListNode*)&v5, 0);

	for (i = 0; i < LinkList_Length(list); i++)
	{
		struct Value* pv = (struct Value*)LinkList_Get(list, i);
		printf("%d\n", pv -> val);
	}
	
	LinkList_Destroy(list);							 //当不需要单链表时,必须清除掉 
	
	return 0;
}

  得到的结果为:
在这里插入图片描述
可以看出元素每次都从表头插入。

4.2尾插法

  尾插法是通过将新结点逐个插到链表的尾部来创建链表。

#include <stdio.h>
#include <stdlib.h>
#include "LinkList.h" 

struct Value											//数据元素的定义 
{
	LinkListNode header;
	int val;
};

int main(int argc, char *argv[])
{
	LinkList* list = LinkList_Create();					//创建空链表 
	int i = 0; 
	
	struct Value v1;
	struct Value v2;
	struct Value v3;
	struct Value v4;
	struct Value v5;
	
	v1.val = 1;
	v2.val = 2;
	v3.val = 3;
	v4.val = 4;
	v5.val = 5;

	LinkList_Insert(list, (LinkListNode*)&v1, LinkList_Length(list));		//尾插法,插入链表 
	LinkList_Insert(list, (LinkListNode*)&v2, LinkList_Length(list));
	LinkList_Insert(list, (LinkListNode*)&v3, LinkList_Length(list));
	LinkList_Insert(list, (LinkListNode*)&v4, LinkList_Length(list));
	LinkList_Insert(list, (LinkListNode*)&v5, LinkList_Length(list));

	
	for (i = 0; i < LinkList_Length(list); i++)
	{
		struct Value* pv = (struct Value*)LinkList_Get(list, i);
		printf("%d\n", pv -> val);
	}

	LinkList_Destroy(list);							 //当不需要单链表时,必须清除掉 
	
	return 0;
}

得到的结果为:
在这里插入图片描述

5.总结

  对于线性表的链式存储结构,其优点为:
  (1)无需一次性定制链表的容量;
  (2)在插入和删除元素时无需移动大量的元素。
  其缺陷为:
  (1)数据元素必须保存后继元素的位置信息;
  (2)获取指定数据的元素操作需要访问之前的元素。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值