【数据结构与算法】---链表

作者:旧梦拾遗186

专栏:数据结构成长日记

【数据结构与算法】---链表_第1张图片

 

每日励志

在通往未来的路上,每个人都是孤独的旅行者。你的人生不会辜负你的。那些转错的弯,那些流下的泪水,那些滴下的汗水,全都让你成为独一无二的自己。

前言:

今天小编带大家学习数据结构中的链表。

目录

链表

1.1链表的概念及结构

1.2链表的分类  

1.3链表的实现

1.4完整代码: 

SList.h

SList.c 

test.c

 


链表

1.1链表的概念及结构

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

【数据结构与算法】---链表_第2张图片

现实中 数据结构中 (箭头实际上并不存在,这里只是形象化):

【数据结构与算法】---链表_第3张图片

1.2链表的分类  

实际中链表的结构非常多样,以下情况组合起来就有 8 种链表结构:
1. 单向或者双向
【数据结构与算法】---链表_第4张图片

2. 带头或者不带头

【数据结构与算法】---链表_第5张图片

3. 循环或者非循环

【数据结构与算法】---链表_第6张图片

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

【数据结构与算法】---链表_第7张图片

 

1. 无头单向非循环链表: 结构简单 ,一般不会单独用来存数据。实际中更多是作为 其他数据结 构的子结构 ,如哈希桶、图的邻接表等等。另外这种结构在 笔试面试 中出现很多。
2. 带头双向循环链表: 结构最复杂 ,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了

1.3链表的实现

 结点的声明:

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

头插的实现:

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

需要注意,即使存储头结点的指针是一个指针,但我们在函数内操作时,需要改变指针的指向位置,所以需要使用二级指针。我们可能对最后两条代码存在疑问,我在这里解释一下:

 【数据结构与算法】---链表_第8张图片

链表打印实现

//打印
void SListPrint(SLTNode* phead)
{
	//phead不需要断言。因为phead有可能有空,没有数据
	//而顺序表的结构体不可能为空,所以要进行断言
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");


}

创建新结点

SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("mail fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

【数据结构与算法】---链表_第9张图片

尾插的实现:

//尾插——有无结点。需要找前一个
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);

	//空,改变的是SListNode*,需要二级指针

	//非空。尾插要改变的是结构体SListNode,只需要结构体的指针

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

 可以看到,我们尾插的情况是有两个的,一个链表里面没有结点,那么就让这个要尾插的结点直接为头。而另一种则是链表中存在结点,我们需要遍历找到最后一个结点,并让这个结点的指针指向新的结点。同时需要注意,我们传的参数是二级指针,我们要解引用才能找到存放头结点地址的指针。

【数据结构与算法】---链表_第10张图片

头删的实现:

//头删
void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	/*if (*pphead == NULL)
	{
		return;
	}*/
	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

【数据结构与算法】---链表_第11张图片

尾删的实现:

//尾删
void SListpopback(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* tail = *pphead;
	SLTNode* prve = NULL;
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (tail->next != NULL)
		{
			prve = tail;
			tail = tail->next;
		}
		prve->next = NULL;
		free(tail);
		tail = NULL;
	}
}

数据的查找:

//查找
SLTNode* SListFind(SLTNode* pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

 

在 pos 位置插入数据:

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

在 pos 位置删除结点:


//删除pos位置,需要找前一个
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
			//检查pos不是链表中的结点
			assert(prev);
		}
		prev->next = pos->next;
		free(pos);
	}
}

 

1.4完整代码: 

SList.h

#define _CRT_SECURE_NO_WARNINGS

#pragma once
#include 
#include 
#include 

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

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

//创建新结点
SLTNode* BuySLTNode(SLTDataType x);

//销毁
void SListDestory(SLTNode** pphead);

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

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

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

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

//查找
SLTNode* SListFind(SLTNode* pphead,SLTDataType x);

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

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

//删除pos
void SListErase(SLTNode** pphead,SLTNode*pos);


//删除pos后面位置,了解
void SListEraseAfter(SLTNode* pos);

SList.c 

#define _CRT_SECURE_NO_WARNINGS

#include "SList.h"
//打印
void SListPrint(SLTNode* phead)
{
	//phead不需要断言。因为phead有可能有空,没有数据
	//而顺序表的结构体不可能为空,所以要进行断言
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");


}

//创建新结点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("mail fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//销毁
void SListDestory(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}


//尾插——有无结点。需要找前一个
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);

	//空,改变的是SListNode*,需要二级指针

	//非空。尾插要改变的是结构体SListNode,只需要结构体的指针

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

//尾删——有无结点。需要找前一个
void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* tail = *pphead;
	SLTNode* prev = NULL;
	assert(*pphead != NULL);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		prev->next = NULL;
		free(tail);
		tail = NULL;
		/*while (tail->next->next!=NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;*/

	}
}

//头删
void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	/*if (*pphead == NULL)
	{
		return;
	}*/
	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

//查找
SLTNode* SListFind(SLTNode* pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


//在pos之前插入,需要找前一个
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SListPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = BuySLTNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
			assert(prev);
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}


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


//删除pos位置,需要找前一个
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
			//检查pos不是链表中的结点
			assert(prev);
		}
		prev->next = pos->next;
		free(pos);
	}
}


//删除pos后面位置
void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLTNode* next = pos->next;
		pos->next = next->next;
		free(next);

	}
}

test.c

#define _CRT_SECURE_NO_WARNINGS

#include "SList.h"


//头插、头删
void TestSList1()
{
	SLTNode* plist = NULL;
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 4);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPrint(plist);
	SListPopFront(&plist);
	SListPrint(plist);
	SListPopFront(&plist);
	SListPrint(plist);
	SListPopFront(&plist);
	SListPrint(plist);
	SListPopFront(&plist);
	SListPrint(plist);

	SListDestory(&plist);

}

//尾插、尾删
void TestSList2()
{
	SLTNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);

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

	SListDestory(&plist);
}


//查找、在pos之前插入
void TestSList3()
{
	SLTNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);

	SLTNode* pos = SListFind(plist, 3);
	if (pos)
	{
		//修改
		pos->data *= 10;
		printf("找到了\n");
	}
	else
	{
		printf("找不到\n");
	}

	pos = SListFind(plist, 1);
	if (pos)
	{
		SListInsert(&plist, pos, 10);
	}


	SListPrint(plist);
	SListDestory(&plist);

}

//删除pos位置、删除pos后面的位置
void TestSList4()
{

	SLTNode* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SListPrint(plist);

	SLTNode* pos = SListFind(plist, 3);
	if (pos)
	{
		SListErase(&plist, pos);
	}
	SListPrint(plist);

	pos = SListFind(plist, 1);
	if (pos)
	{
		SListErase(&plist, pos);
	}
	SListPrint(plist);
}


int main()
{
	//TestSList1();
	//TestSList2();
	//TestSList3();
	TestSList4();
	return 0;
}

 

以上就是单链表的相关操作,我们不难发现,单链表的优势在于头插头删

而对于一些操作:如尾插尾删而言,我们都需要去找前一个位置,这是比较麻烦的。单链表我们就先介绍到这里了。

这里同时也有一个问题存在:

删除当前位置我们需要去找前一个位置,这效率是比较低的,我们如何改进这个问题❓

找pos位置删除,而就是不找前一个位置(即要求是O(1)):替换法删除,把pos的值和下一个节点的值进行交换,再把pos进行释放掉。但是有一个缺陷:pos不能是尾节点。尾节点没有下一项。

那如果在pos位置之前插入,要求为O(1)呢:

直接插入到pos后面,然后进行交换。
 

 

你可能感兴趣的:(数据结构成长日记,链表,数据结构,c语言,编辑器,职场和发展)