• 数据结构-带头双向循环链表(增删查改)(2)


    前言

    学过了单链表,其实指的是无头单向不循环链表,今天的内容是有头双向循环链表,本篇博客重点是掌握有头双向循环链表代码,至于两者的区别以及顺序表和链表的区别,我们放到下一篇博客来说。

    代码

    一、List.h

    头文件、函数声明、结构体声明

    #pragma once
    
    #include 
    #include 
    #include 
    
    typedef int LTDataType;//类型于结构体里的data严格对应
    
    typedef struct ListNode
    {
    	struct ListNode* prev;
    	struct ListNode* next;
    	LTDataType data;
    }ListNode;
    
    //初始化
    ListNode* ListInit();
    //销毁空间
    void ListDestroy(ListNode* phead);
    //打印数据
    void ListPrint(ListNode* phead);
    
    //尾插
    void ListPushBack(ListNode* phead, LTDataType x);
    //头插
    void ListPushFront(ListNode* phead, LTDataType x);
    //尾删
    void ListPopBack(ListNode* phead);
    //头删
    void ListPopFront(ListNode* phead);
    //查找  修改可以直接利用查找在TestList函数中修改
    ListNode* ListFindNode(ListNode* phead, LTDataType x);
    //插入到pos的前面
    void ListInsert(ListNode* pos, LTDataType x);
    //删除pos位置数据
    void ListErase(ListNode* pos);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    二、Test.c

    mian函数和ListTest函数

    #define _CRT_SECURE_NO_WARNINGS
    
    #include "List.h"
    
    //带头双向循环链表--任意位置插入删除,时间复杂度都是O(1),只有查找是O(N)。
    void ListTest()
    {
    	ListNode* plist = ListInit();
    	//插入删除修改销毁
    
    }
    
    int main()
    {
    	ListTest();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    三、List.c

    实现函数各个功能

    1、非增删查改

    1.1、BuyNode

    说明:动态申请空间,并返回地址

    //申请空间
    ListNode* BuyNode(LTDataType x)
    {
    	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    	newnode->data = x;
    	newnode->next = NULL;
    	newnode->prev = NULL;
    
    	return newnode;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1.2、ListInit

    说明:初始化使用二级指针或者返回一级指针的方式创建链表的头
    注意:链表的头不存储数据,存储数据的第一个节点放在头的后面

    //初始化
    ListNode* ListInit()
    {
    	ListNode* phead = BuyNode(0);
    	phead->next = phead;
    	phead->prev = phead;
    	return phead;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1.3、ListDestory

    说明:释放动态内存空间

    //销毁空间
    void ListDestroy(ListNode* phead)
    {
    	assert(phead);
    
    	ListNode* cur = phead->next;
    	while (cur != phead)
    	{
    		ListNode* next = cur->next;
    		free(cur);
    		cur = next;
    	}
    
    	free(phead);//头phead是在ListInit函数malloc申请的空间,由ListDestory回收空间
    	phead = NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1.4、ListPrint

    说明:打印数据

    //打印数据
    void ListPrint(ListNode* phead)
    {
    	ListNode* cur = phead->next;
    	while (cur != phead)
    	{
    		printf("%d ", cur->data);
    		cur = cur->next;
    	}
    	printf("\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、头尾增删

    2.1、ListPushBack

    说明:尾插

    //尾插
    void ListPushBack(ListNode* phead, LTDataType x)
    {
    	assert(phead);
    
    	//法一
    	ListNode* tail = phead->prev;
    	ListNode* newnode = BuyNode(x);
    
        //技巧:先连后断,先next后prev
    	newnode->next = phead;
    	phead->prev = newnode;
    	tail->next = newnode;
    	newnode->prev = tail;
    
    	法二
    	//ListInsert(phead, x);//注意,是插入在phead的前面,就是尾插
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    2.2、ListPushFront

    说明:头插

    //头插
    void ListPushFront(ListNode* phead, LTDataType x)
    {
    	assert(phead);
    
    	//法一
    	ListNode* first = phead->next;
    	ListNode* newnode = BuyNode(x);
    
    	newnode->next = first;
    	first->prev = newnode;
    	phead->next = newnode;
    	newnode->prev = phead;
    
    	法二
    	//ListInsert(phead->next, x);//是插入在第一个节点前面,phead是头,下一个才是储存数据的第一个节点,所以是插入到phead->next的前面
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    2.3、ListPopBack

    说明:尾删

    //尾删
    void ListPopBack(ListNode* phead)
    {
    	assert(phead);
    	assert(phead->next != phead);
    
    	//法一
    	ListNode* tail = phead->prev;
    	ListNode* prev = tail->prev;
    	prev->next = phead;
    	phead->prev = prev;
    
    	free(tail);
    	tail = NULL;
    
    	法二
    	//ListErase(phead->prev);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    2.4、ListPopFront

    说明:头删

    //头删
    void ListPopFront(ListNode* phead)
    {
    	assert(phead);
    	assert(phead->next != phead);
    
    	//法一
    	ListNode* first = phead->next;
    	ListNode* second = first->next;
    	phead->next = second;
    	second->prev = phead;
    
    	free(first);
    	first = NULL;
    
    	法二
    	//ListErase(phead->next);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3、指定增删+查(改)

    3.1、ListFindNode

    说明:查找数据并返回这个数据所在的结构体地址
    注意:修改可以使用查找返回的地址直接在ListTest函数中修改

    //查找
    ListNode* ListFindNode(ListNode* phead, LTDataType x)
    {
    	assert(phead);
    
    	ListNode* cur = phead->next;
    	while (cur != phead)
    	{
    		if (cur->data == x)
    		{
    			return cur;
    		}
    		cur = cur->next;
    	}
    	return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    3.2、ListInsert

    说明:插入到ListFindNode返回的结构体,pos的前面

    //插入到pos的前面
    void ListInsert(ListNode* pos, LTDataType x)
    {
    	assert(pos);
    	ListNode* prev = pos->prev;
    	ListNode* newnode = BuyNode(x);
    
    	newnode->next = pos;
    	pos->prev = newnode;
    	prev->next = newnode;
    	newnode->prev = prev;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    3.3、ListErase

    说明:删除链表中ListFindNode返回的结构体,pos位置

    //删除pos位置数据
    void ListErase(ListNode* pos)
    {
    	assert(pos);
    	ListNode* prev = pos->prev;
    	ListNode* next = pos->next;
    
    	prev->next = next;
    	next->prev = prev;
    	free(pos);
    	pos = NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    总结

    1. 初始化:带头双向循环链表,ListInit使用二级指针或者返回一级指针创建链表的头完成初始化。
    2. phead理解:链表的头不存储数据,存储数据的第一个节点放在头的后面。
    3. 时间复杂度:带头双向循环链表,任意位置插入删除,时间复杂度都是O(1),只有查找是O(N)
    4. 插入时技巧:先连后断,先next后prev
    5. 函数复用:头插尾插可以用ListInsert函数实现,头删尾删可以用ListErase实现
    6. 难点理解:尾插用ListInser实现时,pos位置是phead,因为是循环链表,即让数据newnode插入到phead的前面就完成了尾插
    7. 重要的事情说三遍:画图!画图!!画图!!!
  • 相关阅读:
    Spark简单回顾
    QT-day4作业
    uni-app路由
    SpringBoot 创建非web工程——2种实现方法
    8月22日计算机视觉理论学习笔记——CNN
    关于RabbitMQ的一些面试题
    多线程事务在Junit、Mybatis中使用
    使用Locust进行分布式性能测试
    ZZNUOJ_C语言1038:绝对值最大(完整代码)
    Educational Codeforces Round 133 (Rated for Div. 2) C补题
  • 原文地址:https://blog.csdn.net/m0_63979882/article/details/126816735