IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 数据结构(2)——单链表的实现 -> 正文阅读

[数据结构与算法]数据结构(2)——单链表的实现

在“数据结构(1)”中,我在博客中实现了顺序表的定义功能和基本功能函数。那么这篇博客我想以相同的思路来实现单链表。

单链表也是一种数据存储结构,与顺序表性比,单链表存储数据所用的空间不是连续的,在内存中是一块块零散的空间,通过指针相连。
因此,在单链表的结构体中,有两个成员:

typedef int DataType;
struct SListNode
{
	DataType data;//用于存储数据
	struct SListNode* next;//用于指向下一块空间
};

示意图:

在这里插入图片描述当然,这是是最为简单的一种链表形式——单向无头单链表
链表一共有三个变量因素:

1. 是否带哨兵位头指针(哨兵位的数据成员data不存放实际数据内容)
2. 单向/双向
3. 循环/非循环

但是实际中,用的最多的是 单向无头非循环链表 和 双向带头循环链表。
双向带头循环链表结构最为复杂,但用起来最为方便。结构图为:
在这里插入图片描述
今天我们先来实现单向无头非循环链表。在下一篇博客中,再来实现双向带头循环链表。

老规矩,这次还是分为三个文件来写:

  1. Slist.h
  2. Slust.c
  3. Test.c

一、Slist.h
实现单链表结点的结构体:

#pragma once
typedef int DataType;
struct SListNode
{
	DataType data;
	struct SListNode* next;
};

我们要实现的功能函数:

//1.开辟一个新结点
//这是内部函数使用的,不属于对外开放的接口函数
struct SListNode* BuynewNode(DataType x);

//2.打印
void SListPrint(struct SListNode* plist);

//3.尾插
void SListPushBack(struct SListNode** pplist,DataType x);

//4.头插
void SListPushFront(struct SListNode** pplist, DataType x);

//5.尾删
void SListPopBack(struct SListNode** pplist);

//6.头删
void SListPopFront(struct SListNode** pplist);

//7.查找
struct SListNode* SListFind(struct SListNode* plist, DataType x);

//8.指定位置的后面插入
void SListInsertAfter(struct SListNode** pplist, DataType x);

//9.指定位置前面插入
void SListInsertBefore(struct SListNode** pplist, struct SListNode* pos, DataType x);

//10.删去指定位置的后一个结点
void SListEreaseAfter(struct SListNode* pos); 

二、Slist.c

1.开辟一个新结点

struct SListNode* BuynewNode(DataType x)
{
	struct SListNode* node = (struct SListNode*)malloc(sizeof(struct SListNode));
	node->data = x;
	node->next = NULL;

	return node;
}

2.打印

void SListPrint(struct SListNode* plist)
{
	struct SListNode* cur = plist;
	while (cur != NULL)
	{
		printf("%d->",cur->data);
		cur = cur->next;
	}
	printf("NULL");
	printf("\n");
}

3.尾插

void SListPushBack(struct SListNode** pplist, DataType x)
{
	//第一步:创建一个新结点,malloc开辟
	struct SListNode* newNode = BuynewNode(x);

	//第二步:先判断给的链表是非为NULL   
	//若为空,则直接将新结点赋给plist;若为非空,则先遍历找出尾再串接
	if (*pplist == NULL)
	{
		*pplist = newNode;
	}
	else
	{
		struct SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

4.头插

示意图:
在这里插入图片描述

void SListPushFront(struct SListNode** pplist, DataType x)
{
	//头插比尾插要方便很多,因为省去了找尾的过程
	struct SListNode* newNode = BuynewNode(x);
	newNode->next = *pplist;
	
     //这一步很关键*pplist一直都是头,所以头插后,最后一步要把新结点赋给*pplist
	*pplist = newNode;
}

5.尾删

示意图:
在这里插入图片描述

//尾删时,先要把原链表的最后一个数据空间释放掉,再让最后一个的前一个的next指针指向NULL
void SListPopBack(struct SListNode* plist)
{
	struct SListNode* prev = NULL;
	struct SListNode* tail = plist;
	while (tail->next != NULL)
	{
		prev = tail;
		tail = tail = tail->next;
	}

	free(tail);
	tail = NULL;

	prev->next = NULL;

}

但是会发现上面的这种尾删的写法对于 链表为空链表中只有一个结点 这两种情况都不成立。

  1. 当链表为空时,plist为空指针,程序中将plist赋给tail然后使用tail->next会导致程序崩溃;
  2. 当链表中只有一个节点时,第一次while循环的判断条件就不满足,直接跳过while循环,因此prev的值没有改变,始终为NULL,那最后prev->next就会导致程序崩溃。

因此我们要将这两种情况单独列出来讨论:

void SListPopBack(struct SListNode** pplist)
{
	if (*pplist == NULL)
	{
		return;
	}
	else if ((*pplist)->next == NULL)
	{
		free(*pplist);
		*pplist = NULL;
	}

	else
	{
		struct SListNode* prev = NULL;
		struct SListNode* tail = pplist;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;

		prev->next = NULL;

	}
}

6.头删

void SListPopFront(struct SListNode** pplist)
{
    //链表为空的情况分开讨论
	if (*pplist == NULL)
	{
		return;
	}
	//这里对链表中有一个结点和多个结点的情况都适用
	else
	{
		//在删除*pplist前,必须先保存一份*pplist->next
		struct SListNode* next = (*pplist)->next;
		free(*pplist);
		*pplist = next;
	}
}

7.查找

struct SListNode* SListFind(struct SListNode* plist, DataType x)
{
	struct SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}

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

8.指定位置的后面插入

示意图:
在这里插入图片描述
注意:step1和step2不能调换。若先让*ppos的next指向新开辟结点,那最后一步的pos->next就找不到原链表中的下一个结点了

void SListInsertAfter(struct SListNode** ppos, DataType x)
{
	assert(*ppos);
	struct SListNode* newNode = BuynewNode(x);

	newNode->next = (*ppos)->next;//相当于先保存原链表的next
	(*ppos)->next = newNode;
}

9.指定位置前面插入

指定位置前插比后插少麻烦一些,因为有一个遍历找*ppos的过程。

void SListInsertBefore(struct SListNode** pplist, struct SListNode* pos, DataType x)
{
	assert(pos);
	struct SListNode* newNode = BuynewNode(x);

    //只有一个结点时,等价于头插
	if (pos == *pplist)
	{
		newNode->next = *pplist;
		*pplist = newNode;
	}
	else
	{
		struct SListNode* prev = NULL;
		struct SListNode* cur = *pplist;
		while (cur != pos)
		{
			prev = cur;
			cur = cur->next;
		}

		prev->next = newNode;
		newNode->next = cur;
	}
}

10.指定位置后面删除

思路图:
在这里插入图片描述
但是如果pos的下一个结点,即tmpNode为空,那程序中的tmpNode->next又会导致程序崩溃。因此要将这种情况单独讨论。

void SListEreaseAfter(struct SListNode* pos)
{
	assert(pos);

	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		struct SListNode* tmpNode = pos->next;
		pos->next = tmpNode->next;
		free(tmpNode);
	}
}

3.Test.c

在这个文件中,我们可以根据自己需要编写代码来调用这个单链表:

void TestSList1()
{
	struct SListNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	

	SListPushFront(&plist, 0);
	SListPushFront(&plist, -1);
	SListPushFront(&plist, -2);
	SListPushFront(&plist, -3);
	SListPrint(plist);

	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPrint(plist);
}
void TestSList2()
{
	struct SListNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);
	
	printf("现在,我想寻找值为3的结点:\n");
	struct SListNode* tmp=SListFind(plist, 3);
	if (tmp == NULL)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了,现修改其值:");
		tmp->data = 30;
		SListPrint(plist);
	}

	printf("在位置后面插入值:");
	SListEreaseAfter(tmp);
	SListPrint(plist);

	printf("在位置前面插入值:");
	SListInsertBefore(&plist, tmp, 100);
	SListPrint(plist);
}

在这里插入图片描述

int main()
{
	TestSList1();
	TestSList2();
	return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-31 15:42:15  更:2021-08-31 15:44:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:49:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码