排序算法(一)—— 选择排序、冒泡排序(python,java版)
排序是数据处理中十分常见且核心的操作,虽说实际项目开发中很小几率会需要我们手动实现,毕竟每种语言的类库中都有n多种关于排序算法的实现。但是了解这些精妙的思想对我们还是大有裨益的。本文简单温习下最基础的两类算法:选择,冒泡
将 选择排序与冒泡排序放在一起总结是因为二者在某种程度上具有相似性。笔者认为:选择排序与冒泡排序其核心结果都是将子数组中的最大或最小元素提出,依次循环达到目的。
注: 冒泡排序在总结时,较大程度上引用了 https://blog.csdn.net/lu_1079776757/article/details/80459370博客,笔者认为此篇博客对冒泡排序的总结十分不错,有兴趣的话,推荐阅读一下。
选择排序
一、算法思想
选择排序,从头至尾扫描无序序列,找出最小的一个元素,和无序序列中的第一个元素交换(注意,这里进行交换后就产生了有序序列),此时无序序列中的的第一个元素相当于有序序列的最后一个元素;接着从剩下的无序序列中继续这种选择和交换方式,最终得到一个有序序列。
每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。
作者理解:
为了节省空间,一般使用交换方式进行,但采用创建和更新数组的方式同样可以实现(不推荐此方法)。
二、算法过程
举例:
原始序列:49、38、65、97、76、13、27、49
1)在进行选择排序过程中分成有序和无序两个部分,开始都是无序序列
结果:49、38、65、97、76、13、27、49
2)从无序序列中取出最小的元素13,将13同无序序列第一个元素交换,此时产生仅含一个元素的有序序列,无序序列减一
结果:{13、} {38、65、97、76、49、27、49}
3)从无序序列中取出最小的元素27,将27同无序序列第一个元素交换,此时产生仅两个元素的有序序列,无序序列减一
结果:{13、27、} {65、97、76、49、38、49}
4)从无序序列中取出最小的元素38,将38同无序序列第一个元素交换,此时产生含三个元素的有序序列,无序序列减一
结果:{13、27、38、} {97、76、49、65、49}
5)从无序序列中取出最小的元素49,将49同无序序列第一个元素交换,此时产生含四个个元素的有序序列,无序序列减一
结果:{13、27、38、49、} {76、97、65、49}
6)从无序序列中取出最小的元素49,将49同无序序列第一个元素交换,此时产生含五个元素的有序序列,无序序列减一
结果:{13、27、38、49、49、} {97、65、76}
7)从无序序列中取出最小的元素65,将65同无序序列第一个元素交换,此时产生含六个元素的有序序列,无序序列减一
结果:{13、27、38、49、49、65} {97、76}
8)从无序序列中取出最小的元素76,将76同无序序列第一个元素交换,此时产生含七个元素的有序序列,无序序列减一
结果:{13、27、38、49、49、65、76、} {97}
9)最后一个元素肯定是最大元素,无序排序直接生产一个有序的序列
结果:{13、27、38、49、49、65、76、97}
三、代码实现
python 版
def select_sort(list):
for i in range(len(list)):
index = i
flag = 1
for j in range(i+1, len(list)):
if list[index] < list[j]:
index = j
flag = 0
if flag == 0:
temp = list[i]
list[i] = list[index]
list[index] = temp
print('第'+str(i+1)+'趟排序',list)
return list
print(select_sort([2,5,7,8,1,3,0,9]))
print(select_sort([2,1,3,0,9]))
代码细节:
(1)j 从 i+1 开始;
(2)设置判定条件,判断是否需要进行交换
java 版:
import java.util.Arrays;
public class selectSort {
public static void selectSort(int[] arr) {
int index,tem;
int len = arr.length;
for (int i = 0; i<len; i++) {
index = i;
for (int j=i+1; j<len; j++) {
if(arr[index]<arr[j]) {
index = j;
}
}
if (index == i) {
continue;
}else {
tem = arr[i];
arr[i] = arr[index];
arr[index] = tem;
}
System.out.println("第"+i+"次排序"+Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr= {49,38,65,97,76,13,27,49};
selectSort(arr);
}
}
输出结果:
第0次排序[97, 38, 65, 49, 76, 13, 27, 49]
第1次排序[97, 76, 65, 49, 38, 13, 27, 49]
第4次排序[97, 76, 65, 49, 49, 13, 27, 38]
第5次排序[97, 76, 65, 49, 49, 38, 27, 13]
四、算法分析
1、简单选择排序算法性能
其中,N2为
N
2
N^2
N2。
2、时间复杂度
简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有 N 个元素,则比较次数总是 N ( N − 1 ) 2 \frac{N (N - 1)}{2} 2N(N−1)。
而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,为 0.
当序列反序时,移动次数最多,为 3 N ( N − 1 ) 2 \frac{3N (N - 1)}{ 2} 23N(N−1)。
所以,综合以上,简单排序的时间复杂度为
O
(
N
2
)
O(N^2)
O(N2)。
3、空间复杂度
简单选择排序需要占用 1 个临时空间,用于保存最小值得索引。
冒泡排序
冒泡排序(Bubble Sort)是常用的数组排序算法之一,它以简洁的思想与实现方法而备受青睐,也是广大学习者最先接触的一种排序算法。
算法思想
冒泡排序的基本思想是:对比相邻的元素值,如果满足条件就交换元素值,把较小的元素值移动到数组前面,把大的元素值移动到数组后面(也就是交换两个元素的位置),这样数组元素就像气泡一样从底部上升到顶部。
Java 中的冒泡排序在双层循环中实现,其中外层循环控制排序轮数,总循环次数为要排序数组的长度减 1。而内层循环代表每一轮的冒泡处理,主要用于对比相邻元素的大小,以确定是否交换位置,对比和交换次数依排序轮数而减少。
算法过程
例子: 有8个数组组成一个无序数列:5,8,6,3,9,2,1,7,希望从大到小排序。按照冒泡排序的思想,我们要把相邻的元素两两进行比较,根据大小交换元素的位置
第一轮排序:
1、首先让5和8进行交换,发现5比8小,因此元素位置不变。
2、接下来让8和6比较,发现8比6大,所以要交换8和6的位置。
3、继续让8和3比较,发现8比3要大,所以8和3 交换位置。
4、继续让8和9进行比较,发现8比9小,不用交换
5、9和2进行比较,发现9比2大,进行交换
6、继续让9和1进行比较,发现9比1大,所以交换9和1的位置。
7、最后,让9和7交换位置
这样一来,元素9作为数列的最大元素,就已经排序好了。
下面我们来进行第二轮排序:
1、首先让5和6比较,发现5比6小,位置不变
2、接下来让6和3比较,发现6比3大,交换位置
3、接下来让6和8比较,6比8小,位置不变
4、8和2比较。8比2大,交换位置
5、接下来让8和1比较,8比1大,因此交换位置
6、继续让8和7比较,发现8比7大,交换位置
第二轮的状态为:
第三轮的状态为:
第四轮的状态为:
第五轮的状态为:
第六轮的状态:
第七轮状态为:(已经有序了)
第八轮的状态为:
到此为止,所有的元素欧式有序的了,这就是冒泡排序的整体思路。
实例
获取用户在控制台输入的 5 个成绩信息,将这些成绩保存到数组中,然后对数组应用冒泡排序,并输出排序后的结果,实现步骤如下。
(1) 创建一个 Test24 类文件,在 main() 方法中开始编码。首先创建 Scanner 类的实例后声明 double 类型的 score 数组,然后接收用户在控制台输入的成绩,并保存到元素中。代码如下:
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
double[] score = new double[5];
for (int i = 0; i < score.length; i++) {
System.out.print("请输入第 " + (i + 1) + " 个成绩:");
score[i] = scan.nextDouble();
}
}
(2) 在对 score 数组排序之前,首先输出数组中各个元素的值。代码如下:
System.out.println("排序前的元素值:");
for(double val:score) {
System.out.print(val+"\t");
}
System.out.println();
(3) 通过冒泡排序方法实现对 score 数组的排序,在实现时需要借助一个临时变量。代码如下:
public static void main(String[] args) {
System.out.println("通过冒泡排序方法对数组进行排序:");
for (int i = 0; i < score.length - 1; i++) {
// 比较相邻两个元素,较大的数往后冒泡
for (int j = 0; j < score.length - 1 - i; j++) {
if (score[j] > score[j + 1]) {
double temp = score[j + 1]; // 把第一个元素值保存到临时变量中
score[j + 1] = score[j]; // 把第二个元素值转移到第一个元素变量中
score[j] = temp; // 把临时变量(第一个元素的原值)保存到第二个元素中
}
System.out.print(score[j] + " "); // 对排序后的数组元素进行输出
}
System.out.print("【");
for (int j = score.length - 1 - i; j < score.length; j++) {
System.out.print(score[j] + " ");
}
System.out.println("】");
}
}
(4) 运行前面的代码进行测试,如下所示。
请输入第 1 个成绩:77
请输入第 2 个成绩:90
请输入第 3 个成绩:68
请输入第 4 个成绩:59
请输入第 5 个成绩:80
排序前的元素值:
77.0 90.0 68.0 59.0 80.0
通过冒泡排序方法对数组进行排序:
77.0 68.0 59.0 80.0 【90.0 】
68.0 59.0 77.0 【80.0 90.0 】
59.0 68.0 【77.0 80.0 90.0 】
59.0 【68.0 77.0 80.0 90.0 】
代码细节:
(1)外层循环控制排序轮数,总循环次数为要排序数组的长度减 1,故
i
<
s
c
o
r
e
.
l
e
n
g
t
h
−
1
i < score.length - 1
i<score.length−1;
(2)内层循环代表每一轮的冒泡处理, 每一层需要排序的数与外层循环控制排序轮数有关,故
j
<
s
c
o
r
e
.
l
e
n
g
t
h
−
1
−
i
j < score.length - 1 - i
j<score.length−1−i
算法分析
原始的冒泡排序是稳定的,由于该排序算法的每一轮都要遍历一遍所有的元素,轮转的次数和元素数量相当,所以时间复杂度为 O ( N 2 ) O(N^2) O(N2)。
空间复杂度: O ( N ) O(N) O(N)
选择排序与冒泡排序的区别
1.冒泡排序是比较相邻位置的两个数,而选择排序是按顺序比较,找最大值或者最小值;
2.冒泡排序每一轮比较后,位置不对都需要换位置,选择排序每一轮比较都只需要换一次位置;
3.冒泡排序是通过数去找位置,选择排序是给定位置去找数;
二: 冒泡排序优缺点
1.优点:比较简单,空间复杂度较低,是稳定的;
2.缺点:时间复杂度太高,效率慢;
三: 选择排序优缺点
1.优点:一轮比较只需要换一次位置;
2.缺点:效率慢,不稳定(举个例子5,8,5,2,9 我们知道第一遍选择第一个元素5会和2交换,那么原序列中2个5的相对位置前后顺序就破坏了)。
参考博客:
https://blog.csdn.net/changhangshi/article/details/82740541