目录
前言
本篇文章讲解了冒泡排序,选择排序和插入排序的算法(思路讲解+动画+代码 (java语言) ),这是三种简单的排序。
1.冒泡排序
1.1冒泡排序思路
依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。
1.2冒泡排序图解
1.3冒泡排序代码实现
//冒泡排序,可直接运行
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {9,6,14,2,55,12,34,7};
sortByBubble(arr);
for (int i = 0;i < arr.length;i++){
System.out.print(arr[i]+" ");
}
}
private static void sortByBubble(int[] arr) {
if (arr==null || arr.length < 2){
return;
}
boolean flag;
for (int i = 0;i < arr.length;i++){
flag = false;
//此处-1是为了防止数组下标溢出 a[j+1]
//若不减一,此处的例子上a[j+1]则会变成a[a.length]
for (int j = 0;j < arr.length - i - 1;j++){
int temp;
if (arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
flag = true;
}
}
if (!flag){
break;
}
}
}
}
第一趟排序:
- 第一次排序:9和6比较, 9大于6,交换位置 [9,6,14,2,55,12,34,7]
- 第二趟排序:9和14比较,9小于14,不交换位置 [6,9,14,2,55,12,34,7]
- 第三趟排序:14和2比较,14大于2,交换位置 [6,9,2,14,55,12,34,7]
- 第四趟排序:14和55比较,14小于55,不交换位置 [6,9,2,14,55,12,34,7]
- 第五趟排序:55和12比较,55大于12,交换位置 [6,9,2,14,12,55,34,7]
- 第六趟排序:55和34比较,55大于34,交换位置 [6,9,2,14,12,34,55,7]
- 第七趟排序:55和7比较,55大于7,交换位置 [6,9,2,14,12,34,7,55]
- 第一趟总共进行了七次比较,排序结果: [6,9,2,14,12,34,7,55]
第二趟排序:
- 第一次排序:6和9比较,6小于9,不交换位置 [6,9,2,14,12,34,7,55]
- 第二趟排序:9和2比较,9大于2, 交换位置 [6,2,9,14,12,34,7,55]
- 第三趟排序:9和14比较,9小于14,不交换位置 [6,2,9,14,12,34,7,55]
- 第四趟排序:12和14比较,14大于12,交换位置 [6,2,9,12,14,34,7,55]
- 第五趟排序:14和34比较,14小于34,不交换位置 [6,2,9,12,14,34,7,55]
- 第六趟排序:34和7比较,34大于7,交换位置 [6,2,9,12,14,7,34,55]
- 第二趟总共进行了六次比较,排序结果: [6,2,9,12,14,7,34,55]
第三趟排序:
- 第一次排序:6和2比较,6大于2,交换位置 [2,6,9,12,14,7,34,55]
- 第二趟排序:6和9比较,6小于9,不交换位置 [2,6,9,12,14,7,34,55]
- 第三趟排序:9和12比较,9小于12,不交换位置 [2,6,9,12,14,7,34,55]
- 第四趟排序:12和14比较,12小于14,不交换位置 [2,6,9,12,14,7,34,55]
- 第五趟排序:14和7比较,14大于7,交换位置 [2,6,9,12,7,14,34,55]
- 第三趟总共进行了五次比较,排序结果: [2,6,9,12,7,14,34,55]
第四趟排序:
- 第一次排序:2和6比较,2小于6,不交换位置 [2,6,9,12,7,14,34,55]
- 第二趟排序:6和9比较,6小于9,不交换位置 [2,6,9,12,7,14,34,55]
- 第三趟排序:9和12比较,9小于12,不交换位置 [2,6,9,12,7,14,34,55]
- 第四趟排序:12和7比较,12大于7,交换位置 [2,6,9,7,12,14,34,55]
- 第四趟总共进行了四次比较,排序结果: [2,6,9,7,12,14,34,55]
第五趟排序:
- 第一次排序:2和6比较,2小于6,不交换位置 [2,6,9,7,12,14,34,55]
- 第二趟排序:6和9比较,6小于9,不交换位置 [2,6,9,7,12,14,34,55]
- 第三趟排序:9和7比较,9大于12,交换位置 [2,6,7,9,12,14,34,55]
- 第五趟总共进行了三次比较,排序结果: [2,6,7,9,12,14,34,55]
第六趟排序:
- 第一次排序:2和6比较,2小于6,不交换位置 [2,6,7,9,12,14,34,55]
- 第二趟排序:6和7比较,6小于7,不交换位置 [2,6,7,9,12,14,34,55]
- 第六趟总共进行了二次比较,排序结果: [2,6,7,9,12,14,34,55]
第七趟排序:
- 第一次排序:2和6比较,2小于6,不交换位置 [2,6,7,9,12,14,34,55]
- 第七趟总共进行了一次比较,排序结果: [2,6,7,9,12,14,34,55]
1.4冒泡排序的时间复杂度分析
在设置标志变量之后:
当原始序列“正序”排列时,冒泡排序总的比较次数为n-1,移动次数为0,也就是说冒泡排序在最好情况下的时间复杂度为O(n);
当原始序列“逆序”排序时,冒泡排序总的比较次数为n(n-1)/2,移动次数为3n(n-1)/2次,所以冒泡排序在最坏情况下的时间复杂度为O(n^2);
当原始序列杂乱无序时,冒泡排序的平均时间复杂度为O(n^2)。
1.5冒泡排序的空间复杂度
冒泡排序排序过程中需要一个临时变量进行两两交换,所需要的额外空间为1,因此空间复杂度为O(1)。
2.选择排序
2.1选择排序思路
1.每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,
如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值, 最后可以找到最小值所在的索引
2.交换第一个索引处和最小值所在的索引处的值.
2.2选择排序图解
2.3选择排序代码实现
/**
* 选择排序,可直接运行
*/
public class SelectionSort {
public static void main(String[] args) {
int[] arr = {9,6,14,2,55,12,34,7};
sortBySelect(arr);
for (int i = 0;i < arr.length;i++){
System.out.print(arr[i]+" ");
}
}
private static void sortBySelect(int[] arr) {
if (arr ==null || arr.length < 2){
return;
}
for (int i = 0; i < arr.length -1; i++) {
//假定本次遍历,最小值所在的索引是i
int min = i;
int temp;
//需要理解这里的 i+1 和 j < arr.length
for (int j = i+1; j < arr.length; j++) {
if (arr[min] > arr[j]){
//找到一个比自己小的就把它记下来,
//接着再用这个比自己小的和后续的比较
min = j;
}
}
//此时arr[min]为最小的,和刚开始假定的最小值arr[i]进行交换
temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
2.4选择排序的时间复杂度分析
选择排序使用了双层for循环,其中外层循环完成了数据交换,内层循环完成了数据比较,所以我们分别统计数据 交换次数和数据比较次数:
数据比较次数: (N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
数据交换次数: N-1
时间复杂度:N^2/2-N/2+(N-1)=N^2/2+N/2-1;
根据大O推导法则,保留最高阶项,去除常数因子,时间复杂度为O(N^2);
2.5选择排序的空间复杂度分析
空间复杂度,最优的情况下(已经有顺序)复杂度为:O(0) ;最差的情况下(全部元素都要重新排序)复杂度为:O(n );;平均的时间复杂度:O(1)
3.插入排序
3.1插入排序原理
1.将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
3.2插入排序图解
3.3插入排序代码实现
/**
* 插入排序,可直接运行
*/
public class InsertSort {
public static void main(String[] args) {
int[] arr = {9, 6, 14, 2, 55, 12, 34, 7};
sortByInsert(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
}
private static void sortByInsert(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
//从数组下标为 1 的地方和前面的元素进行比较,开始选择合适的地方进行插入
//因为下标为0的只有一个元素,默认是有序的
for (int i = 1; i < arr.length; i++) {
//把该需要比较的元素先保存一份
int temp = arr[i];
//从已经排序的最右边开始进行比较,找到比其小的数
int j = i;
while(j>0 && temp < arr[j - 1]){
//如果比较的元素小的话,将比较的对应元素向后移动
arr[j] = arr[j - 1];
j--;
}
//如果j==i,那么排好序的元素都比需要比较的元素小,不需要插入
//如果j!=i,那么说明有比需要比较的元素小的数,并且位置移动了,需要插入
if (j!=i){
arr[j] = temp;
}
}
}
}
3.4插入排序的时间复杂度分析:
插入排序使用了双层for循环,其中内层循环的循环体是真正完成排序的代码,所以,我们分析插入排序的时间复 杂度,主要分析一下内层循环体的执行次数即可。
最坏情况,也就是待排序的数组元素为{55,34,14,12,9,7,6,2},那么:
比较的次数为: (N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
交换的次数为: (N-1)+(N-2)+(N-3)+...+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
总执行次数为: (N^2/2-N/2)+(N^2/2-N/2)=N^2-N;
按照大O推导法则,保留函数中的最高阶项那么最终插入排序的时间复杂度为O(N^2).
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,例如,量级小于千;或者若已知输入元素大致上按照顺序排列,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序
==================================================
另外,如果觉得文章对你有用,下边点个赞吧!
你们的支持,是我坚持的动力。
==================================================