选择排序
核心要点
总是选择出数组索引i右边的最小数,与数组索引i替换内容, 从而让数组索引i左边的元素总是升序的。 然后下标递增,重复上述过程。
推导步骤:
- 首先以数组array第一个元素 i 作为下标, 数组索引i右侧的所有元素的最小值min.
- 交换索引min和i的数组内容,这样确定了数组索引i左侧的元素总是是升序的。
- 移动数组索引i,重复上面两个步骤
- 数组索引移动到末尾时,即i=N-1, 此时,数组索引左边所有元素已经被确定,且是升序,至此排序结束。
算法复杂度:O(n2)
代码片段
@Override
void sort(Comparable[] array) {
if (array == null || array.length == 0){
return;
}
int N = array.length;
for(int i = 0; i<N; i++){
int min = i;
for (int j = i+1; j < N; j++){
if (less(array[j],array[min])){
min = j;
}
}
exchange(array,i,min);
}
}
插入排序
核心要点
插入排序和选择排序类似,把数组索引i左边的元素升序排好,不同是插入排序不用考虑数组索引i右边的元素大小。
推导步骤
为什么叫插入排序?把待排索引i内的元素插入到左侧已排序数组里的比它大的元素前面。
- 数组索引i从开始,排序索引i的左侧元素。
- 索引j = i,如果是索引j比它的前一个元素a[j-1]大,则两者交换元素内容,索引j递减,继续比较索引j左侧剩余的元素,直到array[j-1]小于array[j]为止。
算法复杂度:O(n2)
代码片段
@Override
void sort(Comparable[] array) {
if (array == null || array.length == 0 || array.length == 1) {
return;
}
int N = array.length;
for (int i = 0; i < N; i++) {
for (int j = i; j > 0; j--) {
if (less(array[j], array[j - 1]))
exchange(array, j - 1, j);
else
break;
}
}
}
合并排序
核心思想
归并排序是采用归并的思想来排序的算法,该算法采用经典的分治策略,分治法中,分是指把问题分成一些小问题,然后递归求解;而治则是把分的阶段得到的答案修补在一起,即分而治之。
步骤
1.利用分治法,把数组分层两等份,先排序好前一半份,然后排序好后一半份。
2.两等份合并。合并的时候会利用一个辅助数组,原始数组拷贝到辅助数组,然后合并两个子数组的内容到原始数组中。
3.递归,重复上面的步骤,直到lo>=hi,退出递归
代码片段
public void sort(int[] a, int aux[], int lo, int hi) {
if (lo >= hi) {
return;
}
int mid = lo + (hi - lo) / 2;
sort(a, aux, lo, mid);
sort(a, aux, mid + 1, hi);
merge(a, aux, lo, hi);
}
public void merge(int a[], int aux[], int lo, int hi) {
for (int k = lo; k <= hi; k++) {
aux[k] = a[k];
}
int mid = lo + (hi - lo) / 2;
int i = lo;
int j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid) {
a[k] = aux[j++];
} else if (j > hi) {
a[k] = aux[i++];
} else if (less(aux, i, j)) {
a[k] = aux[i++];
} else {
a[k] = aux[j++];
}
}
}
算法复杂度:O(log2n)
快速排序
核心思想
采用分而治之的思想,利用递归算法可以排序数组。
1.定义一个递归函数 sort(lo: Int, hi: Int, nums: IntArray)
2.推导过程:先拆分问题,选择基准点P,把数组拆分成两个区间,
即[lo, p-1]<=p<= [p+1,p], 把得到的两个区间,分别拆分成两个子问题,分别重复上述过程。
3.终止条件: lo <= hi时, 数组已经不能继续拆分,得到固定的值。
分的过程:
寻找基准点: 定义partition函数,求得p点,使得[lo, p-1]<=p<= [p+1,p]
治的过程:重复上面步骤,直到数组不能继续拆分。
步骤
1.找到数组基准点(交换双指针法)。假定数组第一个数为基准值v=a[lo],定义两个指针i,j,分别从左,右开始扫描数组,当左指针i大于基准值,则暂停左指针向右移动,向左移动右指针,当右指针 j小于基准值,则交换i和j,继续开始前面右移动左指针和左移动右指针,直到指针i和j交错,停止扫描。
交换lo和j,确定新的基准点是j,得到当前数组排序状态是a[lo]…aj-1]<=a[j]<=a[j+1]…a[hi],这样子序列就被排序好了。
2.继续以j为基准点,开始递归排序基准点左右的子数组。
3.直到j大于数组边界。
代码
public int partition(int[] array, int lo, int hi) {
int i = lo;
int j = hi + 1;
int v = array[lo];
while (true) {
while (less(array[++i], v)) { // 如果左指针大于基准点v,则暂停扫描准备交换它
if (i == hi) break;
}
while (less(v, array[--j])) { // v<=j
if (j == lo) break;
}
exch(array, i, j);
if (i >= j) break;
}
exch(array, lo, j);
return j;
}
public void sort(int[] array, int lo, int hi) {
if (lo >= hi) return;
int j = partition(array, lo, hi);
sort(array, lo, j - 1);
sort(array, j + 1, hi);
}
}
算法复杂度:O(nlogn)
堆排序
核心思想
堆排序是基于堆这样的数据结构的唯一一个让比较和插入的次数都不超过对数级别的排序算法。
步骤:
堆是一个数组,以数组下标为1开始的连续存储空间。
堆是解决集合最值问题的高效数据结构。
堆排序的流程:
- 在原数组上建立堆结构。从下往上,对每个子树构建堆。
- 将堆顶元素与堆末元素进行调换,再对堆顶元素进行向下调整。
- 经过n轮操作后,数组中的元素就有序了。
难点:如何构建数据结构堆?如何下沉?
代码
public void swim(Comparable pq[], int k) {
while (k > 1 && less(pq, k / 2, k)) {
exch(pq, k / 2, k);
k = k / 2;
}
}
public void sink(Comparable pq[], int k, int N) {
while (2 * k <= N) { //<=N 为什么是小于等于N。因为左子节点最大为N
int j = 2 * k;//左子节点,由堆的性质而来,父节点为k,两个子节点为2k,2k+1
if (j < N && less(pq, j, j + 1)) j++; // <N
if (!less(pq, k, j)) break;
exch(pq, k, j);
k = j; //=j
}
}
public void sort(Comparable[] array) {
int n = array.length;
for (int k = n / 2; k >= 1; k--) { //如何构建数据结构堆?从底部向上(父节点k = n /2 )开始构造堆。
sink(array, k, n);
}
int k = n;
while (k>1){
exch(array, 1, k--);
sink(array,1, k);
}
}