数据结构和算法(Golang实现)(27)查找算法-二叉查找树
二叉查找树
二叉查找树,又叫二叉排序树,二叉搜索树,是一种有特定规则的二叉树,定义如下:
- 它是一颗二叉树,或者是空树。
- 左子树所有节点的值都小于它的根节点,右子树所有节点的值都大于它的根节点。
- 左右子树也是一颗二叉查找树。

二叉查找树的特点是,一直往左儿子往下找左儿子,可以找到最小的元素,一直往右儿子找右儿子,可以找到最大的元素。
看起来,我们可以用它来实现元素排序,可是我们却使用了二叉堆来实现了堆排序,因为二叉查找树不保证是一个平衡的二叉树,最坏情况下二叉查找树会退化成一个链表,也就是所有节点都没有左子树或者没有右子树,树的层次太深导致排序性能太差。
使用二分查找,可以很快在一颗二叉查找树中找到我们需要的值。
我们来分析二叉查找树添加,删除,查找元素的方法。
一、添加元素
以下是一个二叉查找树的表示:
// 二叉查找树
type BinarySearchTree struct {
Root *BinarySearchTreeNode // 树根节点
}
// 二叉查找树节点
type BinarySearchTreeNode struct {
Value int64 // 值
Times int64 // 值出现的次数
Left *BinarySearchTreeNode // 左子树
Right *BinarySearchTreeNode // 右字树
}
// 初始化一个二叉查找树
func NewBinarySearchTree() *BinarySearchTree {
return new(BinarySearchTree)
}
一个节点代表一个元素,节点的Value值是用来进行二叉查找的关键,当Value值重复时,我们将值出现的次数Times加 1。添加元素代码如下:
// 添加元素
func (tree *BinarySearchTree) Add(value int64) {
// 如果没有树根,证明是颗空树,添加树根后返回
if tree.Root == nil {
tree.Root = &BinarySearchTreeNode{Value: value}
return
}
// 将值添加进去
tree.Root.Add(value)
}
func (node *BinarySearchTreeNode) Add(value int64) {
if value < node.Value {
// 如果插入的值比节点的值小,那么要插入到该节点的左子树中
// 如果左子树为空,直接添加
if node.Left == nil {
node.Left = &BinarySearchTreeNode{Value: value}
} else {
// 否则递归
node.Left.Add(value)
}
} else if value > node.Value {
// 如果插入的值比节点的值大,那么要插入到该节点的右子树中
// 如果右子树为空,直接添加
if node.Right == nil {
node.Right = &BinarySearchTreeNode{Value: value}
} else {
// 否则递归
node.Right.Add(value)
}
} else {
// 值相同,不需要添加,值出现的次数加1即可
node.Times = node.Times + 1
}
}
如果添加元素时是颗空树,那么初始化根节点。
然后添加的值和根节点比较,判断是要插入到根节点左子树还是右子树,还是不用插入。
当值比根节点小时,元素要插入到根节点的左子树中,当值比根节点大时,元素要插入到根节点的右子树中,相等时不插入,只更新次数。
然后再分别对根节点的左子树和右子树进行递归操作即可。
二、查找最大值或最小值的元素
查找最大值和最小值比较简单,一直往左儿子往下找左儿子,可以找到最小的元素,一直往右儿子找右儿子,可以找到最大的元素。
// 找出最小值的节点
func (tree *BinarySearchTree) FindMinValue() *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
return tree.Root.FindMinValue()
}
func (node *BinarySearchTreeNode) FindMinValue() *BinarySearchTreeNode {
// 左子树为空,表面已经是最左的节点了,该值就是最小值
if node.Left == nil {
return node
}
// 一直左子树递归
return node.Left.FindMinValue()
}
// 找出最大值的节点
func (tree *BinarySearchTree) FindMaxValue() *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
return tree.Root.FindMaxValue()
}
func (node *BinarySearchTreeNode) FindMaxValue() *BinarySearchTreeNode {
// 右子树为空,表面已经是最右的节点了,该值就是最大值
if node.Right == nil {
return node
}
// 一直右子树递归
return node.Right.FindMaxValue()
}
三、查找指定元素
二分查找的技巧也在这里有用武之地了:
// 查找节点
func (tree *BinarySearchTree) Find(value int64) *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
return tree.Root.Find(value)
}
func (node *BinarySearchTreeNode) Find(value int64) *BinarySearchTreeNode {
if value == node.Value {
// 如果该节点刚刚等于该值,那么返回该节点
return node
} else if value < node.Value {
// 如果查找的值小于节点值,从节点的左子树开始找
if node.Left == nil {
// 左子树为空,表示找不到该值了,返回nil
return nil
}
return node.Left.Find(value)
} else {
// 如果查找的值大于节点值,从节点的右子树开始找
if node.Right == nil {
// 右子树为空,表示找不到该值了,返回nil
return nil
}
return node.Right.Find(value)
}
}
如果是空树,返回 nil,否则与根节点比较。
如果刚刚好等于根节点的值,返回该节点,否则根据值的比较,继续往左子树或右字树递归查找。
四、查找指定元素的父亲
与查找指定元素一样,只不过追踪的是该元素的父亲节点。
// 查找指定节点的父亲
func (tree *BinarySearchTree) FindParent(value int64) *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
// 如果根节点等于该值,根节点其没有父节点,返回nil
if tree.Root.Value == value {
return nil
}
return tree.Root.FindParent(value)
}
func (node *BinarySearchTreeNode) FindParent(value int64) *BinarySearchTreeNode {
// 外层没有值相等的判定,因为在内层已经判定完毕后返回父亲节点。
if value < node.Value {
// 如果查找的值小于节点值,从节点的左子树开始找
leftTree := node.Left
if leftTree == nil {
// 左子树为空,表示找不到该值了,返回nil
return nil
}
// 左子树的根节点的值刚好等于该值,那么父亲就是现在的node,返回
if leftTree.Value == value {
return node
} else {
return leftTree.FindParent(value)
}
} else {
// 如果查找的值大于节点值,从节点的右子树开始找
rightTree := node.Right
if rightTree == nil {
// 右子树为空,表示找不到该值了,返回nil
return nil
}
// 右子树的根节点的值刚好等于该值,那么父亲就是现在的node,返回
if rightTree.Value == value {
return node
} else {
return rightTree.FindParent(value)
}
}
}
代码相应的进行了调整,方便获取到父亲节点。
如果返回的父亲节点为空,表示没有父亲。
五、删除元素
删除元素有四种情况:
- 第一种情况,删除的是根节点,且根节点没有儿子,直接删除即可。
- 第二种情况,删除的节点有父亲节点,但没有子树,也就是删除的是叶子节点,直接删除即可。
- 第三种情况,删除的节点下有两个子树,因为右子树的值都比左子树大,那么用右子树中的最小元素来替换删除的节点,这时二叉查找树的性质又满足了。右子树的最小元素,只要一直往右子树的左边一直找一直找就可以找到。
- 第四种情况,删除的节点只有一个子树,那么该子树直接替换被删除的节点即可。
代码实现如下:
// 删除指定的元素
func (tree *BinarySearchTree) Delete(value int64) {
if tree.Root == nil {
// 如果是空树,直接返回
return
}
// 查找该值是否存在
node := tree.Root.Find(value)
if node == nil {
// 不存在该值,直接返回
return
}
// 查找该值的父亲节点
parent := tree.Root.FindParent(value)
// 第一种情况,删除的是根节点,且根节点没有儿子
if parent == nil && node.Left == nil && node.Right == nil {
// 置空后直接返回
tree.Root = nil
return
} else if node.Left == nil && node.Right == nil {
// 第二种情况,删除的节点有父亲节点,但没有子树
// 如果删除的是节点是父亲的左儿子,直接将该值删除即可
if parent.Left != nil && value == parent.Left.Value {
parent.Left = nil
} else {
// 删除的原来是父亲的右儿子,直接将该值删除即可
parent.Right = nil
}
return
} else if node.Left != nil && node.Right != nil {
// 第三种情况,删除的节点下有两个子树,因为右子树的值都比左子树大,那么用右子树中的最小元素来替换删除的节点。
// 右子树的最小元素,只要一直往右子树的左边一直找一直找就可以找到,替换后二叉查找树的性质又满足了。
// 找右子树中最小的值,一直往右子树的左边找
minNode := node.Right
for minNode.Left != nil {
minNode = minNode.Left
}
// 把最小的节点删掉
tree.Delete(minNode.Value)
// 最小值的节点替换被删除节点
node.Value = minNode.Value
node.Times = minNode.Times
} else {
// 第四种情况,只有一个子树,那么该子树直接替换被删除的节点即可
// 父亲为空,表示删除的是根节点,替换树根
if parent == nil {
if node.Left != nil {
tree.Root = node.Left
} else {
tree.Root = node.Right
}
return
}
// 左子树不为空
if node.Left != nil {
// 如果删除的是节点是父亲的左儿子,让删除的节点的左子树接班
if parent.Left != nil && value == parent.Left.Value {
parent.Left = node.Left
} else {
parent.Right = node.Left
}
} else {
// 如果删除的是节点是父亲的左儿子,让删除的节点的右子树接班
if parent.Left != nil && value == parent.Left.Value {
parent.Left = node.Right
} else {
parent.Right = node.Right
}
}
}
}
首先查找到要删除元素的节点:tree.Root.Find(value),然后找到该节点父亲:tree.Root.FindParent(value),根据四种不同情况对删除节点进行补位。核心在于,第三种情况下,删除的节点有两个子树情况下,需要用右子树中最小的节点来替换被删除节点。
上面的代码可以优化,可以在查找删除元素的节点时顺道查出其父亲节点,不必要分开查询父亲节点,在第三种情况下查出右子树的最小节点时可以直接将其移除,不必递归使用tree.Delete(minNode.Value)。
由于这种通用形式的二叉查找树实现甚少使用,大部分程序都使用了AVL树或红黑树,以上优化理解即可。
六、中序遍历(实现排序)
使用二叉查找树可以实现排序,只需要对树进行中序遍历即可。
我们先打印出左子树,然后打印根节点的值,再打印右子树,这是一个递归的过程。
// 中序遍历
func (tree *BinarySearchTree) MidOrder() {
tree.Root.MidOrder()
}
func (node *BinarySearchTreeNode) MidOrder() {
if node == nil {
return
}
// 先打印左子树
node.Left.MidOrder()
// 按照次数打印根节点
for i := 0; i <= int(node.Times); i++ {
fmt.Println(node.Value)
}
// 打印右子树
node.Right.MidOrder()
}
七、完整代码
package main
import (
"fmt"
)
// 二叉查找树节点
type BinarySearchTree struct {
Root *BinarySearchTreeNode // 树根节点
}
// 二叉查找树节点
type BinarySearchTreeNode struct {
Value int64 // 值
Times int64 // 值出现的次数
Left *BinarySearchTreeNode // 左子树
Right *BinarySearchTreeNode // 右字树
}
// 初始化一个二叉查找树
func NewBinarySearchTree() *BinarySearchTree {
return new(BinarySearchTree)
}
// 添加元素
func (tree *BinarySearchTree) Add(value int64) {
// 如果没有树根,证明是颗空树,添加树根后返回
if tree.Root == nil {
tree.Root = &BinarySearchTreeNode{Value: value}
return
}
// 将值添加进去
tree.Root.Add(value)
}
func (node *BinarySearchTreeNode) Add(value int64) {
if value < node.Value {
// 如果插入的值比节点的值小,那么要插入到该节点的左子树中
// 如果左子树为空,直接添加
if node.Left == nil {
node.Left = &BinarySearchTreeNode{Value: value}
} else {
// 否则递归
node.Left.Add(value)
}
} else if value > node.Value {
// 如果插入的值比节点的值大,那么要插入到该节点的右子树中
// 如果右子树为空,直接添加
if node.Right == nil {
node.Right = &BinarySearchTreeNode{Value: value}
} else {
// 否则递归
node.Right.Add(value)
}
} else {
// 值相同,不需要添加,值出现的次数加1即可
node.Times = node.Times + 1
}
}
// 找出最小值的节点
func (tree *BinarySearchTree) FindMinValue() *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
return tree.Root.FindMinValue()
}
func (node *BinarySearchTreeNode) FindMinValue() *BinarySearchTreeNode {
// 左子树为空,表面已经是最左的节点了,该值就是最小值
if node.Left == nil {
return node
}
// 一直左子树递归
return node.Left.FindMinValue()
}
// 找出最大值的节点
func (tree *BinarySearchTree) FindMaxValue() *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
return tree.Root.FindMaxValue()
}
func (node *BinarySearchTreeNode) FindMaxValue() *BinarySearchTreeNode {
// 右子树为空,表面已经是最右的节点了,该值就是最大值
if node.Right == nil {
return node
}
// 一直右子树递归
return node.Right.FindMaxValue()
}
// 查找指定节点
func (tree *BinarySearchTree) Find(value int64) *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
return tree.Root.Find(value)
}
func (node *BinarySearchTreeNode) Find(value int64) *BinarySearchTreeNode {
if value == node.Value {
// 如果该节点刚刚等于该值,那么返回该节点
return node
} else if value < node.Value {
// 如果查找的值小于节点值,从节点的左子树开始找
if node.Left == nil {
// 左子树为空,表示找不到该值了,返回nil
return nil
}
return node.Left.Find(value)
} else {
// 如果查找的值大于节点值,从节点的右子树开始找
if node.Right == nil {
// 右子树为空,表示找不到该值了,返回nil
return nil
}
return node.Right.Find(value)
}
}
// 查找指定节点的父亲
func (tree *BinarySearchTree) FindParent(value int64) *BinarySearchTreeNode {
if tree.Root == nil {
// 如果是空树,返回空
return nil
}
// 如果根节点等于该值,根节点其没有父节点,返回nil
if tree.Root.Value == value {
return nil
}
return tree.Root.FindParent(value)
}
func (node *BinarySearchTreeNode) FindParent(value int64) *BinarySearchTreeNode {
// 外层没有值相等的判定,因为在内层已经判定完毕后返回父亲节点。
if value < node.Value {
// 如果查找的值小于节点值,从节点的左子树开始找
leftTree := node.Left
if leftTree == nil {
// 左子树为空,表示找不到该值了,返回nil
return nil
}
// 左子树的根节点的值刚好等于该值,那么父亲就是现在的node,返回
if leftTree.Value == value {
return node
} else {
return leftTree.FindParent(value)
}
} else {
// 如果查找的值大于节点值,从节点的右子树开始找
rightTree := node.Right
if rightTree == nil {
// 右子树为空,表示找不到该值了,返回nil
return nil
}
// 右子树的根节点的值刚好等于该值,那么父亲就是现在的node,返回
if rightTree.Value == value {
return node
} else {
return rightTree.FindParent(value)
}
}
}
// 删除指定的元素
func (tree *BinarySearchTree) Delete(value int64) {
if tree.Root == nil {
// 如果是空树,直接返回
return
}
// 查找该值是否存在
node := tree.Root.Find(value)
if node == nil {
// 不存在该值,直接返回
return
}
// 查找该值的父亲节点
parent := tree.Root.FindParent(value)
// 第一种情况,删除的是根节点,且根节点没有儿子
if parent == nil && node.Left == nil && node.Right == nil {
// 置空后直接返回
tree.Root = nil
return
} else if node.Left == nil && node.Right == nil {
// 第二种情况,删除的节点有父亲节点,但没有子树
// 如果删除的是节点是父亲的左儿子,直接将该值删除即可
if parent.Left != nil && value == parent.Left.Value {
parent.Left = nil
} else {
// 删除的原来是父亲的右儿子,直接将该值删除即可
parent.Right = nil
}
return
} else if node.Left != nil && node.Right != nil {
// 第三种情况,删除的节点下有两个子树,因为右子树的值都比左子树大,那么用右子树中的最小元素来替换删除的节点,这时二叉查找树的性质又满足了。
// 找右子树中最小的值,一直往右子树的左边找
minNode := node.Right
for minNode.Left != nil {
minNode = minNode.Left
}
// 把最小的节点删掉
tree.Delete(minNode.Value)
// 最小值的节点替换被删除节点
node.Value = minNode.Value
node.Times = minNode.Times
} else {
// 第四种情况,只有一个子树,那么该子树直接替换被删除的节点即可
// 父亲为空,表示删除的是根节点,替换树根
if parent == nil {
if node.Left != nil {
tree.Root = node.Left
} else {
tree.Root = node.Right
}
return
}
// 左子树不为空
if node.Left != nil {
// 如果删除的是节点是父亲的左儿子,让删除的节点的左子树接班
if parent.Left != nil && value == parent.Left.Value {
parent.Left = node.Left
} else {
parent.Right = node.Left
}
} else {
// 如果删除的是节点是父亲的左儿子,让删除的节点的右子树接班
if parent.Left != nil && value == parent.Left.Value {
parent.Left = node.Right
} else {
parent.Right = node.Right
}
}
}
}
// 中序遍历
func (tree *BinarySearchTree) MidOrder() {
tree.Root.MidOrder()
}
func (node *BinarySearchTreeNode) MidOrder() {
if node == nil {
return
}
// 先打印左子树
node.Left.MidOrder()
// 按照次数打印根节点
for i := 0; i <= int(node.Times); i++ {
fmt.Println(node.Value)
}
// 打印右子树
node.Right.MidOrder()
}
func main() {
values := []int64{3, 6, 8, 20, 9, 2, 6, 8, 9, 3, 5, 40, 7, 9, 13, 6, 8}
// 初始化二叉查找树并添加元素
tree := NewBinarySearchTree()
for _, v := range values {
tree.Add(v)
}
// 找到最大值或最小值的节点
fmt.Println("find min value:", tree.FindMinValue())
fmt.Println("find max value:", tree.FindMaxValue())
// 查找不存在的99
node := tree.Find(99)
if node != nil {
fmt.Println("find it 99!")
} else {
fmt.Println("not find it 99!")
}
// 查找存在的9
node = tree.Find(9)
if node != nil {
fmt.Println("find it 9!")
} else {
fmt.Println("not find it 9!")
}
// 删除存在的9后,再查找9
tree.Delete(9)
node = tree.Find(9)
if node != nil {
fmt.Println("find it 9!")
} else {
fmt.Println("not find it 9!")
}
// 中序遍历,实现排序
tree.MidOrder()
}
运行程序后,结果:
find min value: &{2 0 <nil> <nil>}
find max value: &{40 0 <nil> <nil>}
not find it 99!
find it 9!
not find it 9!
2
3
3
5
6
6
6
7
8
8
8
13
20
40
八、总结
二叉查找树可能退化为链表,也可能是一颗非常平衡的二叉树,查找,添加,删除元素的时间复杂度取决于树的高度h。
- 当二叉树是满的时,树的高度是最小的,此时树节点数量
n和高度h的关系为:h = log(n)。 - 当二叉树是一个链表时,此时树节点数量
n和高度h的关系为:h = n。
二叉查找树的效率来源其二分查找的特征,时间复杂度在于二叉树的高度,因此查找,添加和删除的时间复杂度范围为log(n)~n。
为了提高二叉查找树查找的速度,树的高度要尽可能的小。AVL树和红黑树都是相对平衡的二叉查找树,因为特殊的旋转平衡操作,树的高度被大大压低。它们查找效率较高,添加,删除,查找操作的平均时间复杂度都为log(n),经常在各种程序中被使用。
二叉查找树是后面要学习的高级数据结构AVL树,红黑树的基础。
系列文章入口
我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook。
- 数据结构和算法(Golang实现)(1)简单入门Golang-前言
- 数据结构和算法(Golang实现)(2)简单入门Golang-包、变量和函数
- 数据结构和算法(Golang实现)(3)简单入门Golang-流程控制语句
- 数据结构和算法(Golang实现)(4)简单入门Golang-结构体和方法
- 数据结构和算法(Golang实现)(5)简单入门Golang-接口
- 数据结构和算法(Golang实现)(6)简单入门Golang-并发、协程和信道
- 数据结构和算法(Golang实现)(7)简单入门Golang-标准库
- 数据结构和算法(Golang实现)(8.1)基础知识-前言
- 数据结构和算法(Golang实现)(8.2)基础知识-分治法和递归
- 数据结构和算法(Golang实现)(9)基础知识-算法复杂度及渐进符号
- 数据结构和算法(Golang实现)(10)基础知识-算法复杂度主方法
- 数据结构和算法(Golang实现)(11)常见数据结构-前言
- 数据结构和算法(Golang实现)(12)常见数据结构-链表
- 数据结构和算法(Golang实现)(13)常见数据结构-可变长数组
- 数据结构和算法(Golang实现)(14)常见数据结构-栈和队列
- 数据结构和算法(Golang实现)(15)常见数据结构-列表
- 数据结构和算法(Golang实现)(16)常见数据结构-字典
- 数据结构和算法(Golang实现)(17)常见数据结构-树
- 数据结构和算法(Golang实现)(18)排序算法-前言
- 数据结构和算法(Golang实现)(19)排序算法-冒泡排序
- 数据结构和算法(Golang实现)(20)排序算法-选择排序
- 数据结构和算法(Golang实现)(21)排序算法-插入排序
- 数据结构和算法(Golang实现)(22)排序算法-希尔排序
- 数据结构和算法(Golang实现)(23)排序算法-归并排序
- 数据结构和算法(Golang实现)(24)排序算法-优先队列及堆排序
- 数据结构和算法(Golang实现)(25)排序算法-快速排序
- 数据结构和算法(Golang实现)(26)查找算法-哈希表
- 数据结构和算法(Golang实现)(27)查找算法-二叉查找树
- 数据结构和算法(Golang实现)(28)查找算法-AVL树
- 数据结构和算法(Golang实现)(29)查找算法-2-3树和左倾红黑树
- 数据结构和算法(Golang实现)(30)查找算法-2-3-4树和普通红黑树
数据结构和算法(Golang实现)(27)查找算法-二叉查找树的更多相关文章
- 数据结构和算法(Golang实现)(26)查找算法-哈希表
哈希表:散列查找 一.线性查找 我们要通过一个键key来查找相应的值value.有一种最简单的方式,就是将键值对存放在链表里,然后遍历链表来查找是否存在key,存在则更新键对应的值,不存在则将键值对链 ...
- 数据结构和算法(Golang实现)(28)查找算法-AVL树
AVL树 二叉查找树的树高度影响了查找的效率,需要尽量减小树的高度,AVL树正是这样的树. 一.AVL树介绍 AVL树是一棵严格自平衡的二叉查找树,1962年,发明者Adelson-Velsky和La ...
- 数据结构和算法(Golang实现)(29)查找算法-2-3树和左倾红黑树
某些教程不区分普通红黑树和左倾红黑树的区别,直接将左倾红黑树拿来教学,并且称其为红黑树,因为左倾红黑树与普通的红黑树相比,实现起来较为简单,容易教学.在这里,我们区分开左倾红黑树和普通红黑树. 红黑树 ...
- 数据结构和算法(Golang实现)(30)查找算法-2-3-4树和普通红黑树
文章首发于 阅读更友好的GitBook. 2-3-4树和普通红黑树 某些教程不区分普通红黑树和左倾红黑树的区别,直接将左倾红黑树拿来教学,并且称其为红黑树,因为左倾红黑树与普通的红黑树相比,实现起来较 ...
- 数据结构和算法(Golang实现)(25)排序算法-快速排序
快速排序 快速排序是一种分治策略的排序算法,是由英国计算机科学家Tony Hoare发明的, 该算法被发布在1961年的Communications of the ACM 国际计算机学会月刊. 注:A ...
- 数据结构和算法(Golang实现)(18)排序算法-前言
排序算法 人类的发展中,我们学会了计数,比如知道小明今天打猎的兔子的数量是多少.另外一方面,我们也需要判断,今天哪个人打猎打得多,我们需要比较. 所以,排序这个很自然的需求就出来了.比如小明打了5只兔 ...
- 数据结构和算法(Golang实现)(19)排序算法-冒泡排序
冒泡排序 冒泡排序是大多数人学的第一种排序算法,在面试中,也是问的最多的一种,有时候还要求手写排序代码,因为比较简单. 冒泡排序属于交换类的排序算法. 一.算法介绍 现在有一堆乱序的数,比如:5 9 ...
- 数据结构和算法(Golang实现)(20)排序算法-选择排序
选择排序 选择排序,一般我们指的是简单选择排序,也可以叫直接选择排序,它不像冒泡排序一样相邻地交换元素,而是通过选择最小的元素,每轮迭代只需交换一次.虽然交换次数比冒泡少很多,但效率和冒泡排序一样的糟 ...
- 数据结构和算法(Golang实现)(21)排序算法-插入排序
插入排序 插入排序,一般我们指的是简单插入排序,也可以叫直接插入排序.就是说,每次把一个数插到已经排好序的数列里面形成新的排好序的数列,以此反复. 插入排序属于插入类排序算法. 除了我以外,有些人打扑 ...
随机推荐
- 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)
要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...
- 如何用 Blazor 实现 Ant Design 组件库
本文主要分享我创建 Ant Design of Blazor 项目的心路历程,已经文末有一个 Blazor 线上分享预告. Blazor WebAssembly 来了! Blazor 这个新推出的前端 ...
- Fiddler3 使用技巧
1.Fiddler抓不到包怎么解决 (1)先确定是HTTP包抓不到还是HTTPS包抓不到.如果只是HTTPS包抓不到,说明是证书的问题,需要重新安装证书. (2)检查浏览器的HTTP代理设置是否正确, ...
- 常用正则表达式(手机号、邮箱、URL地址、身份证等等)
一.前言 不好的习惯:1.每一次用到正则都是上网copy一份,也没有去学习思考,看看都是什么意思: 2.一个项目里不同的地方用到了相同的校验,一直在重复的copy代码,并没有统一起来,万一哪天要修改规 ...
- .NET Core使用NPOI导出复杂,美观的Excel详解
前言: 这段时间一直专注于数据报表的开发,当然涉及到相关报表的开发数据导出肯定是一个不可避免的问题啦.客户要求要导出优雅,美观的Excel文档格式的来展示数据,当时的第一想法就是使用NPOI开源库来做 ...
- MySQL 教程--检视阅读
MySQL 教程--检视阅读 准备:Windows 上安装 MySQL 教程地址,PHP语言基础 教程地址2 教程地址3,有讲数据库的备份和恢复 教程地址4,w3c.china,php基础,扩展阅读 ...
- 参加Folding@Home(FAH)项目,为战胜新冠肺炎贡献出自己的一份力量
鉴于新冠病毒(COVID-19)在全球范围内的大规模传播,PCMR和NVIDIA呼吁全球PC用户加入Folding@home项目贡献自己闲置的GPU计算力,协助抗击新冠状病毒疫情. 目前全球有超过40 ...
- 初识ASP.NET CORE
首先创建一个asp.net core web应用程序 第二步 目前官方预置了7种模板项目供我们选择.从中我们可以看出,既有我们熟悉的MVC.WebAPI,又新添加了Razor Page,以及结合比较流 ...
- C# 基础知识系列- 6 Lambda表达式和Linq简单介绍
前言 C#的lambda和Linq可以说是一大亮点,C#的Lambda无处不在,Linq在数据查询上也有着举足轻重的地位. 那么什么是Linq呢,Linq是 Language Intergrated ...
- C语言自学网官方微信相关功能使用方法
一.微信扫描关注微信公众账号(C语言自学网),我们会不定时更新关于编程的技术文章和相关资讯,了解大佬的成长之路. 二.回复“C语言学习资料”,将显示由我们精心整理的全套C语言学习资料的下载地址及链接. ...