演算法設計與分析04-排序問題
①氣泡排序:量量比較待排序資料元素的大小,發現兩個資料元素的次序相反時進行交換,直到沒有反序的資料元素為止。時間複雜度是O(n*2)。穩定的。下面給出兩種排序演算法,我比較喜歡第二種,因為第二種才能真正解釋冒泡的原理
public class bubble1 {
public static void main(String[] args) {
int[] array = { 7, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
bubbleSort(array);
printArray(array);
}
private static void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i] > array[j]) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
public class bubble2 {
public static void main(String[] args) {
int[] array = { 7, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
bubbleSort(array);
printArray(array);
}
private static void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
for (int j = array.length - 2; j >= i; j--) {
if (array[j] > array[j + 1]) {//從倒數第二個往前一直到第i個,如果有反序的,則調換位置,就這樣小的始終在前面,最後把最小的擠到第i個後面,i往後遞增
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
②選擇排序:每一趟從待排序的資料元素中選出最小(最大)的一個元素,順序放在已經排好序的數列的最後面,
直到全部待排序的資料元素排完,演算法複雜度是O(n*2)。不穩定的。
public class select {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
selectSort(array);
printArray(array);
}
private static void selectSort(int[] array) {
int k = 0;
for (int i = 0; i < array.length; i++) {
k = i;
for (int j = i; j < array.length; j++) {//從第i個元素往後順延,找出第i個元素及其後邊最小的元素,放在第i個元素的位置上
if (array[j] < array[k]) {
k = j;//保證k最終是第i個元素後邊最小的元素的索引
}
}
int temp = array[i];
array[i] = array[k];
array[k] = temp;
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
③插入排序:每次將一個待排序的資料元素,插入到前面已經排好序的數列中的適當位置,使數列依然有序;直到待排序資料元素全部插入完為止。
演算法的時間複雜度是o(n*2)。穩定的。
public class insert {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
insertSort(array);
printArray(array);
}
private static void insertSort(int[] array) {
int temp = 0;
for (int i = 1; i < array.length; i++) {
temp = array[i];//記錄第i個元素
int j = 0;
for (j = i; j > 0; j--) {
if (array[j - 1] > temp) {//如果第i個元素前面的元素大於i元素,則將前面的元素後移
array[j] = array[j - 1];
} else {//如果i元素前邊的元素比i元素大,則跳出迴圈,因為i元素之前的元素都是已經順序排列好的
break;
}
}
array[j] = temp;
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
④快速排序:在當前無序區 R[1..H]中任取一個數據元素作為比較的"基準"(不妨記為 X),用此基準將當前無序區劃分為左右兩個較小的無序區:R[1..I-1]和 R[I+1..H],
且左邊的無序子區中資料元素均小於等於基準元素,右邊的無序子區中資料元素均大於等於基準元素,而基準 X 則位於最終排序的位置上,
即R[1..I-1]≤X.Key≤R[I+1..H](1≤I≤H),當 R[1..I-1]和 R[I+1..H]均非空時,分別對它們進行上述的劃分過程,直至所有無序子區中的資料元素均已排序為止。
最理想情況演算法時間複雜度 O(nlogn),最壞O(n^2) 。不穩定。
public class Quick {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1, 0, 2, 9, 6, 4 };
printArray(array);
quickSort(array);
printArray(array);
}
private static void quickSort(int[] array) {
quick(array, 0, array.length - 1);
}
private static void quick(int[] array, int low, int high) {
int key = 0;
if (low < high) {
key = partition(array, low, high);
quick(array, low, key - 1);
quick(array, key + 1, high);
}
}
private static int partition(int[] array, int low, int high) {
int key = array[low];
while (low < high) {
while (low < high && array[high] >= key) {
high--;
}//這裡是為了找出右邊小於key的值,
swap(array, low, high);//將右邊小於Key的值與第一個值交換
while (low < high && array[low] <= key) {
low++;
}//這裡是為了找出左邊大於Key的值
swap(array, low, high);//將剛剛交換的右邊的high索引所在的值與現在得出的大於Key的值進行交換
}最終low索引所在的值的嘴邊的值都小於array[low],右邊的值都大於array[low]
return low;
}
private static void swap(int[] array, int low, int high) {
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
⑤堆排序:堆排序是一樹形選擇排序,在排序過程中,將 R[1..N]看成是一顆完全二叉樹的順序儲存結構,
利用完全二叉樹中雙親結點和孩子結點之間的內在關係來選擇最小的元素。 演算法時間複雜度O(nlogn)。不wend
public class Heap {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
heapSort(array);
printArray(array);
}
private static void heapSort(int[] array) {
if (array == null || array.length < 0) {
return;
}
buildMaxHeap(array);
for (int i = array.length - 1; i >= 1; i--) {
swap(array, 0, i);
maxHeap(array, i, 0);
}
}
private static void buildMaxHeap(int[] array) {
if (array == null || array.length < 0) {
return;
}
int half = array.length / 2;
for (int i = half; i >= 0; i--) {
maxHeap(array, array.length, i);
}
}
private static void maxHeap(int[] array, int heapSize, int index) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int largest = index;
if (left < heapSize && array[left] > array[index]) {
largest = left;
}
if (right < heapSize && array[right] > array[largest]) {
largest = right;
}
if (index != largest) {
swap(array, index, largest);
maxHeap(array, heapSize, largest);
}
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
⑥希爾排序:其實就是用步長控制的插入排序,希爾排序通過加大插入排序中元素之間的間隔,並在這些有間隔的元素中進行插入排序,從而讓資料項可以大幅度移動,
這樣的方式可以使每次移動之後的資料離他們在最終序列中的位置相差不大,保證資料的基本有序,大大提升了排序速度,運算時間複雜度 N*logN,不穩定。
public class Shell {
public static void main(String[] args) {
int[] array = { 0, 8, 3, 1,1, 2, 9, 6, 4 };
printArray(array);
shellSort(array);
printArray(array);
}
private static void shellSort(int[] array) {
int j = 0;
int temp = 0;
for (int increment = array.length / 2; increment > 0; increment /= 2) {
for (int i = increment; i < array.length; i++) {
temp = array[i];
for (j = i; j >= increment; j -= increment) {
if (temp < array[j - increment]) {//這裡實際上就是插入排序,只是按照規定的步長來插入排序
array[j] = array[j - increment];
} else {
break;
}
}
array[j] = temp;
}
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
⑦歸併排序:Divide: 把長度為 n 的輸入序列分成兩個長度為 n/2 的子序列。Conquer: 對這兩個子序列分別採用歸併排序。
Combine: 將兩個排序好的子序列合併成一個最終的排序序列。時間複雜度是 O(nlogn)。 穩定的 。
public class merge {
public static void main(String[] args) {
int[] array = { 7, 8, 3, 1, 5, 2, 9, 6, 4 };
printArray(array);
mergeSort(array);
printArray(array);
}
private static void mergeSort(int[] array) {
sort(array, 0, array.length - 1);
}
private static void sort(int[] array, int left, int right) {
if (left >= right) {
return;
}
int center = (left + right) / 2;
sort(array, left, center);
sort(array, center + 1, right);
mergesort(array, left, center, right);
}
private static void mergesort(int[] array, int left, int center, int right) {
int[] tempArray = new int[array.length];
int mid = center + 1;
int third = left;
int temp = left;
while (left <= center && mid <= right) {
if (array[left] <= array[mid]) {//這裡就相當於二路歸併排序,
tempArray[third++] = array[left++];
} else {
tempArray[third++] = array[mid++];
}
}
while (left <= center) {
tempArray[third++] = array[left++];
}
while (mid <= right) {
tempArray[third++] = array[mid++];
}
while (temp <= right) {
array[temp] = tempArray[temp++];
}
}
private static void printArray(int[] array) {
if (array.length == 0 || array == null) {
return;
}
for (int i = 0; i < array.length - 1; i++) {
System.out.print(array[i] + " ");
}
System.out.println(array[array.length - 1]);
}
}
2、快速排序的平均時間複雜度是多少?最壞時間複雜度是多少?在哪些情況下會遇到最壞的時間複雜度。
快速排序的平均時間複雜度 O(nlogn),最壞時間複雜度 O(n^2)。
最壞情況:待排序的序列為正序或者逆序。每次劃分只得到一個比上一次劃分少一個記錄的子序列,另一個為空。如果遞迴樹畫出來,它就是一棵斜樹。
當每次 pivot 選擇恰好都把列表元素分成了(1,n-1)。
採取措施:pivot 的選取是通過 random 來進行 。
各個排序演算法的穩定性,並給出理由。
選擇排序、快速排序、希爾排序、堆排序不是穩定的排序演算法,而氣泡排序、插入排序、歸併排序和基數排序是穩定的排序演算法。