数据结构——带头双向循环链表

127 阅读3分钟

链表的分类

链表有8种结构,由以下特征组合而成:

单向或双向

单向:一个节点只包含数据和一个next后继指针,指向下一个节点 image.png

双向:一个节点包含数据,next后继指针,还多了prev前驱指针,指向前一个节点 image.png

带头或不带头

注意:这里的"头"特指哨兵位头节点 哨兵位头节点不存储或记录有效数据,

但它的next指针和prev指针仍然有效.

image.png

循环或不循环

image.png

带头双向循环链表的实现

定义节点

双向链表的节点包含数据,后继指针next指向下一个节点位置,前驱指针prev指向前一个节点位置

typedef int LTDataType;//暂定int为链表存储的数据
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType x;
}LTNode;

初始化链表

带头的链表,在对链表进行增删查改前要创建一个哨兵位头节点,

并且该节点的next和prev刚开始指向自己。 image.png

LTNode* ListInit()//链表初始化
{
	//malloc一个哨兵位头节点,该节点不存储有效数据
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	assert(phead);
	phead->next = phead;
	phead->prev = phead;
}

链表的增删查改

尾插

image.png

void ListPushBack(LTNode* phead, LTDataType x)//尾插
{
        assert(phead);//哨兵位不可能为空
	LTNode* tail = phead->prev;
	LTNode* newNode = BuyListNode(x);//申请新节点的函数
        tail->next = newNode;
	newNode->prev = tail;
	newNode->next = phead;
	phead->prev = newNode;
}

头插

image.png

void ListPushFront(LTNode* phead, LTDataType x)//头插
{
	LTNode* newNode = BuyListNode(x);
	LTNode* head = phead->next;//记录头节点(非哨兵位)
	newNode->next = head;
	head->prev = newNode;
	newNode->prev = phead;
	phead->next = newNode;
}

尾删

image.png

void ListPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(!ListEmpty(phead));
	
	LTNode* tailpre = tail->prev;
        free(tail);
	tailpre->next = phead;
	phead->prev = tailpre;
}

头删

void ListPopFront(LTNode* phead)//头删
{
        assert(phead);
	assert(!ListEmpty(phead));
	LTNode* head = phead->next;//记录头节点(不特指哨兵位)
	LTNode* headnext = head->next;
	phead->next = headnext;
	headnext->prev = phead;
	free(head);
}

在任意位置之前插入一个节点

注意:若pos == phead,即在哨兵位之前插入一个节点,就相当于尾插,

因为phead->prev指向尾节点;

若pos == phead->next, 相当于头插,

因为phead->next就是链表的头结点(不特指哨兵位).

image.png

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

删除任意位置的节点

void ListErase(LTNode* pos, LTNode* phead)//删除pos位置的节点
{
	assert(pos);
	assert(!ListEmpty(phead));//保证链表一定不为空,可能会错传哨兵位来删除
	LTNode* pre = pos->prev;//链接【pos前一个节点】与【pos后一个节点】
	LTNode* next = pos->next;
	pre->next = next;
	next->prev = pre;
	free(pos);
}

其它函数

获取链表长度

int ListSize(LTNode* phead)
{
        assert(phead);
        //有头循环链表的遍历结束条件不能为NULL,否则死循环
	LTNode* cur = phead->next;//从哨兵位的下一个节点开始遍历,直到回到哨兵位后遍历结束
	int num = 0;
	while (cur != phead)
	{
            cur = cur->next;
            num++;
	}
	return num;
}

申请新节点

LTNode* BuyListNode(LTDataType x)//申请新节点
{
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	assert(newNode);
	newNode->x = x;
}

链表判空

bool ListEmpty(LTNode* phead)//判断链表是否为空
{
	assert(phead);
	return phead->next == phead;//或者phead->prev == phead;
}

链表打印

void ListPrint(LTNode* phead)//打印链表
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
            printf("%d ", cur->x);
            cur = cur->next;
	}
	printf("\n");
}

链表的销毁

void ListDestory(LTNode* phead)//销毁链表
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
            LTNode* next = cur->next;//先记录下一个节点,再free掉当前节点,就能找到后面的节点
            free(cur);
            cur = next;
	}
	free(phead);
}