【数据结构】单链表

目录

一、什么是链表

二、顺序表的缺陷

三、链表的实现

1.链表创建

2.接口设计

1.打印链表

2.创建节点

3.头插

4.尾插

5.头删

6.尾删

7.查找

8.中间插入

1.目标插入

2.目标后方插入

9.中间删除

    1.目标删除

    2.目标后方删除

四、全部代码 

SList.h

SList.c

Test.c


一、什么是链表

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

二、顺序表的缺陷

    1.头部/中间的插入删除,时间复杂度为O(N)

    2.增容需要realloc申请新空间,拷贝数据,释放旧空间,也会有大量消耗

    3.增容一般呈2倍增长,也会有一定的空间浪费

三、链表的实现

1.链表创建

        跟顺序表不同,链表每个节点分为两个部分,一部分是数据,另一部分是指向下一节点的指针,物理空间不连续,data存放的数据,next存放的是下一个节点的地址。

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

2.接口设计

        因为创建链表时使用的是一级指针,所以当我们要修改一级指针时需要运用二级指针来接收。

        个别接口不用二级指针接收,例如 打印 ,当我们打印时只需要读取数据,所以用一级指针就能完成。

void test1()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);

	SLPrint(plist);
}
void SLPrint(SLTNode* phead);//打印
SLTNode* SLFind(SLTNode* phead, SLTDataType x);//查找
SLTNode* BuyLTNode(SLTDataType x);//申请空间
void SLPushFront(SLTNode** pphead, SLTDataType x);//头插
void SLPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SLPopFront(SLTNode** pphead);//头删
void SLPopBack(SLTNode** pphead);//尾删
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//中间前面插入
void SLErase(SLTNode** pphead, SLTNode* pos);//中间删除
void SLInsertAfter(SLTNode* pos, SLTDataType x);//中间后面插入
void SLEraseAfter(SLTNode* pos);//中间后面删除
void SLDestroy(SLTNode** pphead);//销毁

1.打印链表

        利用循环遍历链表,通过next指向下一个节点,到NULL为止

void SLPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2.创建节点

SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("BuyLTNode is error");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

3.头插

        创建节点后,指向原来的头节点,再让链表头指向新节点即可,不用考虑链表为空

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为他是头指针plist地址
	//assert(*pphead);//不能断言,链表为空,也需要插入
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

4.尾插

        尾插分为两种情况:

        1.链表为空:直接创建节点让链表头指向新节点即可。

        2.链表不为空:先使用循环找到链表尾,然后使链表尾指向新节点,新节点指向空。

void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

5.头删

        先断言是否传入空指针,然后让链表头指向下一节点,释放刚才的链表头。

void SLPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* head = *pphead;
	*pphead = head->next;
	free(head);
}

6.尾删

        尾删也有两种情况:

        先断言是否传入空指针 

        1.链表只有一个节点:直接释放链表头并置空

        2.链表有一个以上节点:先使用循环找到链表尾的前一个节点,然后释放链表尾并置空。

       (释放前,必须将倒数第二个节点的指针指向空,否则将在指向最后一个被释放的节点时,变成野指针。)

void SLPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//第一种方法
		//SLTNode* prev = NULL;
		//SLTNode* tail = *pphead;
		//assert(tail);
		//while (tail->next != NULL)
		//{
		//	prev = tail;
		//	tail = tail->next;
		//}
		//free(tail);
		//prev->next = NULL;

		//第二种方法
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

7.查找

利用遍历,判断每一个数据,返回数据的地址

SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

8.中间插入

        分为两种情况,一种是目标插入,一种是目标后方插入。

1.目标插入

    1.在链表头插入,相当于头插。

    2.在非链表头插入,先遍历链表找到目标位置的前一个节点,然后申请新节点使其指向目标位置地址,最后使目标位置的前一个节点指向新节点。

//在pos前面插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	if (*pphead == NULL)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}

		SLTNode* newnode = BuyLTNode(x);
		newnode->next = pos;
		cur->next = newnode;
	}
}
2.目标后方插入

        直接申请新节点,插入目标节点后即可。

//在pos后面插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

9.中间删除

        分为两种情况,一种是目标删除,一种是目标后方删除。

    1.目标删除

        1.删除链表头时,删除节点相当于头删

        2.当链表含有多个节点时,需要将目标上一个节点指向目标下一个节点,再释放目标节点。

void SLErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}
    2.目标后方删除

      用变量tail记录下目标地址的next,再让目标地址的next指向tail->next,再释放tail即可。

void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* tail = pos->next;
	pos->next = tail->next;
	free(tail);
}

10.销毁

void SLDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	SLTNode* tail = NULL;

	while (cur)
	{
		tail = cur;
		cur = cur->next;
		free(tail);
	}

}

 

四、全部代码 

SList.h

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

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

void SLPrint(SLTNode* phead);//打印
SLTNode* SLFind(SLTNode* phead, SLTDataType x);//查找
SLTNode* BuyLTNode(SLTDataType x);//申请空间
void SLPushFront(SLTNode** pphead, SLTDataType x);//头插
void SLPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SLPopFront(SLTNode** pphead);//头删
void SLPopBack(SLTNode** pphead);//尾删
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//中间插入
void SLErase(SLTNode** pphead, SLTNode* pos);//中间删除
void SLInsertAfter(SLTNode* pos, SLTDataType x);//中间后面插入
void SLEraseAfter(SLTNode* pos);//中间后面删除
void SLDestroy(SLTNode** pphead);//销毁

SList.c

#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"

void SLPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("BuyLTNode is error");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SLPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//链表为空,pphead也不能为空,因为他是头指针plist地址
	//assert(*pphead);//不能断言,链表为空,也需要插入
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

void SLPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

void SLPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* head = *pphead;
	*pphead = head->next;
	free(head);
}

void SLPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//第一种方法
		//SLTNode* prev = NULL;
		//SLTNode* tail = *pphead;
		//assert(tail);
		//while (tail->next != NULL)
		//{
		//	prev = tail;
		//	tail = tail->next;
		//}
		//free(tail);
		//prev->next = NULL;

		//第二种方法
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

//在pos插入
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	if (*pphead == NULL)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}

		SLTNode* newnode = BuyLTNode(x);
		newnode->next = pos;
		cur->next = newnode;
	}
}

//在pos后面插入
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SLErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

void SLEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* tail = pos->next;
	pos->next = tail->next;
	free(tail);
}

void SLDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	SLTNode* tail = NULL;

	while (cur)
	{
		tail = cur;
		cur = cur->next;
		free(tail);
	}

}

Test.c

#define _CRT_SECURE_NO_WARNINGS
#include"SList.h"

void test1()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);

	SLPrint(plist);
    SLDestroy(&plist);
}

void test2()
{
	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushBack(&plist, 4);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPrint(plist);

	SLPopBack(&plist);
	SLPrint(plist);
	SLDestroy(&plist);
}

void test3()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPrint(plist);

	SLPopFront(&plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLPrint(plist);
	SLDestroy(&plist);
}

void test4()
{
	SLTNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPrint(plist);
	
	SLTNode* prev = SLFind(plist, 3);

	SLInsert(&plist, prev, 10);
	SLInsertAfter(prev, 18);
	SLPrint(plist);

	SLErase(&plist, prev);
	SLPrint(plist);

	prev = SLFind(plist, 2);
	SLEraseAfter(prev);
	SLPrint(plist);
	SLDestroy(&plist);
}

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

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值