排序是在開發過程中經常要面臨的操作,很多複雜的問題往往能夠通過將序列排序而變得更加簡單。一個好的排序算法往往能節省大量的時間和空間資源。
由於待排序的記錄數量不同,時的排序過程中使用到的存儲器不同 ,可將排序方法分爲兩大類:內部排序,指的是待排序記錄全部存放在計算機隨機存儲器中的排序過程;外部排序,指待排序記錄數量非常大,一次不能全部放到內存中,需要對訪問外存中的記錄進行訪問的排序過程。
內部排序
-
分類
按照內部排序的原則進行分類可分爲:插入排序、交換排序、選擇排序、歸併排序和計數排序五大類。
按照排序過程中所需要的工作量來排序可分爲:簡單排序(時間複雜度爲O(n^2))、先進的排序方法(時間複雜度O(nlogn))和基數排序(時間複雜度爲O(d*n))。 -
插入排序
插入排序是一種最簡單的排序方法,他的基本操作是將一個記錄插入到一個有序的列表中,然後獲得一個新的、有序的、記錄數加一的新的列表。
//直接插入排序
void insertSort (int[] a) { //非遞減排序
int n = a.length;
if (n < 2) return; //待排序的記錄少於兩個
for (int i = 1; i < n; i ++) { //a[0]爲初始有序序列,進行n - 1次插入排序
int t = a[i]; //待插入元素
int j = 0;
for (j = i - 1; j >= 0 && a[j] > a[t]; j --) { //對於大於待插入元素的的記錄右移
a[j + 1] = a[j];
}
a[j + 1] = t;
}
}
插入排序是在一個有序序列的基礎上,查找插入記錄的插入位置,然後在此位置上進行插入而實現的,倘若我們將查找插入位置的過程採用折半查找則可以演化出顯得插入排序方式 ——折半插入。
//折半插入排序
void bInsertSort( int[] a) {
int n = a.length;
if (n < 2) return;
for (int i = 1; i < n; i ++) {
int left = 0, right = i - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (a[mid] < a[i]) left = mid + 1;
else right = mid - 1;
}
int t = a[i];
for (int j = i - 1; j >= right + 1; j --) {
a[j + 1] = a[j];
}
a[right + 1] = t;
}
}
折半插入減少了待插入記錄與有序表中記錄比較的次數,但是移動元素的次數並沒有減少,所以時間複雜度仍舊爲O(n^2)。爲了減少記錄移動的次數又提出了2路排序算法。
//2路插入排序
int[] insertSort2(int[] a) {
int n = a.length;
if (n < 2) return a;
int[] b = new int[n];
b[0] = a[0];
int first = 0;
int last = 0;
for (int i = 1; i < n; i ++) {
if (a[i] >= b[0]) {
int j = 0;
for (j = last; a[i] < b[j]; j --) b[j + 1] = b[j];
b[j + 1] = a[i];
last ++;
} else {
if (first == 0) {
b[n - 1] = a[i];
first = n - 1;
} else {
int j = 0;
for (j = first; b[j] < a[i]; j ++) b[j - 1] = b[j];
b[j - 1] = a[i];
first --;
}
}
}
return b;
}
希爾排序又稱縮小增量排序,它也屬於插入排序類的方法,但是在時間效率上大有改進。其時間複雜度與所取得增量序列有關,計算方式較爲複雜,由於涉及到數學上一些尚未解決的問題,所以沒有計算方法。
希爾排序的基本思想是將整個待排序列劃分爲多個子序列,然後對每個子序列進行直接排序,根據增量序列進行多次排序後,整個序列便會變得基本有序,這樣一來再使用直接插入排序將變得更加簡單。一般選擇d[k] = 2^(t - k + 1)作爲增量序列(有人指出按照此序列排序的時間複雜的爲O(n^(3/2))),t 爲排序趟數。
//希爾排序
void sellSort(int a[],int[] d) {
int n = a.length;
int m = d.length;
if(n < 2 || m < 1) return;
for (int i = 0; i < m; i ++) { //對a進行m趟希爾排序
insertSort(a,d[i]);
}
}
/*
* 一趟希爾排序
* */
void insertSort (int[] a,int d) {
int n = a.length;
for (int i = d; i < n;i ++) {
if (a[i] < a[i - d]) {
int t = a[i];
int j = i - d;
while (j >= 0 && t < a[j]) {
a[j + d] = a[j];
j -= d;
}
a[j + d] = t;
}
}
}
- 交換排序
冒泡排序
大的向下下沉。
//冒泡排序
void guLuGuLuSort(int[] a) {
int n = a.length;
if (n < 2) return;
for (int i = 0; i < n - 1; i ++) {
for (int j = 0; j < n - 1 - i; j ++) {
if (a[j] > a[j + 1]) {
int t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
}
快速排序(O(n*lgn))
//遞歸實現
void quickSort(int[] a,int l,int r) {
if (l >= r) return;
int i = l,j = r;
int mid = a[l];
while (l < r) {
while (l < r && a[r] >= mid) r --;
a[l] = a[r];
while (l < r && a[l] <= mid) l ++;
a[r] = a[l];
}
a[l] = mid;
quickSort(a,i,l - 1);
quickSort(a,l + 1,r);
}
-
選擇排序
簡單選擇排
就是選出n個元素中的最小值放到第一個位置,選出n - 1個元素的最小值放到第二個位置…選出n - i個元素的最小值放到第i + 1個位置。
//簡單選擇排序
void selectSort(int[] a) {
int n = a.length;
if (n < 2) return;
for(int i = 0; i < n; i ++) {
int min = a[i];
int p = i;
for (int j = i;j < n; j++) {
if (a[j] < a[i]) {
min = a[j];
p = j;
}
}
a[p] = a[i];
a[i] = min;
}
}
- 歸併排序
歸併排序是建立在兩個有序了列表的基礎上進行的鏈表的合併。排序開始我們將每一個元素都看作做一個有序列表,這樣一來就可以兩兩合併爲多個有兩個元素的有序列表。然後再把這有兩個元素的有序列表兩兩合併,組成多個有四個元素的有序列表… 以此類推直到合成一個有n個元素的有序列表。
時間複雜度O(n*lgn),空間複雜度O(n)
public void mergeSort (int[] a, int l,int t) {
if (l == t) return;
int m = (l + t) / 2;
mergeSort(a,l,m);
mergeSort(a,m + 1,t);
merge(a,l,m,t);
}
public void merge (int[] a, int l, int m, int t) {
int n = a.length;
int[] b = new int[n];
int i = l, j = m + 1;
int k = l;
while (i <= m && j <= t) {
if (a[i] <= a[j]) {
b[k ++] = a[i ++];
} else {
b[k ++] = a[j ++];
}
}
while (i <= m) {
b[k ++] = a[i ++];
}
while (j <= t) {
b[k++] = a[j++];
}
i = l;
while (i <= t) {
a[i] = b[i ++];
}
}
- 基數排序
/*
* 假設待排對象的元素爲正整數
* */
public void sort(int[] a) {
if (a.length < 2) return;
Note head = new Note();
Note p = head;
int max = Integer.MIN_VALUE;
for (int i = 0; i < a.length; i ++) { //將數組轉換鏈表,順便找一下最大值
if (a[i] > max) {
max = a[i];
}
Note note = new Note();
note.data = a[i];
note.next = null;
p.next = note;
p = p.next;
}
int i = 1;
while (max != 0) { //進行多次分發和收集
head = FAC(head, i);
max /= 10;
i ++;
}
p = head.next;
i = 0;
while (p != null) { //放回到數組
a[i ++] = p.data;
p = p.next;
}
}
/*
* 參數l表示關鍵字的位置,如l = 1則表示個位爲此次分發和收集的關鍵字
* */
public Note FAC(Note head,int l) {
Note[] h = new Note[10]; //十個分發鏈的頭
Note p = head.next;
while (p != null) { //分發
int k = (p.data / (int)Math.pow(10,l -1)) % 10;
if (h[k] == null) {
h[k] = new Note();
}
Note q = h[k];
while (q.next != null) {
q = q.next;
}
q.next = p;
p = p.next;
q.next.next = null;
}
Note newHead = new Note();
for (int i = 0; i< 10; i++) { //合併
if (h[i] != null) {
Note q = newHead;
while (q.next != null) {
q = q.next;
}
q.next = h[i].next;
}
}
return newHead;
}
class Note {
int data;
Note next;
}