一个几乎全民都会的算法——二分查找

news2024/11/25 10:14:03

为什么说二分查找是全民都会的算法?

20年前央视2套有一档叫《幸运52》的综艺节目,其中一个环节叫《幸运超市》,每一期已故著名主持人咏哥都会给佳宾们出示几个商品,凡是佳宾猜中价格的,就能获赠这件商品。这档节目红极一时,被很多地方卫视节目复制抄袭。

比如,上面这段视频(gif图)的配音这样的:

佳宾报价:主持人说高或低

1800:高了

1500:低了

1600:低了

1700:高了

1680:高了

1660:低了

1670:正确!

上面这个猜价格过程,首先佳宾预估到这件的商品价格区间是1500到1800之间,然后根据主持人说高还是低进行调节报价高低,直到猜中价格。

是不是很简单! 其实这一个猜价格过程就是二分查找算法。由于2000年前后的手机还没有多媒体化,这档节目收视率极高,所以这种猜价格的方法那时候几乎全民皆会,只是没命名它叫二分查找而已。

二分查找

也被称为折半查找,它的基本思想是:对于一个有序数组,每次查找中间位置的元素,如果该元素等于目标元素,则返回该元素的位置;如果该元素大于目标元素,则在数组的左半部分继续查找;如果该元素小于目标元素,则在数组的右半部分继续查找。重复以上过程,直到找到目标元素或者数组中不存在目标元素为止。

二分查找时间复杂度为O(log n),是一种非常高效的查找算法。

基本过程

1.首先,将数组按照升序或者降序排列。
2.然后,确定数组的中间元素。
3.接着,将目标值与中间元素进行比较。
 3-1.如果目标值等于中间元素,则查找成功,返回中间元素的位置。
 3-2.如果目标值小于中间元素,则在左侧子数组中继续查找。
 3-3.如果目标值大于中间元素,则在右侧子数组中继续查找。
重复执行步骤,直到查找成功或者确定目标元素不存在。

算法实现

给定一个有序数组和一个目标元素,求该元素在数组中的位置,如果数组中不存在该元素,则返回-1。例如,给定数组[1, 2, 3, 6, 8, 9, 10]和目标元素8,则返回3。

用Golang语言实现的代码如下:

package main

import "fmt"

// 二分查找算法
func binarySearch(nums []int, target int) int {
	left, right := 0, len(nums)-1
	for left <= right {
		mid := (left + right) / 2
		if nums[mid] == target {
			return mid
		} else if nums[mid] > target {
			right = mid - 1
		} else {
			left = mid + 1
		}
	}
	return -1
}

func main() {
	nums := []int{1, 2, 3, 6, 8, 9, 10}
	target := 8
	fmt.Println(binarySearch(nums, target))
}

运行结果: 4,表示数组的第5个元素是8.

在本例中,我们定义了一个名为binarySearch的函数,该函数接受两个参数:

nums:有序数组, target:目标元素

在函数中,首先定义两个指针left和right,分别指向数组的左边界和右边界。然后使用一个循环,不断查找中间位置的元素,直到找到目标元素或者数组中不存在目标元素为止。在每次循环中,首先计算中间位置的索引mid,如果中间位置的元素等于目标元素,则返回中间位置的索引;如果中间位置的元素大于目标元素,则在数组的左半部分继续查找,将右边界指向中间位置的左侧;如果中间位置的元素小于目标元素,则在数组的右半部分继续查找,将左边界指向中间位置的右侧。重复以上过程,直到找到目标元素或者数组中不存在目标元素为止。

二分查找算法虽然简单,但是实现起来需要注意几个问题:

如何计算中间位置:在二分查找算法中,需要计算中间位置的索引,通常可以使用left和right指针来计算,即mid := (left + right) / 2,也有用右移运算的:(left + right) >> 1。

特别是当right接近最大整数Int_Max时,避免left,right相加后溢出,尽可能用:

mid := left + (right - left) / 2 或者 mid := left + (right - left) >> 1

递归法

递归法更好地展示了二分查找的过程:

package main

import "fmt"

// 二分查找算法
func binarySearchRecursive(arr []int, target int, left int, right int) int {
	if left > right {
		return -1
	}
	mid := left + (right-left)/2
	if arr[mid] == target {  //“正确”
		return mid
	} else if target < arr[mid]  {  //“高了”
		return binarySearchRecursive(arr, target, left, mid-1)
	} else {  //“低了”
		return binarySearchRecursive(arr, target, mid+1, right)
	}
}

func main() {
	nums := []int{1, 2, 3, 6, 8, 9, 10}
	target := 8
	left, right := 0, len(nums)-1
	fmt.Println(binarySearchRecursive(nums, target, left, right))
}

例程演示

用代码实现前文提到的猜价格游戏:

package main

import "fmt"

// 二分查找算法
func binarySearchRecursive(target int, low int, high int, count int) int {
	if low > high {
		return -1
	}
	count++
	mid := low + (high-low)/2
	fmt.Printf("第%v次报价:%v ", count, mid)
	if mid == target {
		fmt.Println("正确!")
		return mid
	} else if target < mid {
		fmt.Println("高了")
		return binarySearchRecursive(target, low, mid-1, count)
	} else {
		fmt.Println("低了")
		return binarySearchRecursive(target, mid+1, high, count)
	}
}

func main() {
	target := 1670          //商品正确价格为1670元
	low, high := 1500, 1800 //预估商品价格区间
	binarySearchRecursive(target, low, high, 0)
}

运行结果: 

第1次报价:1650 低了

第2次报价:1725 高了

第3次报价:1687 高了

第4次报价:1668 低了

第5次报价:1677 高了

第6次报价:1672 高了

第7次报价:1670 正确!

加上预估区间的2次,共9次猜出正确价格。为什么比人猜多了,因为人猜时默认价格是10的倍数,是没有个位数的。修改代码,也用mid+10 和 mid-10试试:

package main

import "fmt"

// 二分查找算法
func binarySearchRecursive(target int, low int, high int, count int) int {
	if low > high {
		return -1
	}
	count++
	mid := low + (high-low)/2
	fmt.Printf("第%v次报价:%v ", count, mid)
	if mid == target {
		fmt.Println("正确!")
		return mid
	} else if target < mid {
		fmt.Println("高了")
		return binarySearchRecursive(target, low, mid-10, count)
	} else {
		fmt.Println("低了")
		return binarySearchRecursive(target, mid+10, high, count)
	}
}

func main() {
	target := 1670          //商品正确价格为1670元
	low, high := 1500, 1800 //预估商品价格区间
	binarySearchRecursive(target, low, high, 0)
}

运行结果: 

第1次报价:1650 低了

第2次报价:1730 高了

第3次报价:1690 高了

第4次报价:1670 正确!

虽然这个例子是猜中了,但对区间变动不是mid±1的还是谨慎使用,很可能会错失目标的。

盲猜:

如果不知道是什么商品,也就是估不准价格,比如我们指定是100000以内价格区间,看要猜多少次?

package main

import "fmt"

// 二分查找算法
func binarySearchRecursive(target int, low int, high int, count int) int {
	if low > high {
		return -1
	}
	count++
	mid := low + (high-low)/2
	fmt.Printf("第%v次报价:%v ", count, mid)
	if mid == target {
		fmt.Println("正确!")
		return mid
	} else if target < mid {
		fmt.Println("高了")
		return binarySearchRecursive(target, low, mid-1, count)
	} else {
		fmt.Println("低了")
		return binarySearchRecursive(target, mid+1, high, count)
	}
}

func main() {
	target := 1670         //商品正确价格为1670元
	low, high := 0, 100000 //预估商品价格区间
	binarySearchRecursive(target, low, high, 0)
}

运行结果: 

第1次报价:50000 高了

第2次报价:24999 高了

第3次报价:12499 高了

第4次报价:6249 高了

第5次报价:3124 高了

第6次报价:1561 低了

第7次报价:2342 高了

第8次报价:1951 高了

第9次报价:1756 高了

第10次报价:1658 低了

第11次报价:1707 高了

第12次报价:1682 高了

第13次报价:1670 正确!

这个次数不高于log2(100000) ≈ 16.61,所以二分查找的时间复杂度为 O(log n)。


力扣实战

查找元素的首末位置

Find-first-and-last-position-of-element-in-sorted-array

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

进阶:

  • 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums 是一个非递减数组
  • -10^9 <= target <= 10^9

代码: 

package main
 
import "fmt"
 
func searchRange(nums []int, target int) []int {
	left, right := -1, -1
	// 查找左边界
	l, r := 0, len(nums)-1
	for l <= r {
		mid := (l + r) / 2
		if nums[mid] == target {
			left = mid
			r = mid - 1
		} else if nums[mid] > target {
			r = mid - 1
		} else {
			l = mid + 1
		}
	}
	// 如果左边界没找到,直接返回
	if left == -1 {
		return []int{-1, -1}
	}
	// 查找右边界
	l, r = 0, len(nums)-1
	for l <= r {
		mid := (l + r) / 2
		if nums[mid] == target {
			right = mid
			l = mid + 1
		} else if nums[mid] > target {
			r = mid - 1
		} else {
			l = mid + 1
		}
	}
	return []int{left, right}
}
 
func main() {
 
	nums := []int{5, 7, 7, 8, 8, 10}
	fmt.Println(searchRange(nums, 8))
	fmt.Println(searchRange(nums, 6))
	nums = []int{}
	fmt.Println(searchRange(nums, 0))
 
}

x 的平方根  Sqrt x

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

提示:

  • 0 <= x <= 2^31 - 1

代码:  

package main
 
import (
	"fmt"
)
 
func mySqrt(x int) int {
	left, right := 0, x
	res := -1
	for left <= right {
		mid := left + (right-left)/2
		guess := mid * mid
		if guess <= x {
			res = mid
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
	return res
}
 
func main() {
	fmt.Println(mySqrt(4))
	fmt.Println(mySqrt(8))
	fmt.Println(mySqrt(122))
}

寻找旋转排序数组中的最小值

Find-minimum-in-rotated-sorted-array

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

代码:  

package main

import "fmt"

func findMin(nums []int) int {
	left, right := 0, len(nums)-1
	for left < right {
		mid := left + (right-left)/2
		if nums[mid] > nums[right] {
			left = mid + 1
		} else {
			right = mid
		}
	}
	return nums[left]
}

func main() {
	nums := []int{3, 4, 5, 1, 2}
	fmt.Println(findMin(nums))
	nums = []int{4, 5, 6, 7, 0, 1, 2}
	fmt.Println(findMin(nums))
	nums = []int{11, 13, 15, 17}
	fmt.Println(findMin(nums))
}

寻找峰值

Find Peak Element

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

提示:

  • 1 <= nums.length <= 1000
  • -2^31 <= nums[i] <= 2^31 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

代码:  

package main

import "fmt"

func findPeakElement(nums []int) int {
	left, right := 0, len(nums)-1
	for left < right {
		mid := left + (right-left)/2
		if nums[mid] > nums[mid+1] {
			right = mid
		} else {
			left = mid + 1
		}
	}
	return left
}

func main() {
	nums := []int{1, 2, 3, 1}
	fmt.Println(findPeakElement(nums))
	nums = []int{1, 2, 1, 3, 5, 6, 4}
	fmt.Println(findPeakElement(nums))
}

有效的完全平方数

Valid Perfect Square

给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

进阶:不要 使用任何内置的库函数,如  sqrt 。

示例 1:

输入:num = 16
输出:true

示例 2:

输入:num = 14
输出:false

提示:

  • 1 <= num <= 2^31 - 1

代码:

package main

import "fmt"

func isPerfectSquare(num int) bool {
	left, right := 1, num
	for left <= right {
		mid := left + (right - left) / 2
		square := mid * mid
		if square == num {
			return true
		} else if square < num {
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
	return false
}

func main() {
	fmt.Println(isPerfectSquare(16))
	fmt.Println(isPerfectSquare(14))
}

分割数组的最大值

Split-array-largest-sum

给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。

设计一个算法使得这 m 个子数组各自和的最大值最小。

示例 1:

输入:nums = [7,2,5,10,8], m = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。 
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。

示例 2:

输入:nums = [1,2,3,4,5], m = 2
输出:9

示例 3:

输入:nums = [1,4,4], m = 3
输出:4

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 10^6
  • 1 <= m <= min(50, nums.length)
package main

import "fmt"

func splitArray(nums []int, m int) int {
	left, right := 0, 0
	for _, num := range nums {
		right += num
		if left < num {
			left = num
		}
	}
	for left <= right {
		mid := left + (right-left)/2
		if check(nums, m, mid) {
			right = mid - 1
		} else {
			left = mid + 1
		}
	}
	return left
}

func check(nums []int, m, target int) bool {
	sum, cnt := 0, 1
	for _, num := range nums {
		if sum+num <= target {
			sum += num
		} else {
			sum = num
			cnt++
			if cnt > m {
				return false
			}
		}
	}
	return true
}

func main() {
	nums := []int{7, 2, 5, 10, 8}
	fmt.Println(splitArray(nums, 2))

	nums = []int{1, 2, 3, 4, 5}
	fmt.Println(splitArray(nums, 2))

	nums = []int{1, 4, 4}
	fmt.Println(splitArray(nums, 3))

}

第 k 个缺失的正整数

kth-missing-positive-number

给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。

请你找到这个数组里第 k 个缺失的正整数。

示例 1:

输入:arr = [2,3,4,7,11], k = 5
输出:9
解释:缺失的正整数包括 [1,5,6,8,9,10,12,13,...] 。第 5 个缺失的正整数为 9 。

示例 2:

输入:arr = [1,2,3,4], k = 2
输出:6
解释:缺失的正整数包括 [5,6,7,...] 。第 2 个缺失的正整数为 6 。

提示:

  • 1 <= arr.length <= 1000
  • 1 <= arr[i] <= 1000
  • 1 <= k <= 1000
  • 对于所有 1 <= i < j <= arr.length 的 i 和 j 满足 arr[i] < arr[j] 

代码:

package main

import "fmt"

func findKthPositive(arr []int, k int) int {
	left, right := 0, len(arr)
	for left < right {
		mid := left + (right-left)/2
		// 计算当前位置缺失的数字个数
		count := arr[mid] - mid - 1
		// 如果缺失的数字个数小于k,说明第k个缺失的数字在右半部分
		if count < k {
			left = mid + 1
		} else {
			right = mid
		}
	}
	// 缺失的数字个数为k时,需要返回arr[left]-1
	// 因为arr[left]之前的数字都不缺失,所以缺失的第k个数字就是arr[left]+k
	return left + k
}

func main() {
	nums := []int{2, 3, 4, 7, 11}
	fmt.Println(findKthPositive(nums, 5))

	nums = []int{1, 2, 3, 4}
	fmt.Println(findKthPositive(nums, 2))
}

还有很多适用十分查找的题目,不一一列举了。

总结

本文介绍了二分查找算法的原理、实现方法和用 Golang 实现的例程。二分查找算法是一种高效的查找算法,适用于有序数列中的查找问题。它的时间复杂度为 O(log n),相比于线性查找的 O(n),效率更高。在实际应用中,我们可以根据具体情况选择递归或循环实现二分查找算法,以提高算法的效率。

最后,请记牢二分查找标志性的语句:

mid := left + (right-left)/2


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/526134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 类和对象(中)构造函数 和 析构函数 ,const成员

上篇链接&#xff1a;C 类和对象&#xff08;上&#xff09;_chihiro1122的博客-CSDN博客 类的6个默认成员函数 我们在C当中&#xff0c;在写一些函数的时候&#xff0c;比如在栈的例子&#xff1a; 如上述例子&#xff0c;用C 返回这个栈是否为空&#xff0c;直接返回的话&am…

Selenium+Pytest自动化测试框架实战

前言 1、Selenium是一个免费的(开源)自动化测试组件&#xff0c;适用于跨不同浏览器和平台的Web应用程序【selenium是一个自动化工具&#xff0c;而不是自动化框架】。它非常类似于HP Quick Test Pro (QTP现在叫UFT)&#xff0c;只是Selenium侧重于自动化基于Web的应用程序。使…

初、高中生到底该不该学习编程?

从小学&#xff0c;到初中&#xff0c;再到高中&#xff0c;知识的提升主要体现在一个方面上&#xff0c;就是知识越来越抽象了。很多孩子在初中成绩还可以&#xff0c;到了高中就跟不上了&#xff0c;这是最主要的一个原因。 编程主要就是要求学习它的人&#xff0c;有较强的…

2023武生院计科专升本指南

自我介绍一下&#xff0c;我叫啊超&#xff0c;22级专升本上岸武生院考了211的计应学长&#xff0c;社恐&#xff0c;不善言辞&#xff0c;出门都走下水道&#xff0c;吃饭因社恐&#xff0c;屡次不敢买单。单身可撩&#xff0c;哈哈哈~ 我只是提出自己的一些个人建议&#xff…

utittest和pytest中mock的使用详细介绍

Mock是Python中一个用于支持单元测试的库&#xff0c;它的主要功能是使用mock对象替代掉指定的Python对象&#xff0c;以达到模拟对象的行为。 python3.3 以前&#xff0c;mock是第三方库&#xff0c;需要安装之后才能使用。python3.3之后&#xff0c;mock作为标准库内置到 un…

防火墙(firewall)

前言 计算机的安全性历来就是人们热衷的话题之一。而随着Internet的广泛应用&#xff0c;人们在扩展了获取和发布能力的同时也带来信息被污染和破坏的危险。这些安全问题主要是由网络的开放性、无边界性、自由性造成的&#xff0c;还包括以下一些因素。 1. 计算机操作系统本身…

分布式补充技术 01.AOP技术

01.AOP技术是对于面向对象编程&#xff08;OOP&#xff09;的补充。是按照OCP原则进行的编写&#xff0c;(ocp是修改模块权限不行&#xff0c;扩充可以) 02.写一个例子&#xff1a; 创建一个新的java项目&#xff0c;在main主启动类中&#xff0c;写如下代码。 package com.co…

CTSI 基金会储备透明度报告——2023 年 1 月

由于 Cartesi 代币 (CTSI) 的下一次解锁定于 2023 年 1 月 23 日&#xff0c;我们很高兴接着上一份透明度报告&#xff0c;我们本次依旧提供关于 Cartesi 基金会的治理、运营以及 CTSI 代币如何分配的另一份官方透明度报告。 提醒一下&#xff0c;为了保证诚实和可信度&#xf…

软件测试工程师简历项目经验怎么写

软件测试工程师简历项目经验怎么写 面试是我们进入一个公司的门槛&#xff0c;通过了面试才能进入公司&#xff0c;你的面试结果和你的薪资是息息相关的。 在面试之前&#xff0c;不得不聊聊简历&#xff0c;简历是职场的敲门砖&#xff0c;是拿到offer的通行证&#xff0c;那么…

【unity学习记录-场景绘制+物体碰撞,场景物体的层级关系】跟着Unity2D官方入门教程 Ruby‘ Adventure

文章目录 创建tilemap编辑一下资源&#xff0c;瓦片调色对于瓦片没有填满的情况&#xff0c;调整每片瓦片的像素 添加点小树或其他&#xff0c;调整层级给树木增加一些碰撞的效果&#xff0c;调整碰撞范围&#xff0c;角色也要添加刚体人物倒着走路的解决方法&#xff08;解决角…

线程与进程,你真得理解了吗

线程与进程&#xff0c;你真得理解了吗 1 进程与线程的关系和区别2 并行与并发3 线程共享了进程哪些资源 相信大家面试时一定没少被一个问题刁难&#xff0c;那就是进程和线程的区别是什么&#xff1f;这个问题延申开来并不像表面那么简单&#xff0c;今天就来深入一探。 开始…

个人简历html网页代码(使用chatgpt完成web开发课的实验)

使用chatgpt完成web开发课的实验 前提&#xff1a; chatgpt的使用&#xff0c;建议看https://juejin.cn/post/7198097078005841980或者自己随便找 要学会用“出国旅游”软件 vscode的基本使用 炼丹开始&#xff1a; 炼丹材料&#xff1a; 帮我写一个html页面&#xff0c;内…

沁恒 CH32V208(四): CH32V208 网络DHCP示例代码分析

目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置沁恒 CH32V208(四): CH32V208 网络DHCP示例代码分析 硬件部分 CH32V208WBU6 …

Python 网络爬虫与数据采集(一)

Python 网络爬虫与数据采集 第1章 序章 网络爬虫基础1 爬虫基本概述1.1 爬虫是什么1.2 爬虫可以做什么1.3 爬虫的分类1.4 爬虫的基本流程1.4.1 浏览网页的流程1.4.2 爬虫的基本流程 1.5 爬虫与反爬虫1.5.1 爬虫的攻与防1.5.2 常见的反爬与反反爬 1.6 爬虫的合法性与 robots 协议…

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-3

深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践-总结-3 垃圾收集器与内存分配策略垃圾收集算法标记-清除算法标记-复制算法标记-整理算法 垃圾收集器与内存分配策略 垃圾收集算法 标记-清除算法 最基础的垃圾收集算法是“标记-清除”&#xff08;Mark-Sweep&#xff…

2023年推荐几款开源或免费的web应用防火墙

2023年推荐几款开源或免费的web应用防火墙 2023年&#xff0c;数字经济将强势崛起&#xff0c;并且成为新一轮经济发展的动力&#xff0c;传统的黑客破坏性攻击如CC&#xff0c;转为更隐蔽的如0day进行APT渗透。所以无论私有服务器还是云厂商如Cloudflare、阿里云、腾讯云等都…

无线安全操作(2)

目录 用户隔离 用户隔离介绍 1、集中式转发方式 2、分布式转发方式 用户隔离配置 用户隔离举例 用户静默排错帮助 ARP抑制 ARP抑制介绍 ARP抑制配置 ARP抑制举例 动态黑名单 动态黑名单概述 动态黑名单配置 动态黑名单举例 动态黑名单排错帮助 无线SAVI 无线…

正月初八,公司复工,我却失业了

昨天&#xff0c;一条吐槽刷屏。 方便大家阅读&#xff0c;原文先贴出来&#xff1a; 正月初八&#xff0c;公司复工&#xff0c;我却失业了。 一大早地铁转公交赶在九点前到达&#xff0c;老板在大门口迎接我们的到来&#xff0c;还发了一个红包&#xff0c;心里暖暖的。 到了…

以太坊EVM源码分析学习记录

EVM 待办清单结构与流程2020年版本的evm结构大致流程 opcodes.gocontract.goanalysis.gostack.gostack_table.goMemory.goMemory_table.goEVM.go区块上下文交易上下文EVM结构以太坊中的调用call、callcode和delegatecall创建合约 interpreter.gojump_table.goinstructions.goga…

极为罕见的大学生现象 凤毛麟角 是参加这种竞赛

大学里有哪些含金量较高&#xff0c;涉及到综合素质加分的竞赛呢&#xff1f; 一定要耐心耐心的看完 对你现在的境地会很有帮助&#xff01;&#xff01;&#xff01; 大学生竞赛大致可以分为综合类和学科类两种。 综合类竞赛面向的范围更大&#xff0c;各个专业的学生均可参…