上篇文章我们实现了单链表
#3单链表的实现#_努力的小恒的博客
这次我们来实现双向链表
先回顾一下双向链表结构
1.双向链表的概念及其结构
我们将实现带头双向循环链表的增删查改 最常用的双向链表
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
2.准备工作
VS2022下创建
List.h:函数声明
List.c:函数实现
test.c:功能测试
3.双向链表结构体的定义和头文件的引用
List.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDateType;
typedef struct ListNode
{
LTDateType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
data:放数据
next:指向下一个地址
prev:指向上一个
4.初始化
1°思路
开辟一个哨兵位的头节点(带头)
实际上不放数据 当只有哨兵位的头节点时实现尾插和头插不需要分类 因为已经有头了
这个头的prev指向自己 next也要指向自己
当只有head的时候 相当于head->next要指向head自己 head->prev也要指向head自己
2.实现
List.h
//初始化
LTNode* ListInit();
List.c
//初始化
LTNode* ListInit()
{
//哨兵位头节点
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
先得到一个哨兵位的头节点phead返回
测试的时候用一个指针接受头部 这样就拿到了哨兵位的头节点
5.打印
1.思路
遍历整个链表 通过访问data打印数据
和单链表的遍历一样 循环条件改一下
图解可见:
#3单链表的实现#_努力的小恒的博客
2.实现
List.h
//打印
void ListPrint(LTNode* phead);
List.c
//打印
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
assert断言 哨兵位头节点为空的话打印不了
以cur为phead->next往下走 当走了一圈回来后到phead就停
6.尾插
1°思路
开辟一个新节点newnode 放数据
用malloc函数开辟存成newnode
data放数据 prev和next都制空
返回newnode
然后在其他函数内部开辟的时候
用newnode接收
连接phead tail(原来尾部) newnode
tail的next指向newnode的prev
newnode的prev指向tail
newnode的next指向phead
phead的prev指向newnode
2°实现
List.h
//尾插
void ListPushBack(LTNode* phead, LTDateType x);
List.c
//开辟新节点并放入数据
LTNode* BuyListNode(LTDateType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//尾插
void ListPushBack(LTNode* phead, LTDateType x)
{
//哨兵位头节点还在
assert(phead);
//尾部就是头部的prev
LTNode* tail = phead->prev;
LTNode* newnode = BuyListNode(x);
//tail和newnode链接
tail->next = newnode;
newnode->prev = tail;
//newnode和phead链接
newnode->next = phead;
phead->prev = newnode;
}
开辟后返回newnode
assert断言说明至少有一个哨兵位的头节点
3°测试
test.c
#include "List.h"
void TestList1()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//打印1 2 3 4
ListPrint(plist);
}
int main()
{
TestList1();
return 0;
}
运行结果
7.尾删
1°思路
先通过phead的prev找到尾部tail
再通过尾部tail的prev找到tailPrev(也就是尾部的前一个)
再做好phead和tailPrev的连接:
phead->prev=tailPrev
tailPrev->next=phead
最后释放tail
2°实现
List.h
//尾删
void ListPopBack(LTNode* phead);
List.c
//尾删
void ListPopBack(LTNode* phead)
{
//phead不为空
assert(phead);
//说明链表为空 phead->next指向自己
//只有一个哨兵位的头节点 不能再删了
assert(phead->next != phead);
LTNode* tail = phead->prev;
tail->prev->next = phead;
phead->prev = tail->prev;
free(tail);
}
assert断言 phead不为空 有哨兵位的头节点
assert断言 phead->next不指向自己 说明链表不只有phead 还有其他数据 就可以删除
如果只有phead 那就不能删除
3°测试
test.c
#include "List.h"
void TestList2()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//尾删2次
ListPopBack(plist);
ListPopBack(plist);
//打印1 2
ListPrint(plist);
}
int main()
{
TestList2();
return 0;
}
运行结果
8.头插
1°思路
注意我们已经有了一个哨兵位的头
再头插相当于在phead和phead的下一个之间插入一个新节点
先开辟一个新节点newnode
先通过phead的next找到pheadNext
再做好phead newnode pheadNext的连接:
phead->next=newnode
newnode->prev=phead
newnode->next=pheadNext
pheadNext->prev=newnode
2°实现
List.h
//头插
void ListPushFront(LTNode* phead, LTDateType x);
List.c
//头插
void ListPushFront(LTNode* phead, LTDateType x)
{
assert(phead);
LTNode* next = phead->next;
LTNode* newnode = BuyListNode(x);
phead->next = newnode;
newnode->prev = phead;
newnode->next = next;
next->prev = newnode;
}
assert断言 phead不为空 至少有哨兵位的头节点
3°测试
test.c
#include "List.h"
TestList3()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//头插1 2 3 4
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
//打印4 3 2 1
ListPrint(plist);
}
int main()
{
TestList3();
return 0;
}
运行结果
9.头删
1°思路
先通过phead的next找到pheadNext
再通过phead的next的next找到pheadnextNext
再做好phead和pheadnextNext的连接:
phead->next=pheadnextNext
pheadnextNext->prev=phead
最后释放pheadnext
2°实现
List.h
//头删
void ListPopFront(LTNode* phead);
List.c
//头删
void ListPopFront(LTNode* phead)
{
assert(phead);
//防止删除哨兵位
assert(phead->next != phead);
LTNode* next = phead->next;
LTNode* nextNext = next->next;
phead->next = nextNext;
nextNext->prev = phead;
free(next);
}
assert(phead)至少有一个哨兵位节点
assert(phead->next!=phead)说明不只有哨兵位 还有其他节点
如果只有哨兵位就防止删除哨兵位
3°测试
test.c
#include "List.h"
TestList4()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//头删2次
ListPopFront(plist);
ListPopFront(plist);
//打印3 4
ListPrint(plist);
}
int main()
{
TestList4();
return 0;
}
运行结果
10.查找/修改
1°思路
给一个cur指针 从phead的下一个(真正的头部)开始遍历整个链表
找到了就返回cur 找不到就返回NULL
2°实现
List.h
//找
LTNode* ListFind(LTNode* phead, LTDateType x);
List.c
//找
LTNode* ListFind(LTNode* phead, LTDateType x)
{
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
调用的时候定义一个pos指针接收
修改可以通过pos指针访问data进行修改
3°测试
test.c
#include "List.h"
TestList5()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 2 4 2
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 2);
ListPushBack(plist, 4);
ListPushBack(plist, 2);
//查找3
//把第一个3改成30
LTNode* pos = ListFind(plist, 3);
int i = 1;
if (pos)
{
printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
LTNode* ppos = pos;
while (ppos)
{
//后一个位置继续开始找
ppos = ListFind(ppos->next, 3);
if (ppos == pos)
break;
printf("第%d个pos节点:%p->%d\n", i++, ppos, pos->data);
}
if (pos)
pos->data = 30;
else
printf("未找到此数据 修改失败\n");
}
else
{
printf("未找到此数据\n");
}
//打印1 2 30 2 4 2
ListPrint(plist);
printf("\n");
//查找2
//把第二个2改成20
//3个2的地址
pos = ListFind(plist, 2);
if (pos)
{
i = 1;
printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
LTNode* ppos = pos;
while (ppos)
{
//后一个位置继续开始找
ppos = ListFind(ppos->next, 2);
if (ppos == pos)
break;
if (ppos && i == 2)
ppos->data = 20;
printf("第%d个pos节点:%p->%d\n", i++, ppos, pos->data);
}
}
//打印1 2 30 20 4 2
ListPrint(plist);
printf("\n");
//查找100
pos = ListFind(plist, 100);
i = 1;
if (pos)
{
printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
LTNode* ppos = pos;
while (ppos)
{
//后一个位置继续开始找
ppos = ListFind(ppos->next, 3);
if (ppos == pos)
break;
printf("第%d个pos节点:%p->%d\n", i++, ppos, pos->data);
}
}
else
{
printf("未找到此数据\n");
}
}
int main()
{
TestList5();
return 0;
}
相较于单链表中的查找/修改有了升级
1:查找会打印出地址
2:单链表中只实现了查找多个数据 不能修改后面的数据
这里可以修改第二个2
运行结果
11.在pos之前插入
1°思路
开辟一个新节点newnode
先通过pos的prev找到posPrev
再连接posPrev newnode pos:
posPrev->next=newnode
newnode->prev=posPrev
newnode->next=pos
pos->prev=newnode
2°实现
List.h
//在pos之前插入x
void ListInsert(LTNode* pos, LTDateType x);
List.c
//在pos之前插入x
void ListInsert(LTNode* pos, LTDateType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = BuyListNode(x);
//posPrev newnode pos
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
assert(pos) pos不为NULL
如果pos为NULL posPrev=pos->prev pos的解引用 空指针解引用非法访问
3°测试
test.c
#include "List.h"
TestList6()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//在2前面插入10
LTNode* pos = ListFind(plist, 2);
if (pos)
ListInsert(pos, 10);
else
printf("无该数据 插入失败\n");
//打印1 10 2 3 4
ListPrint(plist);
printf("\n");
//在100前面插入10
pos = ListFind(plist, 100);
if (pos)
ListInsert(pos, 10);
else
printf("无该数据 插入失败\n");
//打印1 10 2 3 4
ListPrint(plist);
}
int main()
{
TestList6();
return 0;
}
运行结果
既然实现在pos之前插入
头插和尾插可以复用
改进如下:
头插:
//头插
void ListPushFront(LTNode* phead, LTDateType x)
{
ListInsert(phead->next, x);
}
pos就是phead的下一个
尾插:
//尾插
void ListPushBack(LTNode* phead, LTDateType x)
{
ListInsert(phead, x);
}
phead的上一个就是尾部 所以phead就是pos
测试情况test.c
#include "List.h"
TestList7()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插3 2 1
ListPushBack(plist, 3);
ListPushBack(plist, 2);
ListPushBack(plist, 1);
//打印3 2 1
ListPrint(plist);
//头插4 5 6
ListPushFront(plist, 4);
ListPushFront(plist, 5);
ListPushFront(plist, 6);
//打印6 5 4 3 2 1
ListPrint(plist);
}
int main()
{
TestList7();
return 0;
}
运行结果
12.在pos位置删除
1°思路
先通过pos的prev找到posPrev
再通过pos的next找到posNext
再连接posPrev posNext:
posPrev->next=posNext
posNext->prev=posPrev
最后释放pos
2°实现
List.h
//在pos位置删除
void ListErase(LTNode* pos);
List.c
//在pos位置删除
void ListErase(LTNode* pos)
{
assert(pos);
assert(pos->next != pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
assert(pos) pos不为空 空指针不能删除
assert(pos->next!=pos) pos不为phead phead不能删除
最后释放
3°测试
test.c
#include "List.h"
TestList8()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//删除2
LTNode* pos = ListFind(plist, 2);
if (pos)
{
ListErase(pos);
}
else
printf("无此数据 删除失败\n");
//打印1 3 4
ListPrint(plist);
printf("\n");
//删除100
pos = ListFind(plist, 100);
if (pos)
{
ListErase(pos);
}
else
printf("无此数据 删除失败\n");
//打印1 3 4
ListPrint(plist);
}
int main()
{
TestList8();
return 0;
}
运行结果
实现在pos位置删除
那么头删和尾删可以复用
改进如下:
头删:
//头删
void ListPopFront(LTNode* phead)
{
ListErase(phead->next);
}
phead的下一个就是pos
尾删:
//尾删
void ListPopBack(LTNode* phead)
{
ListErase(phead->prev);
}
phead的上一个就是pos
测试结果
#include "List.h"
TestList9()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//打印1 2 3 4
ListPrint(plist);
//头删1次
ListPopFront(plist);
//打印2 3 4
ListPrint(plist);
//尾删1次
ListPopBack(plist);
//打印2 3
ListPrint(plist);
}
int main()
{
TestList9();
return 0;
}
运行结果
13.销毁
1°思路
给一个cur指针为phead->next开始遍历链表
循环条件cur!=phead
先把下一个存着再释放
定义next=cur->next
释放cur
再把next给到cur
2°实现
List.h
//销毁加制空
void ListDestroy(LTNode* phead);
List.c
//销毁
void Destroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
//里面制空要传二级指针才能改变
//所以要在外面制空 或者写一个大函数里面制空
phead = NULL;
}
//销毁加制空
void ListDestroy(LTNode* phead)
{
Destroy(phead);
phead = NULL;
}
为了使用的时候不用再制空
写一个大函数在大函数里面制空
test.c
#include "List.h"
TestList10()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//打印1 2 3 4
ListPrint(plist);
//销毁
ListDestroy(plist);
plist = NULL;
//链表为空 打印不了 assert断言
ListPrint(plist);
}
int main()
{
TestList10();
return 0;
}
运行结果
14.完整代码
List.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int LTDateType;
typedef struct ListNode
{
LTDateType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;
//初始化
LTNode* ListInit();
//打印
void ListPrint(LTNode* phead);
//尾插
void ListPushBack(LTNode* phead, LTDateType x);
//尾删
void ListPopBack(LTNode* phead);
//头插
void ListPushFront(LTNode* phead, LTDateType x);
//头删
void ListPopFront(LTNode* phead);
//找
LTNode* ListFind(LTNode* phead, LTDateType x);
//在pos之前插入x
void ListInsert(LTNode* pos, LTDateType x);
//在pos位置删除
void ListErase(LTNode* pos);
//销毁加制空
void ListDestroy(LTNode* phead);
List.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "List.h"
//初始化
LTNode* ListInit()
{
//哨兵位头节点
LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
phead->next = phead;
phead->prev = phead;
return phead;
}
//开辟新节点并放入数据
LTNode* BuyListNode(LTDateType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//打印
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
//尾插
void ListPushBack(LTNode* phead, LTDateType x)
{
哨兵位头节点还在
//assert(phead);
尾部就是头部的prev
//LTNode* tail = phead->prev;
//LTNode* newnode = BuyListNode(x);
tail和newnode链接
//tail->next = newnode;
//newnode->prev = tail;
newnode和phead链接
//newnode->next = phead;
//phead->prev = newnode;
ListInsert(phead, x);
}
//尾删
void ListPopBack(LTNode* phead)
{
phead不为空
//assert(phead);
说明链表为空 phead->next指向自己
只有一个哨兵位的头节点 不能再删了
//assert(phead->next != phead);
//LTNode* tail = phead->prev;
//tail->prev->next = phead;
//phead->prev = tail->prev;
//free(tail);
ListErase(phead->prev);
}
//头插
void ListPushFront(LTNode* phead, LTDateType x)
{
//assert(phead);
//LTNode* next = phead->next;
//LTNode* newnode = BuyListNode(x);
//phead->next = newnode;
//newnode->prev = phead;
//newnode->next = next;
//next->prev = newnode;
ListInsert(phead->next, x);
}
//头删
void ListPopFront(LTNode* phead)
{
//assert(phead);
防止删除哨兵位
//assert(phead->next != phead);
//LTNode* next = phead->next;
//LTNode* nextNext = next->next;
//phead->next = nextNext;
//nextNext->prev = phead;
//free(next);
ListErase(phead->next);
}
//找
LTNode* ListFind(LTNode* phead, LTDateType x)
{
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos之前插入x
void ListInsert(LTNode* pos, LTDateType x)
{
assert(pos);
LTNode* posPrev = pos->prev;
LTNode* newnode = BuyListNode(x);
//posPrev newnode pos
posPrev->next = newnode;
newnode->prev = posPrev;
newnode->next = pos;
pos->prev = newnode;
}
//在pos位置删除
void ListErase(LTNode* pos)
{
assert(pos);
assert(pos->next != pos);
LTNode* posPrev = pos->prev;
LTNode* posNext = pos->next;
posPrev->next = posNext;
posNext->prev = posPrev;
free(pos);
pos = NULL;
}
//销毁
void Destroy(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
//里面制空要传二级指针才能改变
//所以要在外面制空 或者写一个大函数里面制空
phead = NULL;
}
//销毁加制空
void ListDestroy(LTNode* phead)
{
Destroy(phead);
phead = NULL;
}
test.c
#include "List.h"
void TestList1()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//打印1 2 3 4
ListPrint(plist);
}
void TestList2()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//尾删2次
ListPopBack(plist);
ListPopBack(plist);
//打印1 2
ListPrint(plist);
}
TestList3()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//头插1 2 3 4
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
//打印4 3 2 1
ListPrint(plist);
}
TestList4()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//头删2次
ListPopFront(plist);
ListPopFront(plist);
//打印3 4
ListPrint(plist);
}
TestList5()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 2 4 2
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 2);
ListPushBack(plist, 4);
ListPushBack(plist, 2);
//查找3
//把第一个3改成30
LTNode* pos = ListFind(plist, 3);
int i = 1;
if (pos)
{
printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
LTNode* ppos = pos;
while (ppos)
{
//后一个位置继续开始找
ppos = ListFind(ppos->next, 3);
if (ppos == pos)
break;
printf("第%d个pos节点:%p->%d\n", i++, ppos, pos->data);
}
if (pos)
pos->data = 30;
else
printf("未找到此数据 修改失败\n");
}
else
{
printf("未找到此数据\n");
}
//打印1 2 30 2 4 2
ListPrint(plist);
printf("\n");
//查找2
//把第二个2改成20
//3个2的地址
pos = ListFind(plist, 2);
if (pos)
{
i = 1;
printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
LTNode* ppos = pos;
while (ppos)
{
//后一个位置继续开始找
ppos = ListFind(ppos->next, 2);
if (ppos == pos)
break;
if (ppos && i == 2)
ppos->data = 20;
printf("第%d个pos节点:%p->%d\n", i++, ppos, pos->data);
}
}
//打印1 2 30 20 4 2
ListPrint(plist);
printf("\n");
//查找100
pos = ListFind(plist, 100);
i = 1;
if (pos)
{
printf("第%d个pos节点:%p->%d\n", i++, pos, pos->data);
LTNode* ppos = pos;
while (ppos)
{
//后一个位置继续开始找
ppos = ListFind(ppos->next, 3);
if (ppos == pos)
break;
printf("第%d个pos节点:%p->%d\n", i++, ppos, pos->data);
}
}
else
{
printf("未找到此数据\n");
}
}
TestList6()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//在2前面插入10
LTNode* pos = ListFind(plist, 2);
if (pos)
ListInsert(pos, 10);
else
printf("无该数据 插入失败\n");
//打印1 10 2 3 4
ListPrint(plist);
printf("\n");
//在100前面插入10
pos = ListFind(plist, 100);
if (pos)
ListInsert(pos, 10);
else
printf("无该数据 插入失败\n");
//打印1 10 2 3 4
ListPrint(plist);
}
TestList7()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插3 2 1
ListPushBack(plist, 3);
ListPushBack(plist, 2);
ListPushBack(plist, 1);
//打印3 2 1
ListPrint(plist);
//头插4 5 6
ListPushFront(plist, 4);
ListPushFront(plist, 5);
ListPushFront(plist, 6);
//打印6 5 4 3 2 1
ListPrint(plist);
}
TestList8()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//删除2
LTNode* pos = ListFind(plist, 2);
if (pos)
{
ListErase(pos);
}
else
printf("无此数据 删除失败\n");
//打印1 3 4
ListPrint(plist);
printf("\n");
//删除100
pos = ListFind(plist, 100);
if (pos)
{
ListErase(pos);
}
else
printf("无此数据 删除失败\n");
//打印1 3 4
ListPrint(plist);
}
TestList9()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//打印1 2 3 4
ListPrint(plist);
//头删1次
ListPopFront(plist);
//打印2 3 4
ListPrint(plist);
//尾删1次
ListPopBack(plist);
//打印2 3
ListPrint(plist);
}
TestList10()
{
//初始化 头部为plist
LTNode* plist = ListInit();
//尾插1 2 3 4
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
//打印1 2 3 4
ListPrint(plist);
//销毁
ListDestroy(plist);
plist = NULL;
//链表为空 打印不了 assert断言
ListPrint(plist);
}
int main()
{
//TestList1();//测试尾插
//TestList2();//测试尾删
//TestList3();//测试头插
//TestList4();//测试头删
//TestList5();//测试查找/修改
//TestList6();//测试在pos之前插入
//TestList7();//测试在pos之前插入实现后复用头插和尾插
//TestList8();//测试在pos位置删除
//TestList9();//测试在pos位置删除实现后复用头删和尾删
//TestList10();//测试销毁加制空
return 0;
}
菜单版和单链表原理一样
单链表的菜单版未实现多次插入同一个数据后修改第n个数据
通过一些输入和输出也可以实现
双向链表菜单版可参考单链表的菜单版 把函数名字改一下
详细见:#3单链表的实现#_努力的小恒的博客
15.总结
顺序表和链表的区别
这两个结构各有优势
很难说谁更优
严格来说他们说 相辅相成的两个结构顺序表
优点:
1.支持随机访问 需要随机访问结构支持算法可以很好的适用
2.cpu高速缓存命中率更高缺点:
1.头部中部插入删除时间效率低 O(N)
2.连续的物理空间 空间不够了以后需要增容
增容有一定程度消耗
为了避免频繁增容 一般我们都按倍数去增
用不完可能存在一定的空间浪费链表(双向带头循环链表):
优点:
1.任意位置插入删除效率高 O(1)
2.按需申请释放空间
缺点:
1.不支持随机访问(用下标访问)
意味着:一些排序 二分查找等在这种
结构上不适用
2.链表存储一个值 同时要存储链接指针
也有一定的消耗
3.cpu高速缓存命中率更低
#4双向链表的实现及总结#完