【数据结构】八大排序之快速排序算法

🦄个人主页:修修修也

🎏所属专栏:数据结构

⚙️操作环境:Visual Studio 2022


目录

一.快速排序简介及思路

二.快速排序代码实现的三种方式

📌左右交换法

📌挖坑填坑法

📌前后指针法

三.快速排序的时间复杂度分析

四.快速排序的优化

🎏优化选key方式

📌随机选key法

📌三数取中法

🎏小区间优化

📌小区间优化的原理

📌小区间优化的代码实现

五.借助栈实现非递归快速排序

📌为什么要将递归的快速排序算法改为非递归?

📌递归函数改非递归的思路

📌快速排序改非递归的思路

📌快速排序改非递归的代码实现

六.快速排序的三路划分

结语


一.快速排序简介及思想

快速排序(Quick Sort)是一种效率较高的交换排序算法.

它的基本思想是:

  • 通过一趟排序将待排数据分割成独立的两部分
  • 其中一部分数据的关键字均比另一部分数据的关键字小
  • 可分别对这两部分数据继续进行排序,以达到整个序列有序的目的.

算法动图演示:


二.快速排序代码实现的三种方式

我们了解了快速排序的基本思想通过一趟排序将待排数据分割成独立的两部分之后,在代码的实现上,其实就有很多可以自由发挥的空间,如下较为主流的快速排序有三种实现思路,接下来我将一一带大家理解这三个思路并使用它们实现快排算法:

注:本文的快排实现思路均以升序为例!

📌左右交换法

左右交换法的思路是:

  1. 先选定当前待排序列的首元素位置的值为基准值(key).
  2. 然后设置一个右指针,使其从后向前遍历,找到比基准值(key)小的元素就停下来.
  3. 再设置一个左指针,使其从前向后遍历,找到比基准值(key)大的元素就停下来.
  4. 当左右指针都找到相应元素时,交换它们找到的元素.
  5. 重复步骤2~4,直到左右指针相遇
  6. 左右指针相遇后,将基准值(key)与相遇位置做交换,此时数组已经被重新一分为二成两个新的待排子序列.
  7. 分别继续对新的待排子序列继续执行步骤1~6排序,直到所有元素都排列在相应位置上为止.

左右交换法算法演示:

 清楚了左右交换法实现快排的思路后,我们编写实现代码就比较容易了,代码如下:

//交换函数
void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//快速排序(左右交换法
void QuickSort_swap(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;int keyi = left;//选定序列首元素为基准值while (left < right){//右边找小while (left < right && a[right] >= a[keyi])right--;//左边找大while (left < right && a[left] <= a[keyi])left++;//交换Swap(&a[left], &a[right]);}//相遇时交换key和相遇位置的值Swap(&a[keyi], &a[left]);keyi = left;//用keyi记录下本轮确定的基准值位置QuickSort_swap(a, begin, keyi - 1);//递归排序左区间[begin , keyi-1]QuickSort_swap(a, keyi + 1, end);//递归排序右区间[keyi+1 , end]
}

📌挖坑填坑法

挖坑填坑法是基于快排的基础思想提出的一种创新实现的思路,它的思路是这样的:

  1. 记录下当前待排序列的首元素为基准值(key).
  2. 此时认为首元素的位置空缺的,即该位置成为了一个坑.
  3. 设置一个右指针,使其从后向前遍历,找到比基准值(key)小的元素停下来将其填入刚才的坑位中,此时认为右指针找到的这个元素位置又形成了一个坑.
  4. 设置一个左指针,使其从前向后遍历,找到比基准值(key)大的元素停下来将其填入刚才的坑位中,此时认为左指针找到的这个元素位置又形成了一个坑.
  5. 左右指针不断向中间挪动不断填坑又形成新坑,直到两指针相遇
  6. 最后将基准值(key)填入左右指针相遇位置的坑中,此时数组已经被重新一分为二成两个新的待排子序列.
  7. 分别继续对新的待排子序列继续执行步骤1~6排序,直到所有元素都排列在相应位置上为止.

挖坑填坑法算法演示:

挖坑填坑法实现快排代码如下:

//快速排序(挖坑填坑法
void QuickSort_hole(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;int key = a[left];//选定序列首元素为基准值,记录下基准值int hole = left;  //基准值的位置形成坑位while (left < right){//右边找小while (left < right && a[right] >= key)right--;//找到了就填坑,更新坑a[hole] = a[right];hole = right;//左边找大while (left < right && a[left] <= key)left++;//找到了就填坑,更新坑a[hole] = a[left];hole = left;}//相遇时把key的值填进相遇的坑里a[hole] = key;QuickSort_hole(a, begin, hole - 1);//递归遍历左区间[begin , hole-1]QuickSort_hole(a, hole + 1, end);//递归遍历右区间[hole+1 , end]
}

📌前后指针法

前后指针法思路为:

  1. 先选定当前待排序列的首元素位置的值为基准值(key).
  2. 设立前指针prev,使其指向序列开头,即基准值位置
  3. 设立后指针cur,使其指向prev指针的后一个位置
  4. 判断cur指向的数据是否小于key:如果小于,则prev后移一位,然后将cur指向的内容与prev指向的内容交换, 然后cur指针++;如果不小于,则cur++.
  5. 循环步骤4,直到cur移动到超出序列范围时,交换prev位置和基准位置的值,此时数组已经被重新一分为二成两个新的待排子序列.
  6. 分别继续对新的待排子序列继续执行步骤1~5排序,直到所有元素都排列在相应位置上为止.

前后指针法算法演示:

前后指针法实现快排代码如下:

//交换
void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//快速排序(前后指针法
void QuickSort_follow(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;int keyi = left;//选定序列首元素为基准值,记录下基准值位置int prev = left;int cur = prev + 1;while (cur <= right) //cur没有越界就继续{if (a[cur] < a[keyi] && ++prev != cur) //如果cur指向的值小于key,并且++prev与cur不重叠{Swap(&a[cur], &a[prev]);  //就交换它们}cur++; }Swap(&a[prev], &a[keyi]);//交换prev和key的值keyi = prev;QuickSort_follow(a, begin, keyi - 1);//递归遍历左区间[begin , keyi-1]QuickSort_follow(a, keyi + 1, end);//递归遍历右区间[keyi+1 , end]
}

三.快速排序的时间复杂度分析

"快速排序的平均时间为 T_{avg}(n)=knlog_2n,其中n为待排序序列中数据的个数,k为某个常数,经验证明,在所有同数量级的此类(先进的)排序算法中,快速排序的常数因子k最小.因此,就平均时间而言,快速排序是目前被认为最好的一种内部排序方法.

通常,快速排序被认为是,在所有同数量级(O(nlogn))的排序算法中,其平均性能最好.但是,若初始数据序列按关键字有序或基本有序时,快速排序将蜕化为冒泡排序,其时间复杂度为O(n^2)."

                                ——《数据结构》严蔚敏

也就是说,快排的时间复杂度我们可以认为是O(nlogn),但当遇到原数组本身有序的情况下,其时间复杂度就会退化至O(n^2),这个其实很好理解,举个例子就明白了:

最优情况下,即每趟选择key时都恰好选择到数组的中间值时(第n层可以确定2^{n-1}个数字位置),快排的时间复杂度如下图完全(满)二叉树:

该树每层需要遍历一遍数组,时间复杂度为n,而树高为h=log_2n,因此最优状态下快排的时间复杂度仅为O(nlogn).

最坏情况下,即每趟选择key时都恰好选择到数组最大或最小的值时(即每一层都只能确定一个数字位置),快排的时间复杂度如下单支树:

树每层遍历一遍数组,时间复杂度为n,而树高也为n,因此最坏状态下快排的时间复杂度为O(n^2).

综上,对快排时间复杂度的分析,我们不光理解了为什么快排排先天有序的数组时反而效率最差,同样也为我们后续对快排算法的优化提供了思路.


四.快速排序的优化

🎏优化选key方式

既然在快排在面对原本就接近有序的数组时排序会因为key值的选取导致效率降低,那么我们不妨优化一下我们快排时选key的方式,下面为大家介绍两种常用的优化选key的方式:

📌随机选key法

随机选key的思路为:

  1. 先使用rand()函数随机选出一个在[left,right]范围内的下标值randi
  2. 将randi下标的数据和keyi下标的数据互换

随机选key函数的实现:

//随机选key法
void SwapRand_key(int* a,int left, int right)
{int randi = left + (rand() % (right - left));Swap(&a[left], &a[randi]);
}

结合随机选key法实现快排

我们写好随机选key函数后只需要在正常快排函数中选定keyi后(如下函数的第15行后)调用一下随机选keyi函数就可以将随机选出的key值和原本的key值做交换了.

实现代码如下:

//随机选key法
void SwapRand_key(int* a,int left, int right)
{int randi = left + (rand() % (right - left));Swap(&a[left], &a[randi]);
}//快速排序(前后指针法
void QuickSort_follow(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;int keyi = left;    //选定序列首元素为基准值,记录下基准值SwapRand_key(a,left, right);  //随机选出一个数据交换基准值keyint prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[keyi]);keyi = prev;QuickSort_follow(a, begin, keyi - 1);//递归遍历左区间[begin , keyi-1]QuickSort_follow(a, keyi + 1, end);//递归遍历右区间[keyi+1 , end]
}

📌三数取中法

三数取中法的思路是:

  1. 比较序列首元素,尾元素,中间元素,取三者中的中间值作为midi
  2. 将midi下标的数据和keyi下标的数据互换

三数取中函数的实现:

//三数取中法
void SwapMid_key(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){midi = midi;}else if (a[left] > a[right]){midi = left;}else {midi = right;}}else // a[left] > a[mid]{if (a[midi] > a[right]){midi = midi;}else if (a[left] < a[right]){midi = left;}else{midi = right;}}if (midi != left)Swap(&a[midi], &a[left]);
}

结合三数取中法实现快排

我们写好三数取中函数后只需要在正常快排函数中选定keyi后(如下函数的第45行后)调用一下三数取中函数就可以将三数取中选出的key值和原本的key值做交换了.

实现代码如下:

//三数取中函数
void SwapMid_key(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){midi = midi;}else if (a[left] > a[right]){midi = left;}else {midi = right;}}else // a[left] > a[mid]{if (a[midi] > a[right]){midi = midi;}else if (a[left] < a[right]){midi = left;}else{midi = right;}}if (midi != left)Swap(&a[midi], &a[left]);
}//快速排序(前后指针法
void QuickSort_follow(int* a, int left, int right)
{if (left >= right)return;int begin = left, end = right;int keyi = left;    //选定序列首元素为基准值,记录下基准值SwapMid_key(a, left, right);   //三数取中法选出基准值后交换基准值int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[keyi]);keyi = prev;QuickSort_follow(a, begin, keyi - 1);//递归遍历左区间[begin , keyi-1]QuickSort_follow(a, keyi + 1, end);//递归遍历右区间[keyi+1 , end]
}

🎏小区间优化

📌小区间优化的原理

快排的递归展开思路类似于二叉树,因此它们拥有同样的弊病,就是越靠近树的底部,空递归的情况就越多,并且空递归的规模量非常大,拿下面这颗树来举例:

我们递归遍历该树,发现空递归(紫色)访问次数竟然和总有效访问次数(绿色)是相同.而对于快排来说,这样的空递归不仅浪费时间,而且是没有任何实际意义的.

因此我们可以考虑采用一种办法,将快排的递归范围加以限制,比如当我们不断分割快排子区间,当子区间数组元素小于10个数时,我们就不再进行快排递归排序,而使用直接插入排序来对该小区间进行排序,这样就可以有效的消灭超过一半的递归,从而提升快排的效率.


📌小区间优化的代码实现

清楚了上面的原理之后,我们实现小区间优化的思路为:

  1. 判断小区间数组是否小于10个数.
  2. 如果区间不小于10,则执行快排逻辑.
  3. 如果区间小于等于10,则执行直接插入排序逻辑.

综上所述,小区间代码实现如下:

//交换函数
void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//三数取中法取key函数
void SwapMid_key(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){midi = midi;}else if (a[left] > a[right]){midi = left;}else {midi = right;}}else // a[left] > a[mid]{if (a[midi] > a[right]){midi = midi;}else if (a[left] < a[right]){midi = left;}else{midi = right;}}if (midi != left)Swap(&a[midi], &a[left]);
}//直接插入排序函数
void InsertSort(int* a, int n)
{for (int i = 1; i < n; i++){int end = i - 1;int tmp = a[i];//将tmp插入到[0,end]这个区间里while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}//小区间优化版快排
void QuickSort(int* a, int left, int right)
{if (left >= right)return;if ((right - left + 1 ) > 10){int keyi = left;SwapMid_key(a, left, right);//双指针前后移动法快排int prev = left;int cur = left + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[cur], &a[prev]);++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}else{InsertSort( a + left , right - left + 1 );}
}

五.借助栈实现非递归快速排序

📌为什么要将递归的快速排序算法改为非递归?

递归函数有以下几个缺点:

  • 内存消耗大:递归调用会占用大量的内存空间,因为每次调用都需要在内存中保存当前的状态和参数。

  • 性能低下:递归调用会增加函数调用的开销,因此在一些情况下会导致程序的性能下降。

  • 可读性差:递归函数通常比较复杂,难以理解和调试,降低了代码的可读性。

  • 可能导致栈溢出:如果递归调用层次过深,会导致栈溢出的问题,使程序崩溃

  • 难以优化:一些编译器和优化工具难以对递归函数进行有效的优化,导致性能不佳。


📌递归函数改非递归的思路

  1. 直接改为循环.(如:斐波那契数列)
  2. 利用栈辅助改为循环.(如:二叉树的前序遍历)

📌快速排序改非递归的思路

  1. 将初始数组区间压入栈
  2. 在栈里取一段区间,单趟排序
  3. 单趟分割子区间入栈
  4. 子区间只有一个值或着不存在就不入栈
  5. 重复步骤2-4,直到栈为空,则排序完成.

📌快速排序改非递归的代码实现

因为快排改非递归时要借助栈结构,因此我先将栈相关定义的头文件贴在这里,具体栈的C语言完整实现可以移步我的另一篇博客,在文末有数据结构栈实现的完整代码,大家可以直接粘贴过来使用:

【数据结构】C语言实现顺序栈(附完整运行代码)icon-default.png?t=N7T8http://t.csdnimg.cn/FL0V3(注:如果本身没有自己实现数据结构栈的工程文件的,一定要将该博客末尾的Stack.h文件和Stack.c文件粘贴在排序项目文件里才可以正常使用栈的相关功能,否则C语言是不支持直接使用的!)

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int STDataType;typedef struct Stack
{STDataType*arr;int top;int capacity;
}ST;void STInit(ST* ps);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
STDataType STTop(ST* ps);
void STMenu();

综上,快排的非递归代码实现如下:

//交换函数
void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}//三数取中法取key函数
void SwapMid_key(int* a, int left, int right)
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){midi = midi;}else if (a[left] > a[right]){midi = left;}else {midi = right;}}else // a[left] > a[mid]{if (a[midi] > a[right]){midi = midi;}else if (a[left] < a[right]){midi = left;}else{midi = right;}}if (midi != left)Swap(&a[midi], &a[left]);
}//快速排序主函数(非递归版
void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);//先入后出STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int keyi = begin;SwapMid_key(a, begin, end);//双指针前后移动法快排int prev = begin;int cur = begin + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[cur], &a[prev]);++cur;}Swap(&a[prev], &a[keyi]);keyi = prev;if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}STDestroy(&st);
}

六.快速排序的三路划分

//主要解决快速排序面对大量重复数据时效率低下的问题

//该部分内容待补


结语

希望这篇快速排序算法详解能对大家有所帮助,欢迎大佬们留言或私信与我交流.

有关更多排序相关知识可以移步:

【数据结构】八大排序算法​​icon-default.png?t=N7T8https://blog.csdn.net/weixin_72357342/article/details/135038495?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22135038495%22%2C%22source%22%3A%22weixin_72357342%22%7D&fromshare=blogdetail

学海漫浩浩,我亦苦作舟!关注我,大家一起学习,一起进步!

相关文章推荐

【数据结构】八大排序之冒泡排序算法

【数据结构】八大排序之希尔排序算法

【数据结构】八大排序之直接插入排序算法

【数据结构】八大排序之简单选择排序

【数据结构】八大排序之堆排序算法


 数据结构排序篇思维导图:


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

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

相关文章

ssm基于HTML5的交流论坛的设计与实现+vue论文

摘 要 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上自古…

【Java EE初阶九】多线程进阶一(锁策略)

前言 锁的策略&#xff1a;加锁过程中&#xff0c;即处理冲突的过程中&#xff0c;需要涉及到的一些不同的处理方式&#xff08;此处锁策略并不是java独有的&#xff09;&#xff0c;本篇内容主要是讲解一下关于锁的相关知识点&#xff1b; 1. 关于锁的分组 1.1 第一组&#x…

动态内存管理:malloc free

//——————1.动态内存管理&#xff08;内存空间&#xff09; 共四个函数&#xff1a;malloc free calloc realloc 1. 为什么存在动态内存分配 我们已经掌握的内存开辟方式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] { 0 };//在栈空间上开…

一、Qt介绍

一、Qt介绍 1、介绍 Qt是一套程序开发库&#xff0c;但是与MFC&#xff08;依赖于Windows API&#xff09;不同&#xff0c;Qt是跨平台开发库。 Qt获取&#xff1a;[Qt下载地址](https://download.qt.io/archive/qt/)2、Qt安装 QtMinGWSourcesQt ChartsQt Data Visualizatio…

如何在Android Glide中结合使用CenterCrop和自定义圆角变换(图片部分圆角矩形)

如何在Android Glide中结合使用CenterCrop和自定义圆角变换&#xff08;图片部分圆角矩形&#xff09; 在Android开发中&#xff0c;使用Glide加载图片时&#xff0c;我们经常需要对图片进行特定的处理&#xff0c;比如裁剪和圆角变换&#xff0c;特别是一些设计稿&#xff0c;…

cpufreq子系统

cpufreq是linux上负责实现动态调频的关键&#xff0c;这篇笔记总结了linux内核cpufreq子系统的关键实现&#xff08;Linux 3.18.140&#xff09;。 概述 借用一张网络上的图片来看cpufreq子系统的整体结构&#xff1a; 用户态接口&#xff1a;cpufreq通过sysfs向用户态暴露接…

Java知识点:泛型、类加载器、内部类、可变参数

文章目录 1、this关键字2、泛型2.1 泛型介绍2.2 泛型分类定义2.2.1 泛型类2.2.2 泛型方法2.2.3 泛型接口 2.3 泛型通配符 3、可变参数4、日志4.1 使用步骤4.2 日志级别4.3 配置文件 5、类加载器5.1 类加载器5.2 类加载的完整过程5.2.1 类加载时机5.2.2 类加载过程 5.3 类加载的…

【开源项目】超经典开源项目实景三维数字孪生智慧工厂

数字孪生工厂&#xff0c;以模型驱动的自动化&#xff0c;与数据驱动的人工智能技术紧密融合与协同&#xff0c;实现机器、工件与组件间全面的和点对点的数据通信。飞渡科技基于自研DTS平台&#xff0c;将物联网IOT、人工智能、大数据、云计算等技术应用于工厂&#xff0c;实现…

期货日数据维护与使用_日数据维护_模块运行演示

写在前面&#xff1a; 本文默认已经创建了项目&#xff0c;如果不知道如何创建一个空项目的&#xff0c;请参看以下两篇博文 PyQt5将项目搬到一个新的虚拟环境中 https://blog.csdn.net/m0_37967652/article/details/122625280 python_PyQt5开发工具结构基础 https://blog.cs…

TMC4671闭环调试步进、伺服、音圈、永磁、无刷电机

一、IDE 连接开发板 下 面 讲 解 IDE 和 开 发 板 连 接 的 详 细 操 作 。 这 里 我 们 选 择 用 主 控 板 TMC671-EVALTMC6200-EVAL 开发板做讲解。其它型号的开发板也是大同小异 的操作步骤。 1&#xff0e;首先我连接好开发板&#xff0c;并给开发板上电 连接好的开发板如下…

【激活函数】SELU 激活函数

1、介绍 SELU (Scaled Exponential Linear Unit) SELU是对ELU激活函数的改进&#xff0c;通过引入自动标准化机制&#xff0c;使得神经网络的隐藏层在训练过程中可以自动地保持输出的均值和方差接近于1。 # 定义 SELU 激活函数 def selu(x, alpha1.67326, lambda_1.0507):retu…

【Python百宝箱】数据清洗艺术:Python库助力打磨完美数据

数据清洗与预处理&#xff1a;Python库大揭秘 前言 在数据科学领域&#xff0c;数据清洗和预处理是构建可靠模型的关键步骤。本文深入探讨了一系列强大的Python库&#xff0c;它们在处理重复数据、字符串匹配、数据整理以及降维等方面发挥着重要作用。通过学习这些库&#xf…

单片机相关知识点

在STM32上运行FreeRTOS&#xff0c;十分简练的小文章FreeRTOS&#xff08;STM32CubeMX&#xff09;_cubemx freertos-CSDN博客 STM32CubeMX转Keil使用STM32CubeMX生成Keil工程并完成流水灯-CSDN博客

unity PDFRender Curved UI3.3

【PDF】PDFRender 链接&#xff1a;https://pan.baidu.com/s/1wSlmfiWTAHZKqEESxuMH6Q 提取码&#xff1a;csdn 【曲面ui】 Curved UI3.3 链接&#xff1a;https://pan.baidu.com/s/1uNZySJTW0-pPwi2FTE6fgA 提取码&#xff1a;csdn

【unity小技巧】FPS游戏实现相机的震动、后坐力和偏移

最终效果 文章目录 最终效果前言相机的震动实现后坐力和偏移相机震动相机震动脚本换弹节点震动 武器射击后退效果完结 前言 关于后坐力之前其实已经分享了一个&#xff1a;FPS游戏后坐力制作思路 但是实现起来比较复杂&#xff0c;如果你只是想要简单的实现&#xff0c;可以看…

Linux ssh 实现远程免密登录

一、背景 我搭建了一个 zookeeper 集群&#xff0c;写了一个 shell 脚本来控制集群的启动和关闭&#xff0c;但是我发现每次我执行 shell 脚本的时候&#xff0c;都需要我输入各个服务器的密码才可以运行&#xff0c;感觉很麻烦。shell 脚本里面连接其他服务器用的就是 ssh 的方…

Linux安装JDK和Maven并配置环境变量

文章目录 一、安装JDK并配置环境变量二、安装maven并配置环境变量 一、安装JDK并配置环境变量 将JDK的安装包上传到Linux系统的usr/local目录 使用xftp上传文件 解压JDK的压缩包 xshell连接到云主机 [roottheo ~]# cd /usr/local[roottheo local]# ls aegis apache-tomcat-…

equals()方法和“==”运算符

equals()equals()方法和“”运算符比较 回到顶部 equals() 超类Object中有这个equals()方法&#xff0c;该方法主要用于比较两个对象是否相等。该方法的源码如下&#xff1a; public boolean equals(Object obj) {return (this obj);} 我们知道所有的对象都拥有标识(内存…

使用LVM分区方式安装Manjaro发行版

使用LVM分区方式安装Manjaro发行版 为什么单独介绍LVM方式呢&#xff1f; 主要是由于使用系统的图形安装工具创建卷组会出问题&#xff0c;会导致图形安装工具直接挂掉&#xff0c;唯一的方法是提前手动创建好卷组。 GPT分区表 分区表有&#xff1a; MBR(主引导记录分区表)…

【华为OD真题 Python】两数之和绝对值最小

文章目录 题目描述输入描述输出描述示例1输入输出说明代码实现题目描述 给定一个从小到大的有序整数序列(存在正整数和负整数)数组 nums ,请你在该数组中找出两个数,其和的绝对值(|nums[x]+nums[y]|)为最小值,并返回这个绝对值。 每种输入只会对应一个答案。但是,数组中…