链表的快速排序(C/C++实现)

news2024/9/29 15:35:32

一、前言

大家在做需要排名的项目的时候,需要把各种数据从高到低排序。如果用的快速排序的话,处理数组是十分简单的。因为数组的存储空间的连续的,可以通过下标就可以简单的实现。但如果是链表的话,内存地址是随机分配的,不能像数组那样通过下标就直接实现。所以在这里给大家介绍一个简单实现的快速排序。

二、快速排序简单介绍

快速排序的是交换排序其中的一种,主要是采用分治的思想,每次迭代在当前区间中选取一个数作为哨兵,通过一系列的交换操作将当前区间分为左区间和右区间【使得左区间的值全部小于等于哨兵,右区间的值全部大于等于哨兵】。然后再对左区间、右区间执行这种划分区间的策略操作,当区间的长度为1时停止。等到所有分治的区间长度都为1时,此时的原数组就已经是一个排好序的数组了。代码如下:

void quickSort(int q[], int l, int r)
{
    // 当前区间的长度小于等于1时停止循环
    if (l >= r)  return;
    // 创建哨兵 mid
    int mid = q[l];
    // 创建i,j指针进行移动
    int i = l, j = r;
    // 进行区间数字交换,使得左侧区间全小于等于mid,右侧区间全大于等于mid
    while (i < j)
    {
        // j指针从右向左移动,至到遇到第一个小于哨兵的值
        while (q[j] >= mid && i < j) j--;
        // 将该值移动到左区间中
        q[i] = q[j];
        // i指针从左向右移动,至到遇到第大个小于哨兵的值
        while (q[i] <= mid && i < j) i++;
        // 将该值移动到右区间中
        q[j] = q[i];
    }
    // 交换结束后此时i,j指针指向的同一个位置,即哨兵应该放的位置
    // 而左区间已经是全部小于等于哨兵的值,右区间已经是全部大于等于哨兵的值了。
    q[i] = mid;
    // 对划分出来的左右区间的再一次进行快排
    quickSort(q, l, i - 1);
    quickSort(q, i + 1, r);
}

但这只适用于连续存储空间。对于链表来说,不太适用。所以接下来我将介绍一个比较简单的代码来实现链表的快速排序。

三、代码实现:

该链表是包含有头结点的,即第一个结点不放数据):

// 自定义的结构体,用来存储链表的内容
typedef struct node
{
    int data = -1; // 数据域
    struct node* next = NULL; // 指针域,指向后一个元素
    struct node* pre = NULL; // 指针域,指向前一个元素
}NODE;//别名
// 函数声明
// 判断l是否走到了r的右边,如果在返回true,如果不在则返回false 【要求l和r在同一个链表上,且定义时l是r左侧的节点】
bool isRight(const NODE* l, const NODE* r);
// 判断l是否还在r的左边,如果在返回true,如果不在则返回false【要求l和r在同一个链表上,且定义时l是r左侧的节点】
bool isLeft(const NODE* l, const NODE* r);
// 在链表中挖出这个节点,即断开这个节点和链表的关系
NODE* dug(NODE* node);
// 在node所属的链表中,在node节点前插入target节点【此时target节点是一个孤立的节点,并没有链接链表】
void insertBefore(NODE* node, NODE* target);
// 在node所属的链表中,在node节点后插入target节点【此时target节点是一个孤立的节点,并没有链接链表】
void insertAfter(NODE* node, NODE* target);
// 进行快速排序
void quickSort(NODE* l, NODE* r) 
{
    // 如果当前区间的元素已经只剩一个或者不剩了,那么结束递归
    if (isRight(l, r)) 
    {
        return;
    }
    // 记录当前区间的外的前一个节点和当前区间外的后一个节点,防止进行交换后这个区间的第一个节点和最后一个节点的顺序找不到了
    // 由于头节点的存在,可以保证该区间外一定存在前一个节点
    NODE* preL = l->pre;
    // 但是不一定该区间外是否还有后一个节点
    NODE* nextR;
    bool tailDug = false; //标记是否给该区间外创建了后一个节点
    // 如果该区间外没有后一个节点了
    if (r->next == NULL) 
    {
        // 则创建一个新的节点用来记录区间的最后一个节点
        nextR = (NODE*)malloc(sizeof(NODE));
        r->next = nextR;
        nextR->pre = r;
        nextR->next = NULL;
        tailDug = true;
    } 
    // 如果该区间外后面还有一个节点,则直接使用这个节点来帮助记录区间的末尾节点
    else
    {
        nextR = r->next;
    }
    // 选择哨兵节点【区间的第一个节点】
    NODE* mid = dug(l);
    // 更新左、右指针的位置
    NODE* i = preL->next, * j = nextR->pre;
    // 进行快排的交换移动
    // 当左指针还在右指针的左侧时,进行交换判断
    while (isLeft(i, j))
    {
        // 右指针左移,至到遇到第一个小于哨兵的节点或者i,j指针相遇了,那么就停下
        while (j->data >= mid->data && isLeft(i, j)) { j = j->pre; }
        NODE* temp = NULL;
        // 如果此时i,j指针还未相遇,则右指针当前指向的节点是小于哨兵的节点,需要移动到左区间中。【说明它满足的上面while的 j->data >= mid->data】
        if (isLeft(i, j)) 
        {
            // j指针前移一位,因为上面的判断已经知道当前这个节点应该放在左区间了,故下一次判断时j指针不需要判断这个节点了,直接从前一个节点开始判断即可。
            j = j->pre;
            // 将刚才j指针找到的节点移动到i节点前面,【因为i节点的左侧都应该是小于等于哨兵值的节点】
            temp = j->next;
            dug(temp);
            insertBefore(i, temp);
        }
        // 左指针右移,至到遇到第一个大于哨兵的节点或者i,j指针相遇了,那么就停下
        while (i->data <= mid->data && isLeft(i, j)) { i = i->next; }
        // 如果此时i,j指针还未相遇,则左指针当前指向的节点是大于哨兵的节点,需要移动到右区间中。【说明它满足的上面while的 i->data <= mid->data 】
        if (isLeft(i, j)) 
        {
            // i指针后移一位,因为上面的判断已经知道当前这个节点应该放在右区间了,故下一次判断时i指针不需要判断这个节点了,直接从后一个节点开始判断即可。
            i = i->next;
            // 将刚才i指针找到的节点移动到j节点后面,【因为j节点的右侧都应该是大于等于哨兵值的节点】
            temp = i->pre;
            dug(temp);
            insertAfter(j, temp);
        }
    }
    // 此时的i,j指针已经相遇了,故下面使用到i的地方都可以替换为j
    // 最后一次填坑,此时只剩哨兵这个节点的位置需要重新放进来了。
    // 如果当前节点小于哨兵,则代表当前相遇位置的节点【i,j】是左区间的最后一个节点,那么哨兵应该放在该节点的右侧
    if (i->data < mid->data) 
    {
        insertAfter(i, mid);//当前相遇的节点【i,j】代表左区间的最后一个元素,故放在该节点后面即代表放在了左区间和右区间的正中间
    } 
    // 否则代表当前相遇位置的节点【i,j】是右区间的第一个节点,那么哨兵应该放在该节点的左侧
    else 
    {
        insertBefore(i, mid);//当前相遇的节点【i,j】代表右区间的第一个元素,故放在该节点前面即代表放在了左区间和右区间的正中间
    }
    // 更新这个区间的区间内的第一个节点和最后一个节点
    l = preL->next;
    r = nextR->pre;
    // 如果区间外的后一个节点实际上是不存在的,那么删除这个被我们临时创建出来的节点
    if (tailDug) 
    {
        dug(nextR);
    }
    // 进行左右区间的快排
    quickSort(l, mid->pre), quickSort(mid->next, r);
}
// 函数实现
bool isRight(const NODE* l, const NODE* r)
{
    return l == NULL || r == NULL || l == r || l == r->next;
}
bool isLeft(const NODE* l, const NODE* r)
{
    return !isRight(l, r);//如果不在右边,则代表在左边
}
NODE* dug(NODE* node)
{
    // 如果当前节点在链表中是最后一个节点
    if (node->next == NULL)
    {
        // 则将前一个节点指向NULL即可
        NODE* l = node->pre;
        l->next = NULL;
        return node;
    }
    // 如果当前节点不是最后一个节点
    else
    {
        // 将当前节点在链表中的前一个节点与当前节点在链表中的后一个节点联系起来
        NODE* l = node->pre;
        NODE* r = node->next;
        l->next = r;
        r->pre = l;
        // 断开当前节点与链表的联系
        node->pre = NULL;
        node->next = NULL;
        return node;
    }
}
void insertBefore(NODE* node, NODE* target)
{
    // 获取链表中node的前一个节点
    NODE* l = node->pre;
    // 将target节点插入到 l 节点和 node 节点之间
    l->next = target;
    node->pre = target;
    target->pre = l;
    target->next = node;
}
void insertAfter(NODE* node, NODE* target)
{
    // 获取链表中node的后一个节点
    NODE* r = node->next;
    // 将target节点插入到 node 节点和 r 节点之间
    r->pre = target;
    node->next = target;
    target->pre = node;
    target->next = r;
}

四、分析讲解

假设需要排序的链表数据为:2 3 6 1 9。那么链表的结构如下:
在这里插入图片描述

4.1 第一个递归过程

4.1.1 数据预处理

4.1.1.1 需要处理的链表结点【区间数据】

在这里插入图片描述

4.1.1.2 本次递归的结点介绍【未获取哨兵结点之前】

在这里插入图片描述
提示:由于需要处理的区间结点的最后一个结点9 后面不再有结点,故需要 创建一个新的结点nextR 插入在9这个结点后面,这样就可以精准的找到我们这个区间的最后一个数据结点的位置【由于nextR这个结点是不会进行位置交换的,故nextR的前一个结点一定是当前区间的最后一个数据结点】。

4.1.1.3 获取哨兵结点

在这里插入图片描述
提示:哨兵结点mid是一般取的是区间的第一个结点,即2这个结点。而本文的快速排序会将哨兵结点从链表中挖出去,这样可以方便后续的处理。故现在区间结点只有四个数据了,即3 6 1 9

4.1.1.4 本次递归所有结点介绍

在这里插入图片描述
结点介绍:
mid:哨兵结点,用于确定其他每个结点的位置,来使得其他结点进行移动。
l:区间的第一个结点,一般只在初始化其他结点的时候用到。
r:区间的最后一个结点,一般只在初始化其他结点的时候用到。
preL:区间第一个结点的前一个结点,由于位置不会发生改变,故用来找区间的第一个结点。
nextR:区间最后一个结点的后一个结点,由于位置不会发生改变,故用来找区间的最后一个结点。
i:从区间的第一个结点向区间的最后一个结点进行移动,用于每一次循环移动数据的结点判断。
j:从区间的最后一个结点向区间的第一个结点进行移动,用于每一次循环移动数据的结点判断。

4.1.2 循环移动数据

4.1.2.1 第一次循环结果

目的:第一次循环,判断j所在的结点是否需要移动位置。
过程
第一步: j当前所在的结点是9,由于9大于哨兵结点2,故当前结点不需要移动,j结点向左移动,此时j结点成为了1结点。
第二步: j当前所在的结点是1,由于1小于于哨兵结点2,故当前结点需要移动。将当前j所在的结点【1结点】移动到i所在的结点【3结点】前面。让j的位置仍然保持距离区间最后一个结点的距离不变,那么此时j所指向的结点即为6结点。【因为从区间结尾开始处理的话,此时只有9结点正常通过判断确定了位置,但是6结点还未通过判断确定位置,那么j就应该指向区间最后一个未确认位置的结点
在这里插入图片描述

4.1.2.2 第二次循环结果

目的:第二次循环,判断i所在的结点是否需要移动位置。
过程
第一步: i当前所在的结点是3,由于3大于哨兵结点2,故当前结点需要移动。将当前i所在的结点【3结点】移动到j所在的结点【6结点】后面。让i的位置仍然保持距离区间第一个结点的距离不变,那么此时i所指向的结点即为6结点。【因为从区间开始开始处理的话,此时只有1结点正常通过判断确定了位置,但是6结点还未通过判断确定位置,那么i就应该指向区间第一个未确认位置的结点
在这里插入图片描述

4.1.2.3 第三次循环结果

目的:由于ij指针已经相遇,那么目前只需要确定哨兵结点的位置,本轮循环就结束了。
过程:由于快排算法的性质决定了,i指针左侧的结点都小于哨兵结点的值,j指针右侧结点的值都大于哨兵结点的值。故我们比较一下当前i结点(或者j结点,因为每次循环到了最后一步的时候,ij结点都会相遇)的值和哨兵结点的值的大小。由于i结点6大于哨兵结点2,那么哨兵结点2就需要插入到链表中i结点6的左侧。此时本轮位置移动已经完成,哨兵结点2的左侧结点均为小于哨兵结点值的结点,哨兵结点2的右侧结点均为大于哨兵结点值的结点。
在这里插入图片描述

4.1.2.4 下一次循环的区间划分

目的:本轮位置移动已经完成了,已经确定了结点2的位置,但其他结点的位置还未确定,还需要继续进行分冶结点2两侧的区间进行快排位置移动。
过程:最开始指向区间头、尾的结点的lr,在进行了无数次交换了,我们可以发现它们已经不在区间的第一个结点和最后一个结点了。但由于preLnextR结点始终没有移动过位置,preL的下一个结点一直指向的是区间的第一个结点【目前指向的结点1,即快排结束后区间的第一个结点】,nextR的上一个结点一直指向的是区间的最一个结点【目前指向的结点9,即快排结束后区间的最后一个结点】。故我们可以确定需要继续进行快排的两个区间为:preL->next, mid->premid->next, nextR->pre。【即(preL的下一个结点,哨兵结点的上一个结点)、(哨兵结点的下一个结点,nextR的上一个结点)】。而哨兵结点已经确定好在整个链表的位置了,故不需要参与后续的其他区间迭代的位置移动了。而由于此时nextR结点是创建的新结点,在原因链表中不需要,在最后我们将该结点从链表中删掉即可。
故下一轮的递归区间为:
在这里插入图片描述

五、可运行的完整代码:

#include<stdio.h>
#include<stdlib.h>

// 自定义的结构体,用来存储链表的内容
typedef struct node
{
    int data = -1; // 数据域
    struct node* next = NULL; // 指针域,指向后一个元素
    struct node* pre = NULL; // 指针域,指向前一个元素
}NODE;//别名


// 找到链表的最后一个节点
NODE* findTail(NODE* head) 
{
    while (head->next != NULL) { //当没有下一个元素的时候,就代表当前元素是链表的最后一个元素
        head = head->next;
    }
    return head;
}

// 给链表新增节点
void add(NODE* head, int data)
{
    NODE* tail = findTail(head); //获取链表的最后一个节点
    // 创建新节点
    NODE* p = (NODE*)malloc(sizeof(NODE));// p 为新增的结点
    p->data = data; // 把值赋给p中的data域
    p->next = NULL; // 初始化操作,p是最后一个节点,故没有下一个节点了,因此next指针域需要指向NULL
    // 将新创建的结点与当前链表创建关联
    p->pre = tail; //新节点左指针域应该指向链表中的最后一个节点
    tail->next = p; //链表中最后一个节点的右指针域应该指向这个新节点。【至此新节点成功加入到链表中】
}

// 判断l是否走到了r的右边,如果在返回true,如果不在则返回false 【要求l和r在同一个链表上,且定义时l是r左侧的节点】
bool isRight(const NODE* l, const NODE* r) 
{
    return l == NULL || r == NULL || l == r || l == r->next;
}

// 判断l是否还在r的左边,如果在返回true,如果不在则返回false【要求l和r在同一个链表上,且定义时l是r左侧的节点】
bool isLeft(const NODE* l, const NODE* r) 
{
    return !isRight(l, r);//如果不在右边,则代表在左边
}

// 在链表中挖出这个节点,即断开这个节点和链表的关系
NODE* dug(NODE* node) 
{
    // 如果当前节点在链表中是最后一个节点
    if (node->next == NULL) 
    {
        // 则将前一个节点指向NULL即可
        NODE* l = node->pre;
        l->next = NULL;
        return node;
    }
    // 如果当前节点不是最后一个节点
    else 
    {
        // 将当前节点在链表中的前一个节点与当前节点在链表中的后一个节点联系起来
        NODE* l = node->pre;
        NODE* r = node->next;
        l->next = r;
        r->pre = l;
        // 断开当前节点与链表的联系
        node->pre = NULL;
        node->next = NULL;
        return node;
    }
}

// 在node所属的链表中,在node节点前插入target节点【此时target节点是一个孤立的节点,并没有链接链表】
void insertBefore(NODE* node, NODE* target)
{
    // 获取链表中node的前一个节点
    NODE* l = node->pre;
    // 将target节点插入到 l 节点和 node 节点之间
    l->next = target;
    node->pre = target;
    target->pre = l;
    target->next = node;
}

// 在node所属的链表中,在node节点后插入target节点【此时target节点是一个孤立的节点,并没有链接链表】
void insertAfter(NODE* node, NODE* target)
{
    // 获取链表中node的后一个节点
    NODE* r = node->next;
    // 将target节点插入到 node 节点和 r 节点之间
    r->pre = target;
    node->next = target;
    target->pre = node;
    target->next = r;
}

// 进行快速排序
void quickSort(NODE* l, NODE* r) 
{
    // 如果当前区间的元素已经只剩一个或者不剩了,那么结束递归
    if (isRight(l, r)) 
    {
        return;
    }
    // 记录当前区间的外的前一个节点和当前区间外的后一个节点,防止进行交换后这个区间的第一个节点和最后一个节点的顺序找不到了
    // 由于头节点的存在,可以保证该区间外一定存在前一个节点
    NODE* preL = l->pre;
    // 但是不一定该区间外是否还有后一个节点
    NODE* nextR;
    bool tailDug = false; //标记是否给该区间外创建了后一个节点
    // 如果该区间外没有后一个节点了
    if (r->next == NULL) 
    {
        // 则创建一个新的节点用来记录区间的最后一个节点
        nextR = (NODE*)malloc(sizeof(NODE));
        r->next = nextR;
        nextR->pre = r;
        nextR->next = NULL;
        tailDug = true;
    } 
    // 如果该区间外后面还有一个节点,则直接使用这个节点来帮助记录区间的末尾节点
    else
    {
        nextR = r->next;
    }
    // 选择哨兵节点【区间的第一个节点】
    NODE* mid = dug(l);
    // 更新左、右指针的位置
    NODE* i = preL->next, * j = nextR->pre;
    // 进行快排的交换移动
    // 当左指针还在右指针的左侧时,进行交换判断
    while (isLeft(i, j))
    {
        // 右指针左移,至到遇到第一个小于哨兵的节点或者i,j指针相遇了,那么就停下
        while (j->data >= mid->data && isLeft(i, j)) { j = j->pre; }
        NODE* temp = NULL;
        // 如果此时i,j指针还未相遇,则右指针当前指向的节点是小于哨兵的节点,需要移动到左区间中。【说明它满足的上面while的 j->data >= mid->data】
        if (isLeft(i, j)) 
        {
            // j指针前移一位,因为上面的判断已经知道当前这个节点应该放在左区间了,故下一次判断时j指针不需要判断这个节点了,直接从前一个节点开始判断即可。
            j = j->pre;
            // 将刚才j指针找到的节点移动到i节点前面,【因为i节点的左侧都应该是小于等于哨兵值的节点】
            temp = j->next;
            dug(temp);
            insertBefore(i, temp);
        }
        // 左指针右移,至到遇到第一个大于哨兵的节点或者i,j指针相遇了,那么就停下
        while (i->data <= mid->data && isLeft(i, j)) { i = i->next; }
        // 如果此时i,j指针还未相遇,则左指针当前指向的节点是大于哨兵的节点,需要移动到右区间中。【说明它满足的上面while的 i->data <= mid->data 】
        if (isLeft(i, j)) 
        {
            // i指针后移一位,因为上面的判断已经知道当前这个节点应该放在右区间了,故下一次判断时i指针不需要判断这个节点了,直接从后一个节点开始判断即可。
            i = i->next;
            // 将刚才i指针找到的节点移动到j节点后面,【因为j节点的右侧都应该是大于等于哨兵值的节点】
            temp = i->pre;
            dug(temp);
            insertAfter(j, temp);
        }
    }
    // 此时的i,j指针已经相遇了,故下面使用到i的地方都可以替换为j
    // 最后一次填坑,此时只剩哨兵这个节点的位置需要重新放进来了。
    // 如果当前节点小于哨兵,则代表当前相遇位置的节点【i,j】是左区间的最后一个节点,那么哨兵应该放在该节点的右侧
    if (i->data < mid->data) 
    {
        insertAfter(i, mid);//当前相遇的节点【i,j】代表左区间的最后一个元素,故放在该节点后面即代表放在了左区间和右区间的正中间
    } 
    // 否则代表当前相遇位置的节点【i,j】是右区间的第一个节点,那么哨兵应该放在该节点的左侧
    else 
    {
        insertBefore(i, mid);//当前相遇的节点【i,j】代表右区间的第一个元素,故放在该节点前面即代表放在了左区间和右区间的正中间
    }
    // 更新这个区间的区间内的第一个节点和最后一个节点
    l = preL->next;
    r = nextR->pre;
    // 如果区间外的后一个节点实际上是不存在的,那么删除这个被我们临时创建出来的节点
    if (tailDug) 
    {
        dug(nextR);
    }
    // 进行左右区间的快排
    quickSort(l, mid->pre), quickSort(mid->next, r);
}

int main()
{
    NODE* head = (NODE*)malloc(sizeof(NODE));//定义头结点
    int n;//链表的长度
    printf("请输入链表的长度: ");
    scanf("%d", &n);

    printf("请输入链表中的数字: ");
    for (int i = 0; i < n; i++)
    {
        int x;//每一个结点的值
        scanf("%d", &x);
        add(head, x);//向链表追加结点
    }
    //进行冒泡排序
    quickSort(head->next, findTail(head));

    NODE* p = head->next;//因为第一个结点没有存放数据,所以从第二个结点开始输出
    while (p != NULL)
    {
        printf("%d\t", p->data);
        p = p->next;
    }

    return 0;
}

**

温馨提示:第一个结点不是用来放数据的,是用来辅助完成排序的。

题目测试地址:C语言网-快速排序题目测试

**

相关文章

快速排序(C/C++实现)—— 简单易懂系列
单链表的冒泡排序(C/C++实现)

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

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

相关文章

并发控制全解析

在数据库操作中&#xff0c;并发控制 是确保数据一致性和事务隔离性的关键。然而&#xff0c;多个事务的并发操作可能导致数据不一致&#xff0c;破坏数据库的ACID特性。本文将深入探讨并发操作可能带来的问题&#xff0c;并介绍常见的并发控制技术。 1. 并发操作带来的挑战 …

注册接口参数校验

前面只是开发了注册接口&#xff0c;没有对注册用户名、密码长度进行任何限制&#xff0c;因此我们还需要对注册接口参数进行校验。 参数校验框架Spring Validation 引入Spring Validation起步依赖 在pom.xml文件引入依赖 <!-- validation依赖--> <dependenc…

【实践】应用访问Redis突然超时怎么处理?

目录标题 问题描述分析过程查看监控数据系统监控指标JVM监控指标Redis监控指标分析应用异常单机异常规律集群异常规律统计超时的key 初步结论验证结论访问Redis链路slowlogRedis单节点info all定位redis节点定位异常keybigkeystcpdump定位大key影响 经验总结 问题描述 某产品线…

CORS漏洞及其防御措施:保护Web应用免受攻击

1. 背景- 什么是CORS&#xff1f; 在当今互联网时代&#xff0c;Web 应用程序的架构日益复杂。一个后端服务可能对应一个前端&#xff0c;也可能与多个前端进行交互。跨站资源共享&#xff08;CORS&#xff09;机制在这种复杂的架构中起着关键作用&#xff0c;但如果配置不当&…

Vue 中常用的基础指令

一. 什么是 Vue 指令 指令的定义和作用 指令是通过 Vue 实例的directives选项进行定义的。在指令的定义中&#xff0c;需要提供一个bind函数&#xff0c;它在指令第一次绑定到元素时被调用&#xff0c;可以执行一些初始化的操作。还可以提供update函数&#xff0c;它在指令所…

性能测试-跨线程组使用变量(十四)

说明&#xff1a; 两个线程组相当于两个函数&#xff0c;比如A线程组使用json提取器提取的“city”变量&#xff0c;在B线程组中不能使用&#xff08;相当于是局部变量&#xff09;jmeter中&#xff0c;没有全局变量&#xff0c;环境变量 解决方法&#xff1a; 添加两个线程…

源码运行springboot2.2.9.RELEASE

1 环境要求 java 8 maven 3.5.2 2 下载springboot源码 下载地址 https://github.com/spring-projects/spring-boot/releases/tag/v2.2.9.RELEASE 3 修改配置 修改spring-boot-2.2.9.RELEASE/pom.xml 修改spring-boot-2.2.9.RELEASE/spring-boot-project/spring-boot-tools…

谷歌的 DataGemma 人工智能是一个统计精灵

谷歌正在扩大其人工智能模型家族&#xff0c;同时解决该领域的一些最大问题。 今天&#xff0c;该公司首次发布了 DataGemma&#xff0c;这是一对开源的、经过指令调整的模型&#xff0c;在缓解幻觉挑战方面迈出了一步&#xff0c;幻觉是指大型语言模型&#xff08;LLM&#xf…

Docker镜像下载-使用github action- 解决无法下载docker镜像的问题

最近不知道怎么jdk的本地镜像被不小心干掉了&#xff0c;然后action全部失败。服务器也不能从远程拉取镜像 由于国内已经无法从docker官方源下载镜像了&#xff0c;但是这个自动化运维的需求还是有的。其实有很多种方法&#xff0c;但是都很麻烦。 这里我写的docker compose…

运行PaddleOCR报错:requests.exceptions.SSLError: HTTPSconnectionPool……

文章目录 问题描述解决方法 问题描述 在运行以下代码时报错&#xff1a; ocr PaddleOCR(lang"en")解决方法 打开cmd&#xff0c;输入以下命令&#xff0c;查找Python解释器所在路径。 找到 Lib\site-packages\paddleocr\ppocr\utils\network.py&#xff0c;将代码…

【Python小知识 - 2】:在VSCode中切换Python解释器版本

文章目录 在VSCode中切换Python解释器版本 在VSCode中切换Python解释器版本 在VSCode中按下快捷键CtrlShiftP&#xff0c;出现命令框。 输入以下命令&#xff1a; Python: Select Interpreter输入命令回车后即出现不同的Python解释器选项&#xff0c;选择想要切换的Python解释器…

火焰检测算法、明烟明火检测、烟火检测算法

烟火检测算法主要用于火灾早期预警系统中&#xff0c;能够在火灾初期阶段及时发现烟雾或火焰&#xff0c;从而快速响应并采取行动&#xff0c;以减少火灾带来的损失。以下是对烟火检测算法的应用场景及优势的详细介绍。 烟火检测算法广泛应用于多种场景中&#xff0c;以下是一些…

C++ inline内联函数

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 概念概述 用 inline 修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用的地方…

Netty笔记05-组件Handler Pipeline

文章目录 概述ChannelHandler方法 ChannelPipeline特点 总结 代码示例服务器端客户端 EmbeddedChannelEmbeddedChannel 的方法 概述 ChannelHandler ChannelHandler 是 Netty 中的一个接口&#xff0c;它定义了处理 I/O 事件的方法。ChannelHandler 可以处理各种类型的事件&a…

ESP01的AT指令连接到阿里云平台

物联网平台提供安全可靠的设备连接通信能力&#xff0c;支持设备数据采集上云&#xff0c;规则引擎流转数据和云端数据下发设备端。此外&#xff0c;也提供方便快捷的设备管理能力&#xff0c;支持物模型定义&#xff0c;数据结构化存储&#xff0c;和远程调试、监控、运维。总…

C++学习笔记之引用(基础)

C学习笔记之引用 https://www.runoob.com/cplusplus/cpp-references.html 引用变量是一个别名&#xff0c;它是已存在变量的另一个名字 一旦把引用初始化为某个变量&#xff0c;可以使用该引用名称或变量名称来指向变量 1、引用vs指针 引用和指针之间有一些相似&#xff0c;也…

计算机的错误计算(九十三)

摘要 探讨 log(y,x) 即以 x 为底 y 的对数的计算精度问题。 Log(y,x)运算是指 x 为底 y 的对数。 例1. 计算 log(123667.888, 0.999999999999999) . 不妨在Python中计算&#xff0c;则有&#xff1a; 若在 Excel 单元格中计算&#xff0c;则有几乎同样的输出&#xff1a; 然…

树莓派交叉编译

目录 一、交叉编译的认知 1.1 本地编译&#xff1a; 1.2 交叉编译是什么&#xff1a; 1.3 为什么要交叉编译&#xff1a; 1.4 什么是宿主机&#xff1f;什么是目标机&#xff1f; 1.5 如何进行交叉编译&#xff1a; 二、交叉编译工具链的安装 2.1 下载交叉编译工具&…

CesiumJS+SuperMap3D.js混用实现可视域分析 S3M图层加载 裁剪区域绘制

版本简介&#xff1a; cesium&#xff1a;1.99&#xff1b;Supermap3D&#xff1a;SuperMap iClient JavaScript 11i(2023)&#xff1b; 官方下载文档链家&#xff1a;SuperMap技术资源中心|为您提供全面的在线技术服务 示例参考&#xff1a;support.supermap.com.cn:8090/w…

设置 AutoCAD双击 DWG 文件时启动新的程序

1 问题描述 原CAD打开多个文件时&#xff0c;会在该程序打开新的标签&#xff0c;合并显示。 有时想打开新文件时启动新的程序&#xff0c;单独显示&#xff0c;如下&#xff1a; 2 解决办法 2.1 方法1 SDI变量可以将CAD设置成单文档模式&#xff0c;设置为1的时候就能实…