数据结构——单链表

前言

顺序表有自己独特的优势,但是当我们在运行过程中会发现空间会有不小的损耗,而且时间复杂度也会很大。那么有没有其他的办法来解决这样的问题呢?这样,我们就引入了链表

1.概念与结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

我们可以将链表比作一个个车厢。如下形象图:

与顺序表不同的是,链表中的每节车间都是独立申请下来的空间,我们称为结点 

如图所示,我们可以看到结点的组成主要有两个部分:数据+下一个结点的地址

在链表中,每个结点都是独立申请的,我们需要通过指针变量来保存下一个结点位置,才能找到下一个结点的位置。

1.1 链表的性质

1.在逻辑上是连续的,在物理结构上不一定连续;

2.结点一般是从堆上申请的;

3.申请的空间可能连续,也可能不连续。(即结点之间的地址不一定是连续的)

2.链表的实现过程及解析

2.1 定义链表的结构

//定义链表的结构
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

在定义结构中,存储的数据我们不确定,所以我们将int进行重命名,这样可以快速改变存储的类型。

接下来,进行测试,在text.c中:

2.1.1 创建一个链表

//创建一个链表,并打印链表
void createSList()
{
	//链表是由一个个的结点组成的
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;
}

int main()
{
	createSList();
	return 0;
}

具体结构可如下图所示: 

2.2 链表的打印

//打印链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

最后运行结果为: 

可见这样是正确的。首先我们用pcur来作为判断条件,当pcur为NULL时,说明到达了最后一个结点,这样每次取每个结点的data就可以将链表中的内容打印出来。 

 2.3 插入数据

2.3.1 尾插

具体过程如上图所示。

2.3.1.1 申请新结点

在插入数据时,我们需要申请一个新结点 :

//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = NULL;

	return node;
}
 2.3.1.2 代码实现

因为我们要知道头结点在哪里,所以我们应该定义一个新变量,这样phead所指向的位置保持不变。

void SLTPushBack(SLTNode* phead, SLTDataType x)
{
	//申请新节点
	SLTNode* newnode = SLTBuyNode(x);
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		//找尾结点
		SLTNode* pcur = phead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

你以为这样是对的吗?当我们调试出来之后就会发现,并没有像我们想象中的那样,那么,是为什么呢?那么,我们下期再见~

哈哈,骗你的~

其实,在顺序表中,我么遇到了同样的问题,当时我只是一笔略过了,那么究竟是为什么呢?在很早之前,我们就强调了传值和传址的区别,所以,现在的你知道是为什么了吗?当我们要改变其中的数据时,我们就要考虑传址调用

所以,这里要使用二级指针来进行代码改善!

//插入数据
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
	//申请新节点
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾结点
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

要记得在最开始时要有断言! 

2.3.2 头插

 

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

2.4 删除数据

2.4.1 尾删

//删除数据
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表为空,不可以执行删除
	assert(pphead && *pphead);//pphead表示参数不能为空,*pphead表示链表不为空
	//找 prev ptail
	SLTNode* ptail = *pphead;
	SLTNode* prev = NULL;
	while (ptail->next)
	{
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	free(ptail);
	ptail = NULL;
}

 但是,当我们运行之后可以看到:

其中,返回值不为0,说明存在问题,那么问题时什么呢?

原因是我们忽略了链表只有一个结点的情况!

所以,改进如下:

//删除数据
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表为空,不可以执行删除
	assert(pphead && *pphead);//pphead表示参数不能为空,*pphead表示链表不为空
	//找 prev ptail
	//只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = NULL;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}

}

 

可以看出,当前的代码是正确的。

2.4.2 头删

 

2.5 查找数据

 因为查找数据时,不会改变链表中的内容,所以只需要用传值调用就可以了。

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

 2.6 在指定位置插入数据

2.6.1 在指定位置之前插入数据

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//找前一个结点
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	newnode->next = pos;
	prev->next = newnode;
}

然而,如果进行验证可以看见: 

返回值不为0,则该代码是错误的。那么,错在哪里呢?

应该考虑头插的情况!

正确的代码应该是:

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		//找前一个结点
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}

}

 

2.6.2 在指定位置之后插入数据 

 

//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);

	newnode->next = pos->next;
	pos->next = newnode;
}

 2.7 删除指定位置数据

2.7.1 删除pos位置的数据

//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

    //头删
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}

}

注意在这种情况下,我们也要考虑头删的情况。 

 2.7.2 删除pos之后的结点

//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

需要注意的是,我们应该用del作为中间的变量,最终实现释放的作用。

2.8 销毁链表 

销毁链表实际上就是将一个个的结点进行释放的过程。

//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

 

需要注意的是,我们在每一个函数的实现之后,就要进行检验!!!

3.总的代码

3.1 SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//定义链表的结构
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//打印
void SLTPrint(SLTNode* phead);

//插入数据
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//删除数据
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SListDestroy(SLTNode** pphead);

3.2 SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"

//打印链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = NULL;

	return node;
}

//插入数据
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//申请新节点
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾结点
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//删除数据
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表为空,不可以执行删除
	assert(pphead && *pphead);//pphead表示参数不能为空,*pphead表示链表不为空
	//找 prev ptail
	//只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = NULL;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}

}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);

	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		//找前一个结点
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}

}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);

	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

    //头删
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}

}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

3.3 test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"

//创建一个链表,并打印链表
void createSList()
{
	//链表是由一个个的结点组成的
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//第一个结点的地址传过去
	SLTNode* plist = node1;
	SLTPrint(plist);
}

void SListTest01()
{
	SLTNode* plist = NULL;
	/*SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);*/

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	/*SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);*/

	/*SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);*/

	SLTNode* find = SLTFind(plist, 4);
	/*if (find == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	}*/
	//SLTInsertAfter(find, 11);
	//SLTPrint(plist);
	//SLTEraseAfter(find);
	SListDestroy(&plist);
	SLTPrint(plist);
}

int main()
{
	//createSList();
	SListTest01();
	return 0;
}

好了,今天就到这里,我们下一个知识点见(* ̄︶ ̄) !

 

 

 

 

 

 

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值