算法06链表

news2024/11/23 14:03:23

算法06链表

  • 一、链表概述
    • 1.1概述
    • 1.2链表的组成部分:
    • 1.3链表的优缺点:
  • 二、链表典例
    • 力扣707.设计链表
      • 难点分析:
        • (1)`MyLinkedList`成员变量的确定:
        • (2)初始化自定义链表:
        • (3)底层链表的选择:
    • 完整代码:
    • 力扣206.翻转链表
      • 难点分析:
        • (1)翻转方式的选择:
      • 完整代码:
        • (1)借助栈翻转:
        • (2)原地翻转:
    • 力扣92.翻转链表Ⅱ
    • 力扣.24两两交换链表中的节点
      • 难点分析:
        • (1)处理边界:
      • 完整代码:
    • 力扣.19删除链表倒数第n个节点
      • 难点分析
      • 完整代码
    • 力扣142.环形链表
      • 难点分析:
      • 完整代码:
        • 哈希法:
        • 逻辑推导+快慢指针
  • 三、感受与思考

一、链表概述

在这里插入图片描述

1.1概述

在C++中,链表是一种线性数据结构,但它并不像数组那样在内存中存储连续的元素,而是通过每个元素(通常称为节点)包含一个指向下一个节点的指针来链接各个元素。链表的节点通常包含两部分:数据域(存储元素值)和指针域(存储指向下一个节点的指针)。

1.2链表的组成部分:

  1. 节点(Node)
    一个节点通常定义为一个结构体或类,包含数据和指向下一个节点的指针(在单链表中),或者前后节点指针(在双向链表中)。

    // 单链表节点示例
    struct ListNode {
        int value; // 数据域
        ListNode* next; // 指针域,指向下一个节点
    };
    
  2. 链表操作

    • 创建链表:链表通常从一个空节点(即头节点)开始,头节点的 next 指针可能指向实际数据的第一个节点,也可能为空(表示链表为空)。
    • 插入节点:在链表的任意位置插入一个新节点,涉及更改相应节点的指针以建立新的链接关系。
    • 删除节点:从链表中移除一个节点,需要更新其前驱节点的指针跳过被删除节点。
    • 遍历链表:通过从头节点开始,沿着 next 指针逐个访问节点,直至遇到 nextnullptr 的节点(链表尾部)。
    • 查找节点:通过遍历链表查找具有特定值的节点。

1.3链表的优缺点:

  • 优点
    • 动态扩展:无需预先知道数据规模,可以根据需要动态添加或删除节点。
    • 插入和删除效率较高:在链表头部或尾部插入和删除的时间复杂度一般为O(1),在中间插入和删除的时间复杂度为O(n)(需要找到插入或删除位置)。
  • 缺点
    • 访问效率相对较低:随机访问一个节点的时间复杂度为O(n),因为需要从头节点开始顺着指针找到目标节点。
    • 需要额外存储指针的空间开销。

在实际编程中,链表还会有多种变形,比如双向链表循环链表带头节点的链表等,每种都有其特定的应用场景和操作特点。

二、链表典例

力扣707.设计链表

原题链接

难点分析:

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */
(1)MyLinkedList成员变量的确定:

由题目中透露出的信息:

  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。

我们可以知道,我们最好进行链表长度的维护,这样在验证index的合法性时,不需要每一次都遍历整个链表,直接调用我们维护的长度即可:

    //选择单链表作为此类的底层实现,定义单链表结构体
    struct ListNode{
        int val; //值
        ListNode* next; //链,指向下一个节点的内存地址
        ListNode(int val):val(val), next(nullptr){
        }
    };
    //虚拟头节点,简化代码,后面详细介绍
    ListNode* _dummyNode;
    //维护链表长度
    int _size;
(2)初始化自定义链表:

力扣在进行代码测试时,会将MyLinkedList初始化为一个对象,后续所有函数调用都是通过这个对象进行的,因此我们需要对这个对象进行初始化,即编写构造函数

    //类的构造函数,初始化自定义链表对象
    MyLinkedList() {
        //初始化虚拟头节点,统一节点的删除操作
        _dummyNode = new ListNode(0); //调用底层链表有参构造函数初始化
        _size = 0; //虚拟头节点不计入长度计算
    }
(3)底层链表的选择:

MyLinkedList这个类需要基于单链表或双向链表实现,不同的选择会写出不同的代码,我为了优化代码,选择了带虚拟头结点的链表(单链表)作为底层数据结构。

    //定义单链表结构体
    struct ListNode{
        int val; //值
        ListNode* next; //链,指向下一个节点的内存地址
        ListNode(int val):val(val), next(nullptr){
        }
    };

完整代码:

class MyLinkedList {
private:
    //定义单链表结构体
    struct ListNode{
        int val; //值
        ListNode* next; //链,指向下一个节点的内存地址
        ListNode(int val):val(val), next(nullptr){
        }
    };
    ListNode* _dummyNode;
    int _size;



public:
    //类的构造函数,初始化自定义链表对象
    MyLinkedList() {
        //初始化虚拟头节点,统一节点的删除操作
        _dummyNode = new ListNode(0); //调用有参构造函数初始化
        _size = 0; //虚拟头节点不计入长度计算
    }
    
    
    int get(int index) {
        if(_size == 0 || index > _size - 1|| index < 0){
            return -1;
        }
        else{
            //链表是链式的,在内存中是随机分布的,并不是线性连续,因此不能随机访问,而是通过指针连接
            ListNode* cur = _dummyNode->next;
            while(index--){
                cur = cur->next;
            }
            return cur->val;
        }

    }
    
    void addAtHead(int val) {
        //申请内存,生成新节点
        ListNode* newNode = new ListNode(val);
        //插入头节点
        newNode->next = _dummyNode->next;
        _dummyNode->next = newNode;
        //维护链表长度
        _size++;

    }
    
    void addAtTail(int val) {
        //申请内存,生成新节点
        ListNode* newNode = new ListNode(val);

        //移动指针,找到最后一个链表节点
        int index = _size;
        ListNode* cur = _dummyNode;
        while(cur->next != nullptr){
            cur = cur->next;
        }

        cur->next = newNode;
        _size++;

    }
    
    void addAtIndex(int index, int val) {
        if(index > _size) return;
        if(index < 0) index = 0;

        //申请内存,生成新节点
        ListNode* newNode = new ListNode(val);

        //移动指针到达index下标对应节点的前一个位置
        //_dummyNode开始指向虚拟节点
        ListNode* cur = _dummyNode;
        while(index--){
            cur = cur->next;
        }

        newNode->next = cur->next;
        cur->next = newNode;
        _size++;

    }
    
    void deleteAtIndex(int index) {
        if (index >= _size || index < 0) {
            return;
        }
        ListNode* cur = _dummyNode;
        while(index--){
            cur = cur->next;
        }
        ListNode* temp = cur->next;
        cur->next= cur->next->next; 
        delete temp;

        //避免野指针
        temp = nullptr;
        _size--;

    }
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

力扣206.翻转链表

原题链接

难点分析:

(1)翻转方式的选择:

在提及翻转一个字符串或数组时,我们常常想到STL中的reverse()函数,如果题目中不允许我们使用,那么我们可以使用双指针,一个指针指向待翻转的序列的首元素,另一个指针指向序列的尾元素,向中间循环迭代,交换两个指针指向的元素:

void reverseString(vector<char>& s) {
    for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
        swap(s[i],s[j]);
    }
}

这种方式是基于数组可以随机存取的,由于有这个性质,用上述双指针方式实现序列翻转十分优美,但是链表并不能随机存取,我们每次寻找到前后两个要交换的元素需要维护很多额外的变量,因此链表不适合这种翻转方式。结合链表的性质,我们可以使用两种翻转策略:

  • 原地翻转:考虑链表的指针域的特点,改变链表中所有节点的指针指向,即node1->node2变为node1<-node2
  • 借助额外的空间:利用栈先后进先的特点,实现翻转

完整代码:

(1)借助栈翻转:
class Solution {
public:
    ListNode* reverseList(ListNode* head) {

        if(head == nullptr) return nullptr;
        // 利用栈的性质,将链表的节点逆序链接,形成一个新的链表
        std::stack<ListNode*> st;

        // 定义一个指针遍历链表,装栈
        ListNode* cur = head;
        while (cur != nullptr) {
            // 向栈中压入当前节点的地址
            st.push(cur);
            // 指针向后移动
            cur = cur->next;
        }

        // 翻转后的链表的头节点是栈顶元素
        ListNode* newHead = st.top();
        st.pop();

        ListNode* pre = newHead;
        while (!st.empty()) {
            // 取当前栈顶元素,栈顶元素是一个存储ListNode类型的地址,可以用指针变量暂存
            ListNode* newNode = st.top();
            // 上一个已经出栈节点的指针域指向当前的栈顶元素
            pre->next = newNode;

            // 更新栈顶元素的 next 指针为 nullptr,断开原链表的原始链接
            newNode->next = nullptr;

            st.pop();
            pre = newNode;
        }

        return newHead;
    }
};
(2)原地翻转:
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr) return nullptr;

        //定义一个指针,实现链表的遍历
        ListNode* cur = head;

        //定义一个指针,实现链表的翻转
        ListNode* pre = nullptr;

		//定义一个临时指针,用于存放下一个节点位置
        ListNode* temp;
        while(cur){
            //暂存当前节点的下一个节点
            temp = cur->next;

            //翻转
            cur->next = pre; //将cur指向(连接)上一个节点,也断开了cur当前指向的节点与原链表下一个节点之间的链接

            //迭代(移动cur和pre)
            pre = cur; //将pre移动到本次处理的位置
            cur = temp; //cur移动到本次处理的下一个位置
        }

        return pre;
    }
};

力扣92.翻转链表Ⅱ

原题链接
这道题力扣的官方题解很详尽了,我这里提供一种借助"空间换时间"的解法:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        //本题链表的下标从1开始,并且链表中至少有一个元素
        //本题提供的左右边界一定是合法的

        //如果只有一个节点,或者只翻转1个节点,不论如何翻转都是一样的,可以直接返回
        if(head->next == nullptr || left == right) return head;

        //难点:
        //1.如何确定开始翻转的位置
        //2.如何确定终止翻转的位置
        //3.如何正确处理边界问题:
        
        //边界情况:
        //(1)整个链表都需要翻转
        // (2)left是链表第一个节点
        // (2)right是链表最后一个节点

        //思路:引入栈,将需要翻转局部的节点压入栈中,"倒出"实现局部翻转
        stack<ListNode*> st;

        //引入虚拟头节点,处理左边界问题
        ListNode* dummyNode = new ListNode({0, head});
        ListNode* cur = dummyNode;

        int cnt = 0; //记录cur当前的位置
        while(cur->next != nullptr){
            //还没到开始翻转的左边界,继续向链表后面遍历
            //如果到了翻转局部的前一个节点,就要开始处理了
            if(cnt < left - 1){
                cur = cur->next;
                //每到达一个节点,计数器加一
                cnt++;
                continue;
            }
            else if(cnt <= right){
                //翻转范围之前的节点
                //记录,用于链表的整体串联
                ListNode* pre = cur;


                //从翻转范围的第一个元素开始入栈
                cur = cur->next;
                cnt++;

                //标志变量,right就是链表的最后一个元素
                bool flag = false;

                //只要在链表中的位置不超过right,都入栈
                while(cnt <= right){
                    st.push(cur);

                    //只有下一个节点不为空,才迭代指针cur
                    if(cur->next != nullptr){
                        cur = cur->next;
                    }else{
                        //如果在cnt<=right的情况下,发生了下一个节点为空的情况,说明right位置就是链表的最后一个节点
                        flag = true;
                    }             

                    cnt++;
                }

                //翻转范围内的元素全部入栈完毕,开始翻转
                while(!st.empty()){
                    ListNode* temp = st.top();

                    //链接
                    pre->next = temp;

                    //指针后移
                    pre = temp;
                    pre->next = nullptr; //断开原来的链接
                    st.pop();
                }

                if(!flag)
                pre->next = cur;

            }
            else{
                break;
            }

            



        }
    return dummyNode->next;


    }
};

力扣.24两两交换链表中的节点

原题链接
这道题其实是力扣25.k个一组翻转一次的简化版,两两交换不就是两个一组翻转一次吗?
我在字节客户端一面是遇到了这道算法题。

难点分析:

(1)处理边界:

这道题引入头节点就可以很方便地处理边界问题,简单模拟就可以实现交换

完整代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        //模拟交换,引入虚拟头节点简化边界处理
        ListNode* dummyNode = new ListNode(0);
        dummyNode->next = head;

        ListNode* cur = dummyNode;
        while(cur->next != nullptr && cur->next->next != nullptr){

            //以4个节点为1组,交换中间两个节点,1号和4号节点用于处理边界问题

            //保存1号节点的地址
            ListNode* temp1 = cur->next;
            //保存4号节点的地址
            ListNode* temp2 = cur->next->next->next;


            //1号节点连接3号节点
            cur->next = cur->next->next;

            //将3号节点连接上2号节点
            cur->next->next = temp1;

            //将2号节点的连接4号节点
            cur->next->next->next = temp2;





            //cur移动两位,准备下一轮
            cur = cur->next->next;

        }
        
        return dummyNode->next;
    }
};

力扣.19删除链表倒数第n个节点

原题链接

难点分析

这道题遍历两次,一次找出链表长度,另一次定位删除的节点并删除,解决起来不难,难的是一次遍历就解决完成删除

完整代码


class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //思路:使用双指针,只扫描一次链表
        //具体步骤:
        //(1)快指针从虚拟头节点开始,先移动n步
        //(2)然后快慢指针同时移动,直至快指针到达链表末尾

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* fastIndex = dummyHead;
        ListNode* lowIndex = dummyHead;

        while(fastIndex->next != nullptr){
            //n--是先减1,最后会少移动一次
            if(n-- > 0)
            //快指针先向后移动n个位置
            fastIndex = fastIndex->next;
            else{
                fastIndex = fastIndex->next;
                lowIndex = lowIndex->next;
            }
        }
        //删除慢指针后面的一个节点
        ListNode* temp = new ListNode();
        temp = lowIndex->next;
        lowIndex->next = temp->next;

        delete temp;

        return dummyHead->next;


    }
};

力扣142.环形链表

原题链接

难点分析:

(1)这道题用哈希法比较容易解决:持续向后遍历链表节点,如果同一个链表节点(这里的相同指的是存储节点的地址及地址中的内容都相同)出现了两次,那么就是环的入口节点。
(2)哈希法是一种“空间换时间”的解法,需要借助额外的空间,空间复杂度:O(N)),其中 N 为链表中节点的数目。我们需要将链表中的每个节点都保存在哈希表当中。
(3)除此了哈希法,还可以借助逻辑推导,不借助额外的空间,这是力扣官方题解中的一种解法。

完整代码:

哈希法:
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //思路:哈希法
        //第一个遍历到的键就是环的起点
        unordered_set<ListNode*> mySet;

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;

        while(cur->next != NULL){

            if(mySet.count(cur->next)){
                return cur->next;
            }
            mySet.insert(cur->next);
            cur = cur->next;
        }

    return NULL;

    }
};
逻辑推导+快慢指针
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //快慢指针法+公式推导
        //快慢指针法(双指针)常常被用来解决寻找环的入口、寻找两序列公共尾部入口、寻找距离尾部的第n个节点的问题
        //a = c + (n-1)*(b+c):表明在到达相遇点后,如果有一个点从头节点出发,一步一步走,相遇的位置就是环的入口

        ListNode* fast = head;
        ListNode* slow = head;

        //寻找快慢指针的相遇点, 相遇后自动退出循环,即退出循环时,fast指针和slow指针在它们第一次相遇的节点上
        while(true){
            //如果还没有相遇就到达了链表尾部,表明这是一个无环的链表
            //情况1是空链表,情况2是链表中只有一个节点
            if(fast == nullptr || fast->next ==nullptr){
                return nullptr;
            }
            
            //有环,但是还没有相遇,继续走
            fast = fast->next->next;
            slow = slow->next;

            if(fast == slow) break;
        }

        //可以执行后面的代码说明一定有环
        //定义一个指针从头开始与slow同距离运动
        ListNode* ptr = head; //可以与slow同距离,也可以是fast

        //ptr和slow相遇的节点就是环的入口节点
        while(ptr != slow){
            ptr = ptr->next;
            slow = slow->next;
        }

        return ptr;
        
    }
};

三、感受与思考

前几天在接受字节客户端一面时,遇到了k组翻转链表的问题,在面试小哥的提示下用栈写出来了,但不知道能不能AC,痛定思痛,这一两周把《代码随想录》中的链表题目都做完了。
链表涉及的题型目前我主要了解了几种:

  • 翻转链表
  • 链表的环
  • 链表节点的增删改查
  • 两个链表的关系

在做题时,应当考虑链表的边界处理,考虑引入数据结构协助问题的解决,考虑引入双指针(快慢指针)。如果要充分锻炼自己,还要尝试使用数学推导,以思考换空间和时间

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

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

相关文章

记一次JSON.toJSONString()转换时非属性方法空指针异常排查及toJSONString保留null值属性

记一次JSON.toJSONString()转换时非属性方法空指针异常排查及toJSONString保留null值属性 异常详情 有一个类&#xff0c;里面有两个属性和一个类似工具的getRealName()方法如下&#xff1a; getRealName()方法就是获取这个人的真实名字&#xff0c;如果获取不到就以name返回…

小程序变更主体还要重新备案吗?

小程序迁移变更主体有什么作用&#xff1f;小程序迁移变更主体的作用可不止变更主体这一个哦&#xff01;还可以解决一些历史遗留问题&#xff0c;比如小程序申请时主体不准确&#xff0c;或者主体发生合并、分立或业务调整等情况。这样一来&#xff0c;账号在认证或年审时就不…

全国各地级市财政收入支出明细统计数据2003-2022年

01、数据简介 全国各地级市财政统计主要是按地级市财政支出和财政收入两项统计&#xff0c;反映地区财政资金形成、分配以及使用情况的统计&#xff0c;​是由地区各地级市统计局统计公布&#xff0c;是加强财政资金管理使用的依据&#xff0c;研究国民收入分配和再分配的重要…

C语言----单链表的实现

前面向大家介绍了顺序表以及它的实现&#xff0c;今天我们再来向大家介绍链表中的单链表。 1.链表的概念和结构 1.1 链表的概念 链表是一种在物理结构上非连续&#xff0c;非顺序的一种存储结构。链表中的数据的逻辑结构是由链表中的指针链接起来的。 1.2 链表的结构 链表…

【Dart】双问号表达式报错的解决方案

最近准备学习一下Flutter&#xff0c;现从Dart开始。 Dart ??运算符报错的解决方案 报错代码如下 main() {int a;int b a ?? 123;print(b); }报错表现如下 _D05.8%20%E5%8F%8C%E9%97%AE%E5%8F%B7%E8%BF%90%E7%AE%97%E7%AC%A6.dart:3:11: Error: Non-nullable variable …

Lagent AgentLego 智能体应用搭建-作业六

本次课程由Lagent&AgentLego 核心贡献者樊奇老师讲解【Lagent & AgentLego 智能体应用搭建】课程。分别是&#xff1a; Agent 理论及 Lagent&AgentLego 开源产品介绍Lagent 调用已有 Arxiv 论文搜索工具实战Lagent 新增自定义工具实战&#xff08;以查询天气的工具…

您的计算机已被rmallox勒索病毒感染?恢复您的数据的方法在这里!

引言&#xff1a; 在当今数字化时代&#xff0c;网络安全问题日益突出&#xff0c;其中勒索病毒作为一种新型的网络威胁&#xff0c;正逐渐引起人们的广泛关注。其中&#xff0c;.rmallox勒索病毒作为近期出现的一种新型恶意软件&#xff0c;给个人和企业带来了巨大的经济损失…

电机入门1

文章目录 122.12.22.3 33.13.23.33.4 1 2 2.1 电机板 驱动板电机分类 驱动器分类 转速 转向扭矩定时器 ADC 2.2 PID 自动控制 的核心闭环控制算是 PID的应用 2.3 无刷电机用的 可大大提高其控制效率 和控制精度 3 开发板的IO 电流太小了 20~25ma 电机要A 驱动板 信号放大没舵…

html表格(详解网页表格的制作)

一、表格介绍 HTML 表格由 <table> 标签来定义。 HTML 表格是一种用于展示结构化数据的标记语言元素。 每个表格均有若干行&#xff08;由 <tr> 标签定义&#xff09;&#xff0c;每行被分割为若干单元格&#xff08;由 <td> 标签定义&#xff09;&#x…

用卷积网络对城市住区进行分类

这将是解释我的人工智能硕士最终项目的几篇文章中的第一篇&#xff0c;我想在其中详细解释从项目的想法到结论&#xff0c;我将在其中展示给定解决方案的所有代码。 总体思路 城市扩张地图集 https://www.lincolninst.edu/es/publications/books/atlas-urban-expansion 项…

【linux】chmod权限开放(整个文件夹)

文章目录 起因权限查看权限修改 失败权限修改成功 起因 想要共享conda环境给同事&#xff0c;发现同事没权限。 权限查看 ls #查看当前目录 ls -l # 查看当前目录的东西和权限正常情况下是显示 三个rwx分别属于user&#xff0c;group&#xff0c;others 前面第一个rwx 是针…

【斐波那契】原来困扰多年的生兔子问题竟然能够轻松拿捏...万能公式法...

上篇文章我们讲解了「矩阵快速幂」技巧&#xff0c;通过快速幂极大的优化了 斐波那契数列 的求解问题。并且通过分析知道了 系数矩阵 是解决问题的关键。 本文我们继续深化对于 系数矩阵 的求解&#xff0c;介绍一种通用方法&#xff0c;一举解决所有 斐波那契及变种类型 的问…

路由器使用docker安装mysql和redis服务

路由器使用docker安装mysql和redis服务 1.先在路由器中开启docker功能 &#xff08;需要u盘 或者 移动硬盘&#xff09; 2. docker 管理地址 :http://192.168.0.1:11180/#/ 3. 拉取镜像 4. mysql容器参数设置 MYSQL_ROOT_PASSWORD 5. redis 容器设置 开发经常需要用到 &…

【工具-PyCharm】

工具-PyCharm ■ PyCharm-简介■ PyCharm-安装■ PyCharm-使用■ 修改主题■ 设置字体■ 代码模板■ 解释器配置■ 文件默认编码■ 快捷键■ 折叠■ 移动■ 注释■ 编辑■ 删除■ 查看■ 缩进■ 替换 ■ PyCharm-简介 官方下载地址 Professional&#xff1a;专业版&#xff0…

贪吃蛇撞墙功能的实现 和自动行走刷新地图 -- 第三十天

1.撞墙 1.1最初的头和尾指针要置为空&#xff0c;不然是野指针 1.2 在增加和删除节点后&#xff0c;判断是否撞墙&#xff0c;撞墙则初始话蛇 1.3在撞墙后初始化蛇&#xff0c;如果头不为空就撞墙&#xff0c;得定义临时指针指向头&#xff0c;释放头节点 2.自动刷新地图 2.1…

EMP.DLL是什么文件?EMP.DLL文件缺失怎么解决

在深入探讨计算机世界的微观结构时&#xff0c;我们会发现诸多支撑着软件正常运行的关键组件——动态链接库文件。其中&#xff0c;EMP.dll便是一个颇具代表性的例子。本文旨在全面解析EMP.dll的本质属性、功能作用以及当它丢失时可能带来的问题&#xff0c;结合实践经验&#…

9节点牛拉法matlab

潮流计算程序matlab 牛拉法 采用matlab对9节点进行潮流计算&#xff0c;采用牛拉法&#xff0c;程序运行可靠。

白平衡简介

文章目录 白平衡的概念白平衡的调节常见的白平衡模式 白平衡的概念 白平衡是指摄影、摄像和显示技术中的一项重要概念&#xff0c;用于调节图像中的白色或中性灰色的色彩&#xff0c;使其看起来在不同光源条件下都是准确的白色或灰色。白平衡的主要目的是确保图像的色彩准确性…

配置jupyter的启动路径

jupyter的安装参考&#xff1a;python环境安装jupyter-CSDN博客 1&#xff0c;背景 继上一篇python环境安装jupyter&#xff0c;里面有一个问题&#xff0c;就是启动jupyter&#xff08;命令jupyter notebook&#xff09;之后&#xff0c;页面默认显示的是启动时候的路径。 …

Synchronized关键字的深入分析

一、引言 在多线程编程中&#xff0c;正确地管理并发是确保程序正确运行的关键。Java提供了多种同步工具&#xff0c;其中synchronized关键字是最基本且最常用的同步机制之一。本文旨在深入解析synchronized的实现原理&#xff0c;探讨其在不同应用场景中的使用&#xff0c;并…