数据结构“入门”—单链表(C语言实现)

目录

1:前言

 2:单链表基本概念

2:单链表的实现

2-1 :单链表的创建

2-2 :创建一个结点

2-3 :打印链表

 2-4 :销毁单链表

 3:增删查改

3-1:插入数据

尾插

头插 

 在pos之前插入

 在pos之后插入

 3-2:删除数据

尾删

 头删

删除pos处数据

 删除pos后一位数据

 3-3:查+改数据

 4:总代码

SList.h文件

SList.c文件

Test.c文件


1:前言

🍎单链表是顺序表的进一步拓展,学好单链表同时也为我们后面学好双向链表打好基础,那单链表相对于顺序表来说有哪些优点,既然单链表比顺序表更加完善我们又为何要引入顺序表概念呢?

下面我们详谈一下顺序表相对于单链表的优缺点。

优点:

   顺序表是连续的一段物理空间,更加方便下标的随机访问。

缺点:

  1. 插入数据,空间不足时要扩容,扩容会导致性能的消耗。
  2. 头部或者中间位置插入或者删除数据时,需要挪动数据,效率较低。
  3. 空间不能现创现用,顺序表的扩容是以双倍增长,开辟的部分空间会产生浪费,同时频繁的扩容也会导致性能的消耗。 

🐂: 顺序表不能按需申请和释放空间。而为了弥补顺序表这一缺陷,做到需要存入一个数据就申请一块空间,删除一个数据就释放一块空间。链表结构由此诞生。

 2:单链表基本概念

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

 链表的每个结点都储存一个数据和下一个结点的地址,正是储存的地址将一个个元素联系起来,这和我们生活中的火车有点类似。

 ⚠:​​​​​1 从上图我们能够看出,链式结构在逻辑上是连续的,但在物理上不一定连续。

        2 现实中的结点(可想象成火车的一节车厢)一般都是从堆上申请出来的。

        3 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续也可能不连续。

 接下来我们还是会用三个文件来创建一个单链表。

2:单链表的实现

2-1 :单链表的创建

Slist.h文件


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;//储存下一个结点的地址。
}SListNode;

2-2 :创建一个结点

Slist.h文件

//创建一个节点
SListNode* BuySListNode(SLTDataType x);

Slist.c文件

//创建一个节点
SListNode* BuySListNode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}
	return newnode;
}

2-3 :打印链表

 Slist.h文件

//打印链表
void SListPrint(SListNode* phead);

Slist.c文件

//打印链表
void SListPrint(SListNode* phead)
{
	SListNode *cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

 2-4 :销毁单链表

Slist.h文件

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

Slist.c文件

//销毁单链表
void SListDestroy(SListNode** pphead)
{
	assert(pphead);

	SListNode* cur = *pphead;
	while (cur)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

 3:增删查改

3-1:插入数据

尾插

思路:

🍎:如果链表本身是空链表,则把新开的结点给phead,需要注意的是形参是实参的临时拷贝,形参的改变不影响实参,这里要改变slist,phead是slist的一份临时拷贝,phead的改变不影响slist,所以要传slist的地址,而slist本身就是指针,所以要传指针的地址,那么就要用二级指针来接收。

🍎:如果链表不是空链表,此时我们就需要创建一个指针变量tail来遍历链表,找到链表的最后一个结点,然后把新创建的结点插入进去。

Slist.h文件

/尾插
void SListPushBack(SListNode** pphead, SLTDataType x);

Slist.c文件:

//尾插
void SListPushBack(SListNode** pphead, SLTDataType x)
{
	assert(pphead);

	SListNode* newnode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
} 

Test.c文件

void TestSList1()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
}

头插 

思路:

🍎头插相对于尾插更简单一点,只需要把新创建的结点放入链表头部,需要注意的是头插和尾插一样,需要传二级指针。

Slist.h文件

//头插
void SListPushFront(SListNode** pphead, SLTDataType x);

Slist.c文件:

//头插
void SListPushFront(SListNode** pphead, SLTDataType x)
{
	assert(pphead);

	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

Test.c文件

 在pos之前插入

思路:

🍎:pos不在头结点 。pos是结点的地址,我们通过SListFind函数找到pos。同时我们还需要找到pos前一个结点的地址,只有这样我们才能将链表的结点依次链接起来,我们可以创建一个指针变量prev,然后遍历链表,当prev->next==pos时prev就是pos前一个结点的地址,然后我们创建一个新的结点,实现结点的插入。

🍎:pos在头结点。如果pos是在头结点那么此时就相当于头插,仅用调用头插函数。

Slist.h文件

//在pos之前插入
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x);

Slist.c文件:

//在pos之前插入
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	//1 pos是第一个节点
	//2 pos不是第一个节点
	if (pos == *pphead)
	{
		SListPushFront(pphead, x);//pos是第一个节点调用头插。
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SListNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

Test.c文件

void TestSList6()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		SListInsertBefore(&slist, pos, 30);
	}
	SListPrint(slist);
}

 在pos之后插入

思路:

🍎:在pos之后插入相较于在pos之前插入来说更加的方便,因为在pos之后插入不用寻找pos上一个结点,不用遍历。

Slist.h文件

//在pos之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x);

Slist.c文件:

/在pos之后插入(相对于pos之前插入优势:不用寻找pos上一个节点,不用遍历。
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);
	
	法一
	//SListNode* next = pos->next;
	//SListNode* newnode = BuySListNode(x);

	//pos->next = newnode;
	//newnode->next = next;

	//法二
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

Test.c文件

void TestSList6()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		SListInsertBefore(&slist, pos, 30);
	}
	SListPrint(slist);
    pos = SListFind(slist, 1);
	if (pos)
	{
		SListInsertAfter(pos, 10);
	}
	SListPrint(slist);
}

 3-2:删除数据

尾删

思路:

🍎:尾删和尾插一样,都是要寻找最后一个结点,所以我们还是定义一个指针变量tail遍历链表,当tail->next==NULL时我们就找到了尾,但是当我们free最后一个结点后我们已无法找到倒数第二个结点,因为当我们尾删后倒数第二个结点的next必须置为空,所以我们还需要创建一个指针变量prev,尾删后将prev->next置为空。此种情况只是常规情况,当链表为空或者只有一个结点时我们还需要进行讨论。

Slist.h文件

//尾删
void SListPopBack(SListNode** pphead);

Slist.c文件:


//尾删
void SListPopBack(SListNode** pphead)
{
	assert(pphead);
	//0 空链表
	//1 只有一个节点
	//2 多个节点
	if (*pphead == NULL) // assert(*pphead != NULL);
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
	//法一
		SListNode* prev = NULL;
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;//不置也无所谓,它是局部变量,出函数销毁。

		prev->next = NULL;

		//法二
		/*SListNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;*/
	}
}

Test.c文件

void TestSList3()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}
	SListPrint(slist);
	SListPopBack(&slist);
	SListPopBack(&slist);
	SListPrint(slist);
}

 头删

思路:

🍎:当链表为空时不用删除直接返回空链表,非空链表我们可以创建一个指针变量next来保存第二个结点的地址,即为第一个结点的next,然后free第一个结点,将next赋值给*pphead

Slist.h文件

//头删
void SListPopFront(SListNode** pphead);

Slist.c文件:

//头删
void SListPopFront(SListNode** pphead)
{
	assert(pphead);

	//1 空
	//2 非空
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		SListNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}

Test.c文件

void TestSList4()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}
	SListPrint(slist);
	SListPopFront(&slist);
	SListPopFront(&slist);
	SListPrint(slist);
}

删除pos处数据

思路:

🍎:当pos为第一个结点时,我们只需要调用头删,当pos不是最后一个结点我们需要创建一个指针变量prev指向pos的前一个结点,然后将prev->next置成pos的下一个结点,再把pos free掉。

Slist.h文件

//删除pos位置
void SListErase(SListNode** pphead, SListNode* pos);

Slist.c文件:

//删除pos位置
void SListErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

Test.c文件

void TestSList7()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		SListErase(&slist, pos);
	}
	SListPrint(slist);
	pos = SListFind(slist, 1);
	if (pos)
	{
		SListErase(&slist, pos);
	}
	SListPrint(slist);
}

 

 删除pos后一位数据

思路:

🍎:删除pos后一位数据相对来说比较简单,因为我们不需要考虑pos前面的地址,不需要遍历。

Slist.h文件

//删除pos位置后面的值
void SListEraseAfter(SListNode* pos);

Slist.c文件:

//删除pos位置后面的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);

	SListNode* next = pos->next;
	if (next)
	{
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}

Test.c文件

void TestSList7()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		SListErase(&slist, pos);
	}
	SListPrint(slist);
	pos = SListFind(slist, 1);
	if (pos)
	{
		SListEraseAfter(pos);
	}
	SListPrint(slist);
}

 3-3:查+改数据

思路:

🍎:查找结点非常简单只需要遍历链表即可,而改数据又是建立查找的基础之上。

Slist.h文件

//查找+修改
SListNode* SListFind(SListNode* phead, SLTDataType x)

Slist.c文件:

//查找+修改
SListNode* SListFind(SListNode* phead, SLTDataType x)
{
	SListNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}
	return NULL;
}

Test.c文件

void TestSList5()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		printf("找到了:%p\n", pos);
		//修改
		pos->data *= 10;
	}
	SListPrint(slist);
}

 4:总代码

SList.h文件

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

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;//储存下一个结点的地址。
}SListNode;

//打印链表
void SListPrint(SListNode* phead);

//创建一个结点
SListNode* BuySListNode(SLTDataType x);

//尾插
void SListPushBack(SListNode** pphead, SLTDataType x);

//头插
void SListPushFront(SListNode** pphead, SLTDataType x);

//尾删
void SListPopBack(SListNode** pphead);

//头删
void SListPopFront(SListNode** pphead);

//查找+修改
SListNode* SListFind(SListNode* phead, SLTDataType x);

//在pos之前插入
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x);

//在pos之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x);

//删除pos位置
void SListErase(SListNode** pphead, SListNode* pos);

//删除pos位置后面的值
void SListEraseAfter(SListNode* pos);

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

SList.c文件

#include"SList.h"

//打印链表
void SListPrint(SListNode* phead)
{
	SListNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//创建一个节点
SListNode* BuySListNode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}
	return newnode;
}

//尾插
void SListPushBack(SListNode** pphead, SLTDataType x)
{
	assert(pphead);
	SListNode* newnode = BuySListNode(x);
	if (*pphead==NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
} 

//头插
void SListPushFront(SListNode** pphead, SLTDataType x)
{
	assert(pphead);

	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//尾删
void SListPopBack(SListNode** pphead)
{
	assert(pphead);
	//0 空链表
	//1 只有一个节点
	//2 多个节点
	if (*pphead == NULL) // assert(*pphead != NULL);
	{
		return;
	}
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//错误写法
	/*SListNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	free(tail);
	tail = NULL;*/
	//法一
		SListNode* prev = NULL;
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;//不置也无所谓,它是局部变量,出函数销毁。

		prev->next = NULL;

		//法二
		/*SListNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;*/
	}
}

//头删
void SListPopFront(SListNode** pphead)
{
	assert(pphead);

	//1 空
	//2 非空
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		SListNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}


//查找+修改
SListNode* SListFind(SListNode* phead, SLTDataType x)
{
	SListNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}
	return NULL;
}

//在pos之前插入
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	//1 pos是第一个结点
	//2 pos不是第一个结点
	if (pos == *pphead)
	{
		SListPushFront(pphead, x);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SListNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}


//在pos之后插入(相对于pos之前插入优势:不用寻找pos上一个节点,不用遍历。
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);
	//法一
	/*SListNode* next = pos->next;
	SListNode* newnode = BuySListNode(x);

	pos->next = newnode;
	newnode->next = next;*/
	//法二
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos位置
void SListErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//删除pos位置后面的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);

	SListNode* next = pos->next;
	if (next)
	{
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}

//销毁单链表
void SListDestroy(SListNode** pphead)
{
	assert(pphead);
	
	SListNode* cur = *pphead;
	while (cur)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

Test.c文件

#include"SList.h"

void TestSList1()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
}

void TestSList2()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}
	SListPrint(slist);
	SListPushFront(&slist, 10);
	SListPrint(slist);
	SListPushFront(&slist, 20);
	SListPrint(slist);
}
void TestSList3()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}
	SListPrint(slist);
	SListPopBack(&slist);
	SListPopBack(&slist);
	SListPrint(slist);
}

void TestSList4()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}
	SListPrint(slist);
	SListPopFront(&slist);
	SListPopFront(&slist);
	SListPrint(slist);
}

void TestSList5()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		printf("找到了:%p\n", pos);
		//修改
		pos->data *= 10;
	}
	SListPrint(slist);
}

void TestSList6()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		SListInsertBefore(&slist, pos, 30);
	}
	SListPrint(slist);
    pos = SListFind(slist, 1);
	if (pos)
	{
		SListInsertAfter(pos, 10);
	}
	SListPrint(slist);
}

void TestSList7()
{
	SListNode* slist = NULL;//空链表
	for (int i = 0; i < 4; i++)
	{
		SListPushBack(&slist, i);
	}

	SListPrint(slist);
	SListNode* pos = SListFind(slist, 3);
	if (pos)
	{
		SListErase(&slist, pos);
	}
	SListPrint(slist);
	pos = SListFind(slist, 1);
	if (pos)
	{
		SListEraseAfter(pos);
	}
	SListPrint(slist);
}



int main()
{
	/*TestSList1();
	TestSList2();
	TestSList3();
	TestSList4();
	TestSList5();
	TestSList6();*/
	TestSList7();
	return 0;
}

  • 33
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 28
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值