堆排序、快速排序和归并排序

堆排序、快速排序和归并排序是所有排序中最重要的三个排序,也是难度最大的三个排序;所以本文单独拿这三个排序来讲解

目录

一、堆排序 

1.建堆

2.堆排序

二、快速排序

1.思想解析

2.Hoare版找基准

3.挖坑法找基准

4.快速排序的优化

5.快速排序非递归

三、归并排序

1.归并思路

2.代码展示及步骤

3.代码分析

 4.归并排序非递归

 5.使用归并排序解决海量数据


一、堆排序 

所谓堆排序,就是在堆的基础上进行排序,那么如何建堆,就是堆排序的重点。

堆排序核心思想:

1.建堆:排升序建大根堆,排降序建小根堆

2.在建好堆后,每次堆顶元素和最后一个位置的元素交换

3.每交换一次,就进行向下调整操作,使其满足堆的性质

4.交换后,最后一个位置向前走一步

1.建堆

堆有两种,大根堆和小根堆。

(1)排升序,建立大根堆

(2)排降序,建立小根堆

再利用大根堆或者小根堆进行排序

(1)建堆

我们建堆使用向下调整的方式,从最后一棵子树开始。每一棵子树都需要进行向下调整。

向下调整创建大根堆

核心:先将一维数组以层序遍历的方式创建成一棵完全二叉树,然后从最后一棵子树(最后一个父亲结点)开始,向下调整(从父亲到最后一个孩子)。每一轮结束,父亲结点减一。

回忆父亲结点和孩子结点的关系:假设知道父亲结点的下标为i,则左孩子的下标为:(2*i)-1;如果知道孩子结点的下标(左右都可)为i,则父亲结点为:(i-1)/2。

通过图示理解大根堆的向下调整创建:

有该数组:{27,15,19,18,28,34,65,49,25,37}

得到一棵逻辑上的完全二叉树:

然后从最后一棵子树开始向下调整:

调整的步骤:

(1)左右孩子比较,找出孩子大的下标(2)大孩子与父亲结点比较,大于父亲结点则交换(3)父亲结点下标走到孩子位置,孩子再等于新的下标

下面是调整的过程:

第一轮:调整最后一棵子树,p的开始位置为4

c=2*9+1=18,上面标错了 

第二轮:p--,走到3的位置

c=2*7+1=15,上面标错了  

第三轮:p--,走到2的位置

c=2*6+1=13,上面标错了  

第四轮:p--,走到1的位置

 

第五轮:p--,走到0的位置

上面就是堆调整的全过程,我们总结一下:

p代表需要调整的子树,每调整完一轮,p--,直到p调整完;而在每一轮的调整中,交换完一小轮,p就要向下走,c也要继续往下走,直到不满足条件。

下面是建立大根堆的代码:

public void create(int[] arr) {
        //从最后一棵子树向下调整
        for (int parent = (arr.length -1-1)/2; parent >= 0 ; parent--) {
            siftDown(arr,parent,arr.length);
        }
    }
    /**
     *  向下调整
     */
public void siftDown(int[] arr,int parent,int len) {
        int child = parent*2 + 1;
        while(child < len) {
            //1.找左右孩子最大的
            if(child+1 < len && arr[child+1] > arr[child]) {
               child = child + 1;
            }
            if(arr[child] > arr[parent]) {
                swap(arr,child,parent);
                parent = child;
                child = parent*2 + 1;
            }else {
                break;
            }

        }
}
public void swap(int[] arr,int i,int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

下面是建立小根堆的代码:

 public void createminHeap() {
        for (int parent = (usedSize-1)/2; parent >= 0 ; parent--) {
            siftDown2(parent,usedSize);
        }
    }
    private void siftDown2(int parent,int end) {
        int child = parent*2+1;//找到左孩子下标
        //循环结束,说明一棵子树调整完成
        while (child < end) {
            //1.找左右孩子最小的一个.进入if说明第二个孩子比较大
            if(child+1 < usedSize && elem[child] > elem[child+1]) {
                child++;
            }
            //2.比较父亲结点和大孩子结点
            if(elem[parent] > elem[child]) {
                swap(parent,child);
                parent = child;
                child = parent*2+1;//找到左孩子下标
            }else {
                break;//满足则结束循环
            }
        }
    }
public void swap(int[] arr,int i,int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

2.堆排序

在学会如何建堆之后,才能进行堆排序操作,堆排序操作是比较简单的

思想:每次堆顶元素和最后一个元素交换,交换完进行一次向下调整,每次过后最后一个元素向前走

 大根堆可以保证堆顶元素是最大的,每次和最后一个位置交换;不断交换,就会形成升序。

 堆排序部分代码:

public void headSort(int[] arr) {
        //1.排升序,建大根堆
        create(arr);
        int end = arr.length - 1;
        while(end > 0) {
            //2.每次交换堆顶和最后一个元素
            swap(arr,0,end);
            //3.交换完调整
            siftDown(arr,0,end);
            end--;
        }
    }

堆排序完整代码:

public class heapSort {


    public void headSort(int[] arr) {
        //1.排升序,建大根堆
        create(arr);
        int end = arr.length - 1;
        while(end > 0) {
            //2.每次交换堆顶和最后一个元素
            swap(arr,0,end);
            //3.交换完调整
            siftDown(arr,0,end);
            end--;
        }
    }
    /**
     * 交换
     */
    public void swap(int[] arr,int i,int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    public void create(int[] arr) {
        //从最后一棵子树向下调整
        for (int parent = (arr.length -1-1)/2; parent >= 0 ; parent--) {
            siftDown(arr,parent,arr.length);
        }
    }
    /**
     *  向下调整
     */
    public void siftDown(int[] arr,int parent,int len) {
        int child = parent*2 + 1;
        while(child < len) {
            //1.找左右孩子最大的
            if(child+1 < len && arr[child+1] > arr[child]) {
               child = child + 1;
            }
            if(arr[child] > arr[parent]) {
                swap(arr,child,parent);
                parent = child;
                child = parent*2 + 1;
            }else {
                break;
            }

        }
    }


}

总结:

(1)时间复杂度:O(n*logn)

(2)空间复杂度:O(1)

(3)稳定性:不稳定

二、快速排序

快速排序是一种基于二叉树形式的交换数据排序,快听名字就知道他很快

下面介绍的快速排序,我们会介绍:快排的思想、Hoare版分割法、挖坑法分割法、如何优化快速排序快速排序的非递归写法

1.思想解析

(1)官方概念

在待排序的 N个记录中任意取一个记录,把该 记录放在最终位置后, 数据序列被此记录分成两部分。 (如何分成两个部分:所有关键字比该记录关键 字小的放在前一部分, 所有比它大的放置在后一部分, 并把该记录排在这两部分 的中间,这个过程称作一次快速排序)之后重复上述过程, 直到每一部分内只有 一个记录为止。

(2)简述概念

(1)每次找一个基准,再定义两个指针(分别指向数据序列的两头),遍历该数据序列。(2)遍历时,满足一定的条件,就交换指针所指向的值或者其他操作,直到两个指针相遇。

(3)指针相遇的位置就将该序列分成左右两部分

(4)左右两个部分再重复(1)-(3)的操作,直到不能再分解,排序完成

我们这里暂时称(1)(2)两个步骤为:寻找基准法 

上面的都是干巴巴的概念,很难理解,下面结合图解。

(3)图解如何完成排序

下面第一步到找到下一个基准的过程称为:找基准法(官方概念为Hoare版);找基准法还有另一种称为:挖坑法

使用Hoare找基本法的每一轮:

(4)部分代码

根据上述的图解,我们得出,每次结束,就会将数组分成两个部分,然后每个部分再重复步骤,可以想到使用递归的方式完成。

partition2方法就是找基准法,具体实现下面介绍

private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = partition2(array,start,end);//记录每一轮基准的位置
        //递归左边
        quick(array,start,pivot-1);
        //递归右边
        quick(array,pivot+1,end);

    }

找基准法一共有三种:Hoare版、挖坑法、前后指针法,其中最常用的是:挖坑法;不常见的是:前后指针法

2.Hoare版找基准

根据Hoare版的思想完成的代码,具体思想不再重复

private static int partition1(int[] array,int left,int right) {
        //1.确定基准
        int tmp = array[left];
        int l = left;
        //2.遍历数组,直到相遇
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            while (left < right && array[left] <= tmp) {
                left++;
            }
            //交换两个值
            swap(array,left,right);
        }
        //3.交换相遇位置和基本位置的值
        swap(array,l,right);
        //4.返回相遇位置,作为下一次的分割点
        return right;
}
private static void swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

第一种快速排序完整代码:

public static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = partition(array,start,end);//记录每一轮基准的位置
        //递归左边
        quick(array,start,pivot-1);
        //递归右边
        quick(array,pivot+1,end);
}
private static int partition(int[] array,int left,int right) {
        //1.确定基准
        int tmp = array[left];
        int l = left;
        //2.遍历数组,直到相遇
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            while (left < right && array[left] <= tmp) {
                left++;
            }
            //交换两个值
            swap(array,left,right);
        }
        //3.交换相遇位置和基本位置的值
        swap(array,l,right);
        //4.返回相遇位置,作为下一次的分割点
        return right;
}
private static void swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
}

3.挖坑法找基准

挖坑法是最常用的一种,具体实现和Hoare版很相似。

(1)挖坑法思想

每一轮的结果:

(2)挖坑法代码

private static int partition(int[] array,int left,int right) {
        //1.将基准存入临时变量中,形成一个坑
        int tmp = array[left];
        //2.遍历数组,直到相遇
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            //3.放入left位置
            array[left] = array[right];
            while (left < right && array[left] <= tmp) {
                left++;
            }
            //4.放入right位置
            array[right] = array[left];
        }
        //5.补坑
        array[right] = tmp;
        return right;
}
private static void swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
 }

第二种快速排序完整代码:

public static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        int pivot = partition(array,start,end);//记录每一轮基准的位置
        //递归左边
        quick(array,start,pivot-1);
        //递归右边
        quick(array,pivot+1,end);
}
private static int partition(int[] array,int left,int right) {
        //1.将基准存入临时变量中,形成一个坑
        int tmp = array[left];
        //2.遍历数组,直到相遇
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            //3.放入left位置
            array[left] = array[right];
            while (left < right && array[left] <= tmp) {
                left++;
            }
            //4.放入right位置
            array[right] = array[left];
        }
        //5.补坑
        array[right] = tmp;
        return right;
}
private static void swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
 }

4.快速排序的优化

对于不同的数据,死板的写法不一定有其他排序快;但是根据不同的场景进行不同的优化,可以大大提高快速排序的效率。

下面介绍两种常见的优化方式 

(1)三数取中法

思想:在一堆序列中,找到最左边的数、中间的数和最右边的数中 中间小的一个数与最左边的数进行交换,再以最左边的数为基准进行划分。可以防止升序或者逆序而造成的单分支情况。

应对情况:常见与已有序的序列(顺序或者逆序) 

核心:找到三个数中排在中间的数

举例:下面为顺序的序列,当默认选择第一个数为基准时,右边都是比基准大的而不会交换;在递归过程中,就会形成单分支的二叉树,复杂度进而变大

 三数取中,取的数为:

 如何找中间值:

代码如下:

private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        //三数取中法(优化)
        int index = findMid(array,start,end);
        swap(array,index,start);
        int pivot = partition(array,start,end);
        //递归左边
        quick(array,start,pivot-1);
        //递归右边
        quick(array,pivot+1,end);

 }
/*
        找中间大的元素下标
     */
    private static int findMid(int[] array,int left,int right) {
        int mid = left+( (right - left) >> 1 );
        if(array[left] > array[right]) {
           if(array[left] < array[mid]) {
               return left;
           }else if(array[right] > array[mid]) {
               return right;
           }else  {
               return mid;
           }
        }else {
            if(array[mid] > array[right]) {
                return right;
            }else if(array[left] > array[mid]) {
                return left;
            }else {
                return mid;
            }
        }
    }

(2)递归到小区间时,使用插入排序

思想:结合插入排序

应对情况:一棵二叉树的结点,一般2/3都集中在最后面的几层;如果一直递归下去,计算量是非常大的。

核心:会使用插入排序

private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        //递归到一定的区间,使用插入排序(优化)
        if((end-start+1) < 5) {
            insertSort(array,start,end);
            return;
        }
        int pivot = partition2(array,start,end);
        //递归左边
        quick(array,start,pivot-1);
        //递归右边
        quick(array,pivot+1,end);

}
 /**
     * 区间插入排序
     */
    private static void insertSort(int[] array,int start,int end) {
        for (int i = start+1; i <= end; i++) {
            int tmp = array[i];
            int j = i-1;
            for (; j >= start ; j--) {
                if(array[j] > tmp) {
                    array[j+1] = array[j];
                }else {
                    break;
                }
            }
            array[j+1] = tmp;
        }
    }

总结:针对递归法

(1)时间复杂度:O(N*logN)

(2)空间复杂度:O(logN)

(3)稳定性:不稳定

(4)使用场景:数据较乱,不适合趋于有序或者已有序的

5.快速排序非递归

思想:

(1)先找一次基准(借助挖坑法)

(2)(判断:如果基准的左边:pivot-1>left不成立,则不放入栈;右边:pivot+1>right不成立也不放入)放入基准左边头跟尾的两个元素,再放入基准右边的头跟尾两个元素。

(3)栈不为空,取fenbuie除两个元素,分别赋值给右跟左

(4)以左跟右的区间继续找基准

(5)重复(2)-(4)

图解:

判断是否入栈:

代码: 

public static void quickSortNor(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = array.length - 1;
        //1.找第一次基准
        int pivot = partition(array,left,right);
        //2.将基准左右两边存入栈中
        if(pivot-1>left) {
            stack.push(left);
            stack.push(pivot-1);
        }
        if(pivot+1<right) {
            stack.push(pivot+1);
            stack.push(right);
        }
        //3.栈不为空出栈
        while (!stack.empty()) {
            right = stack.pop();
            left = stack.pop();
            pivot = partition2(array,left,right);
            //2.将基准左右两边存入栈中
            if(pivot-1>left) {
                stack.push(left);
                stack.push(pivot-1);
            }
            if(pivot+1<right) {
                stack.push(pivot+1);
                stack.push(right);
            }
        }


}
private static int partition(int[] array,int left,int right) {
        //1.将基准存入临时变量中,形成一个坑
        int tmp = array[left];
        //2.遍历数组,直到相遇
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            //3.放入left位置
            array[left] = array[right];
            while (left < right && array[left] <= tmp) {
                left++;
            }
            //4.放入right位置
            array[right] = array[left];
        }
        //5.补坑
        array[right] = tmp;
        return right;
}
private static void swap(int[] array,int i,int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
 }

时间复杂度:nlogn

空间复杂度:longn

稳定性:不稳定

三、归并排序

下面的归并排序,我们注重介绍:归并排序的思路(包括文字思路,图片思路)、归并排序的代码代码执行过程的讲解

归并排序和快速排序类型,使用了分治的思想,其中,也需要用到递归的。而归并排序的核心就是一分为二,再合并。

1.归并思路

(1)大致思想

(1)将数据不断平分为两段,直到不能再分解,形成一个单独的有序个体。

(2)然后在往回走的过程中,不断合并做到有序。

(2)具体图解:

分解思路:

并起思路:在合并的过程中就完成了排序

(3)如何分解:加入一点数学知识

下面是所有片段的left,mid和right位置

根据这里得出一个规律:当left>=right时,分解停止。

2.代码展示及步骤

代码中的注释很全面的介绍了每个步骤

(1)代码展示

public class mergeSort {


    public static void mergeS(int[] array){
        mergeF(array,0,array.length-1);
    }
    //一.分解的方法(归)
    private static void mergeF(int[] array,int left,int right){
        //1.停止分解的条件
        if(left>=right) {
            return;
        }
        //2.记录拆分的位置
        int mid = left + ((right - left)>>1);
        //3.拆成的左半部分(使用递归)
        mergeF(array,left,mid);
        //4.拆成的右半部分(使用递归)
        mergeF(array,mid+1,right);
        //5.开始合并
        merge(array,left,mid,right);
    }
    //二.合并的方法(并)
    private static void merge(int[] array,int left,int mid,int right) {
        int[] arrTmp = new int[right-left+1];
        int s1 = left;//遍历第一个子序列
        int e1 = mid; //记录第一个子序列末端位置
        int s2 = mid+1;//遍历第二个子序列
        int e2 = right;//记录第二个子序列末端位置
        int k = 0;//遍历临时数组
        //1.将其中一个序列的数据全部拷入临时数组中
        while (s1<=e1 && s2<=e2) {
            if(array[s1] > array[s2]) {
                arrTmp[k++] = array[s2++];
            }else {
                arrTmp[k++] = array[s1++];
            }
        }
        //2.将未拷完的子序列继续拷入
        while (s1<=e1) {
            arrTmp[k++] = array[s1++];

        }
        while (s2<=e2) {
            arrTmp[k++] = array[s2++];

        }
        //3.将排序好的临时数组中的数据拷回原数组
        for (int i = 0; i < arrTmp.length; i++) {
            array[i+left] = arrTmp[i];
        }

    }
}

(2)数据拷贝回原数组部分

//3.将排序好的临时数组中的数据拷回原数组
        for (int i = 0; i < arrTmp.length; i++) {
            array[i+left] = arrTmp[i];
        }

3.代码分析

上面对代码的简述也只是一种简单的概述,下面详细介绍一下代码的执行过程,包括是如何递归,及如何并。

部分过程:

类似二叉树的递归,当递归到一定条件时,才会开始合并;并不是和前面的图片一样,要全部分解完才开始逐一递归。

部分合并过程:

总结:

(1)时间复杂度:O(N*logN)

(2)空间复杂度:O(logn)

(3)稳定性:稳定

(4)使用场景:在磁盘中的外排序问题

 4.归并排序非递归

引入:非递归的归并排序,重点也在后面的合并方法上;递归法是先递归到最小单元才开始合并,而非递归是直接开始合并。

步骤分析:

(1)定义一个变量gap,用来标识最小的有序集;每次合并两个有序集,当gap>=数组长度一半时,代表排序完成。

(2)定义一个变量i,用来遍历数组中所有的有序集,每次跳过两个有序集

(3)我们只需要根据合并的方法拿到对应的下标即可

思路解析:非递归,我们使用直接从最小单元开始合并,也就是以一个数据作为最小单位。


需要拿到的下标:这是根据上面递归法写的合并方法,这里通用。

根据上面得到以下代码:

public static void mergeSortNor(int[] array) {
        int gap = 1;//一组的数据个数
        //循环一次,完成一轮合并
        while (gap < array.length) {
             //每循环一次,代表合并两个组
            for (int i = 0; i < array.length; i = i+2*gap) {
                int left = i;
                int mid = left+gap-1;
                //两个if,防止数组越界
                if(mid >= array.length) {
                    mid = array.length-1;
                }
                int right = mid+gap;
                if(right >= array.length) {
                    right = array.length-1;
                }
                merge(array,left,mid,right);
            }
            gap = gap*2;
        }
    }

完整非递归代码:

public static void mergeSortNor(int[] array) {
        int gap = 1;//一组的数据个数
        //循环一次,完成一轮合并
        while (gap < array.length) {
             //每循环一次,代表合并两个组
            for (int i = 0; i < array.length; i = i+2*gap) {
                int left = i;
                int mid = left+gap-1;
                //两个if,防止数组越界
                if(mid >= array.length) {
                    mid = array.length-1;
                }
                int right = mid+gap;
                if(right >= array.length) {
                    right = array.length-1;
                }
                merge(array,left,mid,right);
            }
            gap = gap*2;
        }
    }
private static void merge(int[] array,int left,int mid,int right) {
        int[] arrTmp = new int[right-left+1];
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int k = 0;
        while (s1<=e1 && s2<=e2) {
            if(array[s1] > array[s2]) {
                arrTmp[k++] = array[s2++];
            }else {
                arrTmp[k++] = array[s1++];
            }
        }
        while (s1<=e1) {
            arrTmp[k++] = array[s1++];

        }
        while (s2<=e2) {
            arrTmp[k++] = array[s2++];

        }
        for (int i = 0; i < arrTmp.length; i++) {
            array[i+left] = arrTmp[i];
        }
    }

 5.使用归并排序解决海量数据

当所排序的对象巨大时,需要使用外部排序,而归并排序就是外部排序的一种典型代表。

外部排序:排序过程需要在磁盘等外部存储进行的排序(不直接借助内存)

使用场景:内存只有1G,而排序的数据却有100G

所以我们需要使用归并排序去完成:

(1)先把文件切分成 200 份,每个 512 M
(2)分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
(3)进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了(这个时候不借助内存)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

程序媛的mac修炼手册-- 2024如何彻底卸载Python

啊&#xff0c;前段时间因为想尝试chatgpt的API&#xff0c;需要先创建一个python虚拟环境来安装OpenAI Python library. 结果&#xff0c;不出意外的出意外了&#xff0c;安装好OpenAI Python library后&#xff0c;因为身份认证问题&#xff0c;根本就没有获取API key的权限…

opencv基础 python与c++

question: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple matplotlib Opencv 一、读取图片 (1).imshow Mat imread(const string& filename, intflags1 );flags: enum { /* 8bit, color or not */CV_LOAD_IMAGE_UNCHANGED -1, /* 8bit, gray */CV_LOAD_I…

红队攻防之powershell上线基础免杀(一)

不努力&#xff0c;你背井离乡干嘛&#xff1f;当卧底啊 环境为win10&#xff0c;在哥斯拉生成的webshell下&#xff0c;执行powershell命令。 测试杀毒软件为&#xff1a;火绒&#xff0c;腾讯电脑管家 哥斯拉生成php文件的webshell 如图 哥斯拉进行连接 把要执行命令的文件…

20.scala视图界定

目录 概述实践代码执行 结束 概述 scala 中的视图界定 实践 代码 /*** 视图界定*/ object Genericity03 {def main(args: Array[String]): Unit {println(new MaxInt(1,2).compare)println(new MaxLong(1L,2L).compare)// 不行 // println(new MaxValue(1,2).compare)// …

AD24-PCB尺寸标注、LOGO添加、装配图即多层线路PC输出

一、PCB尺寸标注 1、切换到机械层 2、按shifee进行捕捉切换 按TAB键或空格键进行横向和纵向标注切换 二、LOGO添加 1、放置-图形 2、在空白位置线绘制一个框 3、进行大小调整 三、装配图输出 1、 导出原材料BOM表&#xff0c;不要勾选 顶层 底层 颜色设置 2、输出只有Value值…

NGINX服务器配置实现加密的WebSocket连接WSS协议

一、背景 最近在做小程序开发&#xff0c;需要在nginx中配置websocket加密模式&#xff0c;即wss。初次配置wss时&#xff0c;踩了两个小时的坑&#xff0c;本文将踩坑过程分享给大家&#xff0c;有需要用到的伙伴可以直接copy即可实现&#xff0c;节省宝贵时间。 二、WebSo…

Redis可以让数据不丢失吗

Redis作为一款备受青睐的开源内存数据存储系统&#xff0c;拥有诸多引人注目的主要特点&#xff0c;为各种应用场景提供了高性能、灵活多用途的解决方案。本文将详细介绍Redis的主要特点&#xff0c;并探讨其在现代应用开发中的重要作用。 首先&#xff0c;Redis以其卓越的性能…

高等数学(函数)

目录 一、函数的定义 二、几种函数 2.1 分段函数 2.2 反函数 2.3 显函数与隐函数 显函数 隐函数 三、几种特性 3.1 奇偶性 偶函数 奇函数 3.2 周期性 3.3 单调性 一、函数的定义 其中x是自变量&#xff0c;y是因变量。 二、几种函数 2.1 分段函数 2.2 反函数 ——…

Qt MDI应用方法:QMdiArea和QMdiSubWindows类

重点&#xff1a; 1.使用MDI应用程序&#xff0c;需要在主窗口的工作区放置一个QMdiArea组件。 并将QMdiArea组件设置成中心窗口 2.MDI有两个显示模式&#xff1a;Tab多页显示模式和子窗口显示模式 子窗口显示模式有两种显示方法&#xff1a;窗口级联展开和平铺展开 窗口级联…

仿12306校招项目业务二(列车检索)

目录 验证数据 加载城市数据 查询列车站点信息 查询列车余票信息 构建列车返回数据 12306 项目中列车数据检索接口路径 &#xfeff; TicketController的pageListTicketQuery&#xfeff;。 GetMapping("/api/ticket-service/ticket/query")public Result<T…

电商评价分析:NLP信息抽取技术在用户评论中的应用与挖掘

一、引言 在2019年&#xff0c;电子商务的蓬勃发展不仅推动了消费市场的增长&#xff0c;也带来了海量的用户评价数据。这些数据&#xff0c;作为消费者对商品和服务直接反馈的载体&#xff0c;蕴含着巨大的价值。然而&#xff0c;由于其非结构化的特性&#xff0c;这些文本信息…

蓝桥云课-第6场小白赛

第一题&#xff1a;输出字符串即可 第二题2.猜灯谜【算法赛】 - 蓝桥云课 (lanqiao.cn)&#xff1a; 猜灯谜 也就是输入自己前后值的总和&#xff0c;除去第一个和最后一个&#xff08;单独输出&#xff09; c代码如下&#xff1a; #include <iostream> using namesp…

DBeaver一段时间不使用,就会自动断开连接,需要刷新数据库或者断开重连解决方案 DB2

DBeaver一段时间不使用&#xff0c;就会自动断开连接&#xff0c;需要刷新数据库或者断开重连解决方案 DB2

【程序员必备技能】Git入门

目录 &#x1f308;前言&#x1f308; &#x1f4c1; Git的概念 &#x1f4c2; 版本控制 &#x1f4c2; 集中式 和 分布式 ​ &#x1f4c1; 创建和配置本地仓库 &#x1f4c1; 理解工作区&#xff0c;暂存区&#xff0c;版本库 &#x1f4c1; Git的基本操作 &#x1f4c2;…

人工智能绘画的时代下到底是谁在主导,是人类的想象力,还是AI的创造力?

#ai作画 目录 一.AI绘画的概念 1. 数据集准备&#xff1a; 2. 模型训练&#xff1a; 3. 生成绘画&#xff1a; 二.AI绘画的应用领域 三.AI绘画的发展 四.AI绘画背后的技术剖析 1.AI绘画的底层原理 2.主流模型的发展趋势 2.1VAE — 伊始之门 2.2GAN 2.2.1GAN相较于…

MES管理系统生产过程控制的核心要素

MES&#xff08;制造执行系统&#xff09;是为优化制造业生产过程和管理而设计的软件系统&#xff0c;其核心要素包括&#xff1a; 工单管理&#xff1a;工单管理是MES系统最基本的功能之一&#xff0c;它可以跟踪和管理各种类型的工单&#xff0c;如生产工单、维修工单和质量…

快速排序C语言实现程序

快速排序 快速排序算法一种最常见的排序算法&#xff0c;其核心思想就是 分治 &#xff0c;具体的&#xff1a;1&#xff09; 选定一个基准数&#xff1b;2&#xff09; 分区&#xff0c;将所有大于基准数的数据分为一区&#xff0c;将所有小于等于基准数的数据分为一区&#x…

自定义神经网络四之编写自定义神经网络

文章目录 前言神经网络组件代码整体的项目结构Tensor张量Layers层NeuralNet神经网络Loss损失函数Optim优化器data数据处理train训练 神经网络解决实际问题实际问题训练和推理代码 总结 前言 自定义神经网络一之Tensor和神经网络 自定义神经网络二之模型训练推理 自定义神经网络…

osg qt5.15 osg3.6.3 osgEarth3.1 编译爬山

Demo演示&#xff1a;Qt5.15.2OSG3.6.3OsgEarth3.1的QtCreator下的msvc2019x64版本 osgQt编译 步骤一&#xff1a;下载解压 步骤二&#xff1a;CMake配置 步骤三&#xff1a;CMake配置添加osg环境 步骤四&#xff1a;CMake配置添加Qt环境 步骤五&#xff1a;CMake修改CMakeLis…

微信小程序uniapp劳务咨询系统知识百科考试系统java+python+nodejs+php均支持

使用劳务咨询服务平台小程序的分别管理员和用户二个权限子模块。 管理员所能使用的功能主要有&#xff1a;首页、个人中心、用户管理、百科分类管理、知识百科管理、地区信息管理、劳务需求管理、试卷管理、试题管理、论坛交流、系统管理、考试管理等。 用户用户端可以实现首页…