一、引言
本文主要是复习总结了下一些常用的基础算法,按照自己的理解手打、验证,因此本文并没有做深入探究,只是描述本人所理解的实现思路,更多算法知识还请研读如:
- 《算法》 第四版
- 《数据结构与算法分析-Java语言描述》
二、常见基础算法
1. 简单排序
1). 冒泡排序
平均时间复杂度: O(n^2)
;
实现思路: 取数组中的每一个元素与数组中的各个元素比较,当满足比较条件(a > b
或 a < b
)时将两个元素交换位置。
static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
if (arr[i] < arr[j]) {
swap(arr, i, j);
}
}
}
}
static void swap(int[] arr, int posOne, int posTwo) {
int temp = arr[posOne];
arr[posOne] = arr[posTwo];
arr[posTwo] = temp;
}
2). 选择排序
平均时间复杂度: O(n^2)
。 比冒泡排序快
实现思路: 取数组中的每一个元素与数组中(i, arr.length)
范围内的各个元素比较,当满足比较条件(a > b
或 a < b
)时将两个元素交换位置。
static void selectSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = arr.length - 1; j > i; j--) {
if (arr[i] > arr[j]) {
swap(arr, i, j);
}
}
}
}
3). 插入排序
平均时间复杂度: O(n^2)
。 比选择排序快
实现思路: 取数组中[0, i]
范围内的相邻的两个数进行比较,当满足比较条件(a > b
或 a < b
)时将两个元素交换位置。
static void insertSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = i; j > 0 && arr[j] < arr[j - 1]; j--) {
swap(arr, j, j - 1);
}
}
}
4). 希尔排序
最好时间复杂度: O(n)
。 最坏时间复杂度为 O( n(logn)^2 )
实现思路: 与插入排序类似,只是希尔排序中多了个叫做增量序列的间隔,使得希尔排序并不是像插入排序那样比较、交换相邻的两个元素,而是比较、交换相邻间隔为gap
的两个元素,这里我们取初始间隔为数组长度的一半,即gap = arr.length / 2
, 并在一轮比较完成之后再取gap
的一般,即gap = gap / 2
, 则有如下实现:
static void shellSort (int[] arr){
for (int gap = arr.length / 2; gap > 0; gap /= 2){
// 注意这里往下是个完整的插入排序算法, 只不过原本间隔为 1 变成了这里的 gap
for (int i = gap; i < arr.length; i++){
for (int j = i; j >= gap && arr[j] < arr[j - gap]; j = j - gap){
swap (arr, j, j - gap);
}
}
}
}
所以希尔排序中的比较过程可以描述如下:
2. 分治算法
1). 二分查找
平均时间复杂度: O(logn)
;
实现思路: 将整个有序数组等分成两部分,取等分位置的元素与目标元素进行比较,因为是有序数组,所以可以根据比较条件(a > b
或 a < b
)来排除数组左半部分或者右半部分的元素,然后重复这个操作流程直到找到元素为止。
整个分解过程如下,需要注意的是,左右两边实际只会排除掉一边,因此可以单纯关注左边或者右边的分解过程。
static int binSearch(int[] arr, int target){
if (arr == null || arr.length == 0) return -1;
int left = 0, right = arr.length;
while (left < right) {
// 一分为二
int center = (left + right) >> 1;
if (arr[center] < target) { // 说明在右边
left = center + 1;
}else if (arr[center] > target ){ // 说明在左边
right = center - 1;
}else{ // 说明找到目标
return center;
}
}
return -1;
}
2). 归并排序
平均时间复杂度: O(nlogn)
;
实现思路: 分解过程还是跟二分查找中的分解过程一致,不同于二分查找,排序并不需要排除,因此会递归向下分解到不能再分解时向上对分解的块进行比较排序,直到排序完成。比较方式如下:
其中center
将数组分成了两个部分,leftIndex
和rightIndex
两个索引分别用于标识左右两部分数组当前用于比较的元素的位置,整体限制在 [left, right]
区间内,而左半部分限制在 [left, center]
,右半部分限制在 (center, right]
。
从左右半部分数组中依次取值比较,当满足比较条件(a > b
或 a < b
)时,将元素存入暂存数组中,直到左右部分中的其中一个超出限制后将另一部分数组的多余部分全部存入暂存数组,最后将暂存数组中的值替换到原数组的区间 [left, right]
内,这就完成了一层数组(分解后)的比较。
static void mergeSort (int[] arr , int[] tempArr, int left, int right){
if (arr == null || left >= right) return;
// 二分
int center = (left + right) >> 1;
// 继续分解左半部分
mergeSort(arr, tempArr, left, center);
// 继续分解右半部分
mergeSort(arr, tempArr, center + 1, right);
// 处理分解好的数据块
merge(arr, tempArr, left, right, center);
}
static void merge(int[] arr, int[] tempArr, int left, int right, int center){
int tempIndex = 0, leftIndex = left, rightIndex = center + 1;
// 比较数量相等部分
while ( leftIndex <= center && rightIndex <= right){
if (arr[ leftIndex] <= arr[rightIndex]){
tempArr[tempIndex++] = arr[leftIndex++];
}else{
tempArr[tempIndex++] = arr[rightIndex++];
}
}
// 如果左半部分数组较多,那么把多出部分直接填充到暂存数组中
while (leftIndex <= center) {
tempArr[tempIndex++] = arr[leftIndex++];
}
// 如果右半部分数组较多,那么把多出部分直接填充到暂存数组中
while (rightIndex <= right) {
tempArr[tempIndex++] = arr[rightIndex++];
}
System.out.println(Arrays.toString(tempArr));
// 把排序完的部分覆盖到源数组的对应位置上
int total = right - left + 1;
for (int i = 0; i < total; i++) {
arr[left + i] = tempArr[i];
}
}
3). 快速排序
平均时间复杂度: O(nlogn)
;最坏情况 O(n^2)
实现思路: 首先需要确定一个基准值,这里最简单的方式就是取数组的第一个元素(切分后则是当且部分的第一个元素)作为基准值,然后建立两个指针从数组开头和末尾依次向数组中央比较、靠拢,这个过程是为了让比基准值小的元素分布在基准值的左边,而比基准值大的元素分布在基准值的右边,比较过程如下草图:
而经过一轮比较后数组元素分布如下:
当然,不可能经过一轮比较就排序完成,因此还需要切分数组,并继续前面的操作,直到比较完成。
static void quickSort(int[] arr, int left, int right) {
if (arr == null || left >= right) {
return;
}
// 1. 确定基准值,这里只是简单的以左边部分第一个元素为
int pivot = arr[left];
int leftIndex = left, rightIndex = right;
while (true) {
// 2. 小于基准值的元素放到其左边
while ( arr[leftIndex] <= pivot && leftIndex < right) leftIndex++;
// 3.大于基准值的元素放到其右边
while ( arr[rightIndex] >= pivot && rightIndex > left) rightIndex--;
// 4. 碰到左边有大于基准值,而右边有小于基准值的元素时,将它们交换位置
if ( leftIndex < rightIndex ){
swap(arr, leftIndex, rightIndex) ;
}else{
break;
}
}
// 5. 重置基准值位置
swap(arr, rightIndex, left);
// 6. 切分数组
quickSort(arr, left, rightIndex - 1);
quickSort(arr, rightIndex + 1, right);
}
三、总结
本文主要回顾了一下一些常用基础算法,手打、验证了一遍,嗯、就这样。