带头结点双向循环 -- 双向链表

目录

一、双向链表初始化

二、尾插

问题1:什么时候传一级指针,什么时候传二级指针呢?

三、打印

四、尾删

五、头插

六、头删

七、pos之前插入

 八、删除pos位置的节点

九、销毁链表

 总代码:

List.h

List.c

 test.c


 

一、双向链表初始化

图解:phead链表的最后一个节点要指向第一个节点,第一个节点要指向最后一个节点,而第一个节点和最后一个节点都是第一个节点,所以phead->next = phead->prev = phead;

带头结点双向循环 -- 双向链表_第1张图片

代码:

LTNode* InitList()
{
	//带头节点,循环链表
	//哨兵位头结点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->next = phead->prev = phead;
	return phead;
}

二、尾插

图解:

当有多个节点:

带头结点双向循环 -- 双向链表_第2张图片

 当只有头结点:

带头结点双向循环 -- 双向链表_第3张图片

代码:

//创建新节点
LTNode* BuyNode(int x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		printf("newnode failed\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = NULL;
	return newnode;
}
//尾插
void ListPushBack(LTNode* phead,int x)
{
	assert(phead);
	LTNode* newnode = BuyNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

问题1:什么时候传一级指针,什么时候传二级指针呢?

1.比如:(传一级指针)

带头结点双向循环 -- 双向链表_第4张图片

 带头结点双向循环 -- 双向链表_第5张图片

 此时传的是一级指针,因为该链表是带有头结点的链表,不会改变头指针所指向的方向,传过去的plist和phead所指向的地址是一样的,就相当于phead = plist;

2.比如:(传二级指针)

带头结点双向循环 -- 双向链表_第6张图片

 带头结点双向循环 -- 双向链表_第7张图片 

 此时传的是二级指针,因为需要改变头指针的指向,传过去的&s1和接收的SL** phead的地址是一样的,就相当于SL**  phead = &s1;

总结:大部分带头节点就不会改变头指针的方向,就不需要传二级指针

三、打印

图解:

带头结点双向循环 -- 双向链表_第8张图片

 代码:

//打印
void ListPrint(LTNode* phead)
{
	assert(phead);//如果phead没有初始化,则会为NULL,而带头结点的指针不会为NULL,所以在此断言
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d",cur->data);
		cur = cur->next;
	}
	printf("\n");
}

四、尾删

图解:

带头结点双向循环 -- 双向链表_第9张图片

 代码:

//尾删
void ListPopBack(LTNode* phead)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* tailprev = tail->prev;
	暴力判定:头指针的下一个结点还是头结点,那么说明只有头结点
	assert(tail != phead);
	
	tailprev->next = phead;
	phead->prev = tailprev;

	//温柔的判定:链表为空,也就是只剩头结点时,就不会再删除的情况
	//if (tail != phead)
	//{
	//	free(tail);
	//}
	//tail = NULL;
}

五、头插

图解:

带头结点双向循环 -- 双向链表_第10张图片

 代码:

//头插
void ListPushFront(LTNode* phead, int x)
{
	assert(phead);
	LTNode* Next = phead->next;
	LTNode* newnode = BuyNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = Next;
	Next->prev = newnode;
}

六、头删

图解:

带头结点双向循环 -- 双向链表_第11张图片

 代码:

//头删
void ListPopFront(LTNode* phead)
{
	assert(phead);
	LTNode* tail = phead->next;
	LTNode* tailnext = tail->next;
	
	//暴力判断:说明只有一个头结点
	assert(phead->next != phead);
	
	phead->next = tailnext;
	tailnext->prev = phead;
	free(tail);
	tail = NULL;
}

七、pos之前插入

图解:
带头结点双向循环 -- 双向链表_第12张图片

 代码:

//pos位置之前插入
void ListInsert(LTNode* pos, DataType x)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* newnode = BuyNode(x);

	posprev->next = newnode;
	newnode->prev = posprev;

	newnode->next = pos;
	pos->prev = newnode;
}

该函数的好处就是可以替代头插与尾插:

图解:

带头结点双向循环 -- 双向链表_第13张图片

//尾插
void ListPushBack(LTNode* phead, DataType x)
{
	assert(phead);
	//LTNode* newnode = BuyNode(x);
	//LTNode* tail = phead->prev;
	//tail->next = newnode;
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;
	
	//改写:
	ListInsert(phead,x);
}
//头插
void ListPushFront(LTNode* phead, DataType x)
{
	assert(phead);
	/*LTNode* Next = phead->next;
	LTNode* newnode = BuyNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = Next;
	Next->prev = newnode;*/

	//改写:
	ListInsert(phead->next,x);
}

效果图:

带头结点双向循环 -- 双向链表_第14张图片

 八、删除pos位置的节点

 图解:带头结点双向循环 -- 双向链表_第15张图片

代码:

//删除pos位置结点
void ListErase(LTNode* pos)
{
	assert(pos);
	//因为phead结点不存数据,所以pos不会指向phead,所以不用判断
	
	LTNode* posnext = pos->next;
	LTNode* posprev = pos->prev;
	
	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
	pos = NULL;
}

 该函数的好处就是可以替代头删和尾删:

//头删
void ListPopFront(LTNode* phead)
{
	assert(phead);
	//LTNode* tail = phead->next;
	//LTNode* tailnext = tail->next;
	//
	暴力判断:说明只有一个头结点
	//assert(phead->next != phead);
	//
	//phead->next = tailnext;
	//tailnext->prev = phead;
	//free(tail);
	//tail = NULL;

	//改写
	ListErase(phead->next);
}
//尾删
void ListPopBack(LTNode* phead)
{
	assert(phead);
	//暴力判定:头指针的下一个结点还是头结点,那么说明只有头结点
	assert(phead->prev != phead);
	//
	//LTNode* tail = phead->prev;
	//LTNode* tailprev = tail->prev;

	//tailprev->next = phead;
	//phead->prev = tailprev;

	//温柔判定:链表为空,也就是只剩头结点时,就不会再删除的情况
	//if (tail != phead)
	//{
	//	free(tail);
	//}
	//tail = NULL;

	//改写:
	ListErase(phead->prev);
}

 效果图:带头结点双向循环 -- 双向链表_第16张图片

九、销毁链表

//销毁链表
void ListDestroy(LTNode** phead)
{
	LTNode* cur = (*phead)->next;
	while (cur != (*phead))
	{
		LTNode* Next = cur->next;
		free(cur);
		cur = Next;
	}
	//头结点也是malloc出来的,也要释放
	free(*phead);
	*phead = NULL;
	//方法一:如果传一级,那么phead的置空并不会影响外面的plist,头结点被释放掉了,但是没有置空,所以在外面置空也可以
	//方法二:如果传二级,这时候*phead置空,才会影响到plist,因为这是传址,改变里面的内容

}

 总代码:

List.h

#pragma once
#include
#include
#include
#include
typedef int DataType;
typedef struct ListNode
{
	DataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

//这次不传二级指针,用返回值
LTNode* InitList();
//尾插
void ListPushBack(LTNode* phead, DataType x);
//尾删
void ListPopBack(LTNode* phead);
//头插
void ListPushFront(LTNode* phead, DataType x);
//头删
void ListPopFront(LTNode* phead);
//查找
LTNode* ListFind(LTNode* phead, DataType x);
//pos位置之前插入
void ListInsert(LTNode* pos, DataType x);
//删除pos位置结点
void ListErase(LTNode* pos);
//销毁链表
void ListDestroy(LTNode** phead);

List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
//增加节点
LTNode* BuyNode(DataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		printf("newnode failed\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = newnode->prev = NULL;
	return newnode;
}
//初始化
LTNode* InitList()
{
	//带头节点,循环链表
	//哨兵位头结点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	phead->next = phead->prev = phead;
	return phead;
}
//尾插
void ListPushBack(LTNode* phead, DataType x)
{
	assert(phead);
	//LTNode* newnode = BuyNode(x);
	//LTNode* tail = phead->prev;
	//tail->next = newnode;
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;
	
	//改写:
	ListInsert(phead,x);
}
//打印
void ListPrint(LTNode* phead)
{
	assert(phead);//如果phead没有初始化,则会为NULL,而带头结点的指针不会为NULL,所以在此断言
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ",cur->data);
		cur = cur->next;
	}
	printf("\n");
}
//尾删
void ListPopBack(LTNode* phead)
{
	assert(phead);
	//暴力判定:头指针的下一个结点还是头结点,那么说明只有头结点
	assert(phead->prev != phead);
	//
	//LTNode* tail = phead->prev;
	//LTNode* tailprev = tail->prev;

	//tailprev->next = phead;
	//phead->prev = tailprev;

	温柔判定:链表为空,也就是只剩头结点时,就不会再删除的情况
	if (tail != phead)
	{
		free(tail);
	}
	tail = NULL;

	//改写:
	ListErase(phead->prev);
}
//头插
void ListPushFront(LTNode* phead, DataType x)
{
	assert(phead);
	/*LTNode* Next = phead->next;
	LTNode* newnode = BuyNode(x);
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = Next;
	Next->prev = newnode;*/

	//改写:
	ListInsert(phead->next,x);
}
//头删
void ListPopFront(LTNode* phead)
{
	assert(phead);
	//LTNode* tail = phead->next;
	//LTNode* tailnext = tail->next;
	//
	暴力判断:说明只有一个头结点
	//assert(phead->next != phead);
	//
	//phead->next = tailnext;
	//tailnext->prev = phead;
	//free(tail);
	//tail = NULL;

	//改写
	ListErase(phead->next);
}
//查找
LTNode* ListFind(LTNode* phead, DataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//pos位置之前插入
void ListInsert(LTNode* pos, DataType x)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* newnode = BuyNode(x);

	posprev->next = newnode;
	newnode->prev = posprev;

	newnode->next = pos;
	pos->prev = newnode;
}
//删除pos位置结点
void ListErase(LTNode* pos)
{
	assert(pos);
	//因为phead结点不存数据,所以pos不会指向phead,所以不用判断
	
	LTNode* posnext = pos->next;
	LTNode* posprev = pos->prev;
	
	posprev->next = posnext;
	posnext->prev = posprev;
	free(pos);
	pos = NULL;
}
//销毁链表
void ListDestroy(LTNode** phead)
{
	LTNode* cur = (*phead)->next;
	while (cur != (*phead))
	{
		LTNode* Next = cur->next;
		free(cur);
		cur = Next;
	}
	//头结点也是malloc出来的,也要释放
	free(*phead);
	*phead = NULL;
	//方法一:如果传一级,那么phead的置空并不会影响外面的plist,头结点被释放掉了,但是没有置空,所以在外面置空也可以
	//方法二:如果传二级,这时候*phead置空,才会影响到plist,因为这是传址,改变里面的内容

}

 test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"

testList1()
{
	LTNode* plist = InitList();
	ListPushBack(plist,1);//因为有头结点,所以不用改变头指针,总结:不改变头指针传一级,改变传二级
	ListPushBack(plist,2);
	ListPushBack(plist,3);
	ListPushBack(plist,4);
	ListPushBack(plist,5);

	ListPrint(plist);
	
	ListPopBack(plist);
	//ListPrint(plist);
	
	ListPopBack(plist);
	ListPrint(plist);
	
	//ListPopBack(plist);
	//ListPushFront(plist,4);
	//ListPrint(plist);

	ListPopFront(plist);
	//ListPrint(plist);
	ListPopFront(plist);
	ListPrint(plist);

	//ListPopFront(plist);
	//ListPopFront(plist);
	//ListPopFront(plist);
	//LTNode* pos = ListFind(plist,3);
	//if (pos != NULL)
	//{
	//	/*printf("%d\n",pos->data);*/
	//	ListInsert(pos, 20);
	//	ListErase(pos);
	//}
	//ListPrint(plist);

	ListDestroy(&plist);
	//传一级指针的话,可以将头指针在此置空即可,因为头结点已经释放,但是没有置空,
    //以下为传一级指针的操作
	/*plist = NULL;*/

}


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

 本文为带头节点双循坏链表,如有问题,请评论区多多评论^_^ 

你可能感兴趣的:(【数据结构学习】,数据结构,链表,c语言)