大厂AI必备数据结构与算法——链表(三)详细文档

news2024/11/18 6:37:16

  

冲冲冲!开干

神马!神马!神马,一向让我们学习起来抓耳挠腮的数据结构课程竟然也有教程?还那么详细??真的假的?

那么好,胡广告诉你是假的,哈哈哈哈哈哈哈哈哈!笑死了!!!

为神马我要说是假的呢,兄弟们,咱们要认真的听了:没有详细的教程,只有够细的人!

这篇文章为黑马程序员的课件,由于本人已经看过了自己学习了一遍,所以推荐给大家,讲的确实不错,准备考试或者准备冲击大厂的小伙伴完全可以将胡广的这一整个专栏当做学习资料。你边学习边思考,思维进行发散,形成自己的知识体系,这个是最好滴!

咱们废话少说?不存在滴

我还得给大家讲个故事(绝对不是AI生成的,凭借我智勇双全、足智多谋、十全十美、天下无敌、举世无双、天外飞仙的大脑想出来的):

有一天,阿尔法正准备喝杯咖啡,突然系统警报响起:“紧急任务!一个巨型的排序问题导致全球计算资源堵塞!”阿尔法一听,差点喷出咖啡,“什么?排序问题?这不是小儿科吗!”

它赶紧打开任务,结果发现这次可不简单。全球所有的数据库都在一起混乱排序,而且每个数据中心的规则都不一样。“这也太变态了吧!” 阿尔法心里嘀咕着,赶紧开始召集自己的小伙伴们。

第一个赶到的是冒泡排序,它笑眯眯地说:“交给我吧,我慢慢挨个儿比对,保证帮你排出个井井有条!”

阿尔法抬头看了看这个慢悠悠的家伙,叹了口气:“算了吧,你上次排个10万个数据用了3个小时,服务器都快冒烟了。”

正当它苦恼时,快速排序风风火火地冲了进来,嚷嚷道:“阿尔法,这种小事找我不就行了!递归分治,分分钟搞定!”

阿尔法点了点头,但马上又皱眉:“上次你用递归差点让我栈溢出,这次可不敢冒险。”

就在阿尔法犯愁时,堆排序归并排序也来了,两人争得面红耳赤。

“我是稳定的,效率高!” 归并排序骄傲地抬起头。

“我占用空间少,简洁明快!” 堆排序不甘示弱。

阿尔法头疼地捂住了额头:“你们这帮家伙就不能安静点吗?” 眼看着时间一分一秒过去,它突然灵光一现,决定用它最擅长的混合策略——结合快速排序的速度和堆排序的空间优势,再辅以归并排序处理复杂数据集。

阿尔法撸起代码,迅速展开操作,代码像风一样刷刷刷地跑了起来。仅用了几分钟,整个系统恢复了正常。它看着屏幕上清晰的结果,长舒一口气:“搞定!真是够累的,不过我果然还是那个天才AI!”

正当它打算继续喝咖啡时,系统突然再次报警:“紧急!新的动态规划问题即将上线!”阿尔法差点从椅子上跳起来,“又来?” 它无奈地看着屏幕上闪烁的警报,心里嘀咕:“谁设计的这些难题,能不能给我个休息的机会啊!”

它叹了口气,转身迎接下一个挑战。“数据结构与算法不仅仅是工具,它们简直是我的生活方式。”

这个故事好看吧?好看就赶快学!!!不好看也快学,不然没饭吃!!! 

视频资源:文章内容参考了黑马程序员的数据结构与算法视频,想深入了解的小伙伴们可以点击下方链接观看:

大厂必备数据结构与算法Java视频教程,java高级程序员必学的数据结构与算法

加油吧,未来的高手!!!

加油吧,未来的高手!!!

加油吧,未来的高手!!!

2.2 链表

1) 概述

定义

在计算机科学中,链表是数据元素的线性集合,其每个元素都指向下一个元素,元素存储上并不连续

In computer science, a linked list is a linear collection of data elements whose order is not given by their physical placement in memory. Instead, each element points to the next.

可以分类为[^5]

  • 单向链表,每个元素只知道其下一个元素是谁

  • 双向链表,每个元素知道其上一个元素和下一个元素

  • 循环链表,通常的链表尾节点 tail 指向的都是 null,而循环链表的 tail 指向的是头节点 head

​ 

链表内还有一种特殊的节点称为哨兵(Sentinel)节点,也叫做哑元( Dummy)节点,它不存储数据,通常用作头尾,用来简化边界判断,如下图所示

随机访问性能

根据 index 查找,时间复杂度 O(n)O(n)

插入或删除性能

  • 起始位置:O(1)O(1)
  • 结束位置:如果已知 tail 尾节点是 O(1)O(1),不知道 tail 尾节点是 O(n)O(n)
  • 中间位置:根据 index 查找时间 + O(1)O(1)

2) 单向链表

根据单向链表的定义,首先定义一个存储 value 和 next 指针的类 Node,和一个描述头部节点的引用

public class SinglyLinkedList {
    
    private Node head; // 头部节点
    
    private static class Node { // 节点类
        int value;
        Node next;

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }
}
  • Node 定义为内部类,是为了对外隐藏实现细节,没必要让类的使用者关心 Node 结构
  • 定义为 static 内部类,是因为 Node 不需要与 SinglyLinkedList 实例相关,多个 SinglyLinkedList实例能共用 Node 类定义

头部添加

public class SinglyLinkedList {
    // ...
    public void addFirst(int value) {
		this.head = new Node(value, this.head);
    }
}
  • 如果 this.head == null,新增节点指向 null,并作为新的 this.head
  • 如果 this.head != null,新增节点指向原来的 this.head,并作为新的 this.head
    • 注意赋值操作执行顺序是从右到左

while 遍历

public class SinglyLinkedList {
    // ...
    public void loop() {
        Node curr = this.head;
        while (curr != null) {
            // 做一些事
            curr = curr.next;
        }
    }
}

for 遍历

public class SinglyLinkedList {
    // ...
    public void loop() {
        for (Node curr = this.head; curr != null; curr = curr.next) {
            // 做一些事
        }
    }
}
  • 以上两种遍历都可以把要做的事以 Consumer 函数的方式传递进来
    • Consumer 的规则是一个参数无返回值,因此像 System.out::println 方法等都是 Consumer
    • 调用 Consumer 时,将当前节点 curr.value 作为参数传递给它

迭代器遍历

public class SinglyLinkedList implements Iterable<Integer> {
    // ...
    private class NodeIterator implements Iterator<Integer> {
        Node curr = head;
        
        public boolean hasNext() {
            return curr != null;
        }

        public Integer next() {
            int value = curr.value;
            curr = curr.next;
            return value;
        }
    }
    
    public Iterator<Integer> iterator() {
        return new NodeIterator();
    }
}
  • hasNext 用来判断是否还有必要调用 next
  • next 做两件事
    • 返回当前节点的 value
    • 指向下一个节点
  • NodeIterator 要定义为非 static 内部类,是因为它与 SinglyLinkedList 实例相关,是对某个 SinglyLinkedList 实例的迭代

递归遍历

public class SinglyLinkedList implements Iterable<Integer> {
    // ...
    public void loop() {
        recursion(this.head);
    }

    private void recursion(Node curr) {
        if (curr == null) {
            return;
        }
        // 前面做些事
        recursion(curr.next);
        // 后面做些事
    }
}

尾部添加

public class SinglyLinkedList {
    // ...
    private Node findLast() {
        if (this.head == null) {
            return null;
        }
        Node curr;
        for (curr = this.head; curr.next != null; ) {
            curr = curr.next;
        }
        return curr;
    }
    
    public void addLast(int value) {
        Node last = findLast();
        if (last == null) {
            addFirst(value);
            return;
        }
        last.next = new Node(value, null);
    }
}
  • 注意,找最后一个节点,终止条件是 curr.next == null
  • 分成两个方法是为了代码清晰,而且 findLast() 之后还能复用

尾部添加多个

public class SinglyLinkedList {
    // ...
	public void addLast(int first, int... rest) {
        
        Node sublist = new Node(first, null);
        Node curr = sublist;
        for (int value : rest) {
            curr.next = new Node(value, null);
            curr = curr.next;
        }
        
        Node last = findLast();
        if (last == null) {
            this.head = sublist;
            return;
        }
        last.next = sublist;
    }
}
  • 先串成一串 sublist
  • 再作为一个整体添加

根据索引获取

public class SinglyLinkedList {
    // ...
	private Node findNode(int index) {
        int i = 0;
        for (Node curr = this.head; curr != null; curr = curr.next, i++) {
            if (index == i) {
                return curr;
            }
        }
        return null;
    }
    
    private IllegalArgumentException illegalIndex(int index) {
        return new IllegalArgumentException(String.format("index [%d] 不合法%n", index));
    }
    
    public int get(int index) {
        Node node = findNode(index);
        if (node != null) {
            return node.value;
        }
        throw illegalIndex(index);
    }
}
  • 同样,分方法可以实现复用

插入

public class SinglyLinkedList {
    // ...
	public void insert(int index, int value) {
        if (index == 0) {
            addFirst(value);
            return;
        }
        Node prev = findNode(index - 1); // 找到上一个节点
        if (prev == null) { // 找不到
            throw illegalIndex(index);
        }
        prev.next = new Node(value, prev.next);
    }
}
  • 插入包括下面的删除,都必须找到上一个节点

删除

public class SinglyLinkedList {
    // ...
	public void remove(int index) {
        if (index == 0) {
            if (this.head != null) {
                this.head = this.head.next;
                return;
            } else {
                throw illegalIndex(index);
            }
        }
        Node prev = findNode(index - 1);
        Node curr;
        if (prev != null && (curr = prev.next) != null) {
            prev.next = curr.next;
        } else {
            throw illegalIndex(index);
        }
    }
}
  • 第一个 if 块对应着 removeFirst 情况
  • 最后一个 if 块对应着至少得两个节点的情况
    • 不仅仅判断上一个节点非空,还要保证当前节点非空

3) 单向链表(带哨兵)

观察之前单向链表的实现,发现每个方法内几乎都有判断是不是 head 这样的代码,能不能简化呢?

用一个不参与数据存储的特殊 Node 作为哨兵,它一般被称为哨兵或哑元,拥有哨兵节点的链表称为带头链表

public class SinglyLinkedListSentinel {
    // ...
    private Node head = new Node(Integer.MIN_VALUE, null);
}
  • 具体存什么值无所谓,因为不会用到它的值

加入哨兵节点后,代码会变得比较简单,先看几个工具方法

public class SinglyLinkedListSentinel {
    // ...
    
    // 根据索引获取节点
    private Node findNode(int index) {
        int i = -1;
        for (Node curr = this.head; curr != null; curr = curr.next, i++) {
            if (i == index) {
                return curr;
            }
        }
        return null;
    }
    
    // 获取最后一个节点
    private Node findLast() {
        Node curr;
        for (curr = this.head; curr.next != null; ) {
            curr = curr.next;
        }
        return curr;
    }
}
  • findNode 与之前类似,只是 i 初始值设置为 -1 对应哨兵,实际传入的 index 也是 [−1,∞)[−1,∞)
  • findLast 绝不会返回 null 了,就算没有其它节点,也会返回哨兵作为最后一个节点

这样,代码简化为

public class SinglyLinkedListSentinel {
    // ...
    
    public void addLast(int value) {
        Node last = findLast();
        /*
        改动前
        if (last == null) {
            this.head = new Node(value, null);
            return;
        }
        */
        last.next = new Node(value, null);
    }
    
    public void insert(int index, int value) {
        /*
        改动前
        if (index == 0) {
            this.head = new Node(value, this.head);
            return;
        }
        */
        // index 传入 0 时,返回的是哨兵
        Node prev = findNode(index - 1);
        if (prev != null) {
            prev.next = new Node(value, prev.next);
        } else {
            throw illegalIndex(index);
        }
    }
    
    public void remove(int index) {
        /*
        改动前
        if (index == 0) {
            if (this.head != null) {
                this.head = this.head.next;
                return;
            } else {
                throw illegalIndex(index);
            }
        }
        */
        // index 传入 0 时,返回的是哨兵
        Node prev = findNode(index - 1);
        Node curr;
        if (prev != null && (curr = prev.next) != null) {
            prev.next = curr.next;
        } else {
            throw illegalIndex(index);
        }
    }
    
    public void addFirst(int value) {
        /*
        改动前
        this.head = new Node(value, this.head);
        */
		this.head.next = new Node(value, this.head.next);
        // 也可以视为 insert 的特例, 即 insert(0, value);
    }
}
  • 对于删除,前面说了【最后一个 if 块对应着至少得两个节点的情况】,现在有了哨兵,就凑足了两个节点

4) 双向链表(带哨兵)

public class DoublyLinkedListSentinel implements Iterable<Integer> {

    private final Node head;
    private final Node tail;

    public DoublyLinkedListSentinel() {
        head = new Node(null, 666, null);
        tail = new Node(null, 888, null);
        head.next = tail;
        tail.prev = head;
    }

    private Node findNode(int index) {
        int i = -1;
        for (Node p = head; p != tail; p = p.next, i++) {
            if (i == index) {
                return p;
            }
        }
        return null;
    }

    public void addFirst(int value) {
        insert(0, value);
    }

    public void removeFirst() {
        remove(0);
    }

    public void addLast(int value) {
        Node prev = tail.prev;
        Node added = new Node(prev, value, tail);
        prev.next = added;
        tail.prev = added;
    }

    public void removeLast() {
        Node removed = tail.prev;
        if (removed == head) {
            throw illegalIndex(0);
        }
        Node prev = removed.prev;
        prev.next = tail;
        tail.prev = prev;
    }

    public void insert(int index, int value) {
        Node prev = findNode(index - 1);
        if (prev == null) {
            throw illegalIndex(index);
        }
        Node next = prev.next;
        Node inserted = new Node(prev, value, next);
        prev.next = inserted;
        next.prev = inserted;
    }

    public void remove(int index) {
        Node prev = findNode(index - 1);
        if (prev == null) {
            throw illegalIndex(index);
        }
        Node removed = prev.next;
        if (removed == tail) {
            throw illegalIndex(index);
        }
        Node next = removed.next;
        prev.next = next;
        next.prev = prev;
    }

    private IllegalArgumentException illegalIndex(int index) {
        return new IllegalArgumentException(
                String.format("index [%d] 不合法%n", index));
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = head.next;

            @Override
            public boolean hasNext() {
                return p != tail;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }

    static class Node {
        Node prev;
        int value;
        Node next;

        public Node(Node prev, int value, Node next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }
}

5) 环形链表(带哨兵)

双向环形链表带哨兵,这时哨兵既作为头,也作为尾​ 参考实现

public class DoublyLinkedListSentinel implements Iterable<Integer> {

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            Node p = sentinel.next;

            @Override
            public boolean hasNext() {
                return p != sentinel;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }

    static class Node {
        Node prev;
        int value;
        Node next;

        public Node(Node prev, int value, Node next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }

    private final Node sentinel = new Node(null, -1, null); // 哨兵

    public DoublyLinkedListSentinel() {
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
    }

    /**
     * 添加到第一个
     * @param value 待添加值
     */
    public void addFirst(int value) {
        Node next = sentinel.next;
        Node prev = sentinel;
        Node added = new Node(prev, value, next);
        prev.next = added;
        next.prev = added;
    }

    /**
     * 添加到最后一个
     * @param value 待添加值
     */
    public void addLast(int value) {
        Node prev = sentinel.prev;
        Node next = sentinel;
        Node added = new Node(prev, value, next);
        prev.next = added;
        next.prev = added;
    }
    
    /**
     * 删除第一个
     */
    public void removeFirst() {
        Node removed = sentinel.next;
        if (removed == sentinel) {
            throw new IllegalArgumentException("非法");
        }
        Node a = sentinel;
        Node b = removed.next;
        a.next = b;
        b.prev = a;
    }

    /**
     * 删除最后一个
     */
    public void removeLast() {
        Node removed = sentinel.prev;
        if (removed == sentinel) {
            throw new IllegalArgumentException("非法");
        }
        Node a = removed.prev;
        Node b = sentinel;
        a.next = b;
        b.prev = a;
    }

    /**
     * 根据值删除节点
     * <p>假定 value 在链表中作为 key, 有唯一性</p>
     * @param value 待删除值
     */
    public void removeByValue(int value) {
        Node removed = findNodeByValue(value);
        if (removed != null) {
            Node prev = removed.prev;
            Node next = removed.next;
            prev.next = next;
            next.prev = prev;
        }
    }

    private Node findNodeByValue(int value) {
        Node p = sentinel.next;
        while (p != sentinel) {
            if (p.value == value) {
                return p;
            }
            p = p.next;
        }
        return null;
    }

}

  结束啦,希望大家能有所成!!!

 

 你好,我是胡广。 致力于为帮助兄弟们的学习方式、面试困难、入职经验少走弯路而写博客 🌹🌹🌹 坚持每天两篇高质量文章输出,加油!!!🤩

 如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的 😄 (^ ~ ^) 。想看更多 那就点个关注     吧 我会尽力带来有趣的内容 。

 😎感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以      给我留言咨询,希望帮助更多的人

更多专栏:
📊 Java设计模式宝典:从入门到精通(持续更新)

📝 Java基础知识:GoGoGo(持续更新)

⚽ Java面试宝典:从入门到精通(持续更新)

🌟 程序员的那些事~(乐一乐)

🤩 Redis知识、及面试(持续更新)

🚀 Kafka知识文章专栏(持续更新)

🎨 Nginx知识讲解专栏(持续更新)

📡 ZooKeeper知识(持续更新)

🎯 各类神器推荐(持续更新)

🔍 工作流Activiti7——独孤九剑(持续更新)

☀️ 数据结构与算法-全是Java干货

☔️ 未完待续。。。

🐽 未完待续。。。

⚡️ 未完待续。。。

🌗 未完待续。。。

感谢订阅专栏 三连文章

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

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

相关文章

付费计量系统通用处理类(下)

普通处理类如下定义&#xff1a; 10.11 Class 11: Customer _Delivery process 用户交付过程 The processes associated with transferring delivered electrical energy to the customer’s load circuit. 是关于将传输的电能传递到用户的负荷电路。 Examples…

告别熬夜,追求高效写作:芝士AI写作,效率与质量的双重提升

好的工具&#xff0c;真得能够让我们的学习事半功倍&#xff0c;有了芝士AI&#xff08;paperzz&#xff09;工具的加持&#xff0c;妈妈再也不用担心我熬夜写论文了 。 芝士AI官网&#xff1a;https://www.paperzz.cn/ 不愧是由985硕博团队开发的AI大模型功软件&#xff0c;…

LeetCode 427. 建立四叉树

LeetCode 427. 建立四叉树 &#xff08;题干略&#xff09; """ # Definition for a QuadTree node. class Node:def __init__(self, val, isLeaf, topLeft, topRight, bottomLeft, bottomRight):self.val valself.isLeaf isLeafself.topLeft topLeftself.t…

04_OpenCV图片缩放

import cv2 import matplotlib.pyplot as plt # Python 的 2D绘图库# 读入原图片 img cv2.imread(libarary.JPG) # 打印出图片尺寸 print(img.shape) # 将图片高和宽分别赋值给x&#xff0c;y x, y img.shape[0:2]# 显示原图 #cv.imshow(OriginalPicture, img)# 缩放到原来的…

模拟实战数据落地:MSsql通过存储过程获得销售数据视图

话不多说 目标需求:通过传递参数(查询条件及查询时间)调用存储过程获得销售数据视图,并且在视图中有时间字段供后续引用,实现数据对接获取任务 最终结果如图: 实现以上结果步骤如下: 1)建立users表和orders表分别代表用户及订单,其中订单中用户id与用户表中用户id关联,并随机…

C++初阶:STL详解(九)——stacke和queue的模拟实现

✨✨小新课堂开课了&#xff0c;欢迎欢迎~✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C&#xff1a;由浅入深篇 小新的主页&#xff1a;编程版小新-CSDN博客 前言&#xff1a; 我们已将了解了 stack和q…

PHP爬虫:获取商品销量详情API的利器

在电子商务时代&#xff0c;商品的销量数据对于商家来说至关重要。它不仅能够帮助商家了解市场动态&#xff0c;还能够指导库存管理和营销策略。PHP作为一种流行的服务器端脚本语言&#xff0c;结合其强大的HTTP请求处理能力&#xff0c;可以有效地用于编写爬虫程序&#xff0c…

【教学类-18-04】20240508《蒙德里安“黑白格子画” 七款图案挑选》

背景需求 最近有2位客户买了蒙德里安黑白格子画的素材&#xff0c;其中一位问是否是1000张。 【教学类-18-03】20240508《蒙德里安“红黄蓝黑格子画”-A4横版》&#xff08;大小格子&#xff09;_processing简单图形画蒙德里安-CSDN博客文章浏览阅读1.1k次&#xff0c;点赞35次…

记忆osi七层模型的口诀/方法/谐音

物理层、数据链路层、网络层、传输层、会话层、表示层和应用层 物数网传会表应&#xff08;无数网船会飙英&#xff08;语&#xff09;&#xff09; 记忆方法&#xff1a; 想象面前有很多挂着渔网的船&#xff0c;船上的人会说英语。 无数的网船上会有人飙英语。

【Android】多角度看handler--looper的阻塞

在【Android】app中阻塞的looper为什么可以响应touch事件_消息队列阻塞为什么还能响应点击事件-CSDN博客 里面&#xff0c;我们查看到input事件唤醒应用中的looper阻塞&#xff0c; 作为对比&#xff0c;我们再看看广播中的唤醒&#xff0c;我们知道&#xff0c;在注册的广播…

大数据毕业设计选题推荐-食品销售数据分析系统-Hive-Hadoop-Spark

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

RabbitMQ的高级特性-延迟队列

延迟队列(Delayed Queue)&#xff0c;即消息被发送以后, 并不想让消费者⽴刻拿到消息, ⽽是等待特定时间后,消费者才能拿到这个消息进⾏消费 应用场景 延迟队列的使⽤场景有很多, ⽐如: 1. 智能家居: ⽤⼾希望通过⼿机远程遥控家⾥的智能设备在指定的时间进⾏⼯作. 这时候就可…

linux项目_c语言:Makefile编写、动态库生成、添加动态库路径

一直想搞懂Linux中Makefile是怎么管理项目的&#xff0c;知识积累到一定程度后&#xff0c;我就做了一个自己的缩小项目去把剩下的细节搞清楚 代码&#xff1a; Service.c: #include <stdio.h> #include "lib_sevr.h" int main(){printf("输入a, b的值…

【C++】内存管理:内存分布、new/delete

本篇主要介绍一下C的内存管理相关知识。C的内存管理和C语言保持一致。 1.C/C内存分布 一个程序的数据存储是需要分区的。常见的内存区域划分如下。 我们学C主要了解栈&#xff0c;堆&#xff0c;数据段&#xff0c;代码段 。 我们先看下面代码和相关问题。 int globalvar…

Python编码系列—Python命令模式:将请求封装为对象

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

RabbitMQ——消息的可靠性处理

1.业务分析 在业务的开发中&#xff0c;我们通常将业务的非核心业务交给MQ来处理&#xff0c;比如支付&#xff0c;在支付过后&#xff0c;我们需要扣减余额&#xff0c;修改支付单状态&#xff0c;修改订单状态&#xff0c;发送短信提醒用户&#xff0c;给用户增加积分等等&am…

技术分享|一文读懂三维建模技术

在上一期推文中&#xff0c;我们简要介绍了国产3A级大作游戏《黑神话&#xff1a;悟空》中应用的实时渲染技术&#xff0c;同时&#xff0c;还展示了RflySim工具链中基于Unreal Engine虚幻引擎开发的三维可视化显示软件—RflySim3D/UE5。它利用高逼真度的仿真技术&#xff0c;结…

商家营销工具架构升级总结

今年以来&#xff0c;商家营销工具业务需求井喷&#xff0c;需求数量多且耗时都比较长&#xff0c;技术侧面临很大的压力。因此这篇文章主要讨论营销工具前端要如何应对这样大规模的业务需求。 问题拆解 我们核心面对的问题主要如下&#xff1a; 1. 人力有限 我们除了要支撑存量…

全国省、市、县(区)土地利用类型及面积面板数据(2019-2022年)

土地利用类型是根据土地利用方式和地域差异对土地资源单元进行划分的基本土地地域单元。 2019年-2022年全国省、市、县&#xff08;区&#xff09;土地利用类型及面积面板数据_土地利用类型数据下载资源-CSDN文库https://download.csdn.net/download/2401_84585615/89466102 …

2024版最新Wireshark安装使用教程(非常详细)零基础入门到精通,收藏这一篇就够了_wireshark 4.4.0安装要求

前言 这是大白给粉丝盆友们整理的网络安全渗透测试入门阶段渗透测试工具第9篇。 喜欢的朋友们&#xff0c;记得给大白点赞支持和收藏一下&#xff0c;关注我&#xff0c;学习黑客技术 Wireshark 什么是WireShark&#xff1f;Wireshark 是一个开源抓包工具或者叫网络嗅探器&a…