一、带头双向循环链表的实现
和无头单向非循环链表的接口实现不同,由于有头节点的存在,该结构的接口实现函数使用的是结构体指针,并且双向指针的存在使得函数实现更为简单
1. 各接口实现
1.1 链表结构体定义
typedef int LTDataType;
//双向带头循环链表
typedef struct ListNode
{
struct ListNode* next;//指向下一个数据
struct ListNode* prev;//指向前一个数据
LTDataType data;
}LTNode;
1.2 链表初始化
//初始化
LTNode* LTInit(LTNode* phead)
{
phead = BuyLTNode(-1);//-1可以是任何值,因为不需要打印,无任何实义
phead->next = phead;
phead->prev = phead;
return phead;
}
1.3 链表打印
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("guard<==>");
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("guard\n");
}
1.4 数据头插与尾插以及链表新节点创建
注:在实现链表指定位置插入数据,即可实现头插和尾插的代码简化
//创建新节点
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = newnode->prev = NULL;
return newnode;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead, x);//在phead前面插入
//LTNode* tail = phead->prev;
//LTNode* newnode = BuyLTNode(x);
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead->next, x);//在phead下一个的前面插入
//LTNode* newnode = BuyLTNode(x);
不保存原头节点(需要考虑节点指向的先后顺序)
//phead->next->prev = newnode;
//newnode->next = phead->next;
//phead->next = newnode;
//newnode->prev = phead;
保存原头节点(不用考虑节点指向的先后顺序)
LTNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
1.5 数据头删与尾删
注:在实现删除链表指定数据,即可实现头删和尾删的代码简化
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));//为空报错,不为空继续
LTErase(phead->prev);
//LTNode* tail = phead->prev;
//LTNode* tailprev = tail->prev;
//
//free(tail);
//tailprev->next = phead;
//phead->prev = tailprev;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->next);
//LTNode* first = phead->next;
//LTNode* second = first->next;
//free(first);
//phead->next = second;
//second->prev = phead;
}
1.6 检查链表是否为空
//检测链表是否为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;//为空返回1,不为空返回0
}
1.7 查找链表指定数据
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
1.8 在链表指定位置插入
//在pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
1.9 删除链表指定数据
void LTErase(LTNode* pos)//不能判断哨兵位,如果链表为空,则把phead删除了
{
assert(pos);
LTNode* posprev = pos->prev;
LTNode* posnext = pos->next;
free(pos);
posprev->next = posnext;
posnext->prev = posprev;
}
1.10 链表销毁
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* del = cur;
cur = cur->next;
free(del);
}
free(phead);
}
2. 整体代码
2.1 List.h:头文件引用及各接口函数声明
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int LTDataType;
//双向带头循环链表
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType data;
}LTNode;
//初始化
LTNode* LTInit(LTNode* phead);
//链表打印
void LTPrint(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头删
void LTPopFront(LTNode* phead);
//检测链表是否为空
bool LTEmpty(LTNode* phead);
//查找链表某元素
LTNode* LTFind(LTNode* phead, LTDataType x);
//在pos之前插入
void LTInsert(LTNode* pos, LTDataType x);
//删除pos的值
void LTErase(LTNode* pos);
//释放链表
void LTDestroy(LTNode* phead);
2.2 List.c:链表各接口函数实现
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
//创建新节点
LTNode* BuyLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = newnode->prev = NULL;
return newnode;
}
//初始化
LTNode* LTInit(LTNode* phead)
{
phead = BuyLTNode(-1);//-1可以是任何值,因为不需要打印,无任何实意
phead->next = phead;
phead->prev = phead;
return phead;
}
//打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("guard<==>");
while (cur != phead)
{
printf("%d<==>", cur->data);
cur = cur->next;
}
printf("guard\n");
}
//检测链表是否为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead;//为空返回1,不为空返回0
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead, x);//在phead前面插入
//LTNode* tail = phead->prev;
//LTNode* newnode = BuyLTNode(x);
//tail->next = newnode;
//newnode->prev = tail;
//newnode->next = phead;
//phead->prev = newnode;
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead->next, x);//在phead下一个的前面插入
//LTNode* newnode = BuyLTNode(x);
不保存原头节点(需要考虑节点指向的先后顺序)
//phead->next->prev = newnode;
//newnode->next = phead->next;
//phead->next = newnode;
//newnode->prev = phead;
保存原头节点(不用考虑节点指向的先后顺序)
LTNode* next = phead->next;
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
//尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));//为空报错,不为空继续
LTErase(phead->prev);
//LTNode* tail = phead->prev;
//LTNode* tailprev = tail->prev;
//
//free(tail);
//tailprev->next = phead;
//phead->prev = tailprev;
}
//头删
void LTPopFront(LTNode* phead)
{
assert(phead);
assert(!LTEmpty(phead));
LTErase(phead->next);
true 可读性差
LTNode* del = phead->next;
phead->next = del->next;
free(del);
phead->next->prev = phead;
//LTNode* first = phead->next;
//LTNode* second = first->next;
//free(first);
//phead->next = second;
//second->prev = phead;
}
//查找链表某元素
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
return cur;
cur = cur->next;
}
return NULL;
}
//在pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyLTNode(x);
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
//删除pos的值
void LTErase(LTNode* pos)//不能判断哨兵位,如果链表为空,则把phead删除了
{
assert(pos);
LTNode* posprev = pos->prev;
LTNode* posnext = pos->next;
free(pos);
posprev->next = posnext;
posnext->prev = posprev;
}
//释放链表
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* del = cur;
cur = cur->next;
free(del);
}
free(phead);
}
二、顺序表和链表的区别
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持,时间复杂度O(1) | 不支持,时间复杂度O(N) |
任意位置插入或者删除元素 | 可能需要一定元素,效率低,O(N) | 只需要修改指针指向 |
插入 | 动态顺序表,空间不够时需要扩容,可能造成空间浪费 | 没有容量的概念,按需申请空间 |
应用场景 | 元素高效存储、频繁访问 | 任意位置插入和删除频繁 |
缓存利用率 | 高 | 低 |