也许冒泡排序,一个刚出大学的的程序员可能写的出来,反而工作了几年的老程序员可能会写不出来,你还写的出来么?在本篇博文中,详细介绍了冒泡排序的概念,同时用数组和双向链表来实现,附带一种通俗的优化方法。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290
1、冒泡排序
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成
之所以称这种排序方式为冒泡,是因为大的元素就表示它的气泡比较大,慢慢的就会按气泡大小,把数据浮上来。
2、排序过程
假设要对如下数据进行排序:
目标待排序数据:4,6,3,1,8
排序次数 | 4,6,3,1,8 | 交换情况 | 轮数量 |
---|---|---|---|
第1次交换 | 4,6,3,1,8 | 4,6进行比较,发现6比4大,不需要交换 | 第一轮 |
第2次交换 | 4,3,6,1,8 | 6,3进行比较,发现3比6小,进行交换 | 第一轮 |
第3次交换 | 4,3,1,6,8 | 6,1进行比较,发现1比6小,进行交换 | 第一轮 |
第4次交换 | 4,3,1,6,8 | 6,8进行比较,发现8比6大,不需要交换 | 第一轮结束,最大的8已经冒泡到最上面 |
第5次交换 | 3,4,1,6,8 | 4,3进行比较,发现3比4小,进行交换 | 第二轮 |
第6次交换 | 3,1,4,6,8 | 4,1进行比较,发现1比4小,进行交换 | 第二轮 |
第7次交换 | 3,1,4,6,8 | 4,6进行比较,发现6比4大,不需要交换 | 第二轮结束,第二大的6已经冒泡到倒数第二位 |
第8次交换 | 1,3,4,6,8 | 3,1进行比较,发现1比3小,进行交换 | 第三轮 |
第9次交换 | 1,3,4,6,8 | 3,4进行比较,发现4比3大,不需要交换 | 第三轮结束,第三大的4已经冒泡到倒数第三位 |
第10次交换 | 1,3,4,6,8 | 1,3进行比较,发现3比1大,不需要交换 | 第四轮结束,排序完成 |
上面就是一次完整的冒泡排序的历程,我们分析一下:
①在上面的目标数据中,有5个数据,会进行4轮排序,所以对于N个数据的排序,必然进行N-1轮的遍历,这表示一个for循环,肯定是for(int i=0, i < target.length; i++)。
②在每一轮中会有递减的判断相邻两者的大小关系,如果关系不符合就进行交换。为什么是递减的呢?因为每一轮中都会把最大的冒泡的最后面,所以不必去判断已经有序的数据。这就表示是另外一个for循环,肯定是for(int j = 0; j< target.length - i; j++)。所以冒泡排序的核心肯定是一个嵌套for循环和相邻数据的位置交换。
3、算法分析
通过上面的分析,算法可能是for循环中嵌套一个for循环。假如说目标数据本身就是有序的,那么只需要遍历一次即可完成排序(一遍之后发现不需要交换就是有序的),那么这个时候时间复杂度就是O(N);如果目标数据是反序的,那么需要遍历N-1遍,同时在每一遍的遍历中需要一直交换相邻位置,这个时候的时间复杂度就是O(N^2)。
我们就用上面提到的目标数据:4,6,3,1,8来进行排序。一下是一般的数组实现:
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月24日下午12:05:43
* 关于类BubbleSort.java的描述:冒泡排序
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class BubbleSort {
/**
* 数组实现经典冒泡排序
*/
public staticsuper T>> void arrBubbleSort(T[] targerArr){
for (int i = 0; i < targerArr.length; i++) {//排序趟数
for (int j = 0; j < targerArr.length - i -1; j++) {//在每一趟的数据比较中,判断和进行相邻两者交换
if(targerArr[j + 1].compareTo(targerArr[j]) < 0){//如果右边的数据比左边的小,那么就需要进行交换
//下面开始两者交换
T temp = targerArr[j];
targerArr[j] = targerArr[j + 1];
targerArr[j + 1] = temp;
}
String result = "";
for (T t : targerArr) {
result += t+" ";
}
System.out.println("目前第"+(i+1)+"轮,当前数组排序结果: "+result);
}
}
}
public static void main(String[] args) {
Integer[] targer = {4,6,3,1,8};
arrBubbleSort(targer);
}
}
//运行结果:
//目前第1轮,当前数组排序结果: 4 6 3 1 8
//目前第1轮,当前数组排序结果: 4 3 6 1 8
//目前第1轮,当前数组排序结果: 4 3 1 6 8
//目前第1轮,当前数组排序结果: 4 3 1 6 8
//目前第2轮,当前数组排序结果: 3 4 1 6 8
//目前第2轮,当前数组排序结果: 3 1 4 6 8
//目前第2轮,当前数组排序结果: 3 1 4 6 8
//目前第3轮,当前数组排序结果: 1 3 4 6 8
//目前第3轮,当前数组排序结果: 1 3 4 6 8
//目前第4轮,当前数组排序结果: 1 3 4 6 8
在代码中,我用泛型来拓展了可以实现排序的对象,只要该对象重写了compareTo方法都可以用上面的方法进行排序。可以看到在我们自己写的冒泡排序每次操作的结果和我们上面的分析是一模一样的。至此,经典的数组型冒泡排序就结束了。
我们对于上面的代码进一步分析,如果在排序的过程中,存在一直不发生交换的情况,也就是说数据本身就是有序的,但是对于上面的代码来说,即使是有序的仍旧需要一次次进行遍历和判断,所以我们试想:
如果在某一轮的遍历当中,如果没有发生位置交换,也就是if中没有为true的判断,那么我们就可以肯定数据已经排序完毕,结束循环。
下面就是对这种情况的一种优化:
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月24日下午12:05:43
* 关于类BubbleSort.java的描述:冒泡排序
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class BubbleSort {
/**
* 数组实现经典冒泡排序
*/
public staticsuper T>> void arrBubbleSort(T[] targerArr){
// 设立一个表示位,如果在某一趟的遍历中一直没有发生位置交换,表示数据排序已结束
boolean flag;
for (int i = 0; i < targerArr.length; i++) {//排序趟数
flag = true;//每次操作前都设置为true
for (int j = 0; j < targerArr.length - i -1; j++) {//在每一趟的数据比较中,判断和进行相邻两者交换
if(targerArr[j + 1].compareTo(targerArr[j]) < 0){//如果右边的数据比左边的小,那么就需要进行交换
//下面开始两者交换
T temp = targerArr[j];
targerArr[j] = targerArr[j + 1];
targerArr[j + 1] = temp;
//如果发生了位置交换,那么就把标志位设为false
flag = false;
}
String result = "";
for (T t : targerArr) {
result += t+" ";
}
System.out.println("目前第"+(i+1)+"轮,当前数组排序结果: "+result);
}
if(flag){
System.out.println("在第" +(i + 1)+"轮中,未发生位置交换,排序已结束,循环结束");
break;
}
}
}
public static void main(String[] args) {
Integer[] targer = {4,1,3,6,8};
arrBubbleSort(targer);
}
}
//输出:
//目前第1轮,当前数组排序结果: 1 4 3 6 8
//目前第1轮,当前数组排序结果: 1 3 4 6 8
//目前第1轮,当前数组排序结果: 1 3 4 6 8
//目前第1轮,当前数组排序结果: 1 3 4 6 8
//目前第2轮,当前数组排序结果: 1 3 4 6 8
//目前第2轮,当前数组排序结果: 1 3 4 6 8
//目前第2轮,当前数组排序结果: 1 3 4 6 8
//在第2轮中,未发生位置交换,排序已结束,循环结束
在上述的代码中,对目标数据{4,1,3,6,8}进行排序,我们发现其实除了4,其他的都是有序的,那么在第一次排序结束之后,数据已经是有序的了。如果不进行优化,那么它还是会进行4轮排序,但是如上面代码的优化方式,在第二轮执行结束,发现没有发生位置交换,那么就排序结束。
package com.brickworkers;
import java.awt.Point;
/**
*
* @author Brickworker
* Date:2017年4月24日下午12:05:43
* 关于类BubbleSort.java的描述:冒泡排序
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
public class BubbleSort<T> {
private static class Node<T>{
T data;
Node prev;
Node next;
public Node(T data) {
this.data = data;
}
public Node(T data, Node pr, Node ne) {
this.data = data;
this.prev = pr;
this.next = ne;
}
}
/**
* 用双向链表来实现冒泡排序
*/
public staticsuper T>> void LinkedBubbleSort(T[] targerLinked){//入参还是用数组,不过需要进行转换,我们不考虑转化时间
//先定义两个不计算的头结点和尾节点
Node start = new Node(null, null, null);
Node end = new Node(null, start, null);
start.next = end;//建立两者关系
int size = 0; //给双向链表定义个长度
//把数组转化为linked
for (T t : targerLinked) {
end.prev = end.prev.next = new Node(t, end.prev, end);//把数据按照数组顺序插入到双向链表尾部
size++ ;
}
//进行冒泡排序
//定义一个指针,标志目前操作的元素,去除我们设定的头节点
long startTime = System.currentTimeMillis();
Node pointer = start.next;
for(int i = 0; i < size; i++){
//指针归位
pointer = start.next;
for (int j = 0; j < size - i -1; j++) {
if(pointer.data.compareTo(pointer.next.data) > 0){//如果左边的数据比右边要大,那么进行交换
//处理当前节点的上个节点的指向问题
pointer.prev.next = pointer.next;
//处理比较节点的下个节点
pointer.next.next.prev = pointer;
//处理比较节点的prev
pointer.next.prev = pointer.prev;
//处理point的prev
pointer.prev = pointer.next;
//处理pointer的next
pointer.next = pointer.next.next;
//处理比较节点的next
pointer.prev.next = pointer;
//节点已经被交换,那么pointer节点需要重新指向新的节点
pointer = pointer.prev;
}
String result = "";
Node temp = start.next;
for (int k = 0; k < size; k++) {
result += temp.data+" ";
temp = temp.next;
}
// System.out.println("目前第"+(i+1)+"轮,当前数组排序结果: "+result);
//指针后移动
pointer = pointer.next;
}
}
System.out.println("双向链表冒泡排序总耗时:"+(System.currentTimeMillis() - startTime));
}
/**
* 数组实现经典冒泡排序
*/
public staticsuper T>> void arrBubbleSort(T[] targerArr){
// 设立一个表示位,如果在某一趟的遍历中一直没有发生位置交换,表示数据排序已结束
long startTime = System.currentTimeMillis();
for (int i = 0; i < targerArr.length; i++) {//排序趟数
for (int j = 0; j < targerArr.length - i -1; j++) {//在每一趟的数据比较中,判断和进行相邻两者交换
if(targerArr[j + 1].compareTo(targerArr[j]) < 0){//如果右边的数据比左边的小,那么就需要进行交换
//下面开始两者交换
T temp = targerArr[j];
targerArr[j] = targerArr[j + 1];
targerArr[j + 1] = temp;
}
String result = "";
for (T t : targerArr) {
result += t+" ";
}
// System.out.println("目前第"+(i+1)+"轮,当前数组排序结果: "+result);
}
}
System.out.println("数组冒泡排序总耗时:"+(System.currentTimeMillis() - startTime));
}
public static void main(String[] args) {
Integer[] targer = {4,3,1,6,2,9,7,978,55,54,5,6,1,7,98,4,45,32,56,6,9,5,4,45,85};
LinkedBubbleSort(targer);
arrBubbleSort(targer);
}
}
原理和数组实现一样,在比较的过程中交换相邻的两者。所以在双向链表的实现中,我是用交换两个节点位置来实现的,其实如果交换值能否也算是冒泡排序呢?这个我也不知道。我单纯的考虑从for循环开始来计算两者的运行时间,不过从运行时间结果来说还是数组效果会更好一些,即使把其中的节点交换改成值交换也还是数组效果会更好。
我也不知道用双向链表来实现它有没有意义。应该有点意义吧?最起码自己又熟悉了一边底层的结构,不是么。