1.基本原理:比较相邻的两个数,将值大的数交换到右边
2.大概思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。
(1)第一次比较:首先比较第1个和第2个数,将小数放在前面,将大数放在后面。
(2)比较第2和第3个数,将小数放在前面,大数放在后面。
......
(3)如此继续,直到比较到最后的两个数,将小数放在前面,大数放在后面,重复步骤,直至全部排序完成。
(4)在上面一趟比较完成后,最后一个数一定是数组中最大的一个数,所以在比较第二趟的时候,最后一个数是不参加比较的。
(5)在第二趟比较完成后,倒数第二个数也一定是数组中倒数第二大数,所以在第三趟的比较中,最后两个数是不参与比较的。
(6)依次类推,每一趟比较次数减少一次。
3.算法描述:
(1)n个数字要排序完成,总共进行n-1趟排序,每i趟的排序次数为(n-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数。
(2)冒泡排序的优点:每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推……也就是说,每进行一趟比较,每一趟少比较一次,一定程度上减少了算法的量。
(3)时间复杂度
1.如果我们的数据正序,只需要比较一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值,即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的时间复杂度为O(n)。
2.如果很不幸我们的数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:
综上所述:冒泡排序总的平均时间复杂度为:O(n2) ,时间复杂度和数据状况无关。
package demo.datastructures;
public class BubbleSortDemo {
/**
* n个数字冒泡排序,总共要进行n-1趟比较,每趟的排序次数为(n-i)次
* @param arr
*/
public static void bubbleSort(int[] arr){
if(arr==null || arr.length<2) {return;}
//需要进行arr.length-1趟比较
for(int i=0;i<arr.length-1;i++)
{
//每趟需要进行arr.length-i-1次比较
for(int j=0;j<arr.length-i-1;j++)
{
//如果arr[j]比arr[j+1]的值大,那就交换位置
if(arr[j]>arr[j+1])
{
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
for(int k=0;k<arr.length;k++){
System.out.println(arr[k]);
}
}
public static void main(String[] args) {
int[] arr={4,2,6,8,9,1,3,5,7,0};
bubbleSort(arr);
}
//------------- 附另外两种交换变量值的方法 ------------------
/**
* 加减运算 前提只能是数字
* @param a
* @param b
*/
static void swap1(int a,int b){
a=a+b;
b=a-b;
a=a-b;
System.out.println(a+","+b);
}
/**
* 异或运算 前提只能是整数
* @param a
* @param b
*/
static void swap2(int a,int b){
a=a^b;
b=a^b;
a=a^b;
System.out.println(a+","+b);
}
}
附:大O表示法
大O表示法:算法的时间复杂度通常用大O符号表述,定义为T[n] = O(f(n))。称函数T(n)以f(n)为界或者称T(n)受限于f(n)。 如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n)。T(n)称为这一算法的“时间复杂度”。当输入量n逐渐加大时,时间复杂度的极限情形称为算法的“渐近时间复杂度”。
大O表示法,用来描述复杂度,它表示的是数据规模n对应的复杂度,大O表示法有以下的一些特性:
- 忽略表达式常数、系数、低阶项。
忽略常数,常数直接为1,比如上面第一个方法的复杂度为15,因此直接取1,其时间复杂度使用大O表示为O(1)。
忽略系数,忽略表达式的系数,比如第二个方法的时间复杂度为3n+1,忽略系数和常数,其时间复杂度为O(n)。
忽略低阶项,比如第三个方法的时间复杂度为3n2+3n+1,忽略低阶项3n,忽略常数1,忽略系数3,则其时间复杂度为O(n2)。 - 对数阶一般忽略底数
对于对数直接的转换,一个对数都可以乘以一个常数项成为一个没有底数的对数,比如
log2n = log29 * log9n,因此可以省略底数,比如上面第五个方法的时间复杂度为log2(n),可以忽略底数2,则其时间负责度为logn。 - 大O表示法仅仅是一种粗略的分析模型,是一种估算,能帮助我们短时间内估算一个算法的时间复杂度。
常见的复杂度
执行次数 | 复杂度 | 非正式术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
4n2+zn+2 | O(n2) | 平方阶 |
4log2n+21 | O(logn) | 对数阶 |
3n+2log3n+15 | O(nlogn) | nlogn阶 |
4n3+3n2+22n+11 | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
复杂度的大小关系
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)。
因此上面的十个方法的复杂度如下:
方法名称 | 复杂度 | 大O表式 |
---|---|---|
test1 | 15 | O(1) |
test2 | 3n+1 | O(n) |
test3 | 3n2+3n+1 | O(n2) |
test4 | 48n+1 | O(n) |
test5 | 3log2(n) | O(logn) |
test6 | 3log5(n) | O(logn) |
test7 | 3nlog2(n) + 3log2(n) + 1 | O(nlogn) |
test8 | 3n+5 | O(n) |