数据结构(三)循环链表

news2024/12/26 11:50:13

文章目录

  • 一、循环链表
    • (一)概念
    • (二)示意图
    • (三)操作
      • 1. 创建循环链表
        • (1)函数声明
        • (2)注意点
        • (3)代码实现
      • 2. 插入(头插,尾插,任意位置插入)
        • (1)头插
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (2)尾插
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (3)任意位置插入
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
      • 3. 删除(头删,尾删,任意位置删除)
        • (1)头删
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (2)尾删
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
        • (3)任意位置删除
          • ① 函数声明
          • ② 注意点
          • ③ 代码实现
      • 4. 修改
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 5. 查询
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 6. 清空和销毁
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 7. 打印链表(方便查看实验现象)
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 8. 排序(以正序排序为例)
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
      • 9.剔重
        • (1)函数定义
        • (2)注意点
        • (3)代码实现
    • (四)应用:实现约瑟夫环
      • 1.问题描述
      • 2. 问题分析
      • 3. 代码实现
  • 二、代码源码已上传资源

一、循环链表

(一)概念

操作和单向链表的操作基本一样
只是判断链表结束的条件不同

循环链表又分为有头循环链表和无头循环链表,其中无头结点的循环链表相对更常见些,因此下文以实现无头循环链表为例。

(二)示意图

在这里插入图片描述

(三)操作

1. 创建循环链表

(1)函数声明

int create_list(nd_t **phead,int num);

创建循环链表的第一个节点,
第一个节点的next指向它自己
将第一个节点的堆区地址传给main函数中的指针

(2)注意点
  1. 入参不能为空
  2. 因为需要将申请的第一个节点的地址写入main函数中的指针变量中,因此必须传入二级指针
  3. 申请内存空间后检查是否申请成功
(3)代码实现
int create_list(nd_t **phead,int num){
    if(NULL==phead){
        return -1;
    }
    *phead=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==phead){
        return -1;
    }
    (*phead)->data=num;
    (*phead)->next=*phead; 
    return 0;
}

2. 插入(头插,尾插,任意位置插入)

(1)头插
① 函数声明

int insert_list_by_head(nd_t **phead,int num);

创建新节点
找到尾节点,将尾节点的next指向新的节点
新节点的next指向首节点
*phead指向pnew

② 注意点
  1. 入参不能为NULL
  2. 头插需要更改main函数中phead指针的值,因此需要传二级指针
  3. 需要保证链表中至少有一个节点,无头链表中只要phead不为NULL,就说明至少有一个节点
③ 代码实现
int insert_list_by_head(nd_t **phead,int num){
    //需要保证链表至少有一个节点
    if(NULL==phead){
        return -1;
    }
    //创建新节点
    nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==pnew)
        return -1;
    pnew->data=num;
    //找到尾节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    //插入
    ptemp->next=pnew;
    pnew->next=(*phead);
    *phead=pnew;
    return 0;
}
(2)尾插
① 函数声明

int insert_list_by_tail(nd_t **phead,int num);

创建新节点
找到尾节点,将尾节点的next指向新的节点
新节点的next指向首节点

② 注意点
  1. 头插和尾插的区别仅在于是否需要修改main函数中的指针变量
③ 代码实现
int insert_list_by_tail(nd_t *phead,int num){
    if(NULL==phead)
        return -1;
    //创建新节点
    nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==pnew)
        return -1;
    pnew->data=num;
    //找到尾节点
    nd_t *ptemp=(phead)->next;
    while(ptemp->next!=phead)
    {
        ptemp=ptemp->next;
    }
    //插入
    ptemp->next=pnew;
    pnew->next=phead;
    return 0;
}
(3)任意位置插入
① 函数声明

int insert_list_by_pos(nd_t *phead,int pos,int num);

找到要插入的位置的前一个节点
创建新节点
插入节点

② 注意点
  1. 不支持插入在第一个位置
  2. 如果插入的前一个位置在最后一个可以,但是在第一个就不合理了
③ 代码实现
int insert_list_by_pos(nd_t *phead,int pos,int num){
    if(NULL==phead)
        return -1;
    //不支持插入在第0个位置
    if(pos<=0)
        return -1;
    //找到要插入的节点的前一位
    nd_t *ptemp=phead;
    for(int i=0;i<pos-1;i++){
        ptemp=ptemp->next;        
        //如果插入的前一个位置在最后一个可以,但是在第一个就不合理了
        if(ptemp==phead){
            printf("插入位置不合理\n");
            return -1;
        }
    }
    //创建新节点
    nd_t *pnew=(nd_t *)malloc(sizeof(nd_t));
    if(NULL==pnew)
        return -1;
    pnew->data=num;
    //插入节点
    pnew->next=ptemp->next;
    ptemp->next=pnew;

    return 0;
}

3. 删除(头删,尾删,任意位置删除)

(1)头删
① 函数声明

int delete_list_by_head(nd_t **phead);

找到尾节点
尾节点的next置成*phead->next
*phead=*phead->next
free(pdef)

② 注意点
  1. 需要传入二级指针
  2. 当表中只有一个节点时,进行删除操作时相当于将表销毁了。
③ 代码实现
int delete_list_by_head(nd_t **phead){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    //只有一个节点
    if((*phead)->next==*phead){
        free(*phead);
        *phead=NULL;
        printf("表已清空\n");
        return 0;
    }
    //多个节点
    //找到尾节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    //头删
    nd_t *pdel=*phead;
    *phead=(*phead)->next;
    ptemp->next=*phead;
    free(pdel);
    pdel=NULL;
    return 0;
}
(2)尾删
① 函数声明

int delete_list_by_tail(nd_t **phead);

找到尾节点的前一节点,
尾删操作

② 注意点
  1. 需要传入二级指针
  2. 当表中只有一个节点时,进行删除操作时相当于将表销毁了。
③ 代码实现
int delete_list_by_tail(nd_t **phead){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    //只有一个节点
    if((*phead)->next==*phead){
        free(*phead);
        *phead=NULL;
        printf("表已清空\n");
        return 0;
    }
    //多个节点
    //找到尾节点的前一个节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    //尾删
    nd_t *pdel=ptemp->next;
    ptemp->next=*phead;
    free(pdel);
    pdel=NULL;
    return 0;
}
(3)任意位置删除
① 函数声明

int delete_list_by_pos(nd_t **phead,int pos);

② 注意点
  1. 链表中至少有一个节点
  2. 如果只有一个节点时要删除第0个位置可以成功,此时链表销毁;其他位置均为不合理
  3. 如果多个节点时,需要区分是不是要删除头节点
  4. ptemp是要删除的节点的前一个节点,它不能是尾节点
③ 代码实现
int delete_list_by_pos(nd_t **phead,int pos){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    if(0>pos){
        return -1;
    }
    //如果链表中只有一个节点
    if((*phead)->next==*phead){
        //删除第一个位置的节点
        if(0==pos){
            free(*phead);
            *phead=NULL;
            return 0;
        }
        //删除其他位置节点,均是位置不合理
        printf("位置不合理\n");
        return -1;
    }
    //链表有多个节点
    nd_t *pdel=*phead;
    //删除第一个位置的节点
    //找到尾节点
    nd_t *ptemp=(*phead)->next;
    while(ptemp->next!=*phead)
    {
        ptemp=ptemp->next;
    }
    if(0==pos){
        *phead=(*phead)->next;
        ptemp->next=*phead;
        free(pdel);
        pdel=NULL;
        return 0;
    }
    //删除其他位置节点
    //找到要删除的节点的前一个节点
    ptemp=(*phead);
    for(int i=0;i<pos-1;i++){
        //要删除的节点的前一个节点不能是尾节点
        if(ptemp->next->next==*phead){
            printf("删除位置不合理\n");
            return -1;
        }
        ptemp=ptemp->next;
    }
    pdel=ptemp->next;
    ptemp->next=pdel->next;
    free(pdel);
    pdel=NULL;
    return 0;
}

4. 修改

(1)函数定义

int modify_list_by_pos(nd_t *phead,int pos,int num);

遍历链表找到第pos个位置

(2)注意点
  1. 如果已经到达尾节点就不能再继续向下遍历修改
(3)代码实现
int modify_list_by_pos(nd_t *phead,int pos,int num){
    //至少有一个节点
    if(NULL==phead)
        return -1;
    if(0>pos){
        return -1;
    }
    //找到第pos个位置
    nd_t *ptemp=phead;
    for(int i=0;i<pos;i++){
        if(ptemp->next==phead){
            printf("位置不合理\n");
            return -1;
        }
        ptemp=ptemp->next;
    }
    ptemp->data=num;
    return 0;
}

5. 查询

(1)函数定义

int search_list_by_pos(nd_t *phead,int pos,int *num);

找到第pos个位置
读取数据域数据

(2)注意点
  1. 查询和修改的唯一区别是对数据的处理,修改是将新的数据写到第pos尾的数据域;修改是将pos位的数据域写到num中
  2. 循环结束条件与修改和删除一样
(3)代码实现
int search_list_by_pos(nd_t *phead,int pos,int *num){
    //至少有一个节点
    if(NULL==phead||NULL==num)
        return -1;
    if(0>pos){
        return -1;
    }
    //找到第pos个位置
    nd_t *ptemp=phead;
    for(int i=0;i<pos;i++){
        if(ptemp->next==phead){
            printf("位置不合理\n");
            return -1;
        }
        ptemp=ptemp->next;
    }
    *num=ptemp->data;
    return 0;
}

6. 清空和销毁

(1)函数定义

int destory_list(nd_t **phead);

先判断是不是只有一个节点
采用尾删(使用头删的话还要一直修改main函数中的指针变量的值)

(2)注意点
  1. 使用二级指针
  2. 入参不能为空
(3)代码实现
int destory_list(nd_t **phead){
    if(NULL==phead){
        return -1;
    }
    nd_t *ptemp=*phead;
    //不止一个节点
    //采用尾删,先找到尾节点前一个节点
    while(ptemp->next!=ptemp)
    {
        while (ptemp->next->next!=*phead){
            ptemp=ptemp->next;
        }
        nd_t *pdel=ptemp->next;
        ptemp->next=pdel->next;
        free(pdel);
    }
    //此时只有一个节点了
    free(*phead);
    *phead=NULL;
    printf("表已清空\n");
    return 0;
}

7. 打印链表(方便查看实验现象)

(1)函数定义

int print_list(nd_t *phead);

先打印出第一个节点,
遍历链表,直到ptemp->next==phead时结束

(2)注意点
  1. 在无头链表中phead为NULL,则说明表为空。phead->next==NULL,说明没有第二个节点。
(3)代码实现
int print_list(nd_t *phead){
    if(NULL==phead)
        return -1;
    nd_t *ptemp=phead->next;
    printf("%d ",phead->data);
    while(ptemp!=phead){
        printf("%d ",ptemp->data);
        ptemp=ptemp->next;
    }
    putchar(10);
    return 0;
}

8. 排序(以正序排序为例)

(1)函数定义

int sort_list(nd_t *phead);

选择排序思路

(2)注意点
  1. 外层循环可以不比较最后一个元素
(3)代码实现
int sort_list(nd_t *phead){
    if(NULL==phead){
        return -1;
    }
    nd_t *p=phead;
    nd_t *q=NULL;
    while(p->next!=phead){
        q=p->next;
        while(q!=phead){
            if(p->data>q->data){
                int temp=p->data;
                p->data=q->data;
                q->data=temp;
            }
            q=q->next;
        }
        p=p->next;
    }
    return 0;
}

9.剔重

(1)函数定义

int dedup(nd_t *phead);

动静指针配合
选择排序思路

(2)注意点
  1. 此时外层循环使用p->next!=phead或者p!=phead均可,循环链表此刻不会报段错误,但是用第一种效率会相对略高
(3)代码实现
int dedup_list(nd_t *phead){
    //至少有一个元素
    if(NULL==phead){
        return -1;
    }
    nd_t *p=phead;
    nd_t *q=NULL;
    nd_t *m=NULL;
    while(p->next!=phead){
        m=p;
        q=p->next;
        while(q!=phead){
            if(p->data==q->data){
                m->next=q->next;
                free(q);
                q=m->next;
            }else{
                m=q;
                q=q->next;
            }
        }
        p=p->next;
    }
}

(四)应用:实现约瑟夫环

1.问题描述

有一位叫约瑟夫的将军,在一次战斗中,连同手下的士兵一起被俘虏了。手下的士兵都非常爱国,宁死不投降,约瑟夫将军想了个办法:
让大家站成一圈,开始数数,从1开始数,数到7的人就自杀,
下一个人重新从1开始数,数到7再自杀,依次类推
直到只剩下一个人为止,最终剩下的就是约瑟夫将军,
然后他不想死,他投降了。这种“圈”,我们称之为“约瑟夫环”

要求:编写代码,模拟约瑟夫环淘汰人的过程,
命令行输入 ./a.out 总人数 数到几自杀 (总人数>1 数到几自杀>1 )
要求程序输出:
第x次 淘汰的是y号
以及最终剩下的是几号
如:输入 ./a.out 5 3 则程序输出
第1次 淘汰的是3号
第2次 淘汰的是1号
第3次 淘汰的是5号
第4次 淘汰的是2号
最后剩下的是 4 号

2. 问题分析

首先需要检查参数的合理性,参数都是以字符串形式保存的,需要转换成int型数据;
无头链表创建链表时就是创建第一个节点,即编号为1的人,之后依次开始创建节点

3. 代码实现

main.c文件:

#include "circle_list.h"

int del(nd_t *phead,int n);
int main(int argc, char const *argv[])
{
    if(3 != argc)
    {
        printf("参数不合理\n");
        return -1;
    }
    int num=atoi(argv[1]); //保存个数
    int n=atoi(argv[2]);//数到几
    if(num<=0)
    {
        printf("人数应当大于0\n");
        return-1;
    }
    nd_t *phead=NULL;
    //第一个人及其编号
    create_list(&phead,1);
    //后面的人
    for(int i=2;i<=num;i++){
        if(insert_list_by_tail(phead,i)){
            printf("插入失败\n");
            return -1;
        }
    }
    print_list(phead);  
    del(phead,n);
    return 0;
}

int del(nd_t *phead,int n)
{
    if(NULL==phead)
    {
        printf("传参错误\n");
        return -1;
    }
    if(0>=n)
    {
        printf("传参错误,n应当大于0\n");
        return -1;
    }
    int index=0;
    nd_t *pptemp=NULL;

    while(phead->next!=phead)
    {
        for(int i=0;i<n-1;i++) //phead默认移到下一位了,故只需要再移动n-1次
        {
            pptemp=phead;
            phead=phead->next;
        }
        //此时phead是需要删除的节点,执行删除操作
        pptemp->next=phead->next; 
        printf("第%d次删除%d\n",index+1,phead->data);
        free(phead);
        //删除完成后pead等于后一个节点
        phead=pptemp->next;
        index++;
    }
    printf("%d存活\n",phead->data);
    free(phead);
    phead=NULL;
    return 0;
}

二、代码源码已上传资源

链接:C语言实现循环链表源码链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis数据类型(上篇)

前提&#xff1a;&#xff08;key代表键&#xff09; Redis常用的命令 命令作用keys *查看当前库所有的keyexists key判断某个key是否存在type key查看key是什么类型del key 删除指定的keyunlink key非阻塞删除&#xff0c;仅仅将keys从keyspace元数据中删除&#xff0c;真正的…

【机器学习】模型、算法与数据—机器学习三要素

探索机器学习三要素&#xff1a;模型、算法与数据的交融之旅 一、模型&#xff1a;构建机器学习的基石二、算法&#xff1a;驱动模型学习的引擎三、数据&#xff1a;驱动机器学习的动力源泉四、代码实例&#xff1a;展示三要素的交融与碰撞 在数字时代的浪潮中&#xff0c;机器…

C++模板——函数模板和类模板

目录 泛型编程 函数模板 函数模板概念 函数模板的定义和语法 函数模板的工作原理 函数模板的实例化 隐式实例化 显示实例化 函数模板的匹配原则 类模板 类模板的定义格式 类模板的实例化 泛型编程 什么是泛型编程&#xff1f; 泛型编程&#xff08;Generic Pr…

具身人工智能:人工智能机器人如何感知世界

什么是具身人工智能 虽然近年来机器人在智能城市、工厂和家庭中大量出现,但我们大部分时间都在与由传统手工算法控制的机器人互动。这些机器人的目标很狭隘,很少从周围环境中学习。相比之下,能够与物理环境互动并从中学习的人工智能 (AI) 代理(机器人、虚拟助手或其他智能系…

HCIP-Datacom-ARST自选题库_02_网络安全【道题】

一、单选题 1.关于网络安全性优化的内容&#xff0c;下列哪个选项是错误的? 管理安全 边界安全 访问控制 日志管理 2.如图所示&#xff0c;网络管理员为了抵御DHcP Server仿冒者攻击&#xff0c;在交换机上部署了DHcp snoping功能&#xff0c;那么以下哪一个接口应该被设…

【Java面试】二、Redis篇(中)

文章目录 1、Redis持久化1.1 RDB1.2 AOF1.3 RDB与AOF的对比 2、数据过期策略&#xff08;删除策略&#xff09;2.1 惰性删除2.2 定期删除 3、数据淘汰策略4、主从复制4.1 主从全量同步4.2 增量同步 5、哨兵模式5.1 服务状态监控5.2 哨兵选主规则5.3 哨兵模式下&#xff0c;Redi…

JMETER工具:以录制手机app为例

JMETER工具&#xff1a;以录制手机app为例子 JMETER安装和环境配置 pc需要安装jdk&#xff0c;并进行jdk的环境配置&#xff0c;安装好jdk并配置好后&#xff0c;通过命令行输入java –version出现以下界面就表示安装成功&#xff1a; &#xff08;对应的jdk版本不可太低&…

C++实现定长内存池

项目介绍 本项目实现的是一个高并发的内存池&#xff0c;它的原型是Google的一个开源项目tcmalloc&#xff0c;tcmalloc全称Thread-Caching Malloc&#xff0c;即线程缓存的malloc&#xff0c;实现了高效的多线程内存管理&#xff0c;用于替换系统的内存分配相关函数malloc和fr…

Mysql之主从同步

1.BinLog同步机制 Mysql要去保证高可用&#xff0c;或者去分担请求压力&#xff0c;一般会去主从部署&#xff0c;读写分离。写库只负责写&#xff0c;而读库更多的去承担读的请求&#xff0c;从库不写数据&#xff0c;数据从主库同步&#xff0c;那么到底是怎么同步的呢&…

【2024】HNCTF

Web Please_RCE_Me GET传参输入?moranflag&#xff0c;之后获取源码&#xff1a;<?php if($_GET[moran] flag){highlight_file(__FILE__);if(isset($_POST[task])&&isset($_POST[flag])){$str1 $_POST[task];$str2 $_POST[flag];if(preg_match(/system|eval|a…

【C#】未能加载文件或程序集“CefSharp.Core.Runtime.dll”或它的某一个依赖项。找不到指定的模块。

欢迎来到《小5讲堂》 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景错误提示分析原因解决方法Chromium知识点相关文章 背景 最近在使…

RabbitMQ-默认读、写方式介绍

1、RabbitMQ简介 rabbitmq是一个开源的消息中间件&#xff0c;主要有以下用途&#xff0c;分别是&#xff1a; 应用解耦&#xff1a;通过使用RabbitMQ&#xff0c;不同的应用程序之间可以通过消息进行通信&#xff0c;从而降低应用程序之间的直接依赖性&#xff0c;提高系统的…

有什么普通人可以做的赚钱软件?盘点9个适合普通人长期做的软件

在这个互联网高速发展的时代&#xff0c;智能手机已经成为我们生活中不可分割的一部分。众多APP的涌现&#xff0c;使得许多朋友都在寻求通过手机赚钱的方法。 然而&#xff0c;面对市面上琳琅满目的网上赚钱APP&#xff0c;我们该如何挑选呢&#xff1f;别担心&#xff0c;今…

python web自动化(验证码处理)

1.解决验证码问题的常⻅⼏种⽅式 1&#xff09; Debug模式启动浏览器&#xff08;浏览器复⽤&#xff09;&#xff1a; 原理&#xff1a;浏览器是有缓存记录的&#xff0c;只需要 沿⽤已经保存有登录记录的浏览器 进⾏后续的操作就⾏ 2&#xff09;识别法&#xff1a; 原理…

pycharm中,出现SyntaxError: Non-ASCII character ‘\xe4‘ in file... 的问题以及解决方法

文章目录 一、问题描述二、解决方法 一、问题描述 在pycharm中&#xff0c;使用python中编写中文字符时&#xff0c;会提示如下错误信息&#xff1a; SyntaxError: Non-ASCII character \xe4 in file ...... on line 8, but no encoding declared; see http://python.org/dev…

网上比较受认可的赚钱软件有哪些?众多兼职选择中总有一个适合你

在这个互联网高速发展的时代&#xff0c;网上赚钱似乎成了一种潮流。但是&#xff0c;你是否还在靠运气寻找赚钱的机会&#xff1f;是否还在为找不到靠谱的兼职平台而苦恼&#xff1f; 今天&#xff0c;就为你揭秘那些真正靠谱的网上赚钱平台&#xff0c;让你的赚钱之路不再迷…

MySQL--InnoDB体系结构

目录 一、物理存储结构 二、表空间 1.数据表空间介绍 2.数据表空间迁移 3.共享表空间 4.临时表空间 5.undo表空间 三、InnoDB内存结构 1.innodb_buffer_pool 2.innodb_log_buffer 四、InnoDB 8.0结构图例 五、InnoDB重要参数 1.redo log刷新磁盘策略 2.刷盘方式&…

S1E45:单链表1 课后作业

测试题&#xff1a;0. 相比起数组来说&#xff0c;单链表具有哪些优势呢&#xff1f; 答&#xff1a;长度非固定&#xff0c;可以申请添加长度 答案&#xff1a;对于数组来说&#xff0c;随机插入或者删除其中间的某一个元素&#xff0c;都是需要大量的移动操作&#xff0c;而…

基于tcp实现自定义应用层协议

认识协议 协议&#xff08;Protocol&#xff09; 是一种通信规则或标准&#xff0c;用于定义通信双方或多方之间如何交互和传输数据。在计算机网络和通信系统中&#xff0c;协议规定了通信实体之间信息交换的格式、顺序、定时以及有关同步等事宜的约定。简易来说协议就是通信…

网络工程师---第三十八天

ISIS&#xff1a; ISIS含义&#xff1a;中间系统到中间系统IS-IS。 ISIS特点&#xff1a;①内部网关协议IGP&#xff08;Interior Gateway Protocol&#xff09;&#xff0c;用于自治系统内部&#xff1b; ②IS-IS也是一种链路状态协议&#xff0c;使用最短路径优先SPF算法进…