七、排序算法
1. 排序算法介绍
排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。
排序的分类
- 内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序。
2. 外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储(文件)进行排序。
3. 常见的排序算法分类(见左图)
2. 算法的时间复杂度
1)计算复杂度
-
事后统计法
这种方法可行,但是有两个问题:
一是要想对设计的算法的运行性能进行评测,需要实际运行该程序;
二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,这种方式,要在同一台计算机的相同状态下运行,才能比较那个算法速度更快。
-
事前估算的方法
通过分析某个算法的时间复杂度来判断哪个算法更优
2)时间频度
一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)
。
基本案例
忽略常数项
忽略低频次数
忽略系数
3)时间复杂度计算
-
一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用n表示,
若有某个辅助函数f(n),使得当n趋近于无穷大时,
T(n) / f(n)
的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=o1( f(n) ),称
O( f(n))
为算法的渐进时间复杂度,简称时间复杂度。
1. T(n)不同,但时间复杂度可能相同。如: T(n)=n2+7n+6与T(n)=3n2-+2n+2它们的T(n)不同,但时间复杂度相同,都为O(n2)。
1. 计算时间复杂度的方法:- 常数1代替运行时间中的所有加法常数 T(n)= n 2 n^2 n2 + 7n +6 —>T(n)= n 2 n^2 n2 + 7n +1
- 修改后的运行次数函数中,只保留最高阶项 T(n)= n 2 n^2 n2+7n+1 —> T(n)=n 2 ^2 2
- 去除最高阶项的系数 T(n)= n 2 n^2 n2 —> T(n)= n 2 n^2 n2 —> O( n 2 n^2 n2)
常见的时间复杂度
- 常数阶 O(1)
2. 对数阶 O( l o g 2 log_2 log2 n n n)
3. 线性阶 O(n)
4. 线性对数阶O( n l o g 2 n nlog_2n nlog2n)
5. 平方阶 O( n 2 n^2 n2)
6. 立方阶 O( n 3 n^3 n3)
7. k次方阶 O( n k n^k nk)
8. 指数阶 O( 2 n 2^n 2n)
说明:
常见的算法时间复杂度由小到大依次为:o(1)<o(logzn)<o(n)<o( n l o g 2 n nlog_2n nlog2n)<o( n 2 n^2 n2)<o( n 3 n^3 n3)<o( n k n^k nk)<o( 2 n 2^n 2n),随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低
从图中可见,我们应该尽可能避免使用指数阶的算法
常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,这个代码的时间复杂度就是 O(1)
int i = 1;
int j = 2;
++i;
++j;
int m = i + j;
上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那幺无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时同复杂度。
对数阶 O( l o g 2 n log_2n log2n)
int i = 1;
while(i<n){
i= i*2
}
说明:
在while循环里面,每次都将i乘以2,乘完之后,i距离n就越来越近了。假设循环x次之后,i就大于2了,此时这个循环就退出了,也就是说2的x次方等于n,那么x= l o g 2 n log_2n log2n
也就是说当循环 l o g 2 n log_2n log2n次以后,这个代码就结束了。
因此这个代码的时间复杂度为O( l o g 2 n log_2n log2n)。O( l o g 2 n log_2n log2n)的这个2时间上是根据代码变化的,i=i*3,则是O( l o g 3 n log_3n log3n).
线性阶O(n)
for(m = 1; m < n; m++){
j = 1;
j ++ ;
}
说明:
for循环里面的代码会执行 n 遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度
线性对数阶 O(nlogN)
for(m = 1; m < n; m++){
i = 1;
while (i<n){
i = i * 2;
}
}
说明:
线性对数阶o(nlogN)其实非常容易理解,将时间复杂度为o(logr的代码循环N遍的话,
那么它的时间复杂度就是n * o(logN),也就是了o(nlogN)
平方阶O( n 2 n^2 n2)
for(x = 1; i <= 0; x++){
for(i = 1; i<= n; i++){
j = i;
j++;
}
}
说明:平方阶o(n)就更容易理解了,如果把o(n)的代码再嵌套循环一遍,它的时间复杂度就是o(n2),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是O(nn),
即o( ∗ n 2 ∗ *n^2* ∗n2∗)如果将其中一层循环的n改成m,那它的时间复杂度就变成了o(mn)
4)平均时间复杂度和最坏时间复杂度
-
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
-
最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。
这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
-
平均时间复杂度和最坏时间复杂度是否一致,和算法有关
3. 冒泡排序
1)介绍
冒泡排序(Bubble Sorting)的基本思想是:
通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大
的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,
因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。
2)图解
3)举例图解
4)代码实现
时间复杂度 O( n 2 n^2 n2)
int num = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j +1]){
num = arr[j];
arr[j] = arr[j+1];
arr[j+1] = num;
}
System.out.println(Arrays.toString(arr));
}
}
优化1:加 flag
int[] arr = {
3, 9, -1, 10, 20};
int num = 0;
// 表示是否进行过交换
boolean flag = false;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
num = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = num;
flag = true;
}
}
if (!flag) {
// 没有交换
break;
} else {
flag = false;
}
System.out.println(Arrays.toString(arr));
}
4. 选择排序
1)介绍
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置达到排序的目的。
2)思想
3)图解
4)举例图解
5)代码实现
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
// 从 i + 1 开始,前面的排序好了
if(min > arr[j]){
min = arr[j]; // 重置 min
minIndex = j; // 重置 minIndex
}
}
// 交换
if(minIndex != i){
// 发生了交换
arr[minIndex] = arr[i];
arr[i] = min;
}
}
6. 希尔排序
1)简单插入排序存在的问题
2)介绍
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
3)思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
随着增量逐渐减少,每组包含的关键询越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
4)图解
5)代码实现
交换插入
/*
* @Description //TODO 希尔排序 交换插入
* @Param [arr]
* @return int[]
**/
public static int[] shellSortSwitch(int[] arr) {
// 第一轮
// 1. 初始增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 2. 分组
for (int j = i - gap; j >= 0; j -= gap) {
// 3. 交换元素
if (arr[j] > arr[j + gap]) {
int tem = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = tem;
}
}
}
}
return arr;
}
移动插入
控制了一部分不必要的循环
/*
* @Description //TODO 希尔排序 移动插入
* @Param [arr]
* @return int[]
**/
public static int[] shellSortMove(int[] arr) {
// 第一轮
// 1. 初始增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 1. 从 第 gap 个元素,逐个对其所在的组直接进行插入排序
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
// 移动
arr[j] = arr[j - gap];
j -= gap;
}
// 退出循环,找到位置
arr[j] = temp;
}
}
}
return arr;
}
7. 快速排序
1)介绍
快速排序(Quicksort〉是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,
其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
2)图解
3)代码实现
/*
* @Description //TODO 快速排序
* @Param [arr, l, r]
* @return void
**/
public static void quickSort(int[] arr, int l, int r) {
int left = l;
int right = r;
// 中间值
int pivot = arr[(left + right) / 2];
int temp = 0;
// 让 比 pivot 值小的放到 左边 , 大的放到 右边
while (left < right) {
// 剩一个不循环
// 左边一直找,
while (arr[left] < pivot) {
// 最左边值 小于 中间值
left += 1;
}
// 右边一直找
while (arr[right] > pivot) {
right -= 1;
}
// 说明 左右两边的值 已经 按照 左边全部是 < pivot的,右边都是 >= pivot的
if (left >= right) {
break;
}
// 交换
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
// 如果交换完毕,发现 arr[left] == pivot值, right-- 前移一步
if (arr[left] == pivot) {
System.out.println(pivot);
right -= 1;
}
// 如果交换完毕,发现 arr[right] == pivot值, left++ 前移一步
if (arr[right] == pivot) {
System.out.println(pivot);
left += 1;
}
}
// 如果 left == right 必须 left ++ ,right--,否则视为栈溢出
if (left == right) {
left += 1;
right -= 1;
}
// 向左递归
if (left < r) {
quickSort(arr, l, right);
}
// 向右递归
if (r > left) {
quickSort(arr, left, r);
}
}
七、排序算法
1. 排序算法介绍
排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。
排序的分类
1. 内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序。
2. 外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储(文件)进行排序。
3. 常见的排序算法分类(见左图)
![](https://secure2.wostatic.cn/static/m5FnnHGvctATehhpXuzj8K/image.png)
2. 算法的时间复杂度
1)计算复杂度
-
事后统计法
这种方法可行,但是有两个问题:
一是要想对设计的算法的运行性能进行评测,需要实际运行该程序;
二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,这种方式,要在同一台计算机的相同状态下运行,才能比较那个算法速度更快。
-
事前估算的方法
通过分析某个算法的时间复杂度来判断哪个算法更优
2)时间频度
一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。
一个算法中的语句执行次数称为语句频度或时间频度。记为`T(n)`。
#### 基本案例
![](https://secure2.wostatic.cn/static/7PGGCYmAdCC12DSJcbeB6k/image.png)
#### 忽略常数项
![](https://secure2.wostatic.cn/static/ckK19LLjA31pAuKSKRaA5K/image.png)
#### 忽略低频次数
![](https://secure2.wostatic.cn/static/22wexPbe5EAzeZSGKEBAcP/image.png)
#### 忽略系数
![](https://secure2.wostatic.cn/static/c3tc61gnLQoXR73zhwJAkx/image.png)
3)时间复杂度计算
1. 一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用n表示,
若有某个辅助函数f(n),使得当n趋近于无穷大时,`T(n) / f(n)`的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。
记作T(n)=o1( f(n) ),称`O( f(n))`为算法的渐进时间复杂度,简称时间复杂度。
1. T(n)不同,但时间复杂度可能相同。如: T(n)=n2+7n+6与T(n)=3n2-+2n+2它
们的T(n)不同,但时间复杂度相同,都为O(n2)。
1. 计算时间复杂度的方法:
- 常数1代替运行时间中的所有加法常数 T(n)=$n^2$ + 7n +6 —>T(n)=$n^2$ + 7n +1
- 修改后的运行次数函数中,只保留最高阶项 T(n)=$n^2$+7n+1 —> T(n)=n$^2$
- 去除最高阶项的系数 T(n)=$n^2$ —> T(n)=$n^2$ —> O($n^2$)
#### 常见的时间复杂度
1. 常数阶 O(1)
2. 对数阶 O($log_2$$n$)
3. 线性阶 O(n)
4. 线性对数阶O($nlog_2n$)
5. 平方阶 O($n^2$)
6. 立方阶 O($n^3$)
7. k次方阶 O($n^k$)
8. 指数阶 O($2^n$)
说明:
常见的算法时间复杂度由小到大依次为:o(1)<o(logzn)<o(n)<o( n l o g 2 n nlog_2n nlog2n)<o( n 2 n^2 n2)<o( n 3 n^3 n3)<o( n k n^k nk)<o( 2 n 2^n 2n),随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低
从图中可见,我们应该尽可能避免使用指数阶的算法
![](https://secure2.wostatic.cn/static/x2Tz8Sbcd7z1noq9dSxxf3/image.png)
#### 常数阶O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,这个代码的时间复杂度就是 O(1)
![](https://secure2.wostatic.cn/static/oXwP427b43ZbscyvXoyPMG/image.png)
上述代码在执行的时候,它消耗的时候并不随着某个变量的增长而增长,那幺无论这类代码有多长,即使有几万几十万行,都可以用O(1)来表示它的时同复杂度。
#### 对数阶 O($log_2n$)
int i = 1;
while(i<n){
i= i*2
}
说明:
在while循环里面,每次都将i乘以2,乘完之后,i距离n就越来越近了。假设循环x次之后,i就大于2了,此时这个循环就退出了,也就是说2的x次方等于n,那么x=$log_2n$
也就是说当循环$log_2n$次以后,这个代码就结束了。
因此这个代码的时间复杂度为O($log_2n$)。O($log_2n$)的这个2时间上是根据代码变化的,i=i*3,则是O($log_3n$).
#### 线性阶O(n)
for(m = 1; m < n; m++){
j = 1;
j ++ ;
}
说明:
for循环里面的代码会执行 n 遍,因此它消耗的时间是随着n的变化而变化的,因此这类代码都可以用O(n)来表示它的时间复杂度
#### 线性对数阶 O(nlogN)
for(m = 1; m < n; m++){
i = 1;
while (i<n){
i = i * 2;
}
}
说明:
线性对数阶o(nlogN)其实非常容易理解,将时间复杂度为o(logr的代码循环N遍的话,
那么它的时间复杂度就是n * o(logN),也就是了o(nlogN)
#### 平方阶O($n^2$)
for(x = 1; i <= 0; x++){
for(i = 1; i<= n; i++){
j = i;
j++;
}
}
说明:平方阶o(n)就更容易理解了,如果把o(n)的代码再嵌套循环一遍,它的时间复杂度就是o(n2),这段代码其实就是嵌套了2层n循环,它的时间复杂度就是O(n**n),
即o( ∗ n 2 ∗ *n^2* ∗n2∗)如果将其中一层循环的n改成m,那它的时间复杂度就变成了o(m**n)
4)平均时间复杂度和最坏时间复杂度
-
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
-
最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。
这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
-
平均时间复杂度和最坏时间复杂度是否一致,和算法有关
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUkpHFjh-1650541283247)(https://secure2.wostatic.cn/static/sJCfgnmv48x4zEhGPY5BSU/image.png)]
3. 冒泡排序
1)介绍
冒泡排序(Bubble Sorting)的基本思想是:
通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大
的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,
因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。
2)图解
![](https://secure2.wostatic.cn/static/qncpH1ns24zFEQ34sKxRjv/image.png)
3)举例图解
![](https://secure2.wostatic.cn/static/tyZ99XUeCoJW15s9SrEvqn/image.png)
4)代码实现
时间复杂度 O($n^2$)
int num = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if(arr[j] > arr[j +1]){
num = arr[j];
arr[j] = arr[j+1];
arr[j+1] = num;
}
System.out.println(Arrays.toString(arr));
}
}
优化1:加 flag
int[] arr = {3, 9, -1, 10, 20};
int num = 0;
// 表示是否进行过交换
boolean flag = false;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
num = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = num;
flag = true;
}
}
if (!flag) {
// 没有交换
break;
} else {
flag = false;
}
System.out.println(Arrays.toString(arr));
}
4. 选择排序
1)介绍
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置达到排序的目的。
2)思想
![](https://secure2.wostatic.cn/static/gqxHvY7MwJT7Nwp5xYprmC/image.png)
3)图解
![](https://secure2.wostatic.cn/static/cyJbpHsLxYuL8gShaLGaq8/image.png)
4)举例图解
![](https://secure2.wostatic.cn/static/4iCKeLhmUog9VVzNFtMPNx/image.png)
5)代码实现
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) { // 从 i + 1 开始,前面的排序好了
if(min > arr[j]){
min = arr[j]; // 重置 min
minIndex = j; // 重置 minIndex
}
}
// 交换
if(minIndex != i){ // 发生了交换
arr[minIndex] = arr[i];
arr[i] = min;
}
}
5. 插入排序
1)介绍
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
2)思想
插入排序(Insertion Sorting)的基本思想是:
把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,
排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
3)图解
![](https://secure2.wostatic.cn/static/kAKKDoNs48KdYwquMuh4wQ/image.png)
4)代码实现
int insertVal = 0;
int insertIndex = 0;
// 第一论 {9,3 -1, 10, 20} => {3,9 -1, 10, 20}
for (int i = 1; i < arr.length; i++) {
// 定义待插入数
insertVal = arr[i];
insertIndex = i - 1; // 即 arr[1] 前一个数的下标
// 给insertVal找到一个插入的位置
// 1. insertIndex1 >=0 保证在给 insertVal 找插入位置,不越界
// 2. insertVal < arr[insertIndex] 待插入的数,没找到插入位置
// 3. 就需要将 insertIndex 后移
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
if(insertIndex + 1 != i){
break;
}
arr[insertIndex + 1] = insertVal;
}
6. 希尔排序
1)简单插入排序存在的问题
![](https://secure2.wostatic.cn/static/vnxfzp5h3Bn3Zr1kga7Hwf/image.png)
2)介绍
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
3)思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
随着增量逐渐减少,每组包含的关键询越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
4)图解
![](https://secure2.wostatic.cn/static/88kVUrS3c46GFJq5WUJbcg/image.png)
![](https://secure2.wostatic.cn/static/k6pPg38kGBBpLXUAtENb2E/image.png)
5)代码实现
> 交换插入
/*
* @Description //TODO 希尔排序 交换插入
* @Param [arr]
* @return int[]
**/
public static int[] shellSortSwitch(int[] arr) {
// 第一轮
// 1. 初始增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 2. 分组
for (int j = i - gap; j >= 0; j -= gap) {
// 3. 交换元素
if (arr[j] > arr[j + gap]) {
int tem = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = tem;
}
}
}
}
return arr;
}
> 移动插入
控制了一部分不必要的循环
/*
* @Description //TODO 希尔排序 移动插入
* @Param [arr]
* @return int[]
**/
public static int[] shellSortMove(int[] arr) {
// 第一轮
// 1. 初始增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 1. 从 第 gap 个元素,逐个对其所在的组直接进行插入排序
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
// 移动
arr[j] = arr[j - gap];
j -= gap;
}
// 退出循环,找到位置
arr[j] = temp;
}
}
}
return arr;
}
7. 快速排序
1)介绍
快速排序(Quicksort〉是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,
其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
2)图解
![](https://secure2.wostatic.cn/static/gHLzQwQhapKEyNSpjStmU9/image.png)
3)代码实现
/*
* @Description //TODO 快速排序
* @Param [arr, l, r]
* @return void
**/
public static void quickSort(int[] arr, int l, int r) {
int left = l;
int right = r;
// 中间值
int pivot = arr[(left + right) / 2];
int temp = 0;
// 让 比 pivot 值小的放到 左边 , 大的放到 右边
while (left < right) { // 剩一个不循环
// 左边一直找,
while (arr[left] < pivot) { // 最左边值 小于 中间值
left += 1;
}
// 右边一直找
while (arr[right] > pivot) {
right -= 1;
}
// 说明 左右两边的值 已经 按照 左边全部是 < pivot的,右边都是 >= pivot的
if (left >= right) {
break;
}
// 交换
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
// 如果交换完毕,发现 arr[left] == pivot值, right-- 前移一步
if (arr[left] == pivot) {
System.out.println(pivot);
right -= 1;
}
// 如果交换完毕,发现 arr[right] == pivot值, left++ 前移一步
if (arr[right] == pivot) {
System.out.println(pivot);
left += 1;
}
}
// 如果 left == right 必须 left ++ ,right--,否则视为栈溢出
if (left == right) {
left += 1;
right -= 1;
}
// 向左递归
if (left < r) {
quickSort(arr, l, right);
}
// 向右递归
if (r > left) {
quickSort(arr, left, r);
}
}
8. 归并排序
1)介绍
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer〉策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
2)思想
在这里插入图片描述
3)代码
- 代码
/*
* @Description //TODO 归并排序
* @Param [arr]
* @return void
**/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left; // 初始化 I, 左边有序序列的初始索引
int j = mid + 1; // 初始化 j , 右边有序序列的初始索引
int t = 0; // 临时数组的初始索引
// 1. 先把左右两边的数据按照规则填充到 temp 数组
// 直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {
// 不越界
// left <= right 添加到 temp
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
t++;
i++;
} else {
temp[t] = arr[j];
t++;
j++;
}
}
// 2. 把有剩余数据的一遍的数据依次全部填充到 temp
while (i <= mid) {
// 左边有剩
temp[t] = arr[i];
t++;
i++;
}
while (j <= right) {
// 右边有剩
temp[t] = arr[j];
t++;
j++;
}
// 3. 将 temp 的元素拷贝到 arr
t = 0;
int tempLeft = left;
while (tempLeft <=right) {
arr[tempLeft] = temp[t];
tempLeft++;
t++;
}
}
public static void mergeSort(int[] arr,int left,int right,int[] temp){
if(left < right){
int mid = (left + right) / 2;
//向左递归进行分解
mergeSort(arr,left,mid,temp);
// 向右递归
mergeSort(arr,mid+1,right,temp);
// 合并
merge(arr,left,mid,right,temp);
}
}