Bootstrap

数据结构 | 第三章 | 带头双向循环链表

一、引入

单链表的缺陷

尾删,随机插入,随机删除的时间复杂度都是O(N),都需要找到pos的前一个。

引出—>双向链表

实际中的链表结构:实际中要实现的链表的结构非常多样,以下情况组合起来就有8种链表结构:

  1. 单向 双向
  2. 带头 不带头
  3. 循环 不循环

image-20241021172746706

二、带头双向循环链表

  1. 无头单向非循环链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

image-20241021175627324

三、接口设置

  1. 头文件设置
// 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 );
  1. 接口设置

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.销毁

image-20241030213040898

// 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.尾插

image-20241030211725556

// 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.头插

image-20241030211804151

// 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.头删
image-20241030211837890

// 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.尾删

image-20241030211940989

// 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.查找

image-20241030212026949

// 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.随机插入

image-20241030212117977

// 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.随机删除

image-20241030212851299

// 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();

}
;