#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
//头文件
#include
#include
#include
//对数据类型进行重命名,这样可以对多种数据类型进行存储,有利于控制链表
typedef int LTDataType;
//结点(结构体)
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
不用二级指针的几种解决方案
1.返回值
2.建立哨兵位
//初始化
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);
//删除链表结点时,用于判断链表是否为空,为空返回真,不为空返回假
bool ListEmpty(LTNode* phead);
//计算链表大小
size_t ListSize(LTNode* phead);
//链表查找
LTNode* ListFind(LTNode* phead, LTDataType x);
//链表插入
void ListInsert(LTNode* pos, LTDataType x);
//链表删除
void ListErase(LTNode* pos);
//链表销毁
void ListDestroy(LTNode* phead);
#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"
//初始化
LTNode* ListInit()
{
//建立带哨兵位的头结点
LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
if (guard == NULL)
{
perror("malloc fail");
exit(-1);
}
//带头双向循环链表的初始化
//就是先建立一个头结点
//头结点的两个指针域都指向头结点,形成自环
guard->next = guard;
guard->prev = guard;
return guard;
}
//创建新结点
LTNode* BuyListNode(LTDataType x)
{
//建立哨兵位
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
先创建一个新结点,再尾插。
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;
}
void ListPrint(LTNode* phead)
{
assert(phead);
printf("phead<=>");
LTNode* cur = phead->next;//带哨兵卫的头结点没有数据
//因为是循环链表,所以以cur!=phead作为判断条件,否则就会死循环
while (cur != phead)
{
printf("%d<=>", cur->data);
//迭代
cur = cur->next;
}
printf("\n");
}
测试结果:
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
//先创建新结点
LTNode* newnode = BuyListNode(x);
//不关心顺序
LTNode* first = phead->next;//保留哨兵卫头结点的下一个结点
newnode->next = first;
first->prev = newnode;
phead->next = newnode;
newnode->prev = phead;
}
测试用例:
链表删除之前需要判断链表是否为空(只有带哨兵卫的头结点),为空返回true,不为空返回false。采用bool类型
//删除链表结点时,用于判断链表是否为空,为空返回真,不为空返回假
bool ListEmpty(LTNode* phead)
{
assert(phead);
if (phead->next == phead)//出了头结点,没有其他新增结点
{
return true;
}
else
{
return false;
}
//return phead->next==phead;
}
void ListPopBack(LTNode* phead)
{
assert(phead);
//尾删还要保证,链表不为空
assert(!ListEmpty(phead));
//tail指向最后一个要尾删的结点
LTNode* tail = phead->prev;
//prev指向tail前面一个结点(即将变成尾结点)
LTNode* prev = tail->prev;
phead->prev = prev;
prev->next = phead;
free(tail);
tail = NULL;
}
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(!ListEmpty(phead));
LTNode* first = phead->next;
LTNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
测试用例:
size_t ListSize(LTNode* phead)
{
assert(phead);
size_t n = 0;
LTNode* cur = phead->next;
while (cur != phead)
{
n++;
//迭代
cur = cur->next;
}
printf("\n");
return n;
}
LTNode* ListFind(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前进行插入
//在pos之前插入
void ListInsert(LTNode* pos, LTDataType x)
{
//有可能查找不到pos,则会返回空,如果你输入的数据查找不到,则报错,说明输入错误
assert(pos);
LTNode* prev = pos->prev;
LTNode* newnode = BuyListNode(x);
//prev newnode pos
newnode->next = pos;
pos->prev = newnode;
newnode->prev = prev;
prev->next = newnode;
}
实现了链表的插入后,我们可以将之前的头插和尾插做一个改造。
尾插相当于在头结点前面插入一个新结点
ListInsert(phead, x);
头插,需要记录头结点后面的结点,再插入
LTNode* first = phead->next;
ListInsert(first, x);
//链表删除
void ListErase(LTNode* pos)
{
assert(pos);
//prev pos next
LTNode* prev = pos->prev;
LTNode* next = pos->next;//记录pos前后的位置
prev->next = next;
next->prev = prev;
free(pos);
pos = NULL;
}
void ListDestroy(LTNode* phead)
{
assert(phead);
//边遍历,边释放
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
顺序表的优点:
1.尾插尾删效率很高
2.随机访问(利用下标访问)
3.相比链表结构,顺序表CPU高速缓存命中率更高
顺序表的缺点:
1.头部和中部插入删除效率低。(O(N))
2.扩容:性能消耗,空间浪费
链表的优点:
1.任意位置插入删除效率很高。O(1)
2.按需申请释放
链表的缺点:
1.不支持随机访问