如表格所示,每一趟都將當前亂序序列中最大的數移到尾端。【小夥伴們從表格中看出基本冒泡排序能夠優化的地方了嗎?】下面先來基本實現代碼。java
java實現冒泡排序:算法
private static <T extends Comparable<? super T>> void bubbleSort(T[] nums) {
if (null == nums || nums.length == 0) {
throw new RuntimeException("數組爲null或長度爲0");
}
T temp = null;
int length = nums.length;
//外循環是趟數,每一趟都會將未排序中最大的數放到尾端
for (int i = 0; i < length - 1; i++) {
//內循環是從第一個元素開始,依次比較相鄰元素,
// 比較次數隨着趟數減小,由於每一趟都排好了一個元素
for (int j = 0; j < length - 1 - i; j++) {
if (nums[j].compareTo(nums[j + 1]) > 0) {
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}
複製代碼
從表格中,相信小夥伴已經看出,在第5趟其實已經排好序了,但基本的冒泡排序算法還會進行第7趟比較,這其實只是進行不必的比較,而不會進行元素的交換。(第6趟仍是必需要走的,下面會說明)數組
時間複雜度:因爲內外循環都發生N次迭代,因此時間複雜度爲O(n^2)。而且這個界是精確的。思考最壞的狀況,輸入一個逆序的數組,則比較次數爲:app
(N-1)+(N-2)+(N-3)+..+2+1 = n*(n-1)/2 = O(n^2)優化
空間複雜度:只使用了一個臨時變量,因此爲O(1);spa
是否穩定:穩定排序code
咱們換個角度看待這個問題。基本冒泡算法之因此進行了無用的多餘掃描,是由於不知道已經排好了序;因此只要咱們在第 i 趟(i小於N-1)就知道序列已經排好序,咱們就不用進行以後的掃描了。orm
綜上所述,咱們能夠增長一個boolean變量,來標識是否已經排好序。優化代碼以下:cdn
冒泡排序優化普通版:blog
private static <T extends Comparable<? super T>> void bubbleSort(T[] nums) {
if (null == nums || nums.length == 0) {
throw new RuntimeException("數組爲null或長度爲0");
}
T temp = null;
int length = nums.length;
//用於標識是否已經將序列排好序
boolean isOrdered = false;
for (int i = 0; i < length - 1; i++) {
//每一趟開始前都假設已經有序
isOrdered = true;
for (int j = 0; j < length - 1 - i; j++) {
if (nums[j].compareTo(nums[j + 1]) > 0) {
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
//若是出現有元素交換,則代表此躺可能沒有完成排序
isOrdered = false;
}
}
//若是當前趟都沒有進行元素的交換,證實前面一趟比較已經排好序
//直接跳出循環
if (isOrdered) {
break;
}
}
}
複製代碼
注意:雖然第5趟已經排好序,但對於程序來講,它並不知道此趟已經排好序,須要進行下一趟掃描來肯定上一趟是否已經將原序列排好序。因此第6趟是必需要去掃描的。
你覺得結束了嗎?哈哈哈,尚未,這只是初版優化。
讓咱們想想這樣的狀況。對於下列序列,前半部分亂序,後半部分有序。
簡述排序過程:
第一趟:發生交換的是5和3,接着是5和2;隨後5與6比較,不須要換位置,相同地,6與七、7與八、8與9都不須要更換位置。因此第一趟結果爲:[4,3,2,5,6,7,8,9]。
第二趟:發生交換的是4與3,接着4與2;隨後4與五、5與6,6與七、7與8都不須要更換位置。【8不須要與9比較,由於第一趟已經將最大的數下沉到尾端】。因此第二趟結果爲:[3,2,4,5,6,7,8,9]。
第三趟:發生交換的是3與2;隨後3與4,4與5,5與6,6與7都不須要更換位置。因此第三趟結果爲:[2,3,4,5,6,7,8,9]。
你們看出什麼了嗎?其實進行了不少無心義的比較,由於這些都不須要更換位置,而不少趟都會重複比較。根據冒泡排序思想,咱們知道,有序序列長度,其實跟排序趟數相等,每一趟就是將當前亂序中的最大值下沉到數組尾端。但其實序列真正有序的序列長度是大於當前排序趟數的。也就是說,只要咱們找到了原序列中無序與有序的邊界,就能夠避免再去比較有序序列。
其實最後一次交換的位置,就是無序序列與有序序列的邊界。
從例子中看:
第一趟最後一次交換的位置是元素5與2交換的位置,即數組下標2的位置;
第二趟最後一次交換的位置是元素4與2交換的位置,即數組下標1的位置;
第三趟最後一次交換的位置是元素3與2交換的位置,即數組下標0的位置;
因此,只要咱們記錄下當前趟最後一次交換的位置,在下一趟只比較到這個位置便可。
冒泡排序優化增強版:
private static <T extends Comparable<? super T>> void bubbleSort(T[] nums) {
if (null == nums || nums.length == 0) {
throw new RuntimeException("數組爲null或長度爲0");
}
T temp = null;
int length = nums.length;
boolean isOrdered = false;
int lastExchangeIndex = 0;
//當前趟無序的邊界
int unorderedBorder = length - 1;
for (int i = 0; i < length - 1; i++) {
//每一趟開始前都假設已經有序
isOrdered = true;
for (int j = 0; j < unorderedBorder; j++) {
if (nums[j].compareTo(nums[j + 1]) > 0) {
temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
//若是出現有元素交換,則代表此躺沒有完成排序
isOrdered = false;
//記錄下最後一次交換元素的位置
lastExchangeIndex = j;
}
}
unorderedBorder = lastExchangeIndex;
if (isOrdered) {
break;
}
}
}
複製代碼
其實,還能夠進一步優化, 有興趣的能夠去看看雞尾酒排序,咱們已經很接近了。
冒泡排序能夠經過增長boolean標識是否已經排好序來進行優化;還能夠記錄下最後一次交換元素的位置來進行優化,防止無心義的比較。冒泡排序是穩定排序,時間複雜度爲O(n^2),空間複雜度爲O(1)。