目录
交换排序
冒泡排序(Bubble Sort)
原理:
这种排序方法是通过相邻的两个元素两两比较,根据大小来交换位置,最值元素就像气泡一样从左侧向右侧移动,故名冒泡排序。 冒泡排序是一种计算机科学领域的较简单基础的排序算法。 其基本思路是,对于一组要排序的元素列,依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面,如此继续,直到比较到最后的两个数,将小数放在前面,大数放在后面,重复步骤,直至全部排序完成。
1. fori循环选取arr中第i个元素要操作的元素item
2. forj循环中用item向右依次与每个元素比较,
更大就两者交换顺序,更小则继续向后继续寻找,直到遍历结束
3. 重复1、2步直到arr.length
程序:
static void BubbleSort(int [] arr) {
for (int i = 0;i < arr.length-1;i++){
for (int j = 0;j < arr.length-1-i;j++){
int temp;
if (arr[j] > arr[j+1]) {
swap(arr, j, j+1);
}
}
}
}
快速排序(Quick Sort)
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。
- 从数列中挑出一个元素,称为 “基准”(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
static void QuickSort(int[]arr) {
QuickSort(arr, 0, arr.length-1);
}
static void QuickSort(int[] arr, int low, int high ) {
if (low >= high) return;
int pivotKey = partition(arr, low, high);
QuickSort(arr, low, pivotKey-1);
QuickSort(arr, pivotKey+1, high);
}
static int partition(int[] arr, int low, int high) {
int pivot = arr[low];
while (low < high) {
while (low < high && arr[high] > pivot) {//如果high元素已经大于pivot,左移high
high--;
}//此时high元素<=pivot
arr[low] = arr[high];//比pivot小的记录移到low端
while (low < high && arr[low] <= pivot) {//如果low元素已经小于pivot,右移low
low++;
}//此时low元素>pivot
arr[high] = arr[low];//比pivot大的记录移到high端
arr[low] = pivot;
}
return low;
}
static void swap(int[] arr, int a, int b) {
int temp = arr[b];
arr[b] = arr[a];
arr[a] = temp;
}
插入排序
直接插入排序(Direct Insertion Sort)
插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序数组中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常为in-place排序,因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
- 把待排序数组的左侧视作为一个已经排列好的有序数组
- 选取有序数组右侧的第一个元素item
- 将item从右到左的与有序数组中的元素elem依次比较。如果item更大,则elem往左的所有元素都比item小,item找到位置,在该位置插入;如果item更小,则与elem交换位置,继续向左比较
- 重复步骤2、3,直到整个数组有序
/**
* Common Insertion Sort
* 1.pick an item
* 2.iterating to exchange with left element
* till meet a target element that less than item.
* 3.insert item to index of target.
*/
public static void InsertionSort(int [] arr) {
int sorted;
for (int i = 1; i < arr.length; i++) {// pick out element to insert
sorted = arr[i];
// Direct Insertion Sort
int j;
// iter to leftmost or meet a target element that less than item
for (j = i; j > 0 && sorted < arr[j - 1]; j--) arr[j] = arr[j-1];
arr[j] = sorted;
}
}
希尔排序(Shell Sort)
希尔排序可以认为是直接插入排序的优化形式,它的原理是通过一个gap increment(步长)来把待排序数组分割成许多个大小=gap increment的子数组(序列末尾分割出的子序列长度可能小于gap increment),再对这些子列进行直接插入排序。每一组子列排成有序后,整体就变有序了。
在希尔排序中,gap increment起到十分重要的作用,不同gap increment的选取直接关系到了该算法的性能表现,这也是这个算法之所以优于直接插入排序的地方。
这里给出不同步长对排序性能的影响图
/**
* ShellSort:
* @methodology gap increment sequence -> direct insertion sort
* Using gap increment sequence to divide the array in many groups,
* then use direct insertion sort to those smaller groups.
*/
public static void ShellSort(int[] arr) {
int sorted;
for (int gap = arr.length / 2; gap > 0;
gap = gap == 2 ? 1 : (int)(gap / 2.2)) {//gap increment sequence
//use gap increment sequence to separate arr by groups
for (int i = gap; i < arr.length; i++) {
sorted = arr[i];
int j = i;
//and then direct insertion sort
for (; j >= gap && sorted < arr[j - gap]; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = sorted;
}
}
}
归并排序(Merge Sort)
归并排序(Merge sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
算法思路
归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
在实际实现上,为了确保排序算法的性能表现,我们会通过in-place排序,也就是通过分解操作待排序数组的索引来实现将序列分解的操作。
分解
将待排序的数组通过分割索引指针不断分成两个子数组,直到子数组只有1-2个元素。
合并
创建tmp数组用来存储比较结果
定义三个指针指向i,j,k指向tmp数组与两待合并数组A,B的左侧下标。
比较 A[i] 和 B[j] 的大小,将小的元素放入 tmp[k] 中,并将对应指针向后移动一位。
重复步骤 2,直到其中一个数组的元素全部放入 tmp 中。
将另一个数组中剩余的元素放入 tmp中。
归并排序的原理图:
public static void MergeSort(int[] L) {
if (L != null)
MergeSort(L, 0, L.length-1);
}
private static void MergeSort(int[] L, int l, int r) {
if ((r - l) <= 0 ) {
return;
}
int m = (l+r)/2;
MergeSort(L, l, m);
MergeSort(L, m+1, r);
Merge(L, l, r, m);
}
private static void Merge(int[] L, int l, int r, int m) {
//creat a tmp arr to store comparison result
int[] tmp = new int[r - l + 1];
int i = l, j = m + 1, k = 0;
//compare data between l-m and m+1 r,the smaller place into tmp
while (i <= m && j <= r) {
if(L[i] >= L[j]) {
tmp[k++] = L[j++];
} else {
tmp[k++] = L[i++];
}
}
//copy the rest
while (i <= m) {
tmp[k++] = L[i++];
}
while (j <= r) {
tmp[k++] = L[j++];
}
//copy back
System.arraycopy(tmp, 0, L, l, tmp.length);
}
选择排序(Selection Sort)
选择排序(Selection Sort)是一种简单的排序算法,其基本原理是在未排序的元素中找到最小(或最大)的元素,然后将其放在已排序的序列的末尾。重复这个过程,直到所有元素都被排序完毕。
- 遍历整个待排序的数组,从第一个元素开始。
- 在未排序的部分中,找到最小(或最大)的元素,并将其与第一个元素交换位置。
- 接着从第二个元素开始,重复步骤2,直到所有元素都被排序
static void SelectionSort(int[] arr) {
int small;//{1, 2, 1, 5, 15, 1, 156, 8, 123}
for (int i = 0; i < arr.length; i++){
small = i;
//遍历数组选择最小值索引
for (int j = i; j < arr.length; j++) {
if (arr[j] < arr[small]) small = j;
}
//依次交换到前面,形成有序列
swap(arr, i, small);
}
}
static void swap(int[] arr, int a, int b) {
int temp = arr[b];
arr[b] = arr[a];
arr[a] = temp;
}
算法性能
选择排序 | O(N^2) | O(N^2) | O(N^2) | 不稳定 | O(1) |
插入排序 | O(N) | O(N^2) | O(N^2) | 稳定 | O(1) |
冒泡排序 | O(N) | O(N^2) | O(N^2) | 稳定 | O(1) |
希尔排序 | O(N) | O(N^(3/2)) | O(N^S)(1<S<2) | 不稳定 | O(1) |
快速排序 | O(NlogN) | O(NlogN) | O(N^2) (1<S<2) | 不稳定 | O(logN) |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | 稳定 | O(N) |
要注意的是,条条大路通罗马,以上排序算法的代码通常只是作为实现算法思路的一种形式,不同的排序算法或者代码之间也往往都存在着一些局限性与可优化空间,大家可以自己动手去试着优化一下。
除此之外,经典算法中还有相对来说更高级的堆排序、计数排序、桶排序和基数排序。这些排序往往都会使用一些更为复杂的数据结构来实现。我们会在以后提及。