【数据结构第八章】- 排序(万字详解排序算法并用 C 语言实现)

news2024/11/20 20:33:06

目录

一、基本概念和排序方法概述

1.1 - 排序的基本概念

1.2 - 内部排序的分类

二、插入排序

2.1 - 直接插入排序

2.2 - 希尔排序

三、交换排序

3.1 - 冒泡排序

3.2 - 快速排序

3.2.1 - 递归算法

3.2.2 - 优化

3.2.3 - 非递归算法

四、选择排序

4.1 - 简单选择排序

4.2 - 堆排序

五、归并排序

5.1 - 递归

5.2 - 迭代

六、排序算法复杂度及稳定性分析



一、基本概念和排序方法概述

1.1 - 排序的基本概念

  1. 排序(Sorting)是按照关键字(例如:销量、价格等)的非递减非递增顺序对一组记录重新进行排列的操作。

  2. 关键字相等的两个或两个以上的记录,若在排序后的序列中,它们的前后位置不发生改变,则称所用的排序方法是稳定的;反之,则称所用的排序方法是不稳定的

  3. 根据在排序过程中记录所占用的存储设备,可将排序方法分为两大类:一类是内部排序,指的是待排序记录全部存放在计算机内存中进行排序的过程;另一类是外部排序,指的是待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。

1.2 - 内部排序的分类


二、插入排序

插入排序的基本思想是:每一趟将一个待排序的记录,按其关键字的大小插入到已经排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止。

例如,打扑克牌在抓牌时要保证抓过的牌有序排列,则每抓一张牌,就插入到合适的位置,直到抓完牌为止,即可得到一个有序序列。

 

2.1 - 直接插入排序

直接插入排序(Straight Insertion Sort)的基本操作是将一条记录插入到已排好序的有序表中,从而得到一个新的、记录数量增 1 的有序表。

算法步骤

  1. 设待排序的记录存放在数组 arr[0...n-1] 中, arr[0] 是一个有序序列。

  2. 循环 n - 1 次,每次使用顺序查找法,查找 arr[i]i = 1, ..., n - 1)在已排好序的序列 arr[0...i-1] 中的插入位置,然后将 arr[i] 插入。

    具体操作则是将 arr[i]arr[i - 1]arr[i - 2],...,arr[0] 从后向前顺序比较,并在自 arr[i - 1] 起往前查找插入位置的过程中,同时后移记录。

void InsertSort(int* arr, int n)
{
    for (int i = 1; i < n; ++i)
    {
        // 一趟直接排序:将 arr[i] 插入到已排好序的序列 arr[0...i-1] 中
        int tmp = arr[i];
        int end = i - 1;
        while (end >= 0)  // 从后向前寻找插入位置
        {
            if (tmp < arr[end])  // 后移
            {
                arr[end + 1] = arr[end];
                --end;
            }
            else
            {
                break;
            }
        }
        arr[end + 1] = tmp;
    }
}

注意:需要提前将 arr[i] 保存到临时变量 tmp 中,因为如果 arr[i - 1] < arr[i],在后移的过程中会覆盖 arr[i]

2.2 - 希尔排序

希尔排序(Shell's Sort)又称 "缩小增量排序"(Diminishing Increment Sort),是插入排序的一种,因为 D.L.Shell 于 1959 年提出而得名。

直接插入排序,当待排序的记录个数较少且待排序序列的关键字基本有序时,效率较高。希尔排序基于以上两点,从 "减少记录个数""序列基本有序" 这两个方面对直接插入排序进行了改进。

希尔排序实质上是采用分组插入的方法。先将整个待排序记录分割成几组,从而减少参与直接插入排序的数据量,对每组分别进行直接插入排序,然后增加每组的数据量,重新分组。这样经过几次分组排序后,整个序列中的记录 "基本有序" 时,再对全体记录进行一次直接插入排序。

算法步骤

  1. 第一趟取增量 gap_1(gap_1 < n) 把全部记录分成 组,所有间隔为 gap_1 的记录分在同一组,在各个组中进行直接插入排序。

  2. 第二趟取增量 gap_2(gap_2 < gap_1),重复上述的分组和排序。

  3. 依次类推,直到所取得增量 gap_t = 1(gap_t < gap_{t-1} < ... < gap_2 < gap_1),所有记录在同一组中进行直接插入排序为止。

    gap 的取法有多种。最初 Shell 提出取 gap = ⌊n / 2⌋,gap = ⌊gap / 2⌋,直到 gap = 1,后来 Knuth 提出取 gap = ⌊gap / 3⌋ + 1。还有人提出都取奇数为好,也有人提出各 gap 互质为好。无论哪一种主张都没有得到证明。

例如

void ShellSort(int* arr, int n)
{
    int gap = n;
    while (gap > 1)
    {
        gap /= 2;  // 缩小增量
        for (int i = 0; i < gap; ++i)  // 将整个数组分为 gap 组
        {
            for (int j = gap + i; j < n; j += gap)  // 对每组分别进行直接插入排序
            {
                int tmp = arr[j];
                int end = j - gap;
                while (end >= 0)
                {
                    if (tmp < arr[end])
                    {
                        arr[end + gap] = arr[end];
                        end -= gap;
                    }
                    else
                    {
                        break;
                    }
                }
                arr[end + gap] = tmp;
            }
        }
    }
}

写法二

void ShellSort(int* arr, int n)
{
    int gap = n;
    while (gap > 1)
    {
        gap /= 2;
        for (int i = gap; i < n; ++i)  // 对所有组同时进行直接插入排序
        {
            int tmp = arr[i];
            int end = i - gap;
            while (end >= 0)
            {
                if (tmp < arr[end])
                {
                    arr[end + gap] = arr[end];
                    end -= gap;
                }
                else
                {
                    break;
                }
            }
            arr[end + gap] = tmp;
        }
    }
}


三、交换排序

交换排序的基本思想是:两两比较待排序记录的关键字,一旦发现两个记录不满足次序要求时则进行交换,直到整个序列全部满足要求为止。

3.1 - 冒泡排序

冒泡排序(Bubble Sort)是一种最简单的交换排序方法,它通过两两比较相邻记录的关键字,如果发生逆序,则进行交换,从而使关键字小的记录如气泡一般逐渐往上 "漂浮"(左移),或者使关键字大的记录如石块一样逐渐向下 "坠落"(右移)。

算法步骤

  1. 设待排序的记录存放在数组 arr[0...n-1] 中。首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序(即 arr[0] > arr[1]),则交换两个记录。然后比较第二个记录和第三个记录的关键字。依次类推,直至 arr[n - 2]arr[n - 1] 进行比较过为止。上述过程称作第一趟起泡排序,其结果使得关键字最大的记录被安置到最后一个记录的位置上。

  2. 然后进行第二趟起泡排序,对前 n - 1 个记录进行同样的操作,其结果是使关键字次大的记录被安置到第 n - 1 个记录的位置上。

  3. 重复上述比较和交换的过程,直到某一趟排序过程中没有进行过交换记录的操作,说明序列已全部达到排序要求,则完全排序。

void Swap(int* e1, int* e2)
{
    int tmp = *e1;
    *e1 = *e2;
    *e2 = tmp;
}
​
void BubbleSort(int* arr, int n)
{
    for (int i = 0; i < n - 1; ++i)
    {
        int flag = 0;  // flag 用来标记某一趟排序是否发生交换
        for (int j = 0; j < n - 1 - i; ++j)
        {
            if (arr[j] > arr[j + 1])
            {
                Swap(&arr[j], &arr[j + 1]);
                flag = 1;
            }
        }
        if (flag == 0)
        {
            break;
        }
    }
}

3.2 - 快速排序

快速排序(Quick Sort)是对冒泡排序的一种改进,由 C.A.R.Hoare(霍尔)于 1962 年提出。在冒泡排序过程中,只对相邻的两个记录进行比较,因此每次交换两个相邻的记录时只能消除一个逆序。如果能通过两个(不相邻)记录的一次交换,消除多个逆序,则会大大加快排序的速度。快速排序方法中的一次交换可能消除多个逆序。

快速排序的基本思想是:在待排序的 n 个记录中任取一个记录作为枢轴(或支点),设其关键字为 pivotkey。经过一趟快速排序后,把所有关键字小于 pivotkey 的记录交换到前面,把所有关键字大于 pivotkey 的记录交换到后面,结果将待排序记录分成两个子表,最后将枢轴放置在分界处的位置。然后,分别对左、右子表重复上述过程,直至每一子表只有一个记录时,排序完成。

3.2.1 - 递归算法

Hoare 版本

int Partion(int* arr, int left, int right)
{
    int pivotloc = left;  // 取第一个元素作为枢轴
    int pivotkey = arr[pivotloc]; 
    while (left < right)
    {
        // 从右往左搜索,找到第一个小于 pivotkey 的元素
        while (left < right && arr[right] >= pivotkey)
        {
            --right;
        }
        // 从左往右搜索,找到第一个大于 pivotkey 的元素
        while (left < right && arr[left] <= pivotkey)
        {
            ++left;
        }
        // 交换
        Swap(&arr[left], &arr[right]);
    }
    // left 和 right 相遇,即 left == right
    Swap(&arr[pivotloc], &arr[left]);
    pivotloc = left;
    return pivotloc;
}
​
void QSort(int* arr, int left, int right)
{
    if (left < right)  // 长度大于 1
    {
        // 将 arr[left...right] 一分为二,pivotloc 是枢轴位置
        int pivotloc = Partion(arr, left, right);  
        // 对左子表递归排序
        QSort(arr, left, pivotloc - 1);
        // 对右子表递归排序
        QSort(arr, pivotloc + 1, right);
    }
}
​
void QuickSort(int* arr, int n)
{
    QSort(arr, 0, n - 1);
}

当 left == right,可以分以下两种情况讨论:

  1. 从左往右搜索时,left 遇到了 right,此时 arr[left] == arr[right] < pivotkey。

  2. 从右往左搜索时,right 遇到了 left,若 left 已经移动过了,那么经过交换 Swap(&arr[left], &arr[right]),arr[left] < pivotkey;若 left 没有移动过,则 arr[left] == pivotkey。

挖坑法

int Partion(int* arr, int left, int right)
{
    int pivotloc = left;  // 取第一个元素作为枢轴
    int pivotkey = arr[pivotloc];
    int pit = left;  // 坑位
    while (left < right)
    {
        // 从右往左搜索,找到第一个小于 pivotkey 的元素
        while (left < right && arr[right] >= pivotkey)
        {
            --right;
        }
        arr[pit] = arr[right];
        pit = right;
        // 从左往右搜索,找到第一个大于 pivotkey 的元素
        while (left < right && arr[left] <= pivotkey)
        {
            ++left;
        }
        arr[pit] = arr[left];
        pit = left;
    }
    // left 和 right 相遇,即 left == right
    arr[pit] = pivotkey;
    pivotloc = pit;
    return pivotloc;
}

前后指针法

int Partion(int* arr, int left, int right)
{
    int pivotloc = left;  // 取第一个元素作为枢轴
    int pivotkey = arr[pivotloc];
    int prev = left;
    int cur = prev + 1;
    while (cur <= right)
    {
        if (arr[cur] < pivotkey && ++prev != cur)
        {
            Swap(&arr[prev], &arr[cur]);
        }
        ++cur;
    }
    Swap(&arr[prev], &arr[pivotloc]);
    pivotloc = prev;
    return pivotloc;
}

3.2.2 - 优化

Hoare 版本为例,假设 int arr[10] = { 6, 1, 2, 7, 9, 3, 4, 5, 10, 8 },整个快速排序的递归过程如下图所示:

从快速排序递归算法的递归树可知,快速排序的趟数取决于递归树的深度

最好情况:每一趟排序后都能将记录均匀地分割成两个大致相等的子表,类似于二分查找,时间复杂度为 O(nlogn)

最坏情况:在待排序序列已经排好序的情况下,其递归树成为单支数,每次划分只得到一个比上一次少一个记录的子序列,时间复杂度为 O(n^2)

枢轴的合理选择可以避免最坏情况的出现,如利用 "三者取中" 的规则:比较当前表中第一个记录、最后一个记录和中间一个记录的关键字,取关键字居中的记录作为枢轴记录,事先调换到第一个记录的位置。

int GetMidIndex(int* arr, int left, int right)
{
    int mid = left + (right - left) / 2;
    if (arr[left] < arr[mid])
    {
        if (arr[right] > arr[mid])
            return mid;
        else if (arr[right] > arr[left])
            return right;
        else
            return left;
    }
    else
    {
        if (arr[right] > arr[left])
            return left;
        else if (arr[right] > arr[mid])
            return right;
        else
            return mid;
    }
}
​
// 以 Hoare 版本为例
int Partion(int* arr, int left, int right)
{
    int ret = GetMidIndex(arr, left, right);
    if (ret != left)
    {
        Swap(&arr[ret], &arr[left]);  // 将居中的元素交换到第一个元素的位置
    }
    int pivotloc = left;
    int pivotkey = arr[pivotloc];
    // ... ....
}

除此之外,还可以进一步优化,即小区间优化

#define MAX_LENGTH_INSERT_SORT 7  // 数组长度阈值
​
void QSort(int* arr, int left, int right)
{
    if (right - left + 1 > MAX_LENGTH_INSERT_SORT)
    {
        // 将 arr[left...right] 一分为二,pivotloc 是枢轴位置
        int pivotloc = Partion(arr, left, right);  
        // 对左子表递归排序
        QSort(arr, left, pivotloc - 1);
        // 对右子表递归排序
        QSort(arr, pivotloc + 1, right);
    }
    else
    {
        InsertSort(arr + left, right - left + 1);
    }
}

3.2.3 - 非递归算法

可以借助将递归算法改写成非递归算法。

void QuickSortNonRecursion(int* arr, int n)
{
    Stack st;
    StackInit(&st);
    range r = { 0, n - 1 };  // 区间
    StackPush(&st, r);
    while (!StackEmpty(&st))
    {
        range top = StackTop(&st);
        StackPop(&st);
        if (top.left < top.right)
        {
            int pivotloc = Partion(arr, top.left, top.right);
            range rr = { pivotloc + 1, top.right };  // 右区间
            StackPush(&st, rr);
            range lr = { top.left, pivotloc - 1 };  // 左区间
            StackPush(&st, lr);
        }
    }
    StackDestroy(&st);
}


四、选择排序

选择排序的基本思想是:每一趟从待排序的记录中选出关键字最小的记录,按顺序放在已排序的记录序列的最后,直到全部排完为止。

4.1 - 简单选择排序

简单选择排序(Simple Selection Sort)也称作直接选择排序

算法步骤

  1. 设待排序的记录存放在数组 arr[0...n-1] 中。第一趟从 arr[0] 开始,通过 n - 1 次比较,从 n 个记录中选出关键字最小的记录,记为 arr[k],交换 arr[0]arr[k]

  2. 第二趟从 arr[1] 开始,通过 n - 2 次比较,从 n - 1 个记录中选出关键字最小的记录,记为 arr[k],交换 arr[1]arr[k]

  3. 重复上述过程,经过 n - 1 趟,排序完成。

void SelectSort(int* arr, int n)
{
    for (int i = 0; i < n - 1; ++i)
    {
        int k = i;
        for (int j = i + 1; j < n; ++j)
        {
            if (arr[j] < arr[k])
            {
                k = j;
            }
        }
        if (k != i)  // 交换 arr[i] 和 arr[k]
        {
            Swap(&arr[i], &arr[k]);
        }
    }
}

改进

void SelectSort(int* arr, int n)
{
    for (int i = 0; i < n - 1; ++i, --n)
    {
        int mini = i;  // 最小的元素下标
        int maxi = i;  // 最大的元素下标
        for (int j = i + 1; j < n; ++j)
        {
            if (arr[j] < arr[mini])
            {
                mini = j;
            }
            if (arr[j] > arr[maxi])
            {
                maxi = j;
            }
        }
        if (mini != i)
        {
            Swap(&arr[mini], &arr[i]);
        }
        if (maxi == i)
        {
            maxi = mini;
        }
        if (maxi != n - 1)
        {
            Swap(&arr[maxi], &arr[n - 1]);
        }
    }
}

最开始 arr[i] 可能就是关键字最大的记录,但经过交换后,即 Swap(&arr[mini], &arr[i])arr[i] 就变成了关键字最小的记录,因此,如果不修改 maxi,就会错误。

4.2 - 堆排序

算法步骤

  1. 按堆的定义将待排序记录 arr[0...n-1] 调整为大根堆(这个过程称为建初堆),交换 arr[0]arr[n - 1],则 arr[n - 1] 为关键字最大的记录。

  2. arr[0...n-2] 重新调整为堆,交换 arr[0]arr[n - 2],则 arr[n - 2] 为关键字次大的记录。

  3. 循环 n - 1 次,直到交换了 arr[0]arr[1],得到了一个非递减的有序序列 arr[0...n-1]

同样,可以通过构造小根堆得到一个非递增的有序序列

// 向下调整堆
void HeapAdjustDown(int* arr, int n, int parent)
{
    int child = 2 * parent + 1;  // 假设左孩子较大
    while (child < n)
    {
        if (child + 1 < n && arr[child + 1] > arr[child])
        {
            ++child;
        }
        if (arr[parent] < arr[child])
        {
            Swap(&arr[parent], &arr[child]);
            parent = child;
            child = 2 * parent + 1;
        }
        else
        {
            break;
        }
    }
}
​
// 建初堆
void HeapCreate(int* arr, int n)
{
    for (int i = (n - 2) / 2; i >= 0; --i)
    {
        HeapAdjustDown(arr, n, i);
    }
}
​
// 堆排序
void HeapSort(int* arr, int n)
{
    HeapCreate(arr, n);
    for (int i = n - 1; i > 0; --i)
    {
        Swap(&arr[0], &arr[i]);
        HeapAdjustDown(arr, i, 0);
    }
}


五、归并排序

归并排序(Merging Sort)就是将两个或两个以上的有序表合并成一个有序表的过程。将两个有序表合并成一个有序表的过程称为 2-路归并

归并排序算法的思想是:假设初始序列含有 n 个记录,则可看成是 n 个有序的子序列,每个子序列的长度为 1,然后两两归并,得到 ​ 个长度为 2 或 1 的有序子序列;再两两归并,... ...,如此重复,直至得到一个长度为 n 的有序序列为止。

5.1 - 递归

void Merge(int* arr, int* tmp, int left, int mid, int right)
{
    int i = left;
    int j = mid + 1;
    int k = left;
    while (i <= mid && j <= right)
    {
        if (arr[i] <= arr[j])
        {
            tmp[k++] = arr[i++];
        }
        else
        {
            tmp[k++] = arr[j++];
        }
    }
    while (i <= mid)
    {
        tmp[k++] = arr[i++];
    }
    while (j <= right)
    {
        tmp[k++] = arr[j++];
    }
    // 最后将 tmp 中的内容拷贝到原数组 arr 中
    memmove(arr + left, tmp + left, sizeof(int) * (right - left + 1));
}
​
void MSort(int* arr, int* tmp, int left, int right)
{
    if (left < right)
    {
        int mid = (left + right) / 2;
        // 对子序列 arr[left...mid] 递归归并排序
        MSort(arr, tmp, left, mid); 
        // 对子序列 arr[mid+1...right] 递归归并排序
        MSort(arr, tmp, mid + 1, right);
        // 将相邻的两个有序表 arr[left...mid] 和 arr[mid+1...right] 
        // 归并为有序表 arr[left...right]
        Merge(arr, tmp, left, mid, right);
    }
}
​
void MergeSort(int* arr, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (NULL == tmp)
    {
        perror("malloc failed!");
        return;
    }
    MSort(arr, tmp, 0, n - 1);
    free(tmp);
    tmp = NULL;
}

5.2 - 迭代

void MergeSortNonRecursion(int* arr, int n)
{
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (NULL == tmp)
    {
        perror("malloc failed!");
        return;
    }
    int gap = 1;  // gap 表示间距
    while (gap < n)
    {
        for (int i = 0; i < n; i += 2 * gap)
        {
            int left = i;
            int right = i + 2 * gap - 1;
            int mid = (left + right) / 2;
            if (mid + 1 >= n)
            {
                break;
            }
            if (right >= n)
            {
                right = n - 1;
            }
            Merge(arr, tmp, left, mid, right);
        }
        gap *= 2;
    }
    free(tmp);
    tmp = NULL;
}

迭代过程并非模拟归并排序的递归过程

例一

例二


六、排序算法复杂度及稳定性分析

排序方法平均情况最好情况最坏情况辅助空间稳定性
直接插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(nlogn) ~ O(n^2)O(n^2)O(1)不稳定
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
快速排序O(nlogn)O(n^2)O(nlogn)O(logn) ~ O(n)不稳定
简单选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定

性能测试

void TestPerformace()
{
    srand((unsigned int)time(NULL));
    int n = 1000000;
    int* arr1 = (int*)malloc(sizeof(int) * n);
    int* arr2 = (int*)malloc(sizeof(int) * n);
    int* arr3 = (int*)malloc(sizeof(int) * n);
    int* arr4 = (int*)malloc(sizeof(int) * n);
    int* arr5 = (int*)malloc(sizeof(int) * n);
    int* arr6 = (int*)malloc(sizeof(int) * n);
    int* arr7 = (int*)malloc(sizeof(int) * n);
    if (!arr1 || !arr2 || !arr3 || !arr4 || !arr5 || !arr6 || !arr7)
    {
        perror("malloc failed!");
        return;
    }
    for (int i = 0; i < n; ++i)
    {
        arr1[i] = rand();
        arr2[i] = arr1[i];
        arr3[i] = arr1[i];
        arr4[i] = arr1[i];
        arr5[i] = arr1[i];
        arr6[i] = arr1[i];
        arr7[i] = arr1[i];
    }
    // 开始测试
    int begin1 = clock();
    InsertSort(arr1, n);
    int end1 = clock();
​
    int begin2 = clock();
    ShellSort(arr2, n);
    int end2 = clock();
​
    int begin3 = clock();
    BubbleSort(arr3, n);
    int end3 = clock();
​
    int begin4 = clock();
    QuickSort(arr4, n);
    int end4 = clock();
​
    int begin5 = clock();
    SelectSort(arr5, n);
    int end5 = clock();
​
    int begin6 = clock();
    HeapSort(arr6, n);
    int end6 = clock();
​
    int begin7 = clock();
    MergeSort(arr7, n);
    int end7 = clock();
    
    printf("InsertSort: %d\n", end1 - begin1);
    printf("ShellSort: %d\n", end2 - begin2);
    printf("BubbleSort: %d\n", end3 - begin3);
    printf("QuickSort: %d\n", end4 - begin4);
    printf("SelectSort: %d\n", end5 - begin5);
    printf("HeapSort: %d\n", end6 - begin6);
    printf("MergeSort: %d\n", end7 - begin7);
    
    free(arr1);
    free(arr2);
    free(arr3);
    free(arr4);
    free(arr5);
    free(arr6);
    free(arr7);
}

非比较排序(计数排序、基数排序和桶排序)会在下一篇博客中详解~

创作不易,请点点赞和关注一下博主

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

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

相关文章

关于统信UOS(Linux)系统磁盘无损扩容的方法

前言 针对某托管平台分配的4台虚拟服务器&#xff0c;操作系统统信UOS&#xff08;Linux&#xff09;&#xff0c;数据磁盘空间已满&#xff0c;无损扩容的办法。 &#xff08;在操作硬盘扩容前&#xff0c;为了安全起见&#xff0c;请通过磁盘快照功能备份服务器系统盘与数据盘…

Java 堆外内存

文章目录Java 堆外内存堆外内存的分配方式使用 Unsafe 类进行分配使用 ByteBuffer 进行分配堆外内存的查看方式Java 堆外内存 在 Java 虚拟机中&#xff0c;分配对象基本上都是在堆上进行的&#xff0c;然而在有些情况下&#xff0c;缓存的数据量非常大时&#xff0c;使用磁盘或…

【Python_Scrapy学习笔记(十四)】基于Scrapy框架的文件管道实现文件抓取(基于Scrapy框架实现多级页面的抓取)

基于Scrapy框架的文件管道实现文件抓取(基于Scrapy框架实现多级页面的抓取) 前言 本文中介绍 如何基于 Scrapy 框架的文件管道实现文件抓取(基于Scrapy框架实现多级页面的抓取)&#xff0c;并以抓取 第一PPT 网站的 PPT 模板为例进行展示&#xff0c;同时抓取此网站数据的方式…

Docker安装Nexus搭建Maven私服及介绍

目录前言一、Nexus是什么&#xff1f;二、Docker安装方式1. 拉取镜像2. 创建挂载目录3. 运行4. 容器运行日志 &#xff08;可选&#xff09;三、用户登录四、仓库介绍五、创建代理仓库六、上传依赖&#xff08;重点&#xff09;七、下载依赖常见问题1、如何把新建的仓库添加到组…

【前端之旅】vue-router声明式导航和编程式导航

一名软件工程专业学生的前端之旅,记录自己对三件套(HTML、CSS、JavaScript)、Jquery、Ajax、Axios、Bootstrap、Node.js、Vue、小程序开发(Uniapp)以及各种UI组件库、前端框架的学习。 【前端之旅】Web基础与开发工具 【前端之旅】手把手教你安装VS Code并附上超实用插件…

科研作图-常用的图像分割指标 (Dice, Iou, Hausdorff) 及其计算

1. 简介 本节内容主要是介绍图像分割中常用指标的定义、公式和代码。常用的指标有Dice、Jaccard、Hausdorff Distance、IOU以及科研作图-Accuracy,F1,Precision,Sensitive中已经介绍的像素准确率等指标。在每个指标介绍时&#xff0c;会使用编写相关代码&#xff0c;以及使用M…

TypeScript学习记录Ts基础

安装及初步使用 1.CMD全局安装 npm install -g typescript2.检查是否安装成功 tsc -V 如报错tsc 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件[可参考](https://www.cnblogs.com/sanyekui/p/13157918.html)3.在vscode中新建ts文件01_ts.ts let str:strin…

iPhone上5款视频压缩工具推荐,学会轻松压缩视频

随着技术的不断进步&#xff0c;现在移动端设备已经能够轻松地拍摄高清视频&#xff0c;然而这也带来了存储压力的增大。苹果手机更新换代频繁&#xff0c;但内存空间却没有跟着变大&#xff0c;因此如何压缩视频成为了一个重要的问题。 苹果手机怎么压缩视频&#xff1f;有什…

01-ASPICE体系知识

1. ASPICE是什么&#xff1f; ASPICE: 全称是"Automotive Software Process Improvement and Capacity Determination"&#xff0c;汽车软件过程改进及能力评定&#xff0c;是汽车行业用于评价软件开发团队的研发能力水平的模型框架。最初由欧洲20多家主要汽车制造商…

上位机:创建WPF应用并使用控件完成控件交互

创建WPF应用并使用控件完成控件交互 1.XAML是WPF技术中专门用于设计UI的语言。 2.优点,XAML是一种声明型语言,只能用来声明一些UI元素、绘制UI和动画,不能在其中加入程序逻辑。实现UI与逻辑的剥离。 下面使用visual studio2019建立一个WPF项目: 进入项目,点击运行,然后…

FluxMQ—引领物联网新时代的高性能MQTT网关

FluxMQ—引领物联网新时代的高性能MQTT网关 随着物联网技术的快速发展&#xff0c;人们越来越意识到实时、可靠、安全的数据传输对于智能化的生产与生活的重要性。因此&#xff0c;市场对于高性能的物联网数据传输解决方案有着强烈的需求。FluxMQ正是为满足这一需求而诞生的一…

苹果电脑怎么用移动硬盘ntfs 苹果电脑移动硬盘怎么退出

Mac电脑默认不支持写入NTFS格式移动硬盘&#xff0c;这导致很多Mac用户的工作过程遇到很多不必要的麻烦。如何才能让Mac电脑“永久”拥有写入NTFS格式移动硬盘的权限呢&#xff1f;不少用户使用完移动硬盘后直接拔出&#xff0c;这可能会导致未保存的文件丢失。使用完移动硬盘后…

城市供水绩效指标解释

1.城市供水绩效评价指标体系构成 1.1.1绩效指标体系横向构成包括&#xff1a; 背景信息&#xff1a;用于计算指标的数据。 解释性因素&#xff1a;靠管理不易改变的数据。 绩效指标&#xff1a;若干个基础数据的综合运算结果&#xff0c;用于评价所提供服务的有效性…

Python每日一练(20230418)

目录 1. 将有序数组转换为二叉搜索树 &#x1f31f;&#x1f31f; 2. 四数之和 &#x1f31f;&#x1f31f; 3. 排序数组查找元素的首末位置 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C…

JAVA基于局域网的聊天室系统的设计与实现

本文介绍了采用JAVA编程开发视频聊天系统的一套比较常用的解决方案。文字聊天采用TCP模式&#xff1b;语音视频聊天采用UDP模式&#xff0c;在客户端之间点对点的进行。在该方案中&#xff0c;通过函数库VFW来实现视频捕获、影像压缩以及影像播放。微软公司提供的专门用于视频捕…

Python入门教程+项目实战-10.2节: 列表的操作符

目录 10.2.1 列表中的常用操作符 10.2.2 []操作符: 索引访问列表 10.2.3 [:]操作符&#xff1a;列表的切片 10.2.4 操作符&#xff1a;列表的加法 10.2.5 *操作符&#xff1a;列表的乘法 10.2.6 列表的关系运算 10.2.7 in操作符&#xff1a;查找元素 10.2.8 知识要点 …

香港电讯牌照申请介绍

香港对无线电发射器具的进出口实施法律管制&#xff0e;《电讯条例》规定进出口手提电话的人士须持有许可证或无线电商牌照。例如&#xff1a;手机、3C类产品、手提电脑等无线电子产品&#xff0c;通关时须要出示的证件之一。 一、香港电讯牌照申请需提供资料&#xff1a;1、有…

py逆向-NSSCTF-[NISACTF 2022]ezpython

目录 题目&#xff1a; 学到的点&#xff1a; 题目&#xff1a; 之前没遇到过这样的题&#xff0c;看了大佬的wp了解很多&#xff0c;记录一下 放到ida中打开&#xff0c;尝试分析了一下&#xff0c;没头绪 看了题解知道是一道py逆向的题目&#xff0c;需要用到这个工具pyin…

Elasticsearch+filefeat+Kibana(EFK)架构学习

一. 安装ES7集群 准备三台服&#xff0c;最少配置2core4G,磁盘空间最少20G,并关闭防火墙设置集群免密登录&#xff0c;方便scp文件等操作参考集群免密登录方法下载es7的elasticsearch-7.17.3-x86_64.rpm包安装 yum -y localinstal elasticsearch-7.17.3-x86_64.rpm修改node1配…

【SpringMVC】SpringMVC(一:第一个SpringMVC项目)

文章目录1. SSM优化的方向2.SpringMVC的优点3. SpringMVC的优化方向4.SpringMVC执行流程5.第一个SpringMVC项目5.1 创建工程5.2 添加依赖5.3 替换web.xml5.4 开发流程5.4.1完成springmvc.xml文件的配置5.4.2在web.xml文件中注册SpringMVC框架。5.4.3 编写控制器5.4.4 开发页面&…