进阶,链表的结构

目录

双向链表

带哨兵位的链表

循环链表

带头双向循环链表


#在学习链表的时候我们会了解到双向链表,对比起单向链表,双向链表会有一些优势。事实上,双向链表只是链表的其中一种结构。从有无哨兵位节点、单向还是双向、是否循环可以将链表分为8类

双向链表

不同于顺序表可以下标访问,链表只能通过上一个节点访问下一个节点,在实现单链表在pos位置之前插入和删除pos位置节点时具有局限性。所以引出双向链表的结构。

typedef struct DouList {
	DLDataType val;
	struct DouList* prev;
	struct DouList* next;
}DL;

带哨兵位的链表

带哨兵位是指在链表头部添加一个哨兵位节点,目的是在封装函数时就不用传递二级指针了。

 我们不设置哨兵位时,若要检查链表是否为空,并且对空链表添加新节点的时候,就要通过传递二级指针,来改变链表指针plist的指向,如下链表尾插

void SLNPushBack(SLNode** pphead, SLNDataType x) {
	SLNode* newnode = CreateNode(x);
	if (*pphead == NULL) {//判断链表是否为空
		*pphead = newnode;//如果为空,二级指针解引用,使用链表指针地址对链表指针进行修改
	}
	else {
		SLNode* tail = *pphead;
		while (tail->next != NULL) {
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

 而如果带了哨兵位就不用;哨兵位不存储数据,但是哨兵位是一个结构体,一个节点,不是指针

带哨兵位的链表初始化和尾插: 

DL* ListCreate() {
	DL* phead = (DL*)malloc(sizeof(DL));//需要手动malloc一个节点当作哨兵位
	phead->next = NULL;//对链表置空
	return phead;
}
void ListPushBack(DL* phead, DLDataType x) {
    assert(phead);
	DL* newnode = (DL*)malloc(sizeof(DL));//创建新节点
	newnode->val = x;//对新结点赋值
	newnode->next = NULL;//对新结点下一个节点置空,作为尾节点

	DL* tail = phead->next;
	if (tail == NULL)//判断是否是空链表
		phead->next = newnode;//空链表直接赋值
	while (tail->next != NULL) {//如果不是空链表,找尾节点
		tail = tail->next;
	}
	tail->next = newnode;//把新结点接到尾节点后面
}

循环链表

循环链表很简单,就是尾节点指向头节点,而不是NULL

 这就是链表三种基本结构,可以三三组合成8种结构,但其实工程中用的最多的是双向带头循环链表

带头双向循环链表

在单链表非常麻烦的尾插尾删现在就已经变得非常容易,只需要通过头节点找上一个就是尾节点然后再尾插,这样就直接将O(N)的复杂度变为O(1)。

DL* ListCreate() {
	DL* phead = (DL*)malloc(sizeof(DL));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
void ListPushBack(DL* phead, DLDataType x) {
	assert(phead);

	DL* newnode = (DL*)malloc(sizeof(DL));
	DL* tail = phead->prev;

	newnode->val = x;
	phead->prev = newnode;
	tail->next = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	
}

void ListPopBack(DL* phead) {
	assert(phead);

	DL* tail = phead->prev;
	DL* newtail = tail->prev;//尾节点的前一个作为新的尾

	phead->prev = newtail;//头节点的上一个指向新的尾
	newtail->next = phead;//新的尾节点下一个指向头
	free(tail);//释放掉原尾节点
	tail = NULL;//再置空,避免野指针
}

最后面给出带头双向循环链表的增删查改完整版

链表和顺序表各有利弊,但显然,在工程实践中,链表更利于数据的管理,而顺序表多在竞赛,面试等OJ题中出现,主要是因为顺序表的效率更高,操作也更简单。

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


typedef int DLDataType;
typedef struct DouList {
	DLDataType val;
	struct DouList* prev;
	struct DouList* next;
}DL;


// 创建返回链表的头结点.
DL* ListCreate();
// 双向链表销毁
void ListDestory(DL* phead);
// 双向链表打印
void ListPrint(DL* phead);
// 双向链表尾插
void ListPushBack(DL* phead, DLDataType x);
// 双向链表尾删
void ListPopBack(DL* phead);
// 双向链表头插
void ListPushFront(DL* phead, DLDataType x);
// 双向链表头删
void ListPopFront(DL* phead);
// 双向链表查找
DL* ListFind(DL* phead, DLDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(DL* pos, DLDataType x);
// 双向链表删除pos位置的节点
void ListErase(DL* pos);




DL* ListCreate() {
	DL* phead = (DL*)malloc(sizeof(DL));
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

void ListDestory(DL* phead) {
	assert(phead);

	DL* cur = phead->next, * temp;
	while (cur!=phead) {
		temp = cur;
		cur = cur->next;
		free(temp);
		temp = NULL;
	}
}

void ListPrint(DL* phead) {
	assert(phead);

	DL* cur = phead->next;
	printf("哨兵位<-->");
	while (cur!=phead) {
		printf("%d<-->", cur->val);
		cur = cur->next;
	}
	printf("哨兵位\n");
}

void ListPushBack(DL* phead, DLDataType x) {
	assert(phead);

	DL* newnode = (DL*)malloc(sizeof(DL));
	DL* tail = phead->prev;

	newnode->val = x;
	phead->prev = newnode;
	tail->next = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	
}

void ListPopBack(DL* phead) {
	assert(phead);

	DL* tail = phead->prev;
	DL* newtail = tail->prev;

	phead->prev = newtail;
	newtail->next = phead;
	free(tail);
	tail = NULL;
}

void ListPushFront(DL* phead, DLDataType x) {
	assert(phead);

	ListPushBack(phead->next,x);
}

void ListPopFront(DL* phead) {
	assert(phead);

	ListPopBack(phead->next->next);
}

DL* ListFind(DL* phead, DLDataType x) {
	assert(phead);

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

void ListInsert(DL* pos, DLDataType x) {
	assert(pos);

	DL* newnode = (DL*)malloc(sizeof(DL));
	newnode->val = x;

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

void ListErase(DL* pos) {
	assert(pos);

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值