常见算法基础题思路简析(一)-排序篇

来源:互联网 发布:桃园恋歌动作数据 编辑:程序博客网 时间:2024/06/07 15:15
  • 常见算法基础题思路简析一排序篇
    1、冒泡排序
    2、计数排序
    3、堆排序
    4、插入排序
    5、归并排序
    6、快速排序
    7、选择排序
    8、基数排序
    9、希尔排序
    10、合并两个有序数组
    11、判断数组中是否有重复值
    12、行列排序矩阵找值
    13、排序后相邻两数的最大差值
    14、计算需要排序的最短子数组的长度
    15、荷兰旗问题

本文对和 排序 有关的常见算法基础题思路进行分析和总结,并以 Java 为例,适当指出需要注意的编程细节

相关题目和代码在 GitHub: https://github.com/brianway/algorithms-learning
题目见 com.brianway.learning.algorithms.lectures.sort包
记号约定:按升序排,数组 A 大小记为 n

1、冒泡排序

思路:

依次交换相邻两个数,使大的在后,每趟在末尾确定一个最大值
外循环 n 趟依次确定最大值,次大值,….,确定排序
注意:

内循环的相邻元素下标写法和内循环下标起始

2、计数排序

思路:

找到数组最大值 max 和最小值 min,以差值(max-min)作为桶数组的长度
遍历数组入桶计数
遍历桶,写回原数组
注意:

入桶时,对应桶下标为 A[i]-min
写回数组时下标依次递增, A[i++] = j + min;

3、堆排序

思路:

初始化成大根堆
将堆顶元素(即最大值)和末尾元素交换,并下沉该换上来的末尾元素
将堆的长度减1,重复第二步
注意:

下标从 1 开始较方便,子节点下标就对应为 2*i 和 2*i+1
初始化时从一半位置依次递减下标使用下沉操作
下沉操作挑子节点中较大值与父节点交换,直至 满足父节点大于两个子节点

private static void sink(Comparable[] pq, int k, int N) {
while (2 * k <= N) {
int j = 2 * k;
if (j < N && less(pq, j, j + 1)) j++;
if (!less(pq, k, j)) break;
exch(pq, k, j);
k = j;
}
}

4、插入排序

思路:

将数组前一部作为一个“有序数组”,该“有序数组”长度逐步扩大
每趟循环将当前元素依次和前一个元素比对,插入到“有序数组”中的相应位置
注意:

注意下标起始,外循环 n-1 次,内循环下标递减
插入效果可以使用两两相邻交换来实现

5、归并排序

这里使用非递归的 bottom-up 的实现方式

思路:

每次合并的单位长度为 size , size 依次取 1,2,… ,直到 size>=n
对于每个 size,从前往后按照步长为 2*size 依次合并
归并的内部逻辑是,先从原数组拷贝一份到辅助数组,再按序填回原数组
注意:

需要分配一个辅助数组用于合并
每次合并需要传递五个参数:原数组,辅助数组以及合并的起止下标和中点下标
归并的截止下标需要取小避免越界 Math.min(n, i + 2 * size);
内循环的终止条件是 i + size < n,步长是 2*size

6、快速排序

思路:

shuffle(可选):先把原数组随机打乱
partition:随机选一个数作为参考值,比其小的放左边,大的放右边
递归地排序上述结果的左半部分和右半部分
注意:

一般参考值就取第一个元素,对后面的元素进行划分
划分时注意循环终止条件,大于小于带不带等号,都要注意

7、选择排序

思路:

每次遍历一趟挑取最小元素,将该元素与起始元素交换
起始元素下标依次为0,1,…,n-1
注意:

比较的是值,记录的是下标

8、基数排序

以元素小于等于 2000 为例,位数为 4

思路:

准备一个 10*n 的桶数组(10 代表 0~9 的数字,n代表某个数字下最多容纳的个数,这里为原数组大小 n)
由低位到高位,将3,4步执行位数次循环
遍历数组,按照某一位的数字将元素入桶
从大数字到小数字,依次将桶内元素从后往前写回原数组
注意:

需要两个辅助数组,一个用于存放元素,一个用于记录每个数字桶的元素个数
入桶时一定要先低位,再高位,这样才能保证最高位影响最大
取桶内元素返到数组时一定要倒着取,这样才能保证之前排序的顺序

9、希尔排序

思路:

对数组进行步长为1,4,13,…,(3*前一个步长+1)的插入排序,步长由大到小
注意:

最外层先计算好最大步长,每次除以三
插入排序的外层起始下标为 h,每次下标递增 1
插入排序的内层递减步长为 h

10、合并两个有序数组

题目:Merge

思路:

从两个数组末端取值,每次取较大者,从后往前填空
当数组 B 的元素分配完时结束

11、判断数组中是否有重复值

题目见 Checker

时间最快 O(n),遍历一遍使用哈希表即可。

若要保证额外空间复杂度为O(1),则使用排序,再遍历,看相邻是否有重值

12、行列排序矩阵找值

题目见 lectures.sort.Finder

思路:由于行列分别有序(升序),从矩阵右上/左下开始找均可在 O(行数+列数) 时间复杂度找到。以右上为例,当前数大于待找数,则同行往前找,否则同列往下找。

13、排序后相邻两数的最大差值

题目见 Gap

直接排序做最快是 O(n*log n)的时间复杂度,这里讨论的是 O(n) 的时间复杂度

思路:来源于桶排序

找到数组的最大值和最小值,准备 n+1 个桶(n为数组元素个数)
最大值放在第 n+1 个桶中,其它桶以 (max-min)/n 的间隔均匀分布(所以最小值一定落在第一个第一个桶中)
将数组中元素依次落入桶中,找出空桶前的桶内元素的最大值和空桶后的桶内元素的最小值,求差即可
注意:

n+1 个桶放 n 个元素,一定会有空桶,同一桶内元素的差值一定小于桶区间,所以来自不同桶之间的相邻元素差值更大。
分别使用 hasNum[n+1], maxs[n+1], mins[n+1] 来表示桶是否为空以及桶内元素的最大最小值

14、计算需要排序的最短子数组的长度

题目见 Subsequence

思路:

从左到右遍历,记录元素值比该元素左边的最大值要小的最右下标 right
从右往左遍历,记录元素值比该元素右边的最小值要大的最左下标 left
如果 right > left,则需要排序的长度为 right-left +1
注意:

right 的初始值为 0,left 的初始值为 n-1
遍历的每一步,要么满足递增(减),更新最大(小)值;要么更新下标

15、荷兰旗问题

题目见 ThreeColor

思路一:计数排序,先入桶再出桶即可。

思路二:类似三路快排,下标[0,lo],(lo,mid],(mid,n-1] 之间的元素各表示一个数值区间

注意:

lo, hi, mid 的起始值要注意
交换时的增减顺序要注意,参考示例:

int lo = -1;
int hi = n;
int mid = 0;
while (mid < hi) {
if (A[mid] < 1) {
swap(A, ++lo, mid++);
} else if (A[mid] > 1) {
swap(A, –hi, mid);
} else {
mid++;
}
}

原创粉丝点击