数据结构初阶——进阶链表

上一篇的单链表还存在许多的不方便,那么这一篇就来介绍一下更实用、更方便的链表吧。


目录

链表的分类

初始化链表

增加数据

        尾插

        头插

删除数据

        尾删

         头删

随机插入删除

        随机插入

        随机删除

结尾总结


链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构

上一篇单链表博客介绍的就是无头单向非循环链表,这一篇就来介绍带头双向循环链表。 


初始化链表

prev指向上一个节点,next指向下一个节点。

初始化函数的返回类型我们定义成结构体指针,如果返回值定义成void类型的话,那就需要传入二级指针。

BuyListNode函数用来申请空间,那么链表是带头的链表,初始化就是申请一块空间,也就是哨兵位,不存放有效数据,它的prev和next都指向自己因为是循环,prev和next意义也就是双向。

那BuyListNode函数

 

这样一来,哨兵位就写好了。 


增加数据

        尾插

这个尾插就不用传入二级指针,上一篇插入为什么会传入二级指针是因为需要改变链表。那传入的是哨兵位的地址,我们不需要改变哨兵位,改变的是哨兵位指向的内容,但是传入的是结构体的指针,这样就能插入删除。

 为了测试,再写一个打印函数

 

 

相比之下这个链表就要方便很多,不需要再一步步找尾节点,头节点的上一个直接就是尾。

        头插

 

看到这里就体现出这种链表的优势,插入都不需要写空链表的情况,这样写就可以包含空链表的情况。


删除数据

        尾删

         头删

 测试一下也没有问题。


随机插入删除

        随机插入

  

既然随机插入写好了,那头插和尾插也就可以复用了。

尾插复用

 

 

头插复用

 

 

        随机删除

 

同样,头删和尾删也可以复用

尾删复用

 

头删复用

 

 


结尾总结

       这就是双向带头循环链表的魅力,虽然是链表的进阶,那么结构就会更复杂,但是在操作的时候可以感觉到非常舒服,操作非常方便,尾插不需要一个一个找,而且提到复用,整个链表只需要把随即插入和删除写好,整个链表也就完成了,如果写的熟练的话5分钟就可以写一个链表。

那么所有的代码还是放到了结尾。

List.h文件

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int LTDataType;

typedef struct ListNode
{
	struct List* prev;
	struct List* next;
	LTDataType data;
}LTNode;

//初始化
LTNode* ListInit();

//打印
void ListPrint(LTNode* phead);

//尾插/头插/尾删/头删
void ListPushBack(LTNode* phead, LTDataType x);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);

//在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x);
//删除pos位置
void ListErase(LTNode* pos);

List.c文件

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

LTNode* BuyListNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);//直接退出程序
	}
	newnode->data = x;
	newnode->prev = NULL;
	newnode->next = NULL;

	return newnode;
}

LTNode* ListInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->prev = phead;
	phead->next = phead;

	return phead;
}

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

}

void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	//LTNode* newnode = BuyListNode(x);
	//LTNode* tail = phead->prev;
	//tail->next = newnode;
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;

	ListInsert(phead, x);

}

void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	//LTNode* newnode = BuyListNode(x);
	//LTNode* next = phead->next;
	//newnode->next = next;
	//next->prev = newnode;
	//newnode->prev = phead;
	//phead->next = newnode;

	ListInsert(phead->next, x);
}

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//证明链表为空,就不要删了
	//LTNode* tail = phead->prev;
	//LTNode* tailPrev = tail->prev;

	//free(tail);

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

	ListErase(phead->prev);

}

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	//LTNode* pos = phead->next;
	//LTNode* next = pos->next;

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

	//free(pos);

	ListErase(phead->next);

}

//在pos位置之前插入x
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	newnode->next = pos;
	pos->prev = newnode;
	prev->next = newnode;
	newnode->prev = prev;

}

//删除pos位置
void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;

	prev->next = next;
	next->prev = prev;

	free(pos);

}

同样如果想变得更好看可以自己手写一个菜单,这一篇就介绍到这里。

猜你喜欢

转载自blog.csdn.net/m0_64607843/article/details/124724086