【数据结构】链表

目录

前言

概念

链表的分类

        1.单向或者双向

        2.带头或者不带头(哨兵位)

        3.循环或者非循环

实际中最常用的两种结构分别是

无头单向非循环链表(代码实现)

 常用接口预览

接口具体代码实现

测试

带头双向链表循环(代码实现)

常用接口预览

接口具体代码实现


前言

        在顺序表的实现中,我们发现顺序表的存在以下缺点:

                1.中间头部插入删除数据,需要挪动数据,效率低下O(N)
                2.空间不够,扩容。扩容需要申请新空间,拷贝数据,释放旧空间,会有一定的消耗,其次还可能会有一定的空间浪费。

        而链表就很好的弥补了这些缺点。

概念

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

链表的分类

        1.单向或者双向

【数据结构】链表_第1张图片

        2.带头或者不带头(哨兵位)

【数据结构】链表_第2张图片

        3.循环或者非循环

【数据结构】链表_第3张图片

 上述情况组合起来就有8种链表结构

【数据结构】链表_第4张图片

 上图所示带头的链表结构组合有4种,不带头的同样有4种;

实际中最常用的两种结构分别是

【数据结构】链表_第5张图片

1.无头单向非循环链表:结构简单,一般不会单独用来存储数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等。

2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

无头单向非循环链表(代码实现)

 常用接口预览

#include
#include
#include 

typedef int SLTDataType;
//定义链表结点结构
typedef struct SListNode
{
	SLTDataType data;
	//c语言中 struct SListNode 才能表示类型
	struct SListNode* next;
}SLTNode;

//遍历链表输出
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找 --- 修改与其一致
SLTNode* SListFind(SLTNode* pphead, SLTDataType x);
//pos位置删除
void SListErase(SLTNode** pphead, SLTNode* pos);
//pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x);
//pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//pos位置后面删除
void SLTEraseAfter(SLTNode* pos);

接口具体代码实现

1.定义链表结点结构

//定义链表结点存储的数据的数据类型
typedef int SLTDataType;
//定义链表结点结构
typedef struct SListNode
{
	SLTDataType data;
	//c语言中 struct SListNode 才能表示类型
	struct SListNode* next;
}SLTNode;

2.创建新结点

//创建新结点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //动态申请空间
	if (newnode == NULL) //判断空间是否申请失败
	{
		perror("malloc fail");
		return NULL;
	}
	//初始化新结点
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

3..遍历链表,输出链表元素。

//遍历链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead; //令cur指向链表的头结点
	while (cur!=NULL)   //当cur等于NULL时,代表链表已经遍历完成,结束循环
	{
		printf("%d->", cur->data);  //输出cur指向的结点 的数据
		cur = cur->next;  //next存储的是cur的下一结点
	}
	printf("NULL\n");
}

4.尾插

void SLTPushBack(SLTNode** pphead, SLTDataType x) 
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else {
		//找尾
		//尾插的本质:原尾结点中要存储新的尾结点的地址
		SLTNode* tail = *pphead;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

【数据结构】链表_第6张图片

 5.尾删

void SLTPopBack(SLTNode** pphead)
{
	//处理链表本身就为空,链表为空便不再进行尾删
	assert(pphead);
	assert(*pphead);
	//处理只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
        //查找尾结点的前一个结点
		while (tail->next->next != NULL) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}

}

【数据结构】链表_第7张图片

6.头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    //新建一个结点
	SLTNode* newnode = BuySLTNode(x);
    //让新结点的next指向原链表的头结点
	newnode->next = *pphead;
    //让原本指向头结点的指针指向新节点,新节点变成链表的头结点
	*pphead = newnode;
}

【数据结构】链表_第8张图片

7.头删 

void SLTPopFront(SLTNode** pphead)
{
	//处理链表本身就为空
	assert(pphead);
	assert(*pphead);
    //存储原链表的头节点,方便后续释放空间
	SLTNode* cur = *pphead;    
    //让原本指向链表头节点的指针,指向头节点的下一结点
	*pphead = (*pphead)->next;
	free(cur);
}

【数据结构】链表_第9张图片

8.查找i链表中的某个元素

SLTNode* SListFind(SLTNode* pphead, SLTDataType x) {
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
    //这里处理未找到的情况
	return NULL;
}

9.在pos位置之前插入

void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x)
{
	assert(pos);
	assert(pphead);
    //当pos为头节点的位置时,使用头插即可
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else {
		SLTNode* prev = *pphead;
        //遍历查找pos的前一结点
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}  
}

10. pos位置后面插入

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

11.删除pos位置的结点

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	assert(pphead);
    //删除的结点是头结点时,使用头删即可
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		//外面置空pos指针
	}
}

12.删除pos位置后面结点

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos); 
	assert(pos->next);
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

测试

void TestSList1()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	SLTPushFront(&plist, 0);
	SLTNode* ret=SListFind(plist, 2);
	
	SLTInsert(&plist, ret, 10);
	SLTPrint(plist);

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

带头双向链表循环(代码实现)

常用接口预览

#include
#include
#include
typedef int LTDataType;

//双向链表结点定义
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;
//创建一个新的结点
LTNode* BuyListNode(LTDataType x);
//初始化
LTNode* LTInit();
void LTDestory(LTNode* phead);
//尾插尾删
void LTPushback(LTNode* phead, LTDataType x);
void LTPopback(LTNode* phead);

//头插头删
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);
//在pos位置 之前插入一个值
void LTInsert(LTNode* pos, LTDataType x);
//查找某个结点
LTNode* LTFind(LTNode* phead, LTDataType x);
//删除某个结点
void LTErase(LTNode* pos);
//判断链表是否为空
bool LTEmpty(LTNode* phead);
//输出链表
void LTPrint(LTNode* phead);

接口具体代码实现

1.创建一个新节点

LTNode* BuyListNode(LTDataType x) 
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL) {
		perror("malloc fail");
		return NULL;
	}
    //初始化新结点
	node->next = NULL;
	node->prev = NULL;
	node->data = x;
	return node;
}

2.初始化链表

LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);
    //默认无结点,只有一个哨兵位,prev指向它的前一结点,next指向他的后一结点
    //这里因为链表中没有结点,让它都指向自己
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

【数据结构】链表_第10张图片

 3.链表销毁

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		LTErase(cur);
		cur = next;
	}
	free(phead);
	//phead置空因为这里传的是一级指针,改变不了实参,由调用方自行置空
}

4.在指定结点前插入一个值-即新结点

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

【数据结构】链表_第11张图片

5.删除指定位置结点

void LTErase(LTNode* pos)
{
	assert(pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
}

【数据结构】链表_第12张图片

6.尾插

void LTPushback(LTNode* phead, LTDataType x)
{
	assert(phead);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;*/
	//这里使用代码复用更加方便
	LTInsert(phead, x);
}

7.尾删

void LTPopback(LTNode* phead)
{
	/*assert(phead);
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;*/
	//这里使用代码复用更加方便
	LTErase(phead->prev);
	phead->prev = NULL;
}

8.头插

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	/*LTNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;*/
	//这里使用代码复用更加方便
	LTInsert(phead->next, x);

}

 9.头删

void LTPopFront(LTNode* phead) {
	/*LTNode* del = phead->next;
	phead->next = del->next;
	del->prev = phead;
	free(del);*/
	//这里使用代码复用更加方便
	LTErase(phead->next);
	phead->next = NULL;
}

10.打印链表

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("<=>head<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

11.查找链表中的某个值

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

你可能感兴趣的:(数据结构,数据结构,链表)