【数据结构和算法】双向链表(带头双向循环链表、“增、删、查、改”基本操作)

一、最常用的链表结构

【数据结构和算法】双向链表(带头双向循环链表、“增、删、查、改”基本操作)_第1张图片

与其他的链表结构相比,带头双向循环链表结构最为复杂,同时也最具有结构优势

  1. 双向循环的链表结构,使得链表节点的插入和删除操作没有死角无需从头遍历链表。
  2. 带头(哨兵位)的链表结构,使得在对链表的操作中不需要特殊考虑改变头指针的指向的情况。
  3. 正是因为其结构上的优势,使看似复杂的链表结构实现起来却非常简单。头尾的插入删除函数甚至可以通过复用Insert和Erase函数实现

二、基本操作

1.结构的声明及定义

typedef int DLKlistData;

typedef struct DLKlistNode{
	DLKlistData data;
	struct DLKlistNode *next;//指向下一个节点
	struct DLKlistNode *prev;//指向前一个节点
}DLKlistNode;

DLKlistNode *head = NULL;//定义头指针
head = InitDLKlist();//初始化哨兵位

2.增加节点

创建节点

DLKlistNode* CreatListNode(DLKlistData val){

	DLKlistNode *newnode = (DLKlistNode*)malloc(sizeof(DLKlistNode));
	if (newnode == NULL)
	{
		perror("CreatListNode");
		exit(1);
	}
	newnode->data = val;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

头插

void DLKlistPushFront(DLKlistNode *phead, DLKlistData val){
	assert(phead != NULL);

	DLKlistNode *first = phead->next;
	DLKlistNode *newnode = CreatListNode(val);

	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;

	//可以通过复用Inser函数实现
	//DLKlistInsert(phead->next, val);

}

注意所有传入头指针的函数都要检查phead != NULL,防止传入未经初始化的头指针。


尾插

void DLKlistPushBack(DLKlistNode *phead, DLKlistData val){
	assert(phead != NULL);

	DLKlistNode *tail = phead->prev;
	DLKlistNode *newnode = CreatListNode(val);

	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;

	//可以通过复用Inser函数实现
	//DLKlistInsert(phead, val);

}

任意位置插入

void DLKlistInsert(DLKlistNode *pos, DLKlistData val){
	assert(pos != NULL);

	DLKlistNode *prev = pos->prev;
	DLKlistNode *newnode = CreatListNode(val);

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

}

养成良好习惯,函数入口处检查指针的合法性。检查pos!=NULL。


3.删除节点

头删

void DLKlistPopFront(DLKlistNode *phead){
	assert(phead != NULL);
	assert(phead->next != phead);

	DLKlistNode *first = phead->next;
	DLKlistNode *next = first->next;
	phead->next = next;
	next->prev = phead;
	free(first);

	//可以通过复用Erase函数实现
	//DLKlistErase(phead->next);

}

头删尾删操作除了要检查phead!=NULL(是否初始化)之外,还要检查phead->next != phead(链表是否为空)。


尾删

void DLKlistPopBack(DLKlistNode *phead){
	assert(phead != NULL);
	assert(phead->next != phead);

	DLKlistNode *tail = phead->prev;
	DLKlistNode *prev = tail->prev;
	prev->next = phead;
	phead->prev = prev;
	free(tail);

	//可以通过复用Erase函数实现
	//DLKlistErase(phead->prev);

}

任意位置删除

void DLKlistErase(DLKlistNode *pos){
	assert(pos != NULL);

	DLKlistNode *next = pos->next;
	DLKlistNode *prev = pos->prev;

	prev->next = next;
	next->prev = prev;
	free(pos);
}

4.查找节点

DLKlistNode* DLKlistFind(DLKlistNode *phead, DLKlistData val){

	assert(phead != NULL);
	DLKlistNode *cur = phead->next;

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

与普通单链表不同,该循环链表遍历结束的标志是cur == phead(而不是NULL)


5.其他操作

初始化

DLKlistNode* InitDLKlist(){

	DLKlistNode *phead = CreatListNode(0);
	phead->next = phead;
	phead->prev = phead;
	return phead;

}

打印链表

void PrintDLKlist(DLKlistNode *phead){
	assert(phead != NULL);
	DLKlistNode *cur = phead->next;

	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");

}

销毁链表

void DestroyDLKlist(DLKlistNode *phead){
	assert(phead != NULL);

	DLKlistNode *del;
	while (phead->next != phead)
	{
		del = phead->next;
		phead->next = del->next;
		free(del);

	}
	free(phead);
}
  • 一定要注意在循环结束后销毁哨兵位节点
  • 调用完DestroyDLKlist函数后,记得将头指针置空哦!!

你可能感兴趣的:(数据结构和算法,链表,数据结构,算法,c语言,c++)