当前位置: 首页 > news >正文

初阶数据结构:链表(二)

目录

 一、前言

二、带头双向循环链表

1.带头双向循环链表的结构

(1)什么是带头?

  (2)什么是双向呢?

(3)那什么是循环呢?

2.带头双向循环链表的实现

(1)节点结构

(2)创建链表的头节点,也即哨兵节点

(3)创建其他节点和链表打印

(4)链表尾插和尾删功能的实现

(5)链表的头插和头删

(6)链表的查找

(7)双链表在pos位置前后插入和删除pos位置功能


一、前言

          在上一篇博客中,我们实现的是单链表。我们知道链表有8种结构,由单向和双向、有头和无头、循环和非循环组合而成。单链表就是无头单向非循环链表。它是一种结构简单的链表,通常不会用来单独作为存储数据用,实际中更多的是作为其它数据结构的子结构存在,如哈希桶、图的邻接等等。单链表虽然在头插、头删方面很方便,但在尾插和尾删又比不过顺序表。那么有没有一种链表,在头插头删和尾插尾删上都很方便呢?

         当然有,那就是链表中的“王者”------带头双向循环链表。它的结构非常复杂,但效率极高。让我们来看看带头双向循环链表的结构吧!

二、带头双向循环链表

1.带头双向循环链表的结构

(1)什么是带头?

指链表中没有存储数据的节点时,头指针仍然指向一个节点,这个节点不存储数据,只起到站位的作用,其后才是链表的实际数据节点,因此它也被成为哨兵节点。

它的作用是什么呢?在单链表中,我们在改变头指针的链接对象时,需要使用二级指针。有了哨兵节点,我们就不需要二级指针。在执行插入、删除等操作时,也不需要对链表是否为空或是否为最后一个节点进行特殊判断,从而使代码更加简洁和统一,也让我们不至于被绕晕。

  (2)什么是双向呢?

看图就可以明白。

节点结构体指针域中有两个指针。一个指向上一个节点,一个指向下一个节点。如此就可以由一个链表中的一个节点位置得到所有节点的位置。

(3)那什么是循环呢?

循环链表则是将尾节点的后继指针指向头结点,而头结点的前驱指针指向尾节点,从而形成一个闭环。这样的设计使得从链表的任何一个节点开始都可以很方便地遍历整个链表,无论是向前还是向后。也即:

循环链表的哨兵节点的头指针指向尾节点,尾节点的next指针指向哨兵节点。

不要看带头双向循环链表的结构很复杂,就认为它的实现也很难,正因为结构如此,它的实现也避开了需多难题。相比于单链表的实现,它反而简单。

2.带头双向循环链表的实现

(1)节点结构

为了实现双向,那么作为节点的结构体就需要有两个指针和存储数据的位置。代码如下:

typedef int type;
typedef struct ListNde
{struct ListNde* next;//指向下一个节点struct ListNde* head;//指向上一个节点type data;//存储数据
}ListNode;

(2)创建链表的头节点,也即哨兵节点

先看代码:

ListNode* ListCreate()
{//动态申请一个结构体空间ListNode* head = (ListNode*)malloc(sizeof(ListNode));if (head == NULL){perror("ListCreate::malloc");return NULL;}//使节点不存储数据head->data = NULL;//因为作为头节点存在,因此在没有其他节点时,需要让//前指针和后指针都指向自己head->head = head;head->next = head;//如果不返回,那就需要传二级指针来使头指针和哨兵节点链接return head;
}

为什么哨兵节点的前指针和后指针都要指向自己。看图:

(3)创建其他节点和链表打印

创建其他节点和之前单链表创建节点一样,代码如下:

ListNode* BuyListNode()
{ListNode* ptr = (ListNode*)malloc(sizeof(ListNode));if (ptr == NULL){perror("BuyListNode::malloc");return NULL;}ptr->head = NULL;ptr->next = NULL;return ptr;
}

链表打印和单链表打印大至一样,循环打印即可,但与单链表打印不同的是,它需要有一个循环终止条件,单纯的不为空可不行,带头双向循环链表可没有为空。而且因为哨兵节点没有存储数据,因此要避免打印哨兵节点。代码如下:

void ListPrint(ListNode* plist)
{//不打印哨兵节点,哨兵节点不存储任何数据ListNode* ptr = plist->next;//以这个代表哨兵节点printf("<head>");//循环打印while (ptr){printf("<%d>", ptr->data);//因为带头双向循环链表尾节点和哨兵节点是相链接的//所以需要一个条件来作为循环的终止条件if (ptr->next == plist){//ptr指向尾节点时,ptr->next指向哨兵节点,退出循环break;}ptr = ptr->next;}
}

(4)链表尾插和尾删功能的实现

先看图:

我们想要使新节点和链表链接最好带顺序的去进行指针的互换,不然容易漏掉,或者换错。我们先让尾节点的后指针指向新节点,新节点的前指针指向尾节点,新节点的后指针指向哨兵节点,哨兵节点的前指针指向新节点,这样按顺序来,既不容易漏掉,也具有逻辑美,更容易理解。代码如下

void ListPushBack(ListNode* plist)
{//断言判断plist不为空,因为有哨兵节点存在,那么plist//传过来的必定不为空assert(plist);ListNode* ptr = plist;//由ptr->head得到尾节点ListNode* tail = ptr->head;//申请一个新的节点ListNode* NewNode = BuyListNode();printf("请输入要尾插的数字\n");scanf("%d", &NewNode->data);//尾插:尾节点的next指向新节点tail->next = NewNode;//新节点的前指针指向尾节点NewNode->head = tail;//新节点的后指针指向哨兵节点NewNode->next = ptr;//哨兵节点的前指针指向新节点ptr->head = NewNode;
}

链表的尾删也很简单,只需按上述步骤逆着来然后释放要删除的空间就行了。代码如下:

void ListPopBack(ListNode* plist)
{assert(plist);ListNode* ptr = plist;ListNode* tail = ptr->head;ListNode* tail1 = tail->head;tail1->next = ptr;ptr->head = tail1;free(tail);printf("删除成功\n");
}

(5)链表的头插和头删

链表的头插和头删和链表的尾插尾删差不多,只不过这里的头插和头删是在哨兵节点的下一个节点,不是让你删或者插在哨兵节点后面。并且,在所有带头链表的删除功能里一定不能删除哨兵节点。那会出现野指针的,程序运行也会不安全。代码如下:

// 双向链表头插
void ListPushFront(ListNode* plist)
{assert(plist);ListNode* ptr = plist;ListNode* newnode = BuyListNode();ListNode* ptrnext = ptr->next;ptr->next = newnode;newnode->head = ptr;newnode->next = ptrnext;ptrnext->head = newnode;printf("请输入头插数字\n");scanf("%d", &newnode->data);printf("插入成功\n");
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{assert(plist);ListNode* ptr = plist;ListNode* ptrnext2= ptr->next->next;free(ptr->next);ptr->next = ptrnext2;ptrnext2->head = ptr;printf("删除成功\n");
}

(6)链表的查找

ListNode* ListFind(ListNode* plist)
{assert(plist);int a;printf("请输入要查找的数字\n");scanf("%d", &a);ListNode* ptr = plist->next;while (ptr){//查找到直接返回if (ptr->data == a){printf("查找成功\n");return ptr;}//一定要有这个条件,防止查找不到陷入死循环if (ptr->next == plist){printf("查找失败,未找到\n");return NULL;}ptr = ptr->next;}
}

(7)双链表在pos位置前后插入和删除pos位置功能

这些和头插头删没有多大区别,且双链表在pos位置前插入比单链表简单。代码如下:

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos)
{assert(pos);ListNode* front = pos->head;ListNode* ptr = pos;ListNode* newnode = BuyListNode();front->next = newnode;newnode->head = front;newnode->next = ptr;ptr->head = newnode;printf("请输入要在pos前插入的数\n");scanf("%d", &newnode->data);printf("插入成功\n");
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* front = pos->head;ListNode* posnext = pos->next;free(pos);front->next = posnext;posnext->head = front;printf("删除成功\n");
}
// 双向链表在pos位置之后插入
void ListInsertAfter(ListNode* pos)
{assert(pos);ListNode* posnext = pos->next;ListNode* newnode = BuyListNode();ListNode* ptr = pos;ptr->next = newnode;newnode->head = ptr;newnode->next = posnext;posnext->head = newnode;printf("请输入要插入的数字\n");scanf("%d", &newnode->data);
}

这样一个带头双向循环链表也就完成了。单链表和带头双向循环链表虽然是两个极端,但当我们可以自主实现后,其他的链表结构我们也可以信手拈来。

全部代码如下:

listnode.h:

#pragma once
#pragma warning(disable : 4996)
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int type;
typedef struct ListNde
{struct ListNde* next;//指向下一个节点struct ListNde* head;//指向上一个节点type data;//存储数据
}ListNode;
// 创建链表的头节点,也即哨兵节点.
ListNode* ListCreate();
//创建其他节点
ListNode* BuyListNode();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode* plist);
// 双向链表头删
void ListPopFront(ListNode* plist);
// 双向链表查找
ListNode* ListFind(ListNode* plist);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
// 双向链表在pos位置之后插入
void ListInsertAfter(ListNode* pos);

Flistnode.c:

#include"listnode.h"
ListNode* ListCreate()
{//动态申请一个结构体空间ListNode* head = (ListNode*)malloc(sizeof(ListNode));if (head == NULL){perror("ListCreate::malloc");return NULL;}//使节点不存储数据head->data = NULL;//因为作为头节点存在,因此在没有其他节点时,需要让//前指针和后指针都指向自己head->head = head;head->next = head;//如果不返回,那就需要传二级指针来使头指针和哨兵节点链接return head;
}ListNode* BuyListNode()
{ListNode* ptr = (ListNode*)malloc(sizeof(ListNode));if (ptr == NULL){perror("BuyListNode::malloc");return NULL;}ptr->head = NULL;ptr->next = NULL;return ptr;
}
void ListDestory(ListNode* plist)
{//assert(plist);ListNode* ptr = plist->next;while (ptr){ListNode* ptrnext = ptr->next;free(ptr);if (ptrnext == plist){break;}ptr = ptrnext;}free(plist);printf("销毁成功\n");
}
void ListPrint(ListNode* plist)
{//不打印哨兵节点,哨兵节点不存储任何数据ListNode* ptr = plist->next;//以这个代表哨兵节点printf("<head>");//循环打印while (ptr){printf("<%d>", ptr->data);//因为带头双向循环链表尾节点和哨兵节点是相链接的//所以需要一个条件来作为循环的终止条件if (ptr->next == plist){//ptr指向尾节点时,ptr->next指向哨兵节点,退出循环break;}ptr = ptr->next;}
}void ListPushBack(ListNode* plist)
{//断言判断plist不为空,因为有哨兵节点存在,那么plist//传过来的必定不为空assert(plist);ListNode* ptr = plist;//由ptr->head得到尾节点ListNode* tail = ptr->head;//申请一个新的节点ListNode* NewNode = BuyListNode();printf("请输入要尾插的数字\n");scanf("%d", &NewNode->data);//尾插:尾节点的next指向新节点tail->next = NewNode;//新节点的前指针指向尾节点NewNode->head = tail;//新节点的后指针指向哨兵节点NewNode->next = ptr;//哨兵节点的前指针指向新节点ptr->head = NewNode;
}void ListPopBack(ListNode* plist)
{assert(plist);ListNode* ptr = plist;ListNode* tail = ptr->head;ListNode* tail1 = tail->head;tail1->next = ptr;ptr->head = tail1;free(tail);printf("删除成功\n");
}
// 双向链表头插
void ListPushFront(ListNode* plist)
{assert(plist);ListNode* ptr = plist;ListNode* newnode = BuyListNode();ListNode* ptrnext = ptr->next;ptr->next = newnode;newnode->head = ptr;newnode->next = ptrnext;ptrnext->head = newnode;printf("请输入头插数字\n");scanf("%d", &newnode->data);printf("插入成功\n");
}
// 双向链表头删
void ListPopFront(ListNode* plist)
{assert(plist);ListNode* ptr = plist;ListNode* ptrnext2= ptr->next->next;free(ptr->next);ptr->next = ptrnext2;ptrnext2->head = ptr;printf("删除成功\n");
}ListNode* ListFind(ListNode* plist)
{assert(plist);int a;printf("请输入要查找的数字\n");scanf("%d", &a);ListNode* ptr = plist->next;while (ptr){//查找到直接返回if (ptr->data == a){printf("查找成功\n");return ptr;}//一定要有这个条件,防止查找不到陷入死循环if (ptr->next == plist){printf("查找失败,未找到\n");return NULL;}ptr = ptr->next;}
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos)
{assert(pos);ListNode* front = pos->head;ListNode* ptr = pos;ListNode* newnode = BuyListNode();front->next = newnode;newnode->head = front;newnode->next = ptr;ptr->head = newnode;printf("请输入要在pos前插入的数\n");scanf("%d", &newnode->data);printf("插入成功\n");
}
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{assert(pos);ListNode* front = pos->head;ListNode* posnext = pos->next;free(pos);front->next = posnext;posnext->head = front;printf("删除成功\n");
}
// 双向链表在pos位置之后插入
void ListInsertAfter(ListNode* pos)
{assert(pos);ListNode* posnext = pos->next;ListNode* newnode = BuyListNode();ListNode* ptr = pos;ptr->next = newnode;newnode->head = ptr;newnode->next = posnext;posnext->head = newnode;printf("请输入要插入的数字\n");scanf("%d", &newnode->data);
}

listnode.c:

#include"listnode.h"
ListNode* pplist = NULL;
int main()
{int a;pplist = ListCreate();ListNode* pos = NULL;do{printf("请输入数字");scanf("%d", &a);switch (a){case 1:// 双向链表打印ListPrint(pplist);break;case 2:// 双向链表尾插ListPushBack(pplist);break;case 3:// 双向链表的头插ListPushFront(pplist);break;case 4:// 双向链表的尾删ListPopBack(pplist);break;case 5:// 双向链表头删ListPopFront(pplist);break;case 6:// 双向链表查找pos = ListFind(pplist);break;case 7:// 双向链表在pos位置之后插入xListInsertAfter(pos);break;case 8:// 在pos的前面插入ListInsert(pos);break;case 9:// 删除pos位置ListErase(pos);break;case 0://链表的销毁ListDestory(pplist);printf("退出\n");break;default:printf("输入数字错误,请重新输入\n");break;}} while (a);return 0;
}

至此,链表就完了,大家可以去力扣或者牛客上找一些链表的题做一做来巩固一下。提个建议,如果在链表中,或者说在整个数据结构中,画图永远是你最好的伙伴。

相关文章:

初阶数据结构:链表(二)

目录 一、前言 二、带头双向循环链表 1.带头双向循环链表的结构 &#xff08;1)什么是带头&#xff1f; (2)什么是双向呢&#xff1f; &#xff08;3&#xff09;那什么是循环呢&#xff1f; 2.带头双向循环链表的实现 &#xff08;1&#xff09;节点结构 &#xff08;2…...

postgresql根据主键ID字段分批删除表数据

生产环境针对大表的处理相对比较麻烦。 方案1、直接truncate&#xff0c;可能会遇到系统卡主的情况&#xff0c;因为truncate的过程中会对表进行加锁&#xff0c;会导致数据不能正常的写入 方案2、创建一个同结构的表结构&#xff0c;rename旧表&#xff0c;不停业务rename表担…...

10.business english-global market

eco-friendly case study: 案例学习 At the workshop工作坊, they agreed to emphasize eco-friendliness,adapt messageing, and boost digital marketing to stand out globally. Our study shows that more people want eco-friendly products in different places.Looks …...

C 语言实现计算一年中指定日期是第几天 题】

引言 在编程的世界里&#xff0c;处理日期和时间相关的问题是非常常见的。比如在日历应用、任务管理系统、数据分析等场景中&#xff0c;经常需要计算某个日期在一年中是第几天。本文将详细介绍如何使用 C 语言来实现这一功能&#xff0c;通过分析代码的结构、逻辑以及可能存在…...

深入理解三高架构:高可用性、高性能、高扩展性的最佳实践

引言 在现代互联网环境下&#xff0c;随着用户规模和业务需求的快速增长&#xff0c;系统架构的设计变得尤为重要。为了确保系统能够在高负载和复杂场景下稳定运行&#xff0c;"三高架构"&#xff08;高可用性、高性能、高扩展性&#xff09;成为技术架构设计中的核…...

【反悔堆】力扣1642. 可以到达的最远建筑

给你一个整数数组 heights &#xff0c;表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程&#xff0c;不断向后面的建筑物移动&#xff0c;期间可能会用到砖块或梯子。 当从建筑物 i 移动到建筑物 i1&#xff08;下标 从 0 开始 &#xff09;…...

关于使用Mybatis-plus的TableNameHandler动态表名处理器实现分表业务的详细介绍

引言 随着互联网应用的快速发展&#xff0c;数据量呈爆炸式增长。传统的单表设计在面对海量数据时显得力不从心&#xff0c;容易出现性能瓶颈、查询效率低下等问题。为了提高数据库的扩展性和响应速度&#xff0c;分表&#xff08;Sharding&#xff09;成为了一种常见的解决方案…...

docker 安装 redis 详解

在平常的开发工作中&#xff0c;我们经常会用到 redis&#xff0c;那么 docker 下应该如何安装 redis 呢&#xff1f;简单来说&#xff1a;第一步&#xff1a;拉取redis镜像&#xff1b;第二步&#xff1a;设置 redis.conf 配置文件&#xff1b;第三步&#xff1a;编写 docker-…...

56. 合并区间

【题目】&#xff1a;56. 合并区间 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {// 按照左端点排序sort(intervals.begin(), intervals.end(), [&](vector<int> lhs, vector<int> rhs)…...

BOM对象location与数组操作结合——查询串提取案例

BOM对象location与数组操作结合——查询串提取案例 前置知识 1. Location 对象 Location 对象是 JavaScript 提供的内置对象之一&#xff0c;它表示当前窗口或框架的 URL&#xff0c;并允许你通过它操作或获取 URL 的信息。可以通过 window.location 访问。 主要属性&#…...

Jetson Orin Nano Super之 onnxruntime 编译安装

Jetson Orin Nano Super之 onnxruntime 编译安装 1. 源由2. 步骤步骤一&#xff1a;安装3.26 cmake步骤二&#xff1a;下载代码步骤三&#xff1a;编译代码步骤四&#xff1a;找到安装包步骤五&#xff1a;安装whl包 3. 注意4. 参考资料 1. 源由 Build onnxruntime 1.19.2 fai…...

开发环境搭建-3:配置 nodejs 开发环境 (fnm+ node + pnpm)

在 WSL 环境中配置&#xff1a;WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 node 官网&#xff1a;https://nodejs.org/zh-cn/download 点击【下载】&#xff0c;选择想要的 node 版本、操作系统、node 版本管理器、npm包管理器 根据下面代码提示依次执行对应代码即可 基本概…...

[SWPUCTF 2022 新生赛]js_sign

题目 查看页面源代码 <!DOCTYPE html> <html> <head><meta charset"utf-8"><style>body {background-color: rgb(255, 255, 255);}</style> </head> <body><input id"flag" /><button>Check…...

农业信息化的基本框架

农业信息化的主要研究内容 基于作物模型的相关研究 作物生长模拟模型以及模型评价、模型的应用作物模型应用&#xff0c;包括&#xff1a;作物生态系统过程、生产管理措施、区域作物产量评估与气候变化对产量影响预测、基于作物模型的决策支持系统 数据挖掘、知识工程及应用、管…...

OpenAI的真正对手?DeepSeek-R1如何用强化学习重构LLM能力边界——DeepSeek-R1论文精读

2025年1月20日&#xff0c;DeepSeek-R1 发布&#xff0c;并同步开源模型权重。截至目前&#xff0c;DeepSeek 发布的 iOS 应用甚至超越了 ChatGPT 的官方应用&#xff0c;直接登顶 AppStore。 DeepSeek-R1 一经发布&#xff0c;各种资讯已经铺天盖地&#xff0c;那就让我们一起…...

Vue 3 中的父子组件传值:详细示例与解析

在 Vue 3 中&#xff0c;父子组件之间的数据传递是一个常见的需求。父组件可以通过 props 将数据传递给子组件&#xff0c;而子组件可以通过 defineProps 接收这些数据。本文将详细介绍父子组件传值的使用方法&#xff0c;并通过优化后的代码示例演示如何实现。 1. 父子组件传值…...

回顾2024,展望2025

项目 LMD performance phase2 今年修修补补&#xff0c;设计和做了很多item&#xff0c;有时候自己都数不清做了什么大大小小的item&#xff0c;但是for LMD performance phase2的go-live确实是最大也是最难的了&#xff0c;无论什么系统&#xff0c;只要用的人多了&#xff…...

【Python实现机器遗忘算法】复现2021年顶会 AAAI算法Amnesiac Unlearning

【Python实现机器遗忘算法】复现2021年顶会 AAAI算法Amnesiac Unlearning 1 算法原理 论文&#xff1a;Graves, L., Nagisetty, V., & Ganesh, V. (2021). Amnesiac machine learning. In Proceedings of the AAAI Conference on Artificial Intelligence, volume 35, 115…...

Vue 3 30天精进之旅:Day 03 - Vue实例

引言 在前两天的学习中&#xff0c;我们成功搭建了Vue.js的开发环境&#xff0c;并创建了我们的第一个Vue项目。今天&#xff0c;我们将深入了解Vue的核心概念之一——Vue实例。通过学习Vue实例&#xff0c;你将理解Vue的基础架构&#xff0c;掌握数据绑定、模板语法和指令的使…...

【ArcGIS微课1000例】0141:提取多波段影像中的单个波段

文章目录 一、波段提取函数二、加载单波段导出问题描述:如下图所示,img格式的时序NDVI数据有24个波段。现在需要提取某一个波段,该怎样操作? 一、波段提取函数 首先加载多波段数据。点击【窗口】→【影像分析】。 选择需要处理的多波段影像,点击下方的【添加函数】。 在多…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...