1、插入排序
插入排序由 N − 1 N-1 N−1 趟排序组成,对于 p = 1 p=1 p=1 到 N − 1 N-1 N−1 趟,插入排序保证位置 0 0 0 到 p p p 上的元素为已排序状态。
- 排序思路是,在第 p p p 趟时,将位置 p p p上的元素向左移动,直到在前 p + 1 p+1 p+1 个元素中找到正确位置。
public static void insertionSort(int[] array){
int j;
for (int p = 1; p < array.length; p++){
int tmp = array[p];
for (j = p; j > 0 && tmp < array[j - 1]; j--) {
array[j] = array[j - 1];
}
array[j] = tmp;
}
}
2、希尔排序
- 又称作为缩减增量排序,通过比较一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减少,直到比较相邻的元素为最后一趟排序为止。
- 希尔排序使用一个序列为 h 1 h_{1} h1, h 2 h_{2} h2,…, h t h_{t} ht 叫作增量序列,只要 h 1 = 1 h_{1}=1 h1=1 任何增量序列都是可行的,只不过有些增量序列更好。Shell建议(但是不好)的序列是 h t = ⌊ N / 2 ⌋ h_{t}=⌊N/2⌋ ht=⌊N/2⌋, h k = ⌊ h k + 1 / 2 ⌋ h_{k}=⌊h_{k+ 1}/2⌋ hk=⌊hk+1/2⌋,使用希尔增量的最坏情形是 O ( N 2 ) O(N^2) O(N2)。
- 但希尔增量未必互素,Hibbard 提出了一个增量序列 1 , 3 , 7 , … 2 k − 1 1,3,7,…2^k-1 1,3,7,…2k−1。这些增量没有公因子,最坏情形为 O ( N 3 / 2 ) O(N^{3/2}) O(N3/2),但还有更优的增量序列。
- 一趟 h k h_{k} hk 排序就是对 h k h_{k} hk 个小数组进行插入排序。
public static void shellsort(int[] a){
int j;
for (int gap = a.length / 2; gap > 0; gap = gap / 2) {
for (int i = gap; i < a.length; i++){
int tmp = a[i];
for (j = i; j >= gap && tmp < a[j - gap]; j -= gap){
a[j] = a[j - gap];
}
a[j] = tmp;
}
}
}
3、堆排序
第一步以线性时间建立一个堆,然后通过每次将堆中的最后元素与第一个元素交换,执行 N − 1 N-1 N−1次deleteMax操作,每次将堆的大小缩减1并进行下滤,当算法终止时,数组则以排好的顺序包含这些元素。
public static int leftChild(int i){
return 2 * i + 1;
}
public static void perDown(int[] a, int i, int n){
int child;
int tmp;
for (tmp = a[i]; leftChild(i) < n; i = child) {
child = leftChild(i);
// 找出较大的子节点
if(child != n - 1 && a[child] < a[child + 1]){
child++;
}
// 不断比较,直到tmp移动到了合适的位置
if(tmp < a[child]){
a[i] = a[child];
}else {
break;
}
}
a[i] = tmp;
}
public static void heapsort(int[] a){
for (int i = a.length / 2 - 1; i >= 0; i--) {
perDown(a, i, a.length); /* 从最后一个非叶子节点开始向前bulidheap */
}
for (int i = a.length - 1; i > 0; i--){
swapReference(a, 0 ,i); /* 堆的0号节点放的是最大值,每次选出最大值 */
perDown(a,0, i); /* 对i个数重新bulidheap */
}
}
private static void swapReference(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
4、归并排序
- 归并排序采用分治的思想,递归的将前半部分数据和后半部分数据各自归并排序,然后再合并。
- O ( N l o g N ) O(NlogN) O(NlogN)、空间复杂度 O ( N ) O(N) O(N)、稳定
public static void mergesort(int[] a, int left, int right) {
if(left >= right) return;
int mid = left + ((right - left) >> 1);
mergesort(a, left ,mid);
mergesort(a, mid + 1, right);
merge(a, left, mid, right);
}
public static void merge(int[] a, int leftPos, int rightPos, int rigthEnd) {
int[] tmp = new int[rigthEnd - leftPos + 1];
int q1 = leftPos, q2 = rightPos + 1;
int index = 0;
while (q1 <= rightPos && q2 <= rigthEnd) {
tmp[index++] = a[q1] <= a[q2] ? a[q1++] : a[q2++];
}
while (q1 <= rightPos) {
tmp[index++] = a[q1++];
}
while (q2 <= rigthEnd) {
tmp[index++] = a[q2++];
}
for (int i = 0; i < tmp.length; i++) {
a[leftPos + i] = tmp[i];
}
}
5、快速排序
(1)、 随便选取一元素为枢纽元,随后将数组被分为两组组,第一组小于被选项,第二组大于被选项,然后对第一组和第二组各自再排列。
(2)、选取枢纽元时要注意:
- 一种错误的做法:通常,无知的做法是选取第一个为枢纽元
- 一种安全的做法:随机选取枢纽元,不过随机树的生成开销很大
- 三数中值分割法:选取左端,右端,中心位置的三数中位数为枢纽元
(3)、对于很小的数组(N<20),快排不如插入排序。
// 元素小于CUTOFF时,使用插入排序
private static int CUTOFF = 3;
public static void quicksort(int[] a) {
quicksort(a, 0, a.length - 1);
}
public static void quicksort(int[] a, int left, int right) {
if (left + CUTOFF <= right) {
int pivot = median3(a, left, right);
int i = left, j = right - 1;
for(;;){
while (a[++i] < pivot){ }
while (a[--j] > pivot){ }
if( i < j){
swapReference(a, i ,j);
}else {
break;
}
}
swapReference(a, i ,right - 1);
quicksort(a, left, i - 1);
quicksort(a, i + 1, right);
}else {
insertionSort(a, left, right);
}
}
public static void insertionSort(int[] array, int left, int right){
int j;
for (int p = left + 1; p <= right; p++){
int tmp = array[p];
for (j = p; j > 0 && tmp < array[j - 1]; j--) {
array[j] = array[j - 1];
}
array[j] = tmp;
}
}
/*
三数中值分割法,将中间值放在 right - 1, 最大的在right
*/
public static int median3(int[] a, int left, int right) {
int mid = (left + right) / 2;
if (a[mid] < a[left]) swapReference(a, left, mid);
if (a[right] < a[left]) swapReference(a, left, right);
if (a[right] < a[mid]) swapReference(a, mid, right);
swapReference(a, mid, right - 1);
return a[right - 1];
}
参考文献:Data Structures and Algorithm Analysis in Java, Third Edition, Mark Allen Weiss.