【数据结构初级(2)】单链表的基本操作和实现

文章目录

  • Ⅰ 概念及结构
    • 1. 单链表的概念
    • 2. 单链表的结构
  • Ⅱ 基本操作实现
    • 1. 定义单链表结点
    • 2. 创建新结点
    • 3. 单链表打印
    • 4. 单链表尾插
    • 5. 单链表头插
    • 6. 单链表尾删
    • 7. 单链表头删
    • 8. 单链表查找
    • 9. 在指定 pos 位置前插入结点
    • 10. 删除指定 pos 位置的结点
    • 11. 单链表销毁

本章实现的是不带头结点的单链表。

Ⅰ 概念及结构

1. 单链表的概念

  • 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
  • 单链表的每个结点仅仅要存储该结点本身应该存储的数据,还要存储下一个与自己同类型结点的地址。

【数据结构初级(2)】单链表的基本操作和实现_第1张图片

2. 单链表的结构

1. 物理结构

每个单链表结点都有一个指针域,用来存储下一个结点的地址。

【数据结构初级(2)】单链表的基本操作和实现_第2张图片

2. 逻辑结构

为了方便理解和学习,使用箭头来表示每个结点的指针域所存储的是哪个结点的地址。

【数据结构初级(2)】单链表的基本操作和实现_第3张图片

Ⅱ 基本操作实现

1. 定义单链表结点

  • 每个结点都应该包含数据域和指针域两部分内容。
  • 数据域的数据类型应该使用 typedef 来定义,防止以后更改数据域的数据类型。
typedef int SLTDataType;	//每个单链表结点数据域的数据类型

typedef struct SListNode	//定义一个单链表结点
{
	SLTDataType data;		//单链表的数据域
	struct SListNode* next;	//单链表的指针域,用于存放下一个同类型结点的地址
}SLNode;					//单恋表的结点类型

2. 创建新结点

  • 开辟一个结点空间,将传过来的数据 x 放到结点的数据域,然后将该结点的指针域置 NULL。
SLNode* BuySListNode(SLTDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));	//开辟一个新结点空间

	if (NULL == newnode)	//如果开辟该结点失败
	{
		perror("malloc");
		exit(-1);
	}

	newnode->data = x;		//参数给新结点的数据域
	newnode->next = NULL;	//将新结点的指针域置空

	return newnode;			//返回开辟的新结点的地址
}

3. 单链表打印

1. 遍历链表

因为不能动指向首结点的指针,防止找不到首结点,所以此类操作都是利用额外的指针来实现的。

  • 使用一个指针 cur (current) 指向当前结点,如果 cur 不为空,则说明链表还没走到头。打印 cur 所指向的结点的数据域,然后让 cur 指针指向下一个结点。
  • 在打印的时候会有两种情况。
  1. 单链表为空:此时 cur 直接就指向 NULL,什么也不会打印。

【数据结构初级(2)】单链表的基本操作和实现_第4张图片

  1. 单链表非空:打印 cur 指向结点的数据域的类容,然后将 cur 指向指向下一个结点,直到 cur 走到链表末尾后为止。

【数据结构初级(2)】单链表的基本操作和实现_第5张图片

2. 代码实现

 //单链表打印
void SListPrint(SLNode* plist)
{
	SLNode* cur = plist;			//指向单链表的首结点

	while (cur != NULL)				//单链表还没走到尾
	{
		printf("%d->", cur->data);	//打印当前结点数据域的值
		cur = cur->next;			//继续指向下一个结点
	}
	printf("NULL\n");
}

3. 函数调用

SListPrint(plist);

4. 单链表尾插

1. 插入链表

将数据依次插入到单链表的尾部。
在进行单链表尾插时有两种情况:

  1. 单链表为空:直接创建一个新结点然后插入到单链表即可。

【数据结构初级(2)】单链表的基本操作和实现_第6张图片

  1. 单链表非空:使用一个 tail 指针用来寻找单链表的尾部,要将新结点插入到 tail->next 为 NULL 的位置。

【数据结构初级(2)】单链表的基本操作和实现_第7张图片

2. 代码实现

//传过来的是个指针变量的地址,需要使用二级指针 pplist 来接收
void SListPushBack(SLNode** pplist, SLTDataType x)
{
	assert(pplist);

	SLNode* newnode = BuySListNode(x);	//创建一个新的结点

	if (NULL == *pplist)				//单链表当前为空
	{
		*pplist= newnode;				//直接将新结点插入链表
	}
	else								//单链表非空
	{
		SLNode* tail = *pplist;			//tail 用于寻找单链表的尾结点

		while (tail->next != NULL)		//没有找到尾结点
		{
			tail = tail->next;			//指向下一个结点
		}
		tail->next = newnode;			//将新结点插入到尾结点之后,成为新的尾结点
	}
}

3. 函数调用

【数据结构初级(2)】单链表的基本操作和实现_第8张图片

5. 单链表头插

1. 插入链表

  • 不管链表中有多少个结点,所有的数据都插入到链表的都一个位置

【数据结构初级(2)】单链表的基本操作和实现_第9张图片

2. 代码实现

//要改变链表内容,实参要传指针变量的地址,形参用二级指针接收
void SListPushFront(SLNode** pplist, SLTDataType x)
{
	assert(pplist);						//传过来的不能是 NULL 指针

	SLNode* newnode = BuySListNode(x);	//创建新结点
	newnode->next = *pplist;			//将首结点地址交给新结点指针域
	*pplist= newnode;					//让新结点成为新的首结点
}

3. 调用函数

【数据结构初级(2)】单链表的基本操作和实现_第10张图片

6. 单链表尾删

  • 每次将单链表尾部的的一个结点删除掉。

1. 实现方法

  1. 链表中只一个结点:直接释放链表指针指向的那个结点。

【数据结构初级(2)】单链表的基本操作和实现_第11张图片

  1. 链表中有多个结点:使用一个 tail 指针寻找尾结点的前一个结点,如果 tail 所指向结点的下个结点的指针域为空,则说明 tail->next 指向的结点为尾结点,直接释放即可。

【数据结构初级(2)】单链表的基本操作和实现_第12张图片

2. 实现代码

void SListPopBack(SLNode** pplist)
{
	assert(pplist);							//pplist 指向的 plist 不是 NULL
	assert(*pplist);						//plist 指向的链表不为空

	if (NULL == (*pplist)->next)			//1.一个结点
	{
		free(*pplist);						//释放链表中唯一的一个结点
		*pplist= NULL;
	}
	else									//2.多个结点
	{
		SLNode* tail = *pplist;				//使用 tail 找尾结点

		while (tail->next->next != NULL)	//当前结点的下下个结点不为空
		{
			tail = tail->next;				//tail 指向链表下一个结点
		}

		free(tail->next);					//释放 tail->next 指向的尾结点
		tail->next = NULL;					//将 tail->next 的值置空
	}
}

3. 函数调用

【数据结构初级(2)】单链表的基本操作和实现_第13张图片

7. 单链表头删

  • 每次删除都只删除单链表的首结点。

1. 实现方法

  1. 链表有多个结点:先用一个临时指针变量保存首结点的地址,再让链表指针指向第二个结点,最后释放首结点。

【数据结构初级(2)】单链表的基本操作和实现_第14张图片

  1. 链表只一个结点:步骤和上面相同,只不过只有一个结点时,链表指针在第二步会指向 NULL 而已。

2. 实现代码

void SListPopFront(SLNode** pplist)
{
	assert(pplist);				//传过来的不是 NULL
	assert(*pplist);			//链表不为空

	SLNode* tmp = *pplist;		//先保存链表首结点
	*pplist= (*pplist)->next;	//让链表指针指向第二个结点
	free(tmp);					//释放首结点
}

3. 函数调用

【数据结构初级(2)】单链表的基本操作和实现_第15张图片

8. 单链表查找

  • 在单链表中查找数据域的值等于形参 x 的结点,并返回该结点的地址。
  • 如果链表中不存在 x 则返回 NULL。

实现代码

SLNode* SListFind(SLNode* plist, SLTDataType x)
{
	assert(plist);				//链表不为空

	SLNode* cur = plist;		//cur 用来访问当前结点

	while (cur != NULL)			//cur 没有走到链表末尾
	{
		if (cur->data == x)		//当前结点数据域的值等于形参 x
		{
			return cur;			//返回当前结点的地址
		}
		else					
		{
			cur = cur->next;	//继续往后找数据域等于 x 的结点
		}
	}

	return cur;					//链表中没有 x,返回 NULL
}

调用函数

【数据结构初级(2)】单链表的基本操作和实现_第16张图片

9. 在指定 pos 位置前插入结点

1. 实现方法

  • 定义一个 cur (current) 指针指向当前结点,如果当前结点的下一个结点的地址等于 pos 的地址,则将 pos 指向的结点插入到新结点后,再将新结点插入到 cur 结点后。

【数据结构初级(2)】单链表的基本操作和实现_第17张图片

2. 实现代码

void SLTInsert(SLNode** pplist, SLNode* pos, SLTDataType x)
{
	assert(pplist);						//传过来的不能是空指针
	assert((*pplist) && (pos));			//要么同时不为空
	assert(!(*pplist) && !(pos));		//要么同时为空

	SLNode* newnode = BuySListNode(x);	//创建新结点

	if (*pplist == pos)					//链表只有一个结点 - 头插
	{
		SListPopFront(pplist,x)			//新结点作首结点
	}
	else								//找 pos 位置的前一个
	{
		SLNode* cur = *pplist;			//cur 是 pos 位置的前一个结点

		while (cur->next != pos)		//当前结点的下个结点不是 pos 
		{
			cur = cur->next;			//cur 向后寻找 pos 结点
		}

		newnode->next = pos;			//让新结点指针域指向 pos 结点
		cur->next = newnode;			//当前结点的指针域指向新结点
	}
}

调用函数

【数据结构初级(2)】单链表的基本操作和实现_第18张图片

10. 删除指定 pos 位置的结点

1. 实现思路

  1. 在删除 pos 位置的结点前,先用一个前趋指针 pre 保存 pos 结点的前一个结点。
  2. 让 pre 指向 pos 的下一个结点。
  3. 销毁 pos 指向的结点。

【数据结构初级(2)】单链表的基本操作和实现_第19张图片

2. 实现代码

void SLTErase(SLNode** pplist, SLNode* pos)
{
	assert(pplist);
	assert((*pplist) && (pos));		//两个指针同时不为空

	SLNode* pre = NULL;				//当前位置的前一个结点
	SLNode* cur = *pplist;			//用来寻找 pos 结点

	while (cur != NULL)
	{
		if (cur != pos)			
		{
			pre = cur;
			cur = cur->next;
		}
		else						//cur 找到了 pos 指向的结点
		{
			pre->next = cur->next;	//pos 的前一个结点指向 pos 的后一个结点
			free(cur);				//删除 pos 指向的结点
			cur = NULL;
		}
	}
}

11. 单链表销毁

  • 持续对单链表连续执行头删。

实现代码

void SLTDestroy(SLNode** pplist)
{
	assert(pplist);					//链表指针不能为空
	
	SLNode* cur = *pplist;
	
	while (cur)						//链表不为空则执行头删
	{
		SLNode* next = cur->next;	//保存当前结点的下一个结点
		free(cur);					//释放当前结点
		cur = next;					//对下一个结点执行以上操作
	}
	
	*pplist = NULL;					//无论链表原来为不为空最后都执行这步
}

你可能感兴趣的:(数据结构,-,实操版,数据结构)