前言:内容包括:链表的分类,无头单向非循环链表的增删查改的实现,带头双向循环链表的增删查改的实现
目录
链表的分类
1. 单向或者双向
编辑
2. 带头或者不带头
3. 循环或者非循环
无头单向非循环链表:
编辑 带头双向循环链表:
链表的实现
1、无头+单向+非循环链表增删查改实现
单链表结构:
动态申请一个节点:
单链表打印:
单链表销毁:
单链表尾插:
单链表的头插:
单链表的尾删:
单链表头删 :
单链表查找:
在某个节点之前插入:
在某个节点之后插入:
删除pos位置的值:
删除pos之后的值:
2、带头+双向+循环链表增删查改实现
双向链表结构:
动态申请一个节点:
双向链表初始化(创建哨兵位的头结点):
双向链表销毁:
双向链表打印 :
双向链表尾插 :
双向链表头插:
双向链表尾删 :
双向链表头删 :
双向链表查找 :
双向链表在pos的前面进行插入:
双向链表删除pos位置的结点 :
链表的分类
组合起来共有8种结构:
1. 单向或者双向
双向 :
 
2. 带头或者不带头
不带头:

带头(哨兵位的头结点):
 哨兵位的头结点不存储有效数据,站岗功能
3. 循环或者非循环
非循环:

循环:
 虽然链表的组合有8种结构,但是最常用还是一下两种结构:
虽然链表的组合有8种结构,但是最常用还是一下两种结构:
无头单向非循环链表:
一般不会单独用来存数据,更多是作为其他数据结构的子结构,如哈希桶、图的邻接表
 带头双向循环链表:
 带头双向循环链表:
 
一般用来单独存数据

链表的实现
1、无头+单向+非循环链表增删查改实现
在main函数种,plist指针作为头指针,负责在单链表创建后,指向单链表的第一个节点
SLTNode* plist = NULL;单链表结构:
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;动态申请一个节点:
SLTNode* BuySLTNode(SLTDataType x);malloc一块空间并存入数据后,此函数将会返回这块空间的地址
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");//若malloc开辟失败,打印错误信息
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}单链表打印:
void SLTPrint(SLTNode* phead);void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}单链表销毁:
void SLTDestroy(SLTNode** pphead);当链表的每个节点销毁完成后,指向第一个节点的指针plist需要置空,所以传入plist指针的地址(二级指针接收),plist必须置空,否则成为野指针
void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}在释放一个节点之前,需要找到下一个节点,所以先用next指针保存下一个节点的地址,然后才能放心销毁当前节点
单链表尾插:
void SLTPushBack(SLTNode** pphead, SLTDataType x);尾插分为两种情况:
空链表尾插 和 非空链表尾插
空链表尾插:需要改变头指针plist,使得plist指向插入的第一个节点
所以存在对plist指针进行修改的情况,故而需要传入plist的指针,用二级指针pphead来接收
非空链表尾插: 遍历找到尾节点进行链接
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//pphead是头指针plist的地址一定不为空,用assert进行断言,若是pphead为空会报错
	SLTNode* newnode = BuySLNode(x);
	if (*pphead == NULL)//空链表尾插
	{
		*pphead = newnode;//plist需要指向第一个节点
	}
	else//非空链表尾插
	{
		SLTNode* tail = *pphead;
		while (tail->next)//找到尾节点,尾节点的next就是NULL
		{
			tail = tail->next;
		}
		tail->next = newnode;//链接
	}
}关于assert断言:
pphead一定不能为空,所以需要断言,当pphead为空时,报错
*pphead可以为空,这表明链表为空,空链表是可以插入数据的,打个比方:
银行卡里没钱了,存钱是可以的
单链表的头插:
void SLTPushFront(SLTNode** pphead, SLTDataType x);头插也分两种:
空链表头插:需要对plist指针进行修改,使得它指向插入的第一个节点
非空链表头插:1 新节点链接头结点 2 plist指针指向新节点(新节点成为新的头结点)
但是这两种情况下的头插方法都是通用的,空链表头插代码可以与非空链表头插共用一份代码
原因:空链表时,plist==NULL,即*pphead==NULL

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}单链表的尾删:
void SLTPopBack(SLTNode** pphead);尾删分两种情况:
链表仅有1个节点:删除此节点后,plist指针需要置空
链表不只1个节点:找到尾节点的前一个结点,当尾结点删除后,它将成为新的尾结点,则其成员next指针需要置空
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//链表为空不能删
	if ((*pphead)->next == NULL)//链表仅有一个结点,它的next就是NULL
	{
		free(*pphead);
		*pphead = NULL;//plist指针置空
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next)//找到尾结点的前一个结点,它将会是新的tail,则tail->next就是原来的尾结点,原来的尾结点的next指针就是NULL
		{
			tail = tail->next;
		}
		free(tail->next);//删除尾结点
		tail->next = NULL;//新的尾结点的next要置空
	}
}单链表头删 :
void SLTPopFront(SLTNode** pphead);头删也分两种情况:
链表仅有1个节点:删除此节点后,plist指针要置空
链表不止1个节点:1 保存要删除的节点地址 2 头指针plist指向新的头结点 3 删除节点
但是这两种情况也共用一份代码
原因:当链表仅有1个节点,它没有下一个节点,所以它的next指针指向了NULL,
当删除这个节点后,plist会置成NULL
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//链表为空不能删除
	SLTNode* del = *pphead;//保存要删除的节点地址
	*pphead = (*pphead)->next;//plist指向新的头结点
	free(del);
}单链表查找:
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
在某个节点之前插入:
1 使用单链表查找函数找到某个节点的地址pos
2 在pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);使用二级指针pphead:若是pos是第一个结点,则在pos之前插入就是头插逻辑,会对plist指针进行修改,所以需要传plist的地址
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)//pos是头结点
	{
		SLTPushFront(pphead,x);//在头结点之前插入即头插
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找到pos节点的前一个结点prev
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLNode(x);
		prev->next = newnode;//链接
		newnode->next = pos;
	}
}在某个节点之后插入:
void SLTInsertAfter(SLTNode* pos, SLTDataType x);不需要传入plist,因为在pos之后插入,可以轻而易举得到pos之后所有结点的地址
传入plist,可以认为需要找到某一个结点的前一个结点才需要plist,因为通过遍历才可以找到某个结点的前一个结点
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}删除pos位置的值:
void SLTErase(SLTNode** pphead, SLTNode* pos)存在删除的pos位置是头结点的情况,需要对plist进行修改,所以需要二级指针
无需assert(*pphead)
因为assert(pos)已经保证了pos位置的有效,也保证链表不为空,若是pos位置有效(即不为NULL),则说明链表不会为空
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)//头删
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找到pos的前一个结点
		{
			prev = prev->next;
		}
		prev->next = pos->next;//前一个结点的next指向pos的下一个结点
		free(pos);
	}
}删除pos之后的值:
void SLTEraseAfter(SLTNode* pos);void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//不能对NULL进行删除操作,若是pos是尾结点的地址,则pos->next为NULL
	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}2、带头+双向+循环链表增删查改实现
双向链表结构:
typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;动态申请一个节点:
LTNode* BuyLTNode(LTDataType x)LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}
双向链表初始化(创建哨兵位的头结点):
LTNode* LTInit();开辟一个结点,不存储任何有效数据,作为哨兵位的头结点
当链表为空时,这个哨兵位的头结点需要自己构成一个循环,即自己的next指向自己,自己的prev指向自己
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}双向链表销毁:
void LTDestroy(LTNode* phead);释放完所有的有效节点后才能释放哨兵位的头结点
void LTDestroy(LTNode* phead)
{
	assert(phead);//phead是指向哨兵位的头结点的指针,一定不能为空
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;//记录下一个要释放节点的地址
		free(cur);
		cur = next;
	}
	free(phead);//释放哨兵位的头结点
}双向链表打印 :
void LTPrint(LTNode* phead);void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("guard<==>");
	while (cur != phead)
	{
		printf("%d<==>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}双向链表尾插 :
void LTPushBack(LTNode* phead, LTDataType x);void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);
	
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
	//或者
	//LTInsert(phead,x);
}1 尾节点的next指向newnode
2 newnode的prev指向尾节点
3 newnode的next指向哨兵位的头结点
4 哨兵位头结点的prev指向newnode
双向链表头插:
void LTPushFront(LTNode* phead, LTDataType x);void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* first = phead->next;//哨兵位的头结点后的真正的头结点
	LTNode* newnode = BuyLTNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
	//或者
	//LTInsert(phead->next, x);
}双向链表尾删 :
双向链表判空:链表若为空,则仅有一个哨兵位的头结点,它既是头,也是尾
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}void LTPopBack(LTNode* phead)void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));//链表为空不能删
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;//找到尾节点的上一个节点,它将成为新的尾节点
	free(tail);
	tailprev->next = phead;//新的尾节点要连接哨兵位的头结点
	phead->prev = tailprev;
	//或者
	//LTErase(phead->prev);
}
双向链表头删 :
 
  void LTPopFront(LTNode* phead)void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));//链表为空不能删
	LTNode* first = phead->next;
	LTNode* second = first->next;//真正头结点的下一个节点second会成为新的真正头结点,链接哨兵位的头结点
	
	phead->next = second;
	second->prev = phead;
	free(first);
	//或者
	//LTErase(phead->next);
}
双向链表查找 :
 
  LTNode* LTFind(LTNode* phead, LTDataType x)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;
}双向链表在pos的前面进行插入:
pos是某个节点的地址,可以通过查找函数得到
void LTInsert(LTNode* pos, LTDataType x)void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyLTNode(x);
	LTNode* prev = pos->prev;//找到pos的前一个结点
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}双向链表删除pos位置的结点 :
void LTErase(LTNode* pos)void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;//找到pos的前一个结点
	LTNode* posNext = pos->next;//找到pos的后一个结点
	posPrev->next = posNext;//两个链接
	posNext->prev = posPrev;
	free(pos);
}




















