链表,种类众多,有带哨兵位的,有循环的,还有双向的,排列组合后,你会发现有8种不同的链表。其中,结构最简单的就是单链表了,结构最复杂的则是带头双向循环链表。

但是,别看他结构最复杂,其实他的实现反而是最简单的。

数据结构的操作,就是操作内存中的数据,也就是增删查改这些操作;那么,就让我们来看看带头双向循环链表的增删查改吧。


1.首先是结构的定义:

很明显,和单链表相比,我们多了一个prev指针。

typedef int SLDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	SLDataType data;
}LTNode;


2.接着就是链表的初始化:

Buy函数就是创建并初始化一个节点,写成函数复用更方便。

LTNode* Buy(int x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("newnode malloc");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

LTNode* LTInit()
{
	LTNode* phead = Buy(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}


3.尾插

以前我们写单链表的时候尾插先要while循环找到尾前面那个节点,再尾插,时间复杂度很显然是O(N),但是现在我们实现尾插就很简单了,时间复杂度是O(1):

void LTPushBack(LTNode* phead, SLDataType x)
{
	assert(phead);
	LTNode* newnode = Buy(x);

	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

而且单链表尾插还要分链表为空和不为空的时候,这里就不用了。


4.尾删

当链表为空的时候再删除很显然就错了,所以我们可以写一个判断链表是否为空的函数,一是可以复用,二来 assert 报错的时候可以很清楚错在了哪里。

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	LTNode* tail = phead->prev;
	LTNode* nexttail = tail->prev;
	free(tail);
	tail = NULL;
	phead->prev = nexttail;
	nexttail->next = phead;
}

在尾删的时候,我们可以用一个nextTail指针指向尾巴的前一个节点再写,也可以直接写,都可以,但是直接写的话就要注意好写的顺序,一旦写错顺序就GG了;而用指针的话,顺序就无所谓了,怎么写都行。

单链表在尾删的时候时间复杂度也是O(N),这里复杂度很明显:O(1)。


5.头插、头删

void LTPushFront(LTNode* phead, SLDataType x)
{
	assert(phead);

	LTNode* newnode = Buy(x);

	LTNode* tail = phead->prev;
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	assert(phead->next != phead);
	LTNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	free(del);
	del = NULL;
}

也没什么好说的,时间复杂仍然是O(1);


6.打印

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

最后的效果是这样的:

【数据结构】C语言 实现 带头双向循环链表_#include

7.查找

这里时间复杂度就是O(N)了,循环一次查找 data == x 的节点,返回该位置的指针:

SLTNode* SLFind(SLTNode* phead, SLDataType x)
{
	SLTNode* find = phead;
	while (find)
	{
		if (find->data == x)
		{
			return find;
		}
		find = find->next;
	}
	return NULL;
}

这里要修改节点数据的话就直接在查找函数后面跟上一行就行了,不用再单独写一个修改函数了。


8.在任意位置之前插入 、 任意位置删除

依然是配合着查找函数食用,效果会比较好:

void LTInsert(LTNode* pos, SLDataType x)//在该位置前面插入
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* newnode = Buy(x);
	posPrev->next = newnode;
	newnode->next = pos;
	newnode->prev = posPrev;
	pos->prev = newnode;
}

void LTErase(LTNode* pos)//任意位置删除
{
	assert(pos);

	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	free(pos);
	posPrev->next = posNext;
	posNext->prev = posPrev;
}


9.销毁

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	LTNode* curNext = cur->next;
	while (cur != phead)
	{
		free(cur);
		printf("...\n");
		cur = curNext;
		curNext = cur->next;
	}
	printf("已销毁\n");
}

最后的样子:

【数据结构】C语言 实现 带头双向循环链表_时间复杂度_02

咱就是说追求一个大道至简。


【数据结构】C语言 实现 带头双向循环链表_#include_03


贷马奉上:

头文件:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int SLDataType;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	SLDataType data;
}LTNode;

LTNode* LTInit();
void LTPushBack(LTNode* phead, SLDataType x);
void LTPopBack(LTNode* phead);
void LTPushFront(LTNode* phead, SLDataType x);
void LTPopFront(LTNode* phead);
void LTPrint(LTNode* phead);
bool LTEmpty(LTNode* phead);


LTNode* LTFind(LTNode* phead, int x);

void LTInsert(LTNode* pos, SLDataType x);
void LTErase(LTNode* pos);

void LTDestroy(LTNode* phead);


源文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

LTNode* Buy(int x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("newnode malloc");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

LTNode* LTInit()
{
	LTNode* phead = Buy(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void LTPushBack(LTNode* phead, SLDataType x)
{
	assert(phead);
	LTNode* newnode = Buy(x);

	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	LTNode* tail = phead->prev;
	LTNode* nexttail = tail->prev;
	free(tail);
	tail = NULL;
	phead->prev = nexttail;
	nexttail->next = phead;
}

void LTPushFront(LTNode* phead, SLDataType x)
{
	assert(phead);
	/*if (LTEmpty(phead))
	{
		LTPushBack(phead, x);
	}
	else
	{
		LTNode* newnode = Buy(x);

		LTNode* tail = phead->prev;
		newnode->next = phead->next;
		phead->next->prev = newnode;
		phead->next = newnode;
		newnode->prev = phead;
	}*/

	LTNode* newnode = Buy(x);

	LTNode* tail = phead->prev;
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	assert(phead->next != phead);
	LTNode* del = phead->next;
	phead->next = del->next;
	del->next->prev = phead;
	free(del);
	del = NULL;
}

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

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

void LTInsert(LTNode* pos, SLDataType x)//在该位置前面插入
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* newnode = Buy(x);
	posPrev->next = newnode;
	newnode->next = pos;
	newnode->prev = posPrev;
	pos->prev = newnode;
}

void LTErase(LTNode* pos)//任意位置删除
{
	assert(pos);

	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	free(pos);
	posPrev->next = posNext;
	posNext->prev = posPrev;
}

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	LTNode* curNext = cur->next;
	while (cur != phead)
	{
		free(cur);
		printf("...\n");
		cur = curNext;
		curNext = cur->next;
	}
	printf("已销毁\n");
}

别找辣!没有test函数。。。

【数据结构】C语言 实现 带头双向循环链表_链表_04