Day2:单链表的基本操作

Posted 雨轩(小宇)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day2:单链表的基本操作相关的知识,希望对你有一定的参考价值。

目录目录
顺序表单链表(不带附加头结点)

前言

  • 上一节实现了顺序表的基本操作,这一节我们来学习单链表(不带头结点),实现单链表的一些基本操作以及注意事项。

1、单链表的定义

  1. 单链表是链表中最简单的链表表示,也叫线性链表。链表是不连续的,用指针表示结点间的关系,一个节点包含两个域:

    数据域:一般用data表示,存储一个数据元素
    指针域:存放一个指针,指向下一个结点的开始存储地址,起到链接两个结点的作用

  2. 单链表的结构

    head:头指针,指向第一个结点
    datan:尾结点,没有后继,next指向NULL
    head=NULL的话,则说明该链表为空。

每个节点之间的关系:
第一个结点与头指针:a1=head;
第一个与第二个结点:a2=a1->next;

最后一个结点:a(n)=a(n-1)->next,a(n)->next=NULL

2、单链表的特点

  1. 长度很方便的进行扩充
  2. 逻辑顺序与物理顺序可不一致
  3. 可连续存储数据,也可以不连续
  4. 每个结点都有指针域,存储空间消耗比较大

3、单链表的基本操作

1、头文件

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>//bool头文件
typedef int SLTDataType;//重命名,方便更改数据类型
typedef struct SListNode
{
	SLTDataType data; //数据域
	struct SListNode* next; //指针域
}SLTNode;

2、初始化

  1. 初始化,给头指针开辟空间,开辟成功置空。
void SListInit(SLTNode** phead)
{
	assert(phead);
	*phead = (SLTNode*)malloc(sizeof(SLTNode));
	if (*phead == NULL)
	{
		perror("SListInit");
		return;
	}
	(*phead) = NULL;
}

3、打印

  1. 遍历根据单链表的结构进行遍历即可,记住不要用头指针去遍历,对头指针进行重命名为p在去进行遍历,可读性高
//遍历
void SListPrint(SLTNode* phead)
{
	SLTNode* p = phead;
	while (p)
	{
		printf("%d->", p->data);//打印数据
		p = p->next;//指向下一个结点,知道该结点为NULL
	}
	printf("NULL\\n");
}

4、创建新节点

  1. malloc开辟内存给node,开辟失败则退出,开辟成功则将node->data置为x,node->next 置空
//创建节点
SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("BuySListNode");
		exit(-1);
	}
	node->data = x;
	node->next = NULL;
	return node;
}

5、尾插

  1. 当为空表时,相当于在链表头部插入结点,需要修改指针:newnode->next = *pphead; *pphead = newnode;
  2. 非空表时,在current的下一个节点后插入新节点newnode,需要修改指针:
    newnode->next = tail->next; tail->next = newnode;
  3. 这里利用了BuySListNode这个函数创建新节点,所以只用写current->next = newnode; 读者可以根据自己实现的方法去改变

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	if (*pphead == NULL)//如果链表为NULL,则第一个节点地址给头结点
	{
		SLTNode* newnode = BuySListNode(x);//创建新节点
		*pphead = newnode;//将新节点地址给头节点
	}
	else
	{
		SLTNode* tail = *pphead;
		//当有节点存在时
		while (tail->next)//这里判断下一个结点是否为空,不能是判断该结点是否为空,否则会直接找到末尾,末尾为NULL,插入会失败
		{
			tail = tail->next;
		}
		SLTNode* newnode = BuySListNode(x);//调用函数,创建新节点
		tail->next = newnode;
	}
}

6、尾删

  1. 当只有一个结点时,用tail保存首元结点,修改指针:tail = *pphead; free(tail); tail=NULL;
  2. 当有多个结点,用prev保存前驱节点,tail为后继结点,遍历链表,找到尾结点,修改指针:prev = tail; tail = tail->next; free(tail); prev->next = NULL;

void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	//暴力方式
	assert(*pphead);//检测链表是否为空
	//温和方式
	/*if (*pphead == NULL)
	{
		return;
	}*/
	SLTNode* prev = NULL;
	SLTNode* tail = *pphead;
	if (tail->next == NULL)
	{
		free(tail);
		tail = NULL;
	}
	else
	{
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}

7、头插

  1. 头插顾名思义在第一个节点之前插入,成为第一个节点,修改指针:newnode->next = head; head = newnode;

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *pphead;//指向第一个节点
	*pphead = newnode;//newnode地址给pphead成为第一个节点
}

8、头删

1.头删,删除第一个节点,让头指针指向第二个结点,修改指针:next = head->next; head = next;

void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* nextnode = (*pphead)->next;
	*pphead = nextnode;
	free(*pphead);
	*pphead = NULL;
}

9、插入

  1. 单链表一般在pos位置之后去插入结点
  2. 头部,修改指向:newnode->next = head; head = newnode;
  3. 既不是第一个结点也不是最后一个结点,修改指向:newnode->next = current->next; current->next = newnode;
  4. 最后一个结点,修改指向:newnode->next = current->next; current->next = newnode;

//在pos位置之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

10、删除

  1. 在第一个结点处删除,修改指向:del= head; head = head->next;
  2. 在中间或者尾部删除,修改指向:del= current->next; current->next = del->next;
void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* nextnode = pos->next;
	pos->next = pos->next->next;
	free(nextnode);
	nextnode = NULL;
}

11、查找

  1. 一个一个结点查找值等于x的结点,找到返回该结点,反之返回NULL,修改指针:cur = cur->next;
SLTNode* SListFind(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

12、销毁

  1. 一个一个结点释放,修改指针:next = cur->next; free(cur); cur = next;
void SListDestory(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

13、获取链表节点个数

  1. 有一个结点,size+1,修改指针:cur = cur->next;
size_t SListSize(SLTNode* phead)
{
	size_t size = 0;
	SLTNode* cur = phead;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

4、单链表的注意事项

注意事项:

  1. 尽量不要用头指针进行操作,可以使用类型重命名,防止找不到头指针,丢失链表,找不到头指针是非常麻烦的一件事,每个操作都涉及头结点,比如头插头删,遍历,反转链表等,都需要头指针,这样做的目的是增强可读性
  2. 遍历链表时要检查链表尾部是否为空
  3. 插入和删除结点时,需要找到插入点或删除点之前的结点
  4. 插入结点时,需要注意malloc开辟
  5. 删除结点,需要用free释放开辟的内存,防止内存泄漏(经常忘记)

5、扩展

关于插入和删除的问题:

  1. 为什么不在pos位置之前去插入结点:单链表是单向的,找到一个结点的后继结点相对来说比较容易,反之选择去找一个结点的前继结点是比较麻烦。
  2. 为什么不删除pos位置的结点:跟第一个问题一样,删除pos位置的结点也是需要去找pos位置的前驱结点,让前驱结点指向pos位置的后去结点才能将pos位置的结点删除,也是比较麻烦的。

解决办法:后面的循环双链表可以解决此类问题

6、源码

以上是关于Day2:单链表的基本操作的主要内容,如果未能解决你的问题,请参考以下文章

数据结构关于单链表的一些操作的源代码

单链表的基本操作操作,类C语言

单链表

单链表的基本实现

数据结构 单链表的简单理解和基本操作

考研数据结构之单链代码