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

二叉树链式结构的实现(递归的暴力美学!!)

前言

Hello,小伙伴们。你们的作者菌又回来了,前些时间我们刚学习完二叉树的顺序结构,今天我们就趁热打铁,继续我们二叉树链式结构的学习。我们上期有提到,二叉树的的底层结构可以选为数组和链表,顺序结构我们选用的数组,那我们就不难知道,二叉树的链式结构采用链表为底层结构。

1.实现链式二叉树

用链表来表示一颗二叉树,即用链表来表示元素之间的逻辑关系。通常的方法就是链表中的每一个节点有三个域组成,数据域和左右指针,左右指针分别用来给出该节点左右孩子所在的链接点的存储地址,数据结构的定义如下:

typedef int BTDataType;
// ⼆叉链
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left; // 指向当前结点左孩⼦
struct BinaryTreeNode* right; // 指向当前结点右孩⼦
BTDataType val; // 当前结点值域
}BTNode;

不要忘记,我们在实现数据结构时,都要先创建三个文件来使测试代码变得更加的方便:

为了更好的演示效果,我们就在Test.c文件中手动的创建几个节点

BTNode* BuyBTNode(int val)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;} newnode->val = val;newnode->left = NULL;newnode->right = NULL;return newnode;
}  
BTNode* CreateTree()
{BTNode* n1 = BuyBTNode(1);BTNode* n2 = BuyBTNode(2);BTNode* n3 = BuyBTNode(3);BTNode* n4 = BuyBTNode(4);BTNode* n5 = BuyBTNode(5);BTNode* n6 = BuyBTNode(6);BTNode* n7 = BuyBTNode(7);n1->left = n2;n1->right = n4;n2->left = n3;n4->left = n5;n4->right = n6;n5->left = n7;return n1;}int main()
{BTNode* boot = CreateTree();return 0;
}

我们已经学习了链表,所以这里创建节点的操作我们就不再过多的赘述,因此,boot就是我们创建的这颗树的根节点。

接下来我们来回顾一下二叉树的概念,二叉树分为空树和非空树

 根节点的左子树和右子树分别有是有子树节点、子树节点的左子树和右子树组成,因此二叉树定义是递归式的,后续链式二叉树的操作中基本都是按照该概念实现的!!

1.1前中后序遍历

二叉树的操作离不开树的遍历,我们先来看看二叉树的遍历有哪些方式:

 1.1.1遍历的规则

按照规则,二叉树有:前中后续遍历的规则的递归结构遍历:

前序遍历:访问根节点的操作,发生在遍历其左右子树之前。

访问顺序:根节点、左子树、右子树。

中序遍历:访问根节点的操作发生在遍历其左右节点子树之间(间)

访问顺序:左子树、根节点、右子树

后序遍历:访问根节点的操作发生在遍历其左右子树的遍历之后

访问顺序:左子树、右子树、根节点

1.1.2 代码的实现

1.1.2.1 前序遍历(PreOrder)
void PreOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
} p
rintf("%d ", root->val);
PreOrder(root->left);
PreOrder(root->right);
}

这里我们就涉及到了之前学习函数栈帧的知识,我们可以通过图来理解

函数递归栈帧图:

 

最终我们测试可得:

可知这符合我们前序遍历的规则,因此我们就成功实现了前序遍历。

实现了前序遍历,其他的两个遍历就简单了,几乎和前序遍历一样,中后序遍历也沿用了递归的思想 

后面大家能否根据上面前序遍历的代码实现中序遍历和后序遍历呢?

大家不妨一试。

1.1.2.2中序遍历(InOrder)
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
} 
InOrder(root->left);
printf("%d ", root->val);
InOrder(root->right);
}

1.1.2.3后序遍历(PostOredr)
v
oid PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("N ");
return;
} I
nOrder(root->left);
InOrder(root->right);
printf("%d ", root->val);
}
 

有序他们之间的相似性,在代码上的差异也就只是不同行的代码顺序不一样,但是转化为后面的函数栈帧却又不小的差别。但在本质上,却还是大差不差!!

1.2节点个数以及高度等

1.2.1节点计数

// ⼆叉树结点个数
int BinaryTreeSize(BTNode* root);

我们来想一想,怎样才能达到我们的目的呢?

假设我们现在有这样的二叉树:
 

所以从上所述,我们就能够得出计数代码:

int BinaryNodeCount(BTNode* root)
{if (root == NULL)return 0;return 1 + BinaryNodeCount(root->left) + BinaryNodeCount(root->right);}

 代码测试:

这里我们创建的是这样的二叉树。

可知这样我们就实现了二叉树节点的计数!!

 1.2.2二叉树叶子结点个数

// ⼆叉树叶⼦结点个数
int BinaryTreeLeafSize(BTNode*root)

有了前面写求所有节点个数的经验,其实这个不就不难了:

叶子结点,就是没有子节点的节点。即root->left = root->right = NULL.

所以在这里我们就有两种返回情况,当遍历到NULL时,我们返回0, 当递归到叶子结点时,我们就返回1。借助函数栈帧,就可以进行计数!!

接下来我们来实现一下这个代码:

//求二叉树叶子节点的个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

我们可以先提前看看这颗二叉树有多少个叶子结点:

上面我们可以看出一共有3个没有子节点的节点,所以叶子结点的个数就是3

 

1.2.3 二叉树第K层的节点个数

// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);

我们要得到二叉树第K层节点的个数,其实也并不难,我们还是通过画图的方式来解析过程。

假设我们要求的是第三层的节点数:

 代码实现:

int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1)return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

测试一下:

 1.2.4二叉树的深度/高度

//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root);

在这里我们可以想出怎样的思路呢?

我们还是要先画图求证:

int BinaryTreeDepth(BTNode* root)
{if (root == NULL)return 0;int leftDepth = BinaryTreeDepth(root->left);int rightDepth = BinaryTreeDepth(root->right);return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

 代码测试:

 从上面我们事先创建的那棵树一样,树的深度为4.所以我们实现了我们的目的。

1.2.5二叉树查找置位x的节点

// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

这个操作也十分的简单,我们只需要遍历二叉树,找到与目标值相等的节点,我们就将其返回。

我们先看看实现代码:

// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->val == x)return root;BTNode* left =BinaryTreeFind(root->left, x);if (left)return left;BTNode* right = BinaryTreeFind(root->right, x);if (right)return right;return NULL;
}

在这里我们还是要进行二叉树的左右子树遍历,但是我们为了提高效率,只要在一边找到了符合目标的节点我们就直接返回该节点!!

小伙伴们可以根据上面的知识,自行画出递归栈帧图。

我们来测试以下代码:

可知其成功的找到了,值为6的节点!! 

1.2.6 二叉树的销毁

// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root);

销毁的逻辑也十分的简单了,通过了递归的操作,我们遍历所有不为NULL的节点并销毁,这里我们就直接写出代码,大家可以自己进行递归栈帧的推理:

void BinaryTreeDestory(BTNode** root)
{if (*root == NULL){return;}BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;
}

代码测试:

进行销毁操作:

 2.代码展示

2.1Test.c

#define _CRT_SECURE_NO_WARNINGS 1#include"Tree.h"
BTNode* BuyBTNode(int val)
{BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));if (newnode == NULL){perror("malloc fail");return NULL;} newnode->val = val;newnode->left = NULL;newnode->right = NULL;return newnode;
}  
BTNode* CreateTree()
{BTNode* n1 = BuyBTNode(1);BTNode* n2 = BuyBTNode(2);BTNode* n3 = BuyBTNode(3);BTNode* n4 = BuyBTNode(4);BTNode* n5 = BuyBTNode(5);BTNode* n6 = BuyBTNode(6);BTNode* n7 = BuyBTNode(7);n1->left = n2;n1->right = n4;n2->left = n3;n4->left = n5;n4->right = n6;n5->left = n7;return n1;}int main()
{BTNode* root = CreateTree();PreOrder(root);printf("\n");InOredr(root);printf("\n");PostOrder( root);printf("\n");printf("TreeNodeCount: %d\n", BinaryNodeCount(root));printf("leafSize: %d\n", BinaryTreeLeafSize(root));printf("TreeLevelKSize: %d\n", BinaryTreeLevelKSize(root, 3));printf("TreeLevelDepth: %d\n", BinaryTreeDepth(root));BTNode* find = BinaryTreeFind(root, 6);printf("%s ", find == NULL ? "没找到" : "找到了!!");printf("返回节点的值为:%d\n", find->val);BinaryTreeDestory(&root);PostOrder(root);return 0;
}

2.2Tree.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<time.h>
#include<stdbool.h>
typedef int BTDataType;
// ⼆叉链
typedef struct BinaryTreeNode
{struct BinaryTreeNode* left; // 指向当前结点左孩⼦struct BinaryTreeNode* right; // 指向当前结点右孩⼦BTDataType val; // 当前结点值域
}BTNode;
//前序遍历
void PreOrder(BTNode* root);
//中序遍历
void InOredr(BTNode* root);
//后序遍历
void PostOrder(BTNode* root);
//计数二叉树的节点
int BinaryNodeCount(BTNode* root);
//求二叉树叶子节点的个数
int BinaryTreeLeafSize(BTNode* root);
// ⼆叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
//⼆叉树的深度/⾼度
int BinaryTreeDepth(BTNode* root);
// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// ⼆叉树销毁
void BinaryTreeDestory(BTNode** root);

2.3Tree.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Tree.h"
void PreOrder(BTNode* root)
{if (root == NULL){return;}printf("%d ", root->val);PreOrder(root->left);PreOrder(root->right);
}
void InOredr(BTNode* root)
{if (root == NULL)return;InOredr(root->left);printf("%d ", root->val);InOredr(root->right);
}
void PostOrder(BTNode* root)
{if (root == NULL){return;}PostOrder(root->left);PostOrder(root->right);printf("%d ", root->val);
}
int BinaryNodeCount(BTNode* root)
{if (root == NULL)return 0;return 1 + BinaryNodeCount(root->left) + BinaryNodeCount(root->right);}
//求二叉树叶子节点的个数
int BinaryTreeLeafSize(BTNode* root)
{if (root == NULL){return 0;}if (root->left == NULL && root->right == NULL){return 1;}return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
int BinaryTreeLevelKSize(BTNode* root, int k)
{if (root == NULL){return 0;}if (k == 1)return 1;return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}
int BinaryTreeDepth(BTNode* root)
{if (root == NULL)return 0;int leftDepth = BinaryTreeDepth(root->left);int rightDepth = BinaryTreeDepth(root->right);return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
// ⼆叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{if (root == NULL)return NULL;if (root->val == x)return root;BTNode* left =BinaryTreeFind(root->left, x);if (left)return left;BTNode* right = BinaryTreeFind(root->right, x);if (right)return right;return NULL;
}
void BinaryTreeDestory(BTNode** root)
{if (*root == NULL){return;}BinaryTreeDestory(&((*root)->left));BinaryTreeDestory(&((*root)->right));free(*root);*root = NULL;
}

好,今天的学习就到这里,我们下期再见,拜拜!!

相关文章:

二叉树链式结构的实现(递归的暴力美学!!)

前言 Hello,小伙伴们。你们的作者菌又回来了&#xff0c;前些时间我们刚学习完二叉树的顺序结构&#xff0c;今天我们就趁热打铁&#xff0c;继续我们二叉树链式结构的学习。我们上期有提到&#xff0c;二叉树的的底层结构可以选为数组和链表&#xff0c;顺序结构我们选用的数…...

Python | Leetcode Python题解之第312题戳气球

题目&#xff1a; 题解&#xff1a; class Solution:def maxCoins(self, nums: List[int]) -> int:n len(nums)rec [[0] * (n 2) for _ in range(n 2)]val [1] nums [1]for i in range(n - 1, -1, -1):for j in range(i 2, n 2):for k in range(i 1, j):total v…...

远程访问mysql数据库的正确打开方式

为了安全&#xff0c;mysql数据库默认只能本机登录&#xff0c;但是在有些时候&#xff0c;我们会有远程登录mysql数据库的需求&#xff0c;这时候应该怎么办呢&#xff1f; 远程访问mysql数据&#xff0c;需要两个条件&#xff1a; 首先需要mysql服务器将服务绑定到0.0.0.0…...

网络6 -- udp_socket 实现 echo服务器

目录 1.server 服务端 1.1.完整代码展示&#xff1a; 1.2.代码解析&#xff1a; 1.2.1 给服务端创建套接字 1.2.2 绑定套接字 1.2.3 服务端接受数据并返回 2.客户端&#xff1a; 2.1 完整代码展示&#xff1a; 2.2 代码解析 2.2.1 客户端使用手则&#xff1a; 2.2.2 …...

ASUS/华硕幻15 2020 冰刃4 GX502L GU502L系列 原厂win10系统 工厂文件 带F12 ASUS Recovery恢复

华硕工厂文件恢复系统 &#xff0c;安装结束后带隐藏分区&#xff0c;一键恢复&#xff0c;以及机器所有驱动软件。 系统版本&#xff1a;windows10 原厂系统下载网址&#xff1a;http://www.bioxt.cn 需准备一个20G以上u盘进行恢复 请注意&#xff1a;仅支持以上型号专用…...

simulink绘制bode图

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…...

知识工程视角下的软件研发

知识工程 在我们的工作中存在两类知识&#xff1a;显式知识&#xff08;explicit knowledge&#xff09;、不可言说的知识&#xff08;tacit knowledge&#xff09;。 所谓显式知识就是能够直接表达且在人群中分享的知识。比如&#xff0c;地球的周长、水的密度、三角形面积公…...

深度学习------权重衰退

目录 使用均方范数作为硬性限制使用均方范数作为柔性限制演示最优解的影响参数更新法则总结高纬线性回归多项式的权重衰退从零开始实现初始化模型参数定义L2范数惩罚定义训练代码实现忽略正则化直接训练使用权重衰减从零开始代码实现 多项式的权重衰退的简洁实现简洁函数代码简…...

【算法】退火算法 Simulated Annealing

退火算法&#xff08;Simulated Annealing, SA&#xff09;是一种基于热力学模拟的优化算法&#xff0c;用于求解全局优化问题。它通过模拟物理退火过程来寻找全局最优解。以下是退火算法的基本原理和步骤&#xff1a; 一、基本原理 退火算法的灵感来源于金属在高温下缓慢冷却…...

深入理解 Git `git add -p` 命令中的交互选项

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119@qq.com] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? 专栏导…...

HTML JavaScript 闪光涟漪

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>闪光涟漪</title><style>.ripple-conta…...

FastAPI之Depends

文章目录 基本概念基本用法复杂场景中的 Depends数据库会话管理处理请求用户嵌套依赖全局依赖 作用域与生命周期可选依赖类依赖总结 基本概念 在 FastAPI 中&#xff0c;依赖可以是&#xff1a; 一个函数&#xff0c;它的返回值会被传递给视图函数作为参数。可以被其他依赖函…...

AttributeError: module ‘jwt‘ has no attribute ‘decode‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

C++——C++11

前言&#xff1a;本篇文章将分享一些C11版本所产生的一些新的技术以及对老版本的优化。 目录 一.C11简介 二.统一的列表初始化 1.{}初始化 2.std::initializer_list 三.右值引用和移动语义 1.左值引用和右值引用 2.两者的比较 &#xff08;1&#xff09;左值引用 &#…...

day12 多线程

目录 1.概念相关 1.1什么是线程 1.2什么是多线程 2.创建线程 2.1方式一&#xff1a;继承Thread类 2.1.1实现步骤 2.1.2优缺点 2.1.3注意事项 2.2方式二&#xff1a;实现Runnable接口 2.2.1实现步骤 2.2.2优缺点 2.2.3匿名内部类写法 2.3方式三&#xff1a;实现cal…...

DeferredResult 是如何实现异步处理请求的

最近遇到了一个问题&#xff0c;我们的一个接口需要去轮询另一个第三方接口&#xff0c;导致这个接口占用了太多工作线程&#xff0c;这些工作线程长时间 running&#xff0c;我们需要解决这个问题。 于是&#xff0c;我们的方案是&#xff1a;用 DeferredResult 实现接口异步。…...

VUE3——001(03)、开发环境配置(node.js/mvn/java/ngix/tomact/vue3)

嫌麻烦的请下载安装包&#xff0c;有点强迫&#xff08;懒的&#xff09;可以看看。 解释&#xff1a;安装目录&#xff0c;即软件安装所在目录&#xff0c;如 node.js 我装在 D:\AppFolder\nodejs 系统变量修改 path增加 安装目录 在系统变量 p…...

TCP/IP_TCP协议

目录 一、TCP协议 1.1 确认应答 1.2 超时重传 1.3 连接管理 1.4 TCP状态 1.5 滑动窗口 1.6 流量控制 1.7 拥塞控制 1.8 延迟应答 1.9 捎带应答 1.10 粘包问题 1.11 异常情况 二、TCP/UDP对比 总结 一、TCP协议 TCP 协议和 UDP 协议是处于传输层的协议。 【TCP协…...

鸿蒙应用框架开发【简单时钟】 UI框架

简单时钟 介绍 本示例通过使用ohos.display接口以及Canvas组件来实现一个简单的时钟应用。 效果预览 使用说明 1.界面通过setInterval实现周期性实时刷新时间&#xff0c;使用Canvas绘制时钟&#xff0c;指针旋转角度通过计算得出。 例如&#xff1a;"2 * Math.PI / …...

MySQL是如何实现数据排序的

MySQL是如何实现数据排序的 MySQL实现数据排序主要依赖于其内部的排序和索引机制。当执行包含ORDER BY子句的SQL查询时&#xff0c;MySQL会采用以下一种或多种策略来对数据进行排序 索引排序 如果ORDER BY子句中的列是表的一个索引&#xff08;或索引的一部分&#xff09;&a…...

FastAPI 2.0 AI流式响应性能瓶颈分析与突破方案(源码级内存泄漏定位实录)

第一章&#xff1a;FastAPI 2.0 AI流式响应性能瓶颈分析与突破方案&#xff08;源码级内存泄漏定位实录&#xff09;在高并发AI推理服务场景下&#xff0c;FastAPI 2.0 的 StreamingResponse 在持续返回大模型 token 流时&#xff0c;常出现 RSS 内存持续增长、GC 延迟升高、最…...

LFM2.5-1.2B-Thinking-GGUF实操手册:自定义system prompt提升领域适配性

LFM2.5-1.2B-Thinking-GGUF实操手册&#xff1a;自定义system prompt提升领域适配性 1. 模型简介与核心优势 LFM2.5-1.2B-Thinking-GGUF是Liquid AI推出的轻量级文本生成模型&#xff0c;专为低资源环境优化设计。该模型采用GGUF格式和llama.cpp运行时&#xff0c;在保持高性…...

从晶体管到CPU:CMOS反相器延迟如何决定你的电脑主频

从晶体管到CPU&#xff1a;CMOS反相器延迟如何决定你的电脑主频 当你按下电脑电源键的瞬间&#xff0c;数十亿个晶体管在芯片上开始协同工作。这些微观开关的切换速度&#xff0c;直接决定了处理器主频的上限。而构成所有数字电路基础的CMOS反相器&#xff0c;其动态响应特性就…...

跨平台终端工具cmatrix:打造震撼的数字雨可视化效果

跨平台终端工具cmatrix&#xff1a;打造震撼的数字雨可视化效果 【免费下载链接】cmatrix Terminal based "The Matrix" like implementation 项目地址: https://gitcode.com/gh_mirrors/cm/cmatrix 你是否曾幻想过在自己的终端中重现《黑客帝国》里令人着迷的…...

vLLM-v0.17.1保姆级教程:SSH中查看vLLM实时请求队列与Pending统计

vLLM-v0.17.1保姆级教程&#xff1a;SSH中查看vLLM实时请求队列与Pending统计 1. vLLM框架简介 vLLM是一个专注于大语言模型(LLM)推理和服务的高性能库&#xff0c;它的设计目标是让开发者能够轻松部署和管理大规模语言模型。这个项目最初由加州大学伯克利分校的天空计算实验…...

手把手教你搞定CMT2300A的315MHz匹配电路:从原理图到物料清单(附实测数据)

深入解析CMT2300A在315MHz频段的射频匹配电路设计与实战优化 作为一名长期深耕射频硬件设计的工程师&#xff0c;我最近在工业遥控器项目中遇到了一个典型挑战&#xff1a;如何为CMT2300A设计稳定可靠的315MHz匹配电路。与常见的433MHz应用不同&#xff0c;315MHz频段在元件参数…...

Lumerical圆锥建模避坑指南:从参数计算到3D旋转生成的完整流程

Lumerical圆锥建模避坑指南&#xff1a;从参数计算到3D旋转生成的完整流程 在光学仿真领域&#xff0c;精确的几何建模往往是获得可靠结果的第一步。对于圆锥结构这种在光子晶体、超表面和纳米天线设计中广泛应用的形状&#xff0c;其建模过程看似简单却暗藏玄机。许多研究人员…...

OpenClaw技能商店:基于nanobot开发并分享自定义模块

OpenClaw技能商店&#xff1a;基于nanobot开发并分享自定义模块 1. 为什么要开发OpenClaw技能 去年夏天&#xff0c;我发现自己每天要花大量时间处理重复性的文件整理工作——下载各种技术文档&#xff0c;按日期和项目分类存储&#xff0c;再手动生成目录索引。当我第三次在…...

抖音视频批量下载神器:3分钟搞定复杂内容管理的终极方案

抖音视频批量下载神器&#xff1a;3分钟搞定复杂内容管理的终极方案 【免费下载链接】douyin-downloader 项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloader 抖音作为全球最受欢迎的短视频平台&#xff0c;每天产生海量的精彩内容。然而&#xff0c…...

OpenCore Legacy Patcher终极指南:让你的老Mac焕发新生,体验最新macOS

OpenCore Legacy Patcher终极指南&#xff1a;让你的老Mac焕发新生&#xff0c;体验最新macOS 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 你是否还在为老旧的Mac无法升…...