算法——排序之簡單排序

排序是將一組對象按照某個邏輯順序重新排列的過程。現在計算機的廣泛使用使得數據無處不在,整理數據就變得非常重要了。而整理數據的第一步往往都是進行排序。


簡單排序

冒泡排序

原理:

將所有臨近的兩個的對象按照某個邏輯一一進行比較,通常是由大到小或小到大,比較之後交換兩個對象的位置。

反覆操作,最終即可排序成功。


如上圖所示,我們發現,進行一次循環,冒泡排序只能確定一個對象的位置。第一次循環只能夠確定一個對象的位置。所以我們需要多少次循環呢?我們就可以的得出,我們總共需要n-1次循環。因爲n-1次循環能確定n-1個對象的位置,這時候自然最後一個對象的位置也確定了。

代碼如下:

public static void sort(Comparable[] a) {
	for (int i = 0; i < a.length - 1; i++) {
		for (int j = 0; j < a.length - 1; j++) {
			if (less(a[j + 1], a[j])) {
				swap(a, j, j + 1);
			}
		}
	}
}
實際也就是依靠兩層循環,外層控制循環次數,內層逐個進行比較。


冒泡排序的優點就是簡單,空間複雜度低。缺點就是慢!無用功多。

時間複雜度O(n^2)


選擇排序

原理:

首先找到當前數組中最小的元素,將這個元素和第一個元素進行交換位置。其次,在剩下的數組中找到最小的元素,將它和數組中第二個元素進行位置的交換。依次類推,就可以達到排序的效果。簡單來說就是每次都選最小的那個,放在前面。

代碼:

public static void sort(Comparable[] a) {
	for (int i = 0; i < a.length - 1; i++) {
		int minIndex = i;
		for (int j = i + 1; j < a.length; j++) {
			if (less(a[j], a[minIndex])) {
				minIndex = j;
			}
		}
		swap(a, minIndex, i);
	}
}

選擇排序將數組分成兩個部分,已經排序好的和未排序兩部分。每次都從未排序部分中找到最小的元素,增加到排好序的末尾。

同樣的,外層循環控制次數,內層循環找到最小的元素。外層循環,因爲只要找到n-1個元素的位置,第n個元素的位置就直接確定了。所以外層循環需要n-1次。

我個人認爲選擇排序是最簡單理解的排序之一。

時間複雜度O(n^2)


插入排序

插入排序就和平常打撲克牌一樣。整理撲克牌的一般使用的就是插入排序。整理方法是一張一張來,將每一張牌插入其他已經有序的牌中的適當位置。當然,在數組操作中,如果需要給某個元素插入,騰出空間,則需要將其他元素向後移動一位。


代碼“:

public static void sort(Comparable[] a) {
	for (int i = 1; i < a.length; i++) {
		for (int j = i - 1; j >= 0 && less(a[j+1], a[j]); j--) {
			swap(a, j, j + 1);
		}
	}
}
這個代碼簡潔,但是卻不容易讓人看懂。不個人不推薦這麼做。而是應該將思路表達的清晰。

改變:

public static void sort(Comparable[] a) {
	for (int i = 1; i < a.length; i++) {
		int position = findPosition(a, i); // 找到應該插入的位置
		Comparable tempI = a[i];
		for (int j = i - 1; j >= position; j--) { // 爲插入的位置騰出空間,向後挪動
			a[j + 1] = a[j];
		}
		a[position] = tempI; // 插入值
	}
}

public static int findPosition(Comparable[] a, int i) {
	for (int j = i - 1; j >= 0; j--) {
		if (less(a[j], a[i])) return j + 1;
	}
	return 0;
}

和選擇排序不同,插入排序所需的時間和取決於輸入元素的初始順序。對一個已經接近有序的數組進行排序,會比對順序隨機的數組進行排序快得多。

平均時間複雜度:O(n^2)。最好情況時間複雜度O(n)。

插入排序對於部分有序的數組來說非常高效,這是它最大的優點。


希爾排序

希爾排序是對插入排序的一個優化。對於大規模的亂序數組,插入排序會比較緩慢,因爲他需要慢慢的向前找到位置,所以元素只能一點一點的從數組中移動到另一端。

希爾排序本質就是分組的插入排序,但是它並不需要一個一個比較,而是能將元素一下子移到一個比較遠的地方。

原理:

先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然後依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因爲直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。


上圖中,方框指向的是同一組,也就是將同一組數據排序。當分組越來越少,一組中元素越來越多的時候,排序就基本完成了。而最終,當所有元素都在同一組中的時候,就是簡單插入排序。因爲這個時候數組已經接近有序了,所以排序起來非常快。


代碼:

public static void sort(Comparable[] a) {
	int gap = 1;
	while (gap < a.length /3) gap = 3*gap + 1;
	while (gap >= 1) {
		
		for (int i = 0; i < gap; i++) {
			for (int j = i + gap; j < a.length; j += gap) {
				int position = findPosition(a, i, j, gap);
				Comparable tempJ = a[j];
				for (int k = j - gap; k >= position; k -= gap) {
					a[k + gap] = a[k];
				}
				a[position] = tempJ;
			}
		}
		
		gap /= 3;
	}
}

public static int findPosition(Comparable[] a, int begin, int current, int gap) {
	for (int i = current - gap; i >= begin; i -= gap) {
		if (less(a[i], a[current])) return i + gap;
	}
	return begin;
}
和插入排序非常類似,僅僅是分組的插入排序。


比較:

代碼:

public static void main(String[] args) {
  final int NUM = 10000;
  Integer[] a1 = new Integer[NUM];
  Integer[] a2 = new Integer[NUM];
  Integer[] a3 = new Integer[NUM];
  Integer[] a4 = new Integer[NUM];
  for (int i = 0; i < NUM; i++) {
    a1[i] = (int)(Math.random() * NUM);
    a2[i] = a1[i];
    a3[i] = a1[i];
    a4[i] = a1[i];
  }
  
  long startTime;
  long endTime;
  /*
   * 交換
   * */
  startTime = System.currentTimeMillis();   //獲取開始時間
  switchSort.sort(a2);
  assert isSorted(a2);
  endTime = System.currentTimeMillis();
  System.out.println("交換排序cost: " + (endTime - startTime) + " ms");
  
  /*
   * 插入
   * */
  startTime = System.currentTimeMillis();   //獲取開始時間
  InsertSort.sort(a3);
  assert isSorted(a3);
  endTime = System.currentTimeMillis();
  System.out.println("插入排序cost: " + (endTime - startTime) + " ms");
  
  /*
   * 冒泡
   * */
  startTime = System.currentTimeMillis();   //獲取開始時間
  bubbleSort.sort(a1);
  assert isSorted(a1);
  endTime = System.currentTimeMillis();
  System.out.println("冒泡排序cost: " + (endTime - startTime) + " ms");
  
  /*
   * shell
   * */
  startTime = System.currentTimeMillis();   //獲取開始時間
  ShellSort.sort(a4);
  assert isSorted(a4);
  endTime = System.currentTimeMillis();
  System.out.println("希爾排序cost: " + (endTime - startTime) + " ms");
}
運行多次,我們會發現:

當數據量不大的時候,插入排序比交換排序快。而數據量大起來,交換排序就比插入排序更加快了。

當然,如果數組是接近有序的,插入排序的性能會非常強大。

冒泡排序比較緩慢。

而希爾排序的性能非常不錯。即使數據量非常大,希爾排序的性能也是不錯的。希爾排序的平均時間複雜度是O(n^1.3)左右。相對簡單插入排序來說確實是極大的提升。並且希爾排序的代碼量並不大。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章