目录
#在学习链表的时候我们会了解到双向链表,对比起单向链表,双向链表会有一些优势。事实上,双向链表只是链表的其中一种结构。从有无哨兵位节点、单向还是双向、是否循环可以将链表分为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;
}