一、冒泡算法
冒泡算法的一个重点特征是:体现冒泡的过程,大的沉底,小的上浮
曾经忽略这个过程,面试被考到了。
假定数组下标为[0,1,...,n]
-
首先从第一个数据开始,与第二个数据相比,若大于第二个数据,则将两个数据的位置进行交换
-
将指针由第一个数据指向第二个数据,比较第二个数据与第三个数据,如果第二数据比第三个数据大,则交换第二个数据与第三个数据的位置。
-
以此类推,完成第一轮排序(指针指向 n-1 )后,最大的数据被“沉”到末尾
-
按照上面的过程进行第二遍排序,完成排序后(指针指向 n-2 )后,第二大的数据被放到倒数第二的位置
-
重复上述过程,每排序完成一次,比较次数就减少一次。
Example:
input: 5, 4, 6, 7, 1
第一轮排序:指针指向 5,4 与 5 比较, 5 > 4, 进行位置交换, 结果为: 4, 5, 6, 7, 1
指针指向 5, 5 与 6 比较, 5 < 6, 不进行位置交换, 结果为: 4, 5, 6, 7, 1
指针指向 6, 6 与 7 比较, 6 < 7, 不进行位置交换, 结果为: 4, 5, 6, 7, 1
指针指向 7, 7 与 1 比较, 7 > 1, 进行位置交换, 结果我:4, 5, 6, 1, 7
第一趟完成排序后,最大的数据 7 被 “沉”到末尾的位置了
第二轮排序,我们要将第二大的数据,放到倒数第二的位置,所以结果为:4, 5, 1, 6, 7
第三轮排序结果:4, 1, 5, 6, 7
第四轮排序结果:1, 4, 5, 6, 7
最终的排序结果为 1, 4, 5, 6, 7
分析:由上可知使用冒泡法对 N个数据进行排序,需要进行 N-1 轮排序,第“i”轮要比较的次数为 N-i
则整个冒泡排序过程需要比较的次数为:N-1 + N-2 + N-3 + ... + 1 = 1/2 * (N-1) * (1+N-1) = 1/2 * (N^2-N)
最大需要交换的次数为:N-1 + N-2 + N-3 + ... + 1 = 1/2 * (N-1) * (1+N-1) = 1/2 * (N^2-N)
时间复杂度 :O(n*n)
示例代码:
for(int i = 0; i < length-1; i++){ //限定排序轮数 N-1
for(int j = 0; j < length-1-i; j++){ //每轮比较次数 N-i(i从0开始)
if(nums[j] > nums[j+1]){
int temp = nums[j];
nums[j] = num[j+1];
nums[j+1] = temp;
}
}
}
在不改变算法的情况下,对冒泡法进行优化
看到这个要求,可能会没什么头绪,脑子里浮现的东西都是把冒泡法改变成其他算法,但是这里我们可以先从算法过程分析,我们在循环内部只做两件事情,判断条件 和 交换数据。要优化只能减少不必要的 判断 或者 交换数据。从实现角度我们只能从减少不要的判断出发。当数据已有序时,我们就可以略去这个判断的过程。
方法一:外层循环优化
// BbBBleSort01
for(int i = 0; i < length - 1; i++){
boolean flag = true; //假定每次要排序的数据有序
for(int j = 0; j < length - 1 - i; j++){
if(nums[j] > nums[j+1]){
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = nums[j];
flag = false; //执行交换说明数据无序
}
}
if(flag) //某趟排序时发现数组有序,跳过后面排序
break;
}
-
每轮循环开始前设置一个 flag = true;
-
当该轮循环没有执行一次交换数据操作,说明全部数据有序,直接跳出循环
-
否则下一轮循环,直至有序
方法二:内层循环优化
//BubbleSort02
int index = length-1;
for(int i = 0; i < length - 1; i++){
boolean flag = true; //假定数组有序
int n = 0; //用于数组无序时,储存当前指针位置
for(int j = 0; j < index; j++){
if(nums[j] > nums[j+1]){
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
flag = false; //执行数据交换,数组无序
n = j; //记录每次交换的下标,一轮排序后为最后无序的下标
}
}
if(flag)
break;
index = n; //下次排序只排序无序部分
}
-
每轮循环开始时设置一个 flag(判断是否有序) 和 n 记录无序数据的范围
-
当执行交换操作时,说明无序,并记录下标
-
若数据无序,则下次循环只排序无序部分[0, ... ,n]
二、选择排序
选择排序时对冒泡排序的改进,它的改进之处正是在冒泡排序的交换次数上(所以前面对冒泡排序的优化没有改变这一点,不然就变成选择排序了)。基本选择排序与基本冒泡排序的比较次数相同,但交换次数要小于冒泡排序。所以当数据量较大时,效率会有明显的提升。
选择排序的重要特征是:体现出一个选择的过程,选出需要的那个数据再进行交换
假定数组下标为[0,1,...,n]
-
从第一个元素开始,分别于后面的元素相比较,找到最小的元素与第一个元素交换位置
-
从第二个元素开始,分别于后面的元素相比较,查到剩余元素中的最小元素,与第二个元素交换位置
-
重复上述步骤,值到所有的元素都按从小到大的顺序排列位置
Example:
input: 8, 6, 4, 7, 1
第一轮排序:指针指向 8 ,将 6 与 8 相比,选出小的元素 6
将 6 与 4 相比,选出小的元素 4
将 4 与 7 相比, 选出小的元素 7
将 7 与 1 相比, 选出小的元素 1,将 8 与 1 交换位置 结果为:1, 6, 4, 7, 8
第二轮排序:指针指向 6 ,选择 6, 4, 7, 8 中的最小元素 4,将 4 与 6交换位置,结果为:1, 4, 6, 7, 8
第三轮排序:指针指向 6 ,选出 6, 7, 8 中的最小元素 6 ,不做位置交换,结果为:1, 4, 6, 7, 8
第四轮排序:指针指向 7,选出 7, 8 中的最小元素 7,不做位置交换,结果为:1, 4, 6, 7, 8
所以排序结果为: 1, 4, 6, 7, 8
分析:由上可知,选择排序每排序完一轮都把最小的元素移到了已排序部分的末尾,然后下一轮的比较次数比上一轮减少一次,即第 i 轮排序需要比较N-1次,因此,N 个数据进行选择排序,需要排序 N-1 轮,第 i 轮需要比较 N-1 次
则选择排序需要判断的次数为:N-1 + N-2 + N-3 + ... + 1 = 1/2 * (N-1) * (1+N-1) = 1/2 * (N^2-N)
需要交换的次数为:N-1
时间复杂度:O(n*n)
示例代码:
int minIndex = 0; // 记录最小元素的下标
int temp = 0;
for(int i = 0; i < nums.length; i++){
minIndex = i;
for(int j = i+1; j < nums.length;){
if(nums[j] < nums[minIndex])
minIndex = j;
}
temp = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = temp;
}