基础结构:
初始算法
- 数组
- 链表
- 列表
- 队列
- 栈
- 堆
- 二叉树
基础算法篇:
查找算法
- 二叉搜索树
- 平衡二叉搜索树
- 红黑树
- B树
- 跳表
- 散列表
排序算法
- 插入排序
- 希尔排序
- 快速排序
- 归并排序
- 堆排序
- 计数排序
- 桶排序
进阶篇
进阶数据结构
进阶算法
算法思想:
- 贪心
- 回溯
- 动态规划
- 分治
二分查找
前提:给定一个内含n个元素的有序数组A,一个待查找值target
1.设置 i =0, j = n-1
2.如果 i > j , 结束查找,没找到
3.设置m=floor((i+j)/ 2),m为中间索引,floor是向下取整
4.如果target<Am 设置 j = m-1, 跳到第2步
5. 如果Am<target设置i = m+1,跳到第2步
6. 如果Am=target,结束查找,找到了
package org.example;
public class BinarySearch {
/*二分查找基础模板
Params: a-待查找的升序数组
target:待查找的目标值
Returns:
找到返回索引,找不到返回目标值
*/
public static int binarySearchBasic(int[] a, int target) {
int i = 0, j = a.length - 1;//设置指针和初值
while (i <= j) {
int m = (i + j) >>>1;
if (target < a[m]) {//目标值在左边
j = m - 1;
} else if (a[m] < target) {//目标值在右边
i = m + 1;
} else {
return m;//找到了
}
}
return -1;
}
/*
问题1: 为什么是i<=j意味着区间有未比较的元素,而不是i<j?
i,j它们所指向的元素也会参与比较
问题2:(i+j)/2 有没有问题?
/
同一个二进制数
1011_1111_1111_1111_1111_1111_1111_1110
不把最高位视为符号位,代表3221225478
把最高位视为符号位,代表-1073741826
/
改为(i+j)>>>1
无符号右移一位 可以解决数字太大的问题,也适用更多语言。
问题3:都写成小于符号有啥好处?
增强可读性
*/
/*
1 [2,3,4,5] 6 右侧没找到更差
int i = 0, j = a.length - 1; 2
return -1; 1
元素个数为4-7 循环3次 floor(log_2(4))=2+1 向下取整
元素个数为8-15 循环4次 log_2(8)=3+1
16-31 5次
32-63 6次
循环次数 L=floor(log_2(n))+1
i<=j L+1
int m=(i+j)>>>1; L
target<[m]; L
a[m]<target ; L
i=m+1; L
(floor(log_2(n))+1)*5+4
(3)*5+4=19*t
(10+1)*5+4=59*t
*/
/*
二分查找改进版
*/
public static int binarySearchAlternative(int[] a, int target) {
int i = 0, j = a.length;//第一处
while (i < j) { //第二处
int m = (i + j) / 2;
if (target < a[m]) {//目标值在左边
j = m; //第三处
} else if (a[m] < target) {//目标值在右边
i = m + 1;
} else {
return m;//找到了
}
}
return -1;
}
/*
线性查找
*/
public static int linearSearch(int[] a,int target)
{
for(int i=0;i<a.length;i++){
if(a[i]==target){
return i;
}
}
return -1;
}
/*
1.最差的执行情况:
2. 假设每行语句执行时间一样
数组个数n
int i=0; 1
i<a.length; n+1
i++; n
a[i]==target n
return -1; 1
累加一下 3*n+3
3*4+3=19*t
3*1024+3=3075*t
*/
}
时间复杂度
计算机科学中,时间复杂度是用来衡量:一个算法的执行,随数据规模增大,而增长的时间成本
不依赖与环境元素
如何表示时间复杂度呢?
假设算法要处理的数据规模是n,代码总的执行行数用数f(n)来表示,例如:
线性查找算法的函数f(n)=3*n+3
二分查找算法的函数f(n)=(floor(log_2(n))+1)*5+4
为了对f(n)进行化简,应当抓住主要矛盾,找到一个变化趋势与之相近的表示法。
例1:f(n)=3*n+3
g(n)=n
取c=4,在no=3之后,g(n)可以作为f(n)的渐进上界,因此表示法写作O(n)
- O(1) 常量时间,意味着算法时间并不随数据规模而变化
- O(log(n)) 对数时间
- O(n) 线性时间,算法时间与数据规模成正比
- O(n*log(n)) 拟线性时间
- O(n*n) 平方时间
- O(2^n) 指数时间
- O(n!) 复杂度最高
渐进下界:从某个常数n开始,c*g(n)总是位于f(n)下方,那么记作Ω(g(n))
渐进紧界:从某个常数n开始,f(n)总是在c1*g(n)和c2*g(n)之间,那么记作Θ(g(n))
空间复杂度
与时间复杂度类似,一般也用大O表示法来衡量:一个算法执行随数据规模增大,而增长的额外空间成本。
二分查找性能
下面分析二分查找算法的性能
时间复杂度
最坏程度O(log n)
最好情况:如果待查找元素恰好在数组中央,只要循环一次O(1)
空间复杂度
需要常数个指针i,j,m,因此额外占用的空间是O(1)
public static int binarySearch3(int[] a,int target){
int i=0,j=a.length;
while(1<j-i) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m;
} else {
i = m;
}
}
if (a[i] == target) {
return i;
} else {
return -1;
}
}
1.左闭右开的区间,i 指向的可能是目标,而 j 指向的不是目标
2.不在循环内找出,等范围内只剩 i 时,退出循环,在循环外比较a[ j ]与target
3.循环内的平均次数减少了
4.时间复杂度θ(log(n))
二分法leftmost/rightmost
public static int binarySearchLeftmost(int[] a, int target) {
int i = 0, j = a.length - 1;
int candidate = -1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else if (a[m] < target) {
i = m + 1;
} else {
//记录候选位置
candidate = m;
j = m - 1;
}
}
return candidate;
}
public static int binarySearchLeftmost2(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target <= a[m]) {
j = m - 1;
} else {
i = m + 1;
}
}
return i;
}
public static int binarySearchRightmost(int[] a, int target) {
int i = 0, j = a.length - 1;
int candidate = -1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else if (a[m] < target) {
i = m + 1;
} else {
//记录候选位置
candidate = m;
i = m + 1;
}
}
return candidate;
}
public static int binarySearchRightmost2(int[] a, int target) {
int i = 0, j = a.length - 1;
while (i <= j) {
int m = (i + j) >>> 1;
if (target < a[m]) {
j = m - 1;
} else {
i = m + 1;
}
}
return i - 1;
}
二分查找法Leftmost/Rightmost应用:
求排名 Leftmost(n)+1
求前任 Leftmost(n)-1
求后任 rightmost(n)+1
leetcode 35
class Solution {
public int searchInsert(int[] a, int target) {
//Leftmost 有重复的也可以用
int i=0,j=a.length-1;
while(i<=j){
int m=(i+j)>>>1;
if(target<=a[m]){
j=m-1;
}else if(a[m]<target){
i=m+1;
}
}
return i;
}
}
另解
class Solution {
public int searchInsert(int[] a, int target) {
int low=0;
int high=a.length-1;
while(low<=high){
int mid=(low+high)>>>1;
long midVal=a[mid];
if(midVal < target){
low=mid+1;
}else if(midVal > target){
high=mid-1;
}else
return mid;
}
return low;//找不到目标值
}
}
leetcode 34
class Solution {
public int[] searchRange(int[] a, int target) {
int x=left(a,target);
if(x==-1){
return new int[]{-1,-1};
}else {
return new int[]{x,right(a,target)};
}
}
public int left(int[] a,int target){
int i=0,j=a.length-1;
int candicate=-1;
while(i<=j){
int m=(i+j)>>>1;
if(target<a[m]){
j=m-1;
}else if(a[m]<target){
i=m+1;
}else{
candicate=m;
j=m-1;
}
}
return candicate;
}
public int right(int[] a,int target){
int i=0,j=a.length-1;
int candicate=-1;
while(i<=j){
int m=(i+j)>>>1;
if(target<a[m]){
j=m-1;
}else if(a[m]<target){
i=m+1;
}else{
candicate=m;
i=m+1;
}
}
return candicate;
}
}
leetcode 702
class Solution {
public int search(int[] nums, int target) {
int i=0,j=nums.length;
while(i<j){
int m=(i+j)>>>1;
if(target<nums[m]){
j=m;
}else if(nums[m]<target){
i=m+1;
}else{
return m;
}
}
return -1;
}
}