双向循环链表的实现

本文实现语言:c语言

上篇文章回顾


上几篇博客我们已经实现静态顺序表和动态顺序表的基本功能,在顺序表中存取表元素非常简单,但是插入和删除需要移动大量的数据(时间复杂度O(n) )非常麻烦;因此今天我们来实现插入和删除都非常方便的链表。

其实链表分为好几类,如下;其中以不带头节点,不循环的单链表带头节点,循环的双链表最为重要。

分类方式 类别1 类别1
按照指针数量分 单链表:只有一个指向下一个节点的指针。 双链表:有一个指向下一个节点的指针,还有有一个指向上一个节点的指针。
链表是否循环分 循环链表:即链表的尾指针指向头结点 不循环链表
链表按照头结点分 带头结点的链表:链表中的第一个结点中不存放有效的数据(相当于哨兵) 不带头结点的链表:链表中的第一个节点为有效结点

本文主要实现带头节点的双向循环链表的增,删,插,改等功能。


在实现代码前,有一个重要的问题需要讨论,就是传参问题,什么情况下我们需要传二级指针?

如果我们的形参是普通变量,在传递的时候,如果我们打算对普通变量进行修改,那么我们就需要传递变量的地址;如果我们不打算修改普通变量(比如只是查看一下内容),那么我们的形参传递数值就可以(此时形参是实参的一份临时copy)。

同样的,如果我们的形参是指针变量,如果我们打算修改这个指针变量,那么我们传递的时候就需要二级指针;否则传递一级指针就OK。

这个理论放到链表实现里就是说:我们在设计链表的时候会设计一个头指针pNode,这个头指针pNode会指向头结点(或者指向第一个有效结点),所以头指针里面存的就是头结点的地址。如果我们要修改头结点里面所保存的地址(比如初始化链表时,让头指针指向NULL;再比如头插的时候,头指针保存的地址会发生变化,变成新插入节点的地址)时,在设计函数的时候就需要传递二级指针;如果我们只是查看头指针指向的内容,这样并不会改变头指针本身保存的地址,此时传递一级指针就可以。

所以,一般销毁,初始化,头插等需要用到二级指针;查找,遍历等传递一级指针就OK。


代码:

#define _CRT_SECURE_NO_WARNINGS 1

//--------------------DoubleList.h---------------
//  #pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

typedef int DataType;
typedef struct DListNode
{
	struct DListNode *next;
	struct DListNode *prev;
	DataType data;
}DListNode, *pDListNode, **ppDListNode;

void DListInit(ppDListNode pphead);//初始化
pDListNode CreatDListNode(DataType x);//创造一个节点
void DListPushFront(ppDListNode pphead, DataType data);//头插
void DListPushBack(ppDListNode pphead, DataType data);//尾插
void DListInsert(ppDListNode pphead, pDListNode pos, DataType data);//任意位置插入
void DListPopFront(ppDListNode pphead);//头删
void DListPopBack(ppDListNode pphead);//尾删
void DListErase(ppDListNode pphead, pDListNode pos);//任意位置删除
void DListRemove(ppDListNode pphead, DataType x);//删除遇到的第一个数
void DListRemoveAll(ppDListNode pphead, DataType x);//删除遇到的所有的数字
pDListNode DListFind(pDListNode phead, DataType x);//查x所在的节点位置
int DListSize(pDListNode phead);//算节点数
int DListEmpty(pDListNode phead);//判断是否为空
void DListClear(ppDListNode pphead);//清空
void DListDestory(ppDListNode pphead);//销毁
void DListPrint(const pDListNode phead);//打印

//-------------------DoubleList.c------------

//创造一个节点
pDListNode CreatDListNode(DataType x)
{
	pDListNode newNode = (pDListNode)malloc(sizeof(DListNode));
	assert(newNode);
	newNode->data = x;
	newNode->next = NULL;
	newNode->prev = NULL;
	return newNode;
}

//初始化
void DListInit(ppDListNode pphead)
{
	assert(pphead);
	int num = 0;
	*pphead = CreatDListNode(num);
	(*pphead)->next = *pphead;
	(*pphead)->prev = *pphead;
	return;
}

//头插
void DListPushFront(ppDListNode pphead, DataType data)
{
	assert(pphead);
	pDListNode newNode = CreatDListNode(data);
	pDListNode first = (*pphead)->next;

	(*pphead)->next = newNode;
	newNode->prev = *pphead;
	newNode->next = first;
	first->prev = newNode;
	return;
}

//尾插
void DListPushBack(ppDListNode pphead, DataType data)
{
	assert(pphead);
	pDListNode newNode = CreatDListNode(data);
	pDListNode end = (*pphead)->prev;

	end->next = newNode;
	newNode->prev = end;
	newNode->next = (*pphead);
	(*pphead)->prev = newNode;
}

//任意位置插入:1.创建一个以data为值的新节点newNode 2.在双链表上找到插入位置的前一个节点cur 3.newNode的next指向pos 4.newNode的prev指向cur 5.将pos的prev指向newNode 6.cur的next指向newNode
void DListInsert(ppDListNode pphead, pDListNode pos, DataType data)
{
	assert(pphead);
	assert(pos);

	pDListNode newNode = CreatDListNode(data);
	pDListNode cur = pos->prev;

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

//头删
void DListPopFront(ppDListNode pphead)
{
	assert(pphead);
	if (NULL == (*pphead)->next)
	{
		printf("链表为空,不可以删除\n");
		return;
	}

	pDListNode first = (*pphead)->next;
	pDListNode second = first->next;
	(*pphead)->next = second;
	second->prev = (*pphead);
	free(first);
	first = NULL;
}

//尾删
void DListPopBack(ppDListNode pphead)
{
	assert(pphead);
	if (NULL == (*pphead)->next)
	{
		printf("链表为空,不可以删除\n");
		return;
	}

	pDListNode end = (*pphead)->prev;
	pDListNode penultimate = end->prev;

	penultimate->next = (*pphead);
	(*pphead)->prev = penultimate;
	free(end);
	end = NULL;
}

//任意位置删除
void DListErase(ppDListNode pphead, pDListNode pos)
{
	assert(pphead);
	assert(pos);
	assert(pos != (*pphead));
	if (NULL == (*pphead)->next)
	{
		printf("链表为空,不可以删除\n");
		return;
	}

	pDListNode tmp1 = (pos)->prev;
	pDListNode tmp2 = pos->next;

	tmp1->next = tmp2;
	tmp2->prev = tmp1;

	free(pos);
	pos = NULL;

}

//查x所在的节点位置
pDListNode DListFind(pDListNode phead, DataType x)
{
	assert(phead);
	pDListNode cur = phead->next;

	while (phead != cur)
	{
		if (x == cur->data)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//删除遇到的第一个数
void DListRemove(ppDListNode pphead, DataType x)
{
	assert(pphead);
	pDListNode node = DListFind(*pphead, x);
	if ((NULL != node) && (node != *pphead))
	{
		DListErase(pphead, node);
	}
	else {
		printf("没有这个数字,无法删除\n");
	}
}

//删除遇到的所有的数字
void DListRemoveAll(ppDListNode pphead, DataType x)
{
	assert(pphead);
	pDListNode pos;
	
	while ((NULL != (pos = DListFind(*pphead, x))) && (pos != *pphead))
	{
	
		DListErase(pphead, pos);
		
	}
}

//算节点数
int DListSize(pDListNode phead)
{
	assert(phead);
	pDListNode node = phead->next;
	size_t num = 0;
	while (node != phead)
	{
		num++;
		node = node->next;
	}
	return num;//不算头结点
}

//判断是否为空
int DListEmpty(pDListNode phead)
{
	return phead->next == phead ? 0 : 1;
}

//清空
void DListClear(ppDListNode pphead)
{
	assert(pphead);
	pDListNode first = (*pphead)->next;
	(*pphead)->next = (*pphead);
	(*pphead)->prev = (*pphead);

	pDListNode cur = first;
	while (first != (*pphead))
	{		
		first = first->next;
		free(cur);
		cur = NULL;
	}
}

//销毁
void DListDestory(ppDListNode pphead)
{
	assert(pphead);
	DListClear(pphead);
	free(*pphead);
	*pphead = NULL;
}
//打印
void DListPrint(const pDListNode phead)
{
	assert(phead);
	pDListNode cur = phead->next;
	printf("链表内容为:%2d->", phead->data);
	while (cur != phead)
	{
		printf("%2d->", cur->data);
		cur = cur->next;
	}
	printf("%2d\n", phead->data);
}

//---------------------------test.c---------------------------
void Test()
{
	DListNode *list = NULL;
	DListInit(&list);
	DListPushFront(&list, 1);//头插
	DListPushFront(&list, 2);//头插
	DListPushFront(&list, 3);//头插
	DListPushFront(&list, 4);//头插
	DListPrint(list);
	DListPushBack(&list, 101);//尾插
	DListPushBack(&list, 102);//尾插
	DListPushBack(&list, 103);//尾插
	DListPushBack(&list, 104);//尾插
	DListPrint(list);
	DListInsert(&list, list->next, 100);//任意位置插
	DListPrint(list);
	DListErase(&list, list->next);//任意位置删

	DListPrint(list);
	int ret = DListSize(list);//算节点数
	printf("节点数%d\n", ret);
	int cur = DListEmpty(list);//判断是否为空
	if (cur == 0)
		printf("链表为空\n");
	else
		printf("链表不为空\n");
	DListInsert(&list, list->next->next, 1);//任意位置插
	DListPrint(list);
	DListInsert(&list, list->next->next->next->next, 1);//任意位置插
	DListPrint(list);
	DListPushBack(&list, 1);//尾插
	DListPushFront(&list, 1);//头插
	DListPrint(list);

	DListRemove(&list, 1);//删除x
	DListPrint(list);
	printf("hehe1\n");
	DListRemoveAll(&list, 1);//删除x
	printf("hehe2\n");
	DListPrint(list);

	DListClear(&list);//清空
	DListPrint(list);
	DListDestory(&list);//销毁
}


int main()
{
	Test();
	system("pause");
	return 0;
}

结果:

全部评论

相关推荐

10-28 11:04
已编辑
美团_后端实习生(实习员工)
一个2人:我说几个点吧,你的实习经历写的让人觉得毫无含金量,你没有挖掘你需求里的 亮点, 让人觉得你不仅打杂还摆烂。然后你的简历太长了🤣你这个实习经历看完,估计没几个人愿意接着看下去, sdk, 索引这种东西单拎出来说太顶真了兄弟,好好优化下简历吧
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务