一、引入
单链表的缺陷:
尾删,随机插入,随机删除的时间复杂度都是O(N),都需要找到pos的前一个。
引出—>双向链表
实际中的链表结构:实际中要实现的链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向 双向
- 带头 不带头
- 循环 不循环
二、带头双向循环链表
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
三、接口设置
- 头文件设置
// Created by zjc on 2024/10/21 16:53
#pragma once
#include "stdio.h"
#include "stdlib.h"
#include "assert.h"
#include "stdbool.h"
typedef int LTDateType;
// 带头双向循环 -- 最优链表结构,任意位置插入删除数据都是O(1)
typedef struct ListNode {
// 后继指针和前驱指针
struct ListNode *next;
struct ListNode *prev;
LTDateType data;
}ListNode;
// 0.初始化
ListNode *ListInit();
// 0.打印
ListNode *ListPrint(ListNode* phead);
// 0.销毁
void ListDestory(ListNode* phead);
// 1.尾插
void ListPushBack(ListNode* phead,LTDateType x);
// 2.头插
void ListPushFront(ListNode* phead,LTDateType x);
// 3.头删
void ListPopFront(ListNode* phead);
// 4.尾删
void ListPopBack(ListNode* phead);
// 5.查找同时修改 找到相应值所在的位置pos ,返回pos
ListNode * ListFind(ListNode* phead,LTDateType x);
// 6. 随机插入 pos位置插入
void ListInsert(ListNode* pos,LTDateType x);
// 7. 随机删除 pos位置删除
void ListErase(ListNode* pos);
// 8. 判空: phead->next 是否 等于 phead
bool ListEmpty(ListNode* phead);
// 9. 链表长度:循环到下一个节点是phead就结束
int ListSize(ListNode* phead );
- 接口设置
0.开辟空间:
#include "List.h"
// 0.开辟空间
ListNode *BuyListNode(LTDateType x) {
ListNode *newnode = (ListNode *) malloc(sizeof(ListNode));
newnode->data = x;
newnode->next = NULL; // 只是创建你要的节点,先创建成空
newnode->prev = NULL;
return newnode;
}
0.初始化
// 0.初始化
ListNode *ListInit() {
//1. 初始化先让哨兵位的next和prev都指向自己
//2. 先给值为0,得到空间
ListNode *phead = BuyListNode(0);
// 先赋值给自己
phead->next = phead;
phead->prev = phead;
return phead;
}
0.销毁
// 0.销毁
void ListDestory(ListNode* phead){
assert(phead);
ListNode * cur = phead->next;
// 如果等于phead就退出循环
while(cur != phead){
// 保存他的下一个,要不然就找不到下一个了
ListNode * next = cur->next;
free(cur);
// 释放掉后,将下一个赋值给cur
cur = next;
}
// 完成之后将phead也释放掉
free(phead);
phead = NULL;
}
0.打印
// 0.打印
ListNode *ListPrint(ListNode *phead) {
// 头指针next指向第一个元素
ListNode *cur = phead->next;
// 循环到head结束,不等于head
while (cur != phead) {
printf("%d ", cur->data);
// 输出值后,将下一个next赋值给cur
cur = cur->next;
}
printf("\n");
}
1.尾插
// 1.尾插
void ListPushBack(ListNode *phead, LTDateType x) {
// 加一个断言,如果为NULL直接中止,断言有利于快速找到错误位置
assert(phead);
// 找到尾
ListNode *tail = phead->prev;
// 开辟新空间存储插入的数据
ListNode *newnode = BuyListNode(x);
// 以下连接到链表后:
// 尾节点的下一个指向新节点
tail->next = newnode;
// 新开辟的前驱节点指向上一个节点
newnode->prev = tail;
// 新开辟的后继节点指向头节点
newnode->next = phead;
// 头节点的前驱指向新开辟的节点
phead->prev = newnode;
/*
说明:快速写一个链表只需要写一个随机插入和随机删除
1.这里只需要调用随机插入函数
2.传入phead的位置
ListInsert(phead,x);
*/
}
2.头插
// 2.头插
void ListPushFront(ListNode *phead, LTDateType x) {
assert(phead);
ListNode *first = phead->next;
/*
不定义first也可以这样写:顺序是不能变的
1.将头结点的下一个节点赋值给新节点的下一个
newnode->next = phead->next;
2.将第一个节点的前驱指向新节点
phead->next->prev = newnode;
3.头结点的next指向newnode
phead->next = newnode;
4.新节点的prev指向头结点
newnode->prev = phead;
*/
// 得到一个新的空间
ListNode *newnode = BuyListNode(x);
// phead newnode first 连接之间的关系
phead->next = newnode;
first->prev = newnode;
newnode->prev = phead;
newnode->next = first;
/*
说明:快速写一个链表只需要写一个随机插入和随机删除
1.这里只需要调用随机插入函数
2.将phead->next 传入第一个元素的位置
ListInsert(phead->next,x);
*/
}
3.头删
// 3.头删
void ListPopFront(ListNode *phead) {
assert(phead);
// 如果全部节点都删除完了,一个节点都没有了,需要进行一个判断
assert(phead->next != phead);
ListNode *first = phead->next;
ListNode *second = first->next;
phead->next = second;
second->prev = phead;
// 释放元素,可以置为NULL
free(first);
first = NULL;
/*
说明:快速写一个链表只需要写一个随机插入和随机删除
1.这里只需要调用随机删除函数
2.将phead->next 传入就是头删
ListErase(phead->next);
*/
}
4.尾删
// 4.尾删
void ListPopBack(ListNode *phead) {
assert(phead);
// 如果全部节点都删除完了,一个节点都没有了,需要进行一个判断
assert(phead->next != phead);
ListNode *tail = phead->prev;
ListNode *prev = tail->prev;
phead->prev = prev;
prev->next = phead;
free(tail);
tail = NULL;
/*
说明:快速写一个链表只需要写一个随机插入和随机删除
1.这里只需要调用随机删除函数
2.将phead->prev 传入就是尾删
ListErase(phead->prev);
*/
}
5.查找
// 5.查找 找到相应值所在的位置pos ,返回pos
/*
* 查找算法:1.平衡搜索树(AVL树和红黑树)
* 2. 哈希表
* 3.B树 B+树
*/
ListNode *ListFind(ListNode *phead, LTDateType x) {
assert(phead);
ListNode *cur = phead->next;
while (cur != phead) {
if (cur->data == x) {
// 找到了就返回当前节点指针
return cur;
}
cur = cur->next;
}
// 找完都还没有找到就返回一个空
return NULL;
}
6.随机插入
// 6. 随机插入 pos位置插入
void ListInsert(ListNode *pos, LTDateType x) {
assert(pos);
ListNode *prev = pos->prev;
ListNode *newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
7.随机删除
// 7. 随机删除 pos位置删除
void ListErase(ListNode *pos) {
assert(pos);
ListNode *prev = pos->prev;
ListNode *next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
Test.c文件
// Created by zjc on 2024/10/21 16:52
#include "List.h"
void TestList1(){
// 返回一个结构体指针赋值给plist
ListNode * plist = ListInit();
// 尾插
ListPushBack(plist,1);
ListPushBack(plist,2);
ListPushBack(plist,3);
ListPushBack(plist,4);
// 头插
ListPushFront(plist,5);
// 打印(头插看效果)
ListPrint(plist);
// 头删
ListPopFront(plist);
// 打印(头删看效果)
ListPrint(plist);
// 尾删
ListPopBack(plist);
// 打印(尾删看效果)
ListPrint(plist);
// 调用函数返回查找的值
ListNode * pos = ListFind(plist,2);
// 返回值不等于NULL
if(pos){
// 5. find同时可以改数据: 比如我想把pos位置值*10
pos->data *= 10;
printf("Found\n");
}else{
printf("Not found\n");
}
// 打印(查找修改后看效果)
ListPrint(plist);
// 随机插入
ListInsert(pos,100);
// 打印(随机插入看效果)
ListPrint(plist);
// 随机删除
ListErase(pos);
// 打印(随机删除看效果)
ListPrint(plist);
ListDestory(plist);
}
void TestList2(){
// 返回一个结构体指针赋值给plist
ListNode * plist = ListInit();
// 尾插
ListPushBack(plist,1);
ListPushBack(plist,2);
ListPushBack(plist,3);
ListPushBack(plist,4);
// 头插
ListPushFront(plist,5);
// 打印(头插看效果)
ListPrint(plist);
// 头删
ListPopFront(plist);
// 打印(头删看效果)
ListPrint(plist);
}
int main(){
// TestList1();
TestList2();
}