C语言,排序

前言

        排序,可以说是数据结构中必不可缺的一环。我们创造数据存储它,要想知道数据之间的联系,比较是必不可少的。不然,费劲心思得来的数据若是不能有更多的意义,那么拿到了又有什么用?

        排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分为内部排序和外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程。

一. 排序的种类

        快速排序、希尔排序、堆排序、直接选择排序不是稳定的排序算法,而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。

        稳定排序:假设在待排序的文件中,存在两个或两个以上的记录具有相同的关键字,在用某种排序法排序后,若这些相同关键字的元素的相对次序仍然不变,则这种排序方法是稳定的。其中冒泡,插入,基数,归并属于稳定排序,选择,快速,希尔,归属于不稳定排序。

        具体为什么这些排序是稳定排序,或者是不稳定排序,会在之后进行图片演示。

        根据上述介绍可以知道,总共的排序分为8种,接下来我会挑出重要的和大家讲解。

二. 排序的实现

        一下排序都是按照排升序的方式进行的,请读者注意。如果想要让排序的功能更加丰富,推荐读者像qsort这样的标准函数一样,传入比较的函数指针,由于只是讲解比较原理,故简化了。

1. 插入排序

1.1. 原理

图1-1 插入排序原理图

        如图1-1所示,开始的时候数据的储存入第A行所示,我们需要将从第二个数据的位置开始,向前一次插入数据,将小的放在前面。从目标位置向前遍历的时候,如果目标数据小于比较数据,就将比较的数据向后移动一格。因此我们需要提前记录目标位置的值。

        从A行的第二列开始,2小于5,就将5向后移动一位,到顶了,就将2赋给第一列。同理,到了第三列的4,4小于5,就将5向后挪动一列,再继续比较,4大于2,就不需要挪动2,将4赋给第二列。按照上述规律,依次插入,直到到第E行,将第六列的3插入到第三列为止。

1.2. 代码

// 插入排序
void InsertSort(int* a, int n)
{// a, 不能为空assert(a);for(int i = 0; i < n - 1; ++i){// 一次插入排序int end = i;int tmp = a[end + 1];while(end >= 0){if(tmp < a[end]) // 升序中, 小于插入的值就挪动数据{a[end + 1] = a[end];end--;}else    // 反之跳出循环赋值{break;}}a[end + 1] = tmp;}
}

2. 希尔排序

2.1. 介绍

        希尔希尔(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。

        希尔排序是一种比较厉害的排序,虽然没有稳定性,但是排序的时间复杂度是小于O(N^2)的。所以,希尔排序是一种很厉害的排序。快的表现和快速排序、堆排序、归并排序是一个等级的。

2.2. 原理

图2-1 希尔排序原理图

        如果插入排序是希尔排序的一种特殊情况,插入排序相当于希尔排序增量为1的时候的排序。那么为什么诞生的希尔排序有它的意义呢?从第一趟增量为5的时候,相当于给数据分成了5个部分,大大减小了排序的范围。在第二趟的排序中,会造成的效果就是,交换的时候每次增量不超过上一次排序的增量也就是5。因此提高了效率。

        在性能上,希尔排序的时间复杂度区间在O(N^(3/2))到O(N*logN)之间,不需要大量的辅助空间,因此数据排序在中等规模中表现良好。但是对于规模非常大的数据时不是最佳选择,数据量大的时候仍然推荐快速排序。至于时间复杂度和降低时间复杂度的方法是需要非常复杂的数学模型的,专家们正在研究,如今仍然是数学难题。

2.3. 代码

// 希尔排序
void ShellSort(int* a, int n)
{// a, 不能为空assert(a);// 希尔排序相当于插入排序中有多次预排序int gap = n;while(gap > 1) //  控制预排序间隔,通常分为三块{gap = gap / 3 + 1;for(int i = 0; i < n - gap; ++i){// 一次插入排序int end = i;int tmp = a[end + gap];while(end >= 0){if(tmp < a[end]) // 升序中, 小于插入的值就挪动数据{a[end + gap] = a[end];end -= gap;}else    // 反之跳出循环赋值{break;}}a[end + gap] = tmp;}}  
}

3. 选择排序

3.1. 原理

        直接选择排序(Straight Select Sorting) 也是一种简单的排序方法,它的基本思想是:第一次从R[0]~R[n-1]中选取最小值和最大值,与R[0]、R[n-1]交换,第二次从R[1]~R[n-2]中选取最小值和最大值,与R[1]、R[n-2]交换,....,第i次从R[i-1]~R[n-1-i]中选取最小值和最大值,与R[i-1]、R[n-1-i]交换,.....,重复n/2次,得到一个按排序码从小到大排列的有序序列。

        因为非常的简单,就是反复遍历数组将最小值最大值分别放到数组首和数组尾,所以时间复杂度很稳定,但也很大,一直都是O(N^2)。

3.2. 代码

void swap(int* a, int p1, int p2)
{int tmp = a[p1];a[p1] = a[p2];a[p2] = tmp;
}// 选择排序
void SelectSort(int* a, int n)
{assert(a);int begin = 0, end = n - 1;while(begin < end)  // 一次循环找到最大值和最小值,{int mini = begin, maxi = end;for(int i = begin; i <= end; ++i) // 一次循环找到最大值和最小值{if(a[i] < a[mini]) // 找最小{mini = i;}if(a[i] > a[maxi]) // 找最大{maxi = i;}}if(maxi == begin) // 如果最大值在第一位,就需要改变交换的位置{maxi = mini;}swap(a, begin, mini); // 交换数据swap(a, end, maxi);++begin; // 控制结束循环的条件--end;}
}

4. 堆排序

4.1. 原理

        原理相关的可以参考之前在二叉树部分推出的堆排序,是一样的。利用二叉树的原理建堆,将最大的数放在堆顶,然后将堆顶的数据放到末尾之后重新从堆头调整数据,这样堆的数据每次都会减少一个,直到全部完成排序。

        建堆的方式选择向下建堆,完成排序的时间复杂度是O(N*logN)。

4.2. 代码

// 堆排序
void AdjustDown(int* a, int n, int parent)
{assert(a);// 假设左孩子更大, 公式:child = parent * 2 + 1;int child = parent * 2 + 1;// 孩子节点没越界就继续比较while(child < n){// 调整左右孩子,取更小的if(child + 1 < n && a[child + 1] > a[child]){++child;}// 如果满足条件就交换父子位置并继续向下遍历if(a[parent] < a[child]){swap(a, parent, child);parent = child;child = parent * 2 + 1;}else// 反之,跳出循环(无需继续调整){break;}}
}void HeapSort(int* a, int n)
{assert(a);//向下调整建堆(大堆)int i = n / 2 - 1;while(i >= 0){AdjustDown(a, n, i);--i;}// 得到降序数组while(--n){swap(a, 0, n);AdjustDown(a, n, 0);}
}

5. 冒泡排序

5.1. 原理

        冒泡排序(Bubble Sort)是最简单和最通用的排序方法,其基本思想是:在待排序的一组数中,将相邻的两个数进行比较,若前面的数比后面的数大就交换两数,否则不交换;如此下去,直至最终完成排序。由此可得,在排序过程中,大的数据往下沉,小的数据往上浮,就像气泡一样,于是将这种排序算法形象地称为冒泡排序。

图5-1 冒泡排序原理图

        算法的原理如图5-1所示,在待排序的一组数中,将相邻的两个数进行比较,若前面的数比后面的数大就交换两数,否则不交换。比如第一趟排序,比较26和28,26小于28故不做改变,到了28和24,28大于24,所以就交换数据位置,再向下比较28和11,仍然需要交换,这样就完成了一轮,28不在需要比较。然后依次排序。

5.2. 代码

// 冒泡排序
void BubbleSort(int* a, int n)
{for(int i = 0; i < n - 1; ++i)  // 循环趟数{for(int j = 1; j < n - i; ++j)  // 循环一趟{if(a[j] < a[j - 1]) // 把大的元素往后放,使数据提增{swap(a, j, j - 1);}}}
}

6. 快速排序

6.1. 原理

        快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

        (1)首先设定一个分界值,通过该分界值将数组分成左右两部分。

        (2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值。

        (3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

        (4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

图6-1 快速排序举例图

        快速排序在分部中分为三种方法:最标准的霍尔法、挖坑法以及前后指针法。如图6-1所示,快速排序的方法是挖坑法。首先在数组中记录分界值(这里是49),设置头指针和尾指针。如果要将数据排列为升序,那么尾指针从后向前遍历找小于分界值的数(找到了27)停下来,将这个放到之前记录分界值位置的部分,也可以说是头指针部分,然后将27填充到49处,表示填坑。这样27的位置相当于挖了一个坑。然后就然头指针找大于分界值的数(这里找到的是65),然后将65填充到尾指针的位置处(27)。之后移动尾指针,重复这个循环,直到头指针和尾指针相遇,将记录的分界值填入。结果如图6-1中的一次划分后所示。每次划分都能够确定一个值的具体位置,这里确定的是49。之后将49前后的数据分为新的空间继续进行划分即可完成排序。

        霍尔法,是使用头指针从左找大,尾指针从后找小,然后交换位置。相遇之后交换分界值的位置,和挖坑法最大的不同之处在于49的位置是最后移动的。

        前后指针法,前指针找到小于分界值的数就和后指针位置的数交换,然后后指针也向前推进。与前两者最大的不同是,指针的移动是按照同方向进行的。

6.2. 代码

int Midi(int* a, int left, int right)
{int midi = (left + right) / 2;if(a[left] > a[midi]){if(a[midi] > a[right]){return midi;}else if(a[left] > a[right]){return right;}else{return left;}}else // a[left] < a[midi]{if(a[midi] < a[right]){return midi;}else if(a[left] < a[right]){return right;}else{return left;}}
}// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right)
{int key = left;int begin = left + 1, end = right;while(begin < end){while(begin < end && a[end] >= a[key]){--end;}while(begin < end && a[begin] <= a[key]){++begin;}swap(a, begin, end);}swap(a, key, end);return end;
}// 快速排序挖坑法
int PartSort2(int* a, int left, int right)
{int key = a[left];int begin = left, end = right;while(begin < end){while(begin < end && a[end] >= key){--end;}a[begin] = a[end];while(begin < end && a[begin] <= key){++begin;}a[end] = a[begin];}a[end] = key;return end;
}// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{int key = a[left];int prev = left, cur = left + 1;while(cur <= right){if(a[cur] < key){++prev;swap(a, prev, cur);}++cur;}swap(a, left, prev);return prev;
}void QuickSort(int* a, int left, int right)
{assert(a);//区间过小直接返回if(left >= right){return;}//如果区间过小使用插入排序// if(right - left  < 10)// {//     SelectSort(a, right - left + 1);// }// 三数取中确定keyint key = Midi(a, left, right);swap(a, left, key);key = PartSort3(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);
}

6.3. 非递归的实现

        非递归的实现就需要解决区间的问题,所以我们需要向之前数据结构中,队列和栈中借过来存储区间位置即可。代码如下:


// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{assert(a);Stack st;StackInit(&st);StackPush(&st, right);StackPush(&st, left);while(!StackEmpty(&st)){int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);// 三数取中确定keyint keyi = Midi(a, begin, end);swap(a, begin, keyi);keyi = PartSort2(a, begin, end);//区间过小就不入栈if(begin < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, begin);}if(keyi < end){StackPush(&st, end);StackPush(&st, keyi + 1);}}StackDestroy(&st);
}

7. 归并排序

7.1. 原理

图7-1 归并排序原理图

        归并排序,就是将两组都是有序的数据合成一组的排序。所以对于原来的数组如图7-1所示,先拆分为不可继续分割的区间,然后分别将其合并,排列成有序数组,为下一次归并做准备。例如这里的10和4,组合之后成为[4,10],3和6组合之后成为[3,6]。然后再将这两个区间组合成[3,4,6,10]。直到组合完成所有数据。

        ps:实际在代码中比较需要建立额外的位置存放排序的数据,另外还需要每次排列完成数据后将数据赋值给原来的数组。相互比较的时候也需要注意,会有一个区间的数据没完全存入,需要分出一步完成该过程。

7.2. 代码

// 归并排序,子函数
void _MergeSort(int* a, int* tmp, int left, int right)
{// 区间过小,直接返回if(left >= right){return;}// 分区间int midi = (left + right) / 2;// [left, midi] [mide + 1, right]_MergeSort(a, tmp, left, midi);_MergeSort(a, tmp, midi + 1, right);// 归并int begin1 = left, end1 = midi;int begin2 = midi + 1, end2 = right;int i = begin1;// 控制比较次数while(begin1 <= end1 && begin2 <= end2){//小的先存if(a[begin1] < a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}// 存下剩余部分while(begin1 <= end1){tmp[i++] = a[begin1++];}while(begin2 <= end2){tmp[i++] = a[begin2++];}// 将暂存数据返回memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}// 归并排序
void MergeSort(int* a, int n)
{// 检查数组存在assert(a);// 开辟额外的空间暂存排序后数据int* tmp = (int*)malloc(sizeof(int) * n);if(tmp == NULL){perror("malloc fail");return;}// 分装子函数,实现归并功能_MergeSort(a, tmp, 0, n - 1);free(tmp);
}

7.3. 非递归实现

        和快速排序一样,需要解决的是区域划分的问题。那么如何划分区间呢?

        方法是设置区间的大小值,每次排序都按照这个大小划分区间,没完成一次归并就将这个区间的值翻倍,如图7-1所示,分解区间,开始区间的大小为1,之后是2/4/8。当然划分区间需要调整,因为数组的大小可能并不是分区间的倍数。如果数组剩下的元素不满于一个区间,就不需要继续排序,如果有一个区间但是不足第二个,就需要修剪第二个区间的范围。

        代码如下:

// 归并排序, 非递归
void MergeSortNonR(int* a, int n)
{assert(a);// 开辟额外的空间暂存排序后数据int* tmp = (int*)malloc(sizeof(int) * n);if(tmp == NULL){perror("malloc fail");return;}int gap = 1; // 表示区间间隔while(gap < n){// 将所有区间分为n / gap + 1 份, 每次比较两个区间for(int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = begin1 + gap - 1;int begin2 = end1 + 1, end2 = begin2 + gap - 1;int j = begin1;if(n - 1 < begin2)// 剩余区间过小,不比较{break;}else if(n - 1 < end2) // 如果区间2在数组结束之前,end2就是数组尾{end2 = n - 1;}//归并// 控制比较次数while(begin1 <= end1 && begin2 <= end2){//小的先存if(a[begin1] < a[begin2]){tmp[j++] = a[begin1++];}else{tmp[j++] = a[begin2++];}}// 存下剩余部分while(begin1 <= end1){tmp[j++] = a[begin1++];}while(begin2 <= end2){tmp[j++] = a[begin2++];}// 将暂存数据返回memcpy(a + i, tmp + i, sizeof(int) * (2 * gap));}gap *= 2; // 循环的递增}free(tmp);
}

作者结语

        说到底,排序并不是需要用论文的方式记录的东西,这些排序的存在都已经有很长时间了,可以说计算机行业家喻户晓,所以只能算是整理了自己的学习过程。

        写的也比较简单,原理然后接代码,从文本的角度来讲还是较难理解的,但是接触过的都能回到意思。这也是这篇博客的不足之处,本来是给小白学习的,但是小白却可能看不懂。

        无论如何,博客已经出炉了,希望各大高手指点指点。

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

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

相关文章

风险投资公司正在帮助小投资者购买Anthropic、OpenAI等热门公司的股票

近年来&#xff0c;风险投资公司对于人工智能&#xff08;AI&#xff09;领域的公司&#xff0c;如Anthropic、Groq、OpenAI等&#xff0c;表现出了极高的投资热情。这些公司因为它们在AI技术方面的创新而备受瞩目。但是&#xff0c;对于很多小投资者来说&#xff0c;由于资金有…

[C#]使用C#部署yolov8的目标检测tensorrt模型

【测试通过环境】 win10 x64 vs2019 cuda11.7cudnn8.8.0 TensorRT-8.6.1.6 opencvsharp4.9.0 .NET Framework4.7.2 NVIDIA GeForce RTX 2070 Super 版本和上述环境版本不一样的需要重新编译TensorRtExtern.dll&#xff0c;TensorRtExtern源码地址&#xff1a;TensorRT-CShar…

期权的权利金怎么算的

期权权利金的计算涉及多个因素&#xff0c;包括敲定价格、到期时间以及整个期权合约的具体情况。期权的权利金具体的计算公式和因素可能因不同的期权合约和市场条件而有所不同&#xff0c;下文为大家介绍期权的权利金怎么算的 &#xff1f;本文来自&#xff1a;期权酱 一、期权…

【LeetCode】二叉树oj专题

如有不懂的地方&#xff0c;可查阅往期相关文章&#xff01; 个人主页&#xff1a;小八哥向前冲~ 所属专栏&#xff1a;数据结构【c语言】 目录 单值二叉树 对称二叉树 计算二叉树的深度 二叉树的前序遍历 相同二叉树 另一棵树的子树 二叉树的构建和遍历 翻转二叉树 判…

spring boot 中的异步@Async

spring boot 开启异步调用 1、启动类上添加EnableAsync注解&#xff0c;表示启动异步 2、在具体实现异步的方法上添加Async注解 package com.example.demo;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootAppli…

YOLOv3+mAP实现金鱼检测

YOLOv3mAP实现金鱼检测 Git源码地址&#xff1a;传送门 准备数据集 按帧数读取视频保存图片 video2frame.py使用labelimg标注工具对图片进行标注统一图片大小为 416x416&#xff0c;并把标签等信息写成.xml文件 conver_point.py读取缩放后的标签图片&#xff0c;转为左上角右下…

如何快速部署上线项目

CSDN 的小伙伴们&#xff0c;大家好呀&#xff0c;我是苍何。 今天在群里面看到有小伙伴反馈说&#xff0c;面试的时候一被问到简历中的项目还没上线&#xff0c;就不继续问了&#xff0c;感觉挺奇葩的&#xff0c;要知道就校招来说&#xff0c;项目本身大部分都是练手的项目&…

Linux基础1-基本指令3

上篇文章我们说到了文件&#xff0c;pwd&#xff0c;touch&#xff0c;mkdir等知识。 Linux基础1-基本指令2&#xff08;你真的了解文件吗?&#xff09;-CSDN博客 本文继续梳理其他基础命令 1.本章重点 1.删除一个空目录命令rmdir 2.删除一个文件指令rm(重要!) 3.man命令&am…

做视频号小店保证金要交多少?保证金提现条件是什么?

大家好&#xff0c;我是喷火龙。 做视频号小店也是需要缴纳保证金的&#xff0c;保证金分为类目保证金和浮动保证金。 先来说说类目保证金&#xff0c;类目保证金由视频号小店主体资质类型和经营商品类目决定。 类目保证金有以下三点需要注意&#xff1a; 1. 如果你要申请新…

CentOS 7~9 救援模式恢复root密码实战指南

在管理Linux服务器时&#xff0c;忘记root密码是一件棘手的事情&#xff0c;但幸运的是&#xff0c;CentOS提供了救援模式来帮助我们重置root密码。本文将详细介绍如何通过GRUB引导菜单进入紧急模式&#xff08;或称为救援模式&#xff09;&#xff0c;进而恢复root用户的密码。…

Python量化交易学习——Part4:基于基本面的单因子选股策略

技术分析与基本面分析是股票价格分析最基础也是最经典的两个部分。技术分析是针对交易曲线及成交量等指标进行分析,基本面分析是基于公司的基本素质进行分析。 一般来说选股要先选行业,在选个股,之后根据技术分析选择买卖节点,因此针对行业及个股的基本面分析是选股的基础。…

# 全面解剖 消息中间件 RocketMQ-(4)

全面解剖 消息中间件 RocketMQ-&#xff08;4&#xff09; 一、RocketMQ 顺序消息分析 1、消息有序&#xff1a;指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ 可以严格的保证消息有序&#xff0c;可以分为分区有序或者全局有序。 2、顺序消费的原理解析 在默认的情…

vfrom二开给左边添加字段或者容器

例如&#xff0c;我在左侧加入一个 我的公司 字段 修改三个文件&#xff0c;这是文件目录 这个文件是当界面选择 简体中文 的时候&#xff0c;显示的 字段组件 或者 容器组件的中文名 这个文件是当界面选择 English 的时候&#xff0c;显示的 字段组件 或者 容器组件的英文名 把…

Spring Boot 集成 zxing 生成条形码与二维码

前面我们知道了怎么通过 使用 zxing 生成二维码以及条形码&#xff0c; 由于我们现在都是 web 端的项目了&#xff0c;那么我们看下怎么使用 Spring Boot 集成然后返回给前端展示&#xff1a; 工程源码 对应的工程源码我放到了这里&#xff1a;github源码路径&#xff0c;点击…

d2-crud-plus 使用小技巧(六)—— 表单下拉选择 行样式 溢出时显示异常优化

问题 vue2 elementUI d2-crud-plus&#xff0c;数据类型为select时&#xff0c;行样式显示为tag样式&#xff0c;但是如果选择内容过长就会出现下面这种bug&#xff0c;显然用户体验不够友好。 期望 代码 js export const crudOptions (vm) > {return {...columns:…

图书管理系统(https://github.com/plusmultiply0/bookmanagesystem)

特意去github找了一个用flask框架的项目&#xff0c;一起来学习它吧 这个系统包括很多功能&#xff1a;用户权限管理模块&#xff08;管理员和普通用户&#xff09;&#xff0c;注册登录模块&#xff08;滑块验证码功能&#xff09;&#xff0c;图书有关信息模块&#xff08;借…

毫米级精度3D人脸扫描设备,助推打造元宇宙虚拟分身

在元宇宙中&#xff0c;虚拟分身对应的是一个三维模型&#xff0c;数字化的过程则是三维重建过程&#xff0c;通过3D人脸扫描可以通过多相机同步采集人脸部&#xff0c;可快速、准确地重建出真人地脸部模型及贴图&#xff0c;通过3D人脸扫描设备可快速重建出高逼真的虚拟分身。…

Linux系统下+jmeter分布式压测

一.配置jdk&#xff08;Linux机都需配置同一个版本&#xff09; 下载Linux系统的jdk&#xff0c;下载地址&#xff1a;https://repo.huaweicloud.com/java/jdk/ 下载后的jdk文件上传到 /opt目录下 进入opt目录&#xff0c;查看jdk文件 cd /opt ll 1.解压文件 tar xzvf jd…

真国色码上赞,科技流量双剑合璧,商家获客新纪元开启

在数字化浪潮汹涌的今天,真国色研发团队依托红玉房网络科技公司的雄厚实力,凭借科技领先的核心竞争力,推出了创新性的商家曝光引流工具——码上赞。这款工具借助微信支付与视频号已有功能,为实体商家提供了一种全新的引流获客方式,实现了科技与商业的完美融合。 科技领先,流量黑…

CSS 空间转换 动画

目录 1. 空间转换1.1 视距 - perspective1.2 空间转换 - 旋转1.3 立体呈现 - transform-style1.4 空间转换 - 缩放 2. 动画 - animation2.1 动画的基本用法2.1 animation 复合属性2.2 animation 拆分属性2.3 多组动画 正文开始 1. 空间转换 空间&#xff1a;是从坐标轴角度定义…