GO中树的实现与学习

2023-11-02

go 的书写很像C
然后以前没有弄明白的事情终于弄明白了
这一次是树的重新学习和深入学习

树的基本方法

  1. 创建
  2. 遍历
  3. 插入
  4. 删除
  5. 深度
  6. 广度
  7. 查找

树的进阶用法

  1. B树
  2. 二叉搜索树
  3. 红黑树
  4. 扩展和平衡
  5. 动态规划
  6. 贪心算法
  7. 摊还分析
  8. 斐波那契堆

基本二叉树

树的基本创建

这个有两种创建方法
第一种是仿照插入的方法
第二种是仿照层序遍历的方法

方法一

插入就是仿照二叉树的查找。
二叉搜索树:
一棵二叉树,可以为空;如果不为空,满足以下性质:
1非空左子树的所有键值小于其根结点的键值。
2.非空右子树的所有键值大于其根结点的键值
3.左、右子树都是二叉搜索树。

  1. 创建root
  2. 然后申请一个新的节点
  3. 先看左边再看右边
  4. 如果是空的就插入
func CreateTreeInsert[N Number](TestData []N) (root *Node[N]) {
	root = new(Node[N])
	root.V = TestData[0]
	root.left = nil
	root.right = nil

	var tem = root

	for i := 1; i < len(TestData); i++ {
		var newNode = new(Node[N])
		newNode.V = TestData[i]
		newNode.right = nil
		newNode.left = nil
		tem = root
		
		for {
			if TestData[i] > tem.V {
				if tem.right != nil {
					tem = tem.right
				} else if tem.right == nil {
					tem.right = newNode
					break
				}
			} else if TestData[i] < tem.V {
				if tem.left != nil {
					tem = tem.left
				} else if tem.left == nil {
					tem.left = newNode
					break
				}
			}

		}
	}
	return root
}
方法二

仿照层序遍历的方法
使用队列每一个元素进入后进行层序创建。这样就是一个完全二叉树
(主要插入的时候是先左后右就是。如果先右后左就不是了)

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树

//层序创建

  1. 申请一个队列
  2. 创建root
  3. 将root入队列
  4. 开始循环处理
  5. 弹出一个节点
  6. 申请一个节点
  7. 先填左边
  8. 把节点入队列
  9. 又申请一个节点
  10. 后填右边
  11. 把节点放入队列
  12. 退出是数组越界
func CreateTree[N Number](TestData []N) (root *Node[N]) {
	var myQueue = new(queue[N])
	myQueue.length = 0
	myQueue.Q = make([]*Node[N], 20)
	var j = 0
	var length = len(TestData)
	root = new(Node[N])
	root.V = TestData[j]
	root.left = nil
	root.right = nil
	myQueue.In(root)
	for {
		var tem, ErrorQ = myQueue.Out()
		if ErrorQ != nil {
			fmt.Printf("%v \n", myQueue)
		}
		for {
			if tem.left == nil {
				j = j + 1
				if j >= length {
					break
				}
				var newNode = new(Node[N])
				newNode.V = TestData[j]
				newNode.right = nil
				newNode.left = nil
				myQueue.In(newNode)
				tem.left = newNode
			} else if tem.right == nil {
				j = j + 1
				if j >= length {
					break
				}
				var newNode = new(Node[N])
				newNode.V = TestData[j]
				newNode.right = nil
				newNode.left = nil
				myQueue.In(newNode)
				tem.right = newNode
			} else {
				break
			}
		}
		if j >= length {
			break
		}
	}
	return root
}
树的遍历

树的遍历是经典数据结构的用法
先序,中序,后序,层序

首先是递归用法
首先明白为什么要有先序中序后序

func PrintTree[N Number](root *Node[N]) {
	if root == nil {
		return
	}
	fmt.Printf("%v\n", root.V)//先序
	PrintTree(root.left)
	fmt.Printf("%v\n", root.V)//中序
	PrintTree(root.right)
	fmt.Printf("%v\n", root.V)//后序
}

其实是三个输出位置的不一样导致的输出顺序不一样。
然后就是非递归的用法了
为什么要有非递归
因为有时候要去理解原理。自己去实现压栈的原理

首先是

先序遍历非递归

先说结论
先序是压栈的时候的顺序
中序是出栈的时候的顺序
后序是先序的出栈的时候的排序

首先是先序遍历非递归实现

  1. 申请一个栈和一个队列
  2. 首先将root压进栈
  3. 把root入队列
  4. 开启循环
  5. 把Node.Lift全部压进栈
  6. (只要是压栈操作,都要入队列)
  7. 弹出一个node
  8. 如果Right节点有
  9. right节点压入栈
  10. right节点进队列
  11. 然后把右节点LIFT全部压栈
  12. 重复
  13. 最后在队列的就是完整的先序顺序

这样到栈空的时候退出

就是模拟人栈的情况
先压左边,如果左边没有了
就查看右边。右边有左边又把左边全部压进去
没有了就看右边了。

这样循环压左边

func PushLiftIn[N Number](node *Node[N], stack *Stack[N], Queue *queue[N]) {
	for {
		if node.left != nil {
			stack.Push(node.left)
			Queue.In(node.left)
			node = node.left
		} else if node.left == nil {
			break
		}
	}
}

然后弹出一个元素检查右边有没有

func PopOut[N Number](stack *Stack[N], Queue *queue[N]) (PopError error) {
	var tem, ErrorPop = stack.Pop()
	if ErrorPop != nil {
		fmt.Printf("%v\n", ErrorPop)
		return ErrorPop
	}
	if tem.right != nil {
		stack.Push(tem.right)
		Queue.In(tem.right)
		tem = tem.right
		PushLiftIn(tem, stack, Queue)
	}
	return nil
}

完整的代码
一个栈
一个队列

func (n *Node[Number]) PreorderTraversal() {
	//	从左边开始返回输出
	//先申请一个栈
	var tem = n
	var myStack = new(Stack[Number])
	myStack.length = 0
	myStack.S = make([]*Node[Number], 20)

	var myQueue = new(queue[Number])
	myQueue.length = 0
	myQueue.Q = make([]*Node[Number], 20)

	myStack.Push(tem)
	myQueue.In(tem)

	PushLiftIn(tem, myStack, myQueue)
	for {
		var PopError = PopOut(myStack, myQueue)
		if PopError != nil {
			break
		}
	}
	for {
		var Node, ErrorOut = myQueue.Out()
		if ErrorOut != nil {
			break
		} else {
			fmt.Printf("%v\n", Node.V)
		}
	}
}
中序遍历非递归

先序遍历和中序遍历唯一的区别
就是入队列的时候
先序是入栈的时候入队列
中序是出栈的时候入队列

func PopInOrderOut[N Number](stack *Stack[N], Queue *queue[N]) (PopError error) {
	var tem, ErrorPop = stack.Pop()
	if ErrorPop != nil {
		fmt.Printf("%v\n", ErrorPop)
		return ErrorPop
	} else {
		Queue.In(tem)
	}
	if tem.right != nil {
		stack.Push(tem.right)
		tem = tem.right
		PushInOrderLiftIn(tem, stack)
	}
	return nil
}

只关心弹出的时候入队列

func PushInOrderLiftIn[N Number](node *Node[N], stack *Stack[N]) {
	for {
		if node.left != nil {
			stack.Push(node.left)
			node = node.left
		} else if node.left == nil {
			break
		}
	}
}

func PopInOrderOut[N Number](stack *Stack[N], Queue *queue[N]) (PopError error) {
	var tem, ErrorPop = stack.Pop()
	if ErrorPop != nil {
		fmt.Printf("%v\n", ErrorPop)
		return ErrorPop
	} else {
		Queue.In(tem)
	}
	if tem.right != nil {
		stack.Push(tem.right)
		tem = tem.right
		PushInOrderLiftIn(tem, stack)
	}
	return nil
}

func (n *Node[Number]) InorderTraversal() {
	// 从根节点开始返回输出
	//	从左边开始返回输出
	//先申请一个栈
	var tem = n
	var myStack = new(Stack[Number])
	myStack.length = 0
	myStack.S = make([]*Node[Number], 20)

	var myQueue = new(queue[Number])
	myQueue.length = 0
	myQueue.Q = make([]*Node[Number], 20)
	myStack.Push(tem)
	PushInOrderLiftIn(tem, myStack)
	for {
		var PopError = PopInOrderOut(myStack, myQueue)
		if PopError != nil {
			break
		}
	}
	for {
		var Node, ErrorOut = myQueue.Out()
		if ErrorOut != nil {
			break
		} else {
			fmt.Printf("%v\n", Node.V)
		}
	}

}
后序遍历非递归

后序遍历是压栈出栈顺序

就是反向的先序遍历后压入栈然后进行弹出

反向先序就是先右后左

func PushRightPost[N Number](node *Node[N], stack *Stack[N], stackTwo *Stack[N]) {
	for {
		if node.right != nil {
			stack.Push(node.right)
			stackTwo.Push(node.right)
			node = node.right
		} else if node.right == nil {
			break
		}
	}
}

func PopOutPost[N Number](stack *Stack[N], stackTwo *Stack[N]) (PopError error) {
	var tem, ErrorPop = stack.Pop()
	if ErrorPop != nil {
		fmt.Printf("%v\n", ErrorPop)
		return ErrorPop
	}
	if tem.left != nil {
		stack.Push(tem.left)
		stackTwo.Push(tem.left)
		tem = tem.left
		PushRightPost(tem, stack, stackTwo)
	}
	return nil
}

然后最后输出另一个栈

func (n *Node[Number]) PostOrderTraversal() {
	var tem = n

	var myStack = new(Stack[Number])
	myStack.length = 0
	myStack.S = make([]*Node[Number], 20)

	var myStackTwo = new(Stack[Number])
	myStackTwo.length = 0
	myStackTwo.S = make([]*Node[Number], 20)

	myStack.Push(tem)
	myStackTwo.Push(tem)

	PushRightPost(tem, myStack, myStackTwo)
	for {
		var PopError = PopOutPost(myStack, myStackTwo)
		if PopError != nil {
			break
		}
	}
	for {
		var Value, ErrorPop = myStackTwo.Pop()
		if ErrorPop != nil {
			break
		} else {
			fmt.Printf("%v\n", Value.V)
		}
	}
}
层序遍历

没有什么好说的。

  1. 开一个队列
  2. 先入root
  3. 每次把左右节点入队列
  4. 但队列为空的时候退出
func (n *Node[Number]) LevelOrderTraversal() {
	// 从右边开始返回输出
	var tem = n
	var myQueue = new(queue[Number])
	myQueue.length = 0
	myQueue.Q = make([]*Node[Number], 20)
	myQueue.In(tem)
	for {
		var node, ErrorOut = myQueue.Out()
		if ErrorOut != nil {
			fmt.Printf("myQueueOne %v\n", ErrorOut)
			break
		} else {
			fmt.Printf("%v\n", node.V)
		}
		if node.left != nil {
			myQueue.In(node.left)
		}
		if node.right != nil {
			myQueue.In(node.right)
		}
	}
}
树的深度

这里就有两个算法DFs BFS

深度优先

DFS
func maxDepthDepthFirstSearch[N Number](root *Node[N]) int {
	//如果root为nil,直接返回0
	if root == nil {
		return 0
	}
	//分别统计左节点深度、右节点深度,取最大值最后加上+1
	return max(maxDepthDepthFirstSearch(root.left), maxDepthDepthFirstSearch(root.right)) + 1
}

//获取最大值
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
BFS

利用层序遍历

开两个队列
把下一层的放在另一個队列里面
//层序遍历出来的深度

func (n *Node[Number]) deepBreadthFirstSearch() int {
	var tem = n
	//开两个队列
	var myQueue = new(queue[Number])
	myQueue.length = 0
	myQueue.Q = make([]*Node[Number], 20)
	//先压入root在第一个队列里面
	myQueue.In(tem)

	var myQueueTwo = new(queue[Number])
	myQueueTwo.length = 0
	myQueueTwo.Q = make([]*Node[Number], 20)

	var depth = 0
	for {
		var node, ErrorOut = myQueue.Out()
		if ErrorOut != nil {
			depth = depth + 1
			fmt.Printf("myQueueOne %v\n", ErrorOut)
			//空了就看第二个。把第二个压进去
			for {
				var node, ErrorOut = myQueueTwo.Out()
				if ErrorOut != nil {
					break
				}
				myQueue.In(node)
			}
			var tem, _ = myQueue.Out()
			if tem != nil {
				node = tem
			} else {
				break
			}
		}
		if node.left != nil {
			myQueueTwo.In(node.left)
		}
		if node.right != nil {
			myQueueTwo.In(node.right)
		}
	}
	return depth
}

下面是搜索二叉树

搜索二叉树

搜索二叉树
搜索二叉树
中序排列(左中右),那么会得到一个从小到大的序列

1非空左子树的所有键值小于其根结点的键值。
2.非空右子树的所有键值大于其根结点的键值
3.左、右子树都是二叉搜索树。

当时如果把一个顺序的数组插入进去
搜索二叉树会直接退化为数组,没有查找的意义了

然后出现了平衡二叉树AVL二叉树

AVL

左子树与右子树高度之差的绝对值不超过1
树的每个左子树和右子树都是AVL树
每一个节点都有一个平衡因子(balance factor),任一节点的平衡因子是-1、0、1
(每一个节点的平衡因子 = 右子树高度 - 左子树高度)

AVL节点

(请不要关注把 left 敲成lift 起飞)
value 我设置的是int64 这一次并没有用泛型

type Value int64
type NodeOfAVL struct {
	V             Value
	Lift          *NodeOfAVL
	Right         *NodeOfAVL
	Parent        *NodeOfAVL
	BalanceFactor int //(每一个节点的平衡因子 = 右子树高度 - 左子树高度)
}

队列的基本操作

type queue struct {
	length int
	Q      []*NodeOfAVL
}

func (q *queue) In(value *NodeOfAVL) {
	q.Q[q.length] = value
	q.length = q.length + 1
}

func (q *queue) Out() (value *NodeOfAVL, err error) {
	if q.length != 0 {
		value = q.Q[0]
		q.Q = q.Q[1:]
		q.length = q.length - 1
		err = nil
	} else {
		err = errors.New("this Queue is Empty")
		value = nil
	}
	return value, err
}
AVL基本方法
type AVLTree interface {
	create(data []Value)
	insert(insertValue Value) (ErrorEqual error)
	find(findValue Value) (NodeLocation *NodeOfAVL, ErrorFind error)
	LevelOrderTraversal()
	deepBreadthFirstSearch() int
	SetBalanceFactor()
}
AVL-create 创建

这个准确的来说是搜索二叉树的创建
都是仿照插入的方法。

  1. 创建一个root
  2. 如果更小向左边
  3. 如果更大向右边
func (root *NodeOfAVL) create(data []Value) {
	var tem = root
	for _, datum := range data {
		var newNode = new(NodeOfAVL)
		newNode.V = datum
		newNode.Lift = nil
		newNode.Right = nil
		newNode.Parent = nil
		newNode.BalanceFactor = 0
		tem = root
		for {
			if datum > tem.V {
				if tem.Right != nil {
					tem = tem.Right
				} else if tem.Right == nil {
					newNode.Parent = tem
					tem.Right = newNode
					break
				}
			} else if datum < tem.V {
				if tem.Lift != nil {
					tem = tem.Lift
				} else if tem.Lift == nil {
					newNode.Parent = tem
					tem.Lift = newNode
					break
				}
			}
		}
	}
}
AVL深度

DFS

func maxDepthDepthFirstSearch(root *NodeOfAVL) int {
	//如果root为nil,直接返回0
	if root == nil {
		return 0
	}
	//分别统计左节点深度、右节点深度,取最大值最后加上+1
	return max(maxDepthDepthFirstSearch(root.Lift), maxDepthDepthFirstSearch(root.Right)) + 1
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

BFS

func (root *NodeOfAVL) deepBreadthFirstSearch() int {
	// 从右边开始返回输出
	var tem = root
	var myQueue = new(queue)
	myQueue.length = 0
	myQueue.Q = make([]*NodeOfAVL, 20)

	myQueue.In(tem)

	var myQueueTwo = new(queue)
	myQueueTwo.length = 0
	myQueueTwo.Q = make([]*NodeOfAVL, 20)

	var depth = 0
	for {
		var node, ErrorOut = myQueue.Out()
		if ErrorOut != nil {
			depth = depth + 1
			fmt.Printf("\n")
			for {
				var node, ErrorOut = myQueueTwo.Out()
				if ErrorOut != nil {
					break
				}
				myQueue.In(node)
			}
			var tem, _ = myQueue.Out()
			if tem != nil {
				node = tem
			} else {
				break
			}
		}
		fmt.Printf("%v ", node.V)
		if node.Lift != nil {
			myQueueTwo.In(node.Lift)
		}
		if node.Right != nil {
			myQueueTwo.In(node.Right)
		}
	}
	return depth
}
AVL设置平衡因子

每一个节点都有一个平衡因子(balance factor),任一节点的平衡因子是-1、0、1
(每一个节点的平衡因子 = 右子树高度 - 左子树高度)

所以每一个节点弄左边右边

node.BalanceFactor = maxDepthDepthFirstSearch(node.Right) - maxDepthDepthFirstSearch(node.Lift)

用什么遍历都是可以的

func (root *NodeOfAVL) SetBalanceFactor() {
	var tem = root
	var myQueue = new(queue)
	myQueue.length = 0
	myQueue.Q = make([]*NodeOfAVL, 20)
	myQueue.In(tem)
	for {
		var node, ErrorOut = myQueue.Out()
		if ErrorOut != nil {
			fmt.Printf("myQueueOne %v\n", ErrorOut)
			break
		} else {
			node.BalanceFactor = maxDepthDepthFirstSearch(node.Right) - maxDepthDepthFirstSearch(node.Lift)
			fmt.Printf("%v  Banlence %d\n", node.V, node.BalanceFactor)
		}
		if node.Lift != nil {
			myQueue.In(node.Lift)
		}
		if node.Right != nil {
			myQueue.In(node.Right)
		}
	}
}
AVL的插入和删除平衡-左旋和右旋

在插入和删除的时候都存在让AVL不在平衡的情况

首先平衡因子只会存在-1 0 1
如果插入一个节点或者删除一个节点后

在删除一个节点或插入一个节点后
更新平衡因子
平衡因子 = 右子树高度 - 左子树高度
如果是左边的树多了 root的平衡因子为 -2
然后看左树的节点
如果是1 左树是右倾斜的
如果是-1 左树是左倾斜的

如果是右边的树多了 root的平衡因子为2
同理

每次动头结点的时候。保证是同样倾斜的就是了

  1. 旋转
  2. 看旋转的另一边有节点没有
  3. 没有就直接旋转
  4. 有就保存另一边节点
  5. 变成旋转节点父亲的另一边节点

左旋和右旋

func rotateToTheLeft(root *NodeOfAVL) (newRoot *NodeOfAVL) {
	var leftNode = root.Lift
	if leftNode.Right != nil {
		root.Lift = nil
		root.Parent = leftNode

		leftNode.Parent = nil
		leftNode.Right = root

	} else if leftNode.Right == nil {
		var leftNodeRight = leftNode.Right
		//保存另一边的节点
		root.Lift = leftNodeRight
		root.Parent = leftNode
		leftNodeRight.Parent = root

		leftNode.Parent = nil
		leftNode.Right = root
	}
	newRoot = leftNode
	return newRoot
}
func rotateToTheRight(root *NodeOfAVL) (newRoot *NodeOfAVL) {
	var RightNode = root.Right
	if RightNode.Lift == nil {

		root.Parent = RightNode
		root.Right = nil

		RightNode.Parent = nil
		RightNode.Lift = root

	} else if RightNode.Lift != nil {
		var RightNodeLeft = RightNode.Lift

		root.Parent = RightNode
		root.Lift = RightNodeLeft
		RightNodeLeft.Parent=root

		RightNode.Parent = nil
		RightNode.Lift = root

	}
	newRoot = RightNode

	return newRoot
}

然后就是完整的插入过程了

  1. tem用来进行插入,左右找
  2. temtwo用来平衡
  3. 项目3
func insertWithCheck(root *NodeOfAVL, insertValue Value) (NewRoot *NodeOfAVL, ErrorEqual error) {
	var tem = root
	var temTwo = root
	for {
		if insertValue > tem.V {
			if tem.Right != nil {
				tem = tem.Right
			} else if tem.Right == nil {
				var newNode = new(NodeOfAVL)
				newNode.V = insertValue
				newNode.Lift = nil
				newNode.Right = nil
				newNode.Parent = nil
				newNode.BalanceFactor = 0

				tem.Right = newNode
				newNode.Parent = tem

				break
			}
		} else if insertValue < tem.V {
			if tem.Lift != nil {
				tem = tem.Lift
			} else if tem.Lift == nil {
				var newNode = new(NodeOfAVL)
				newNode.V = insertValue
				newNode.Lift = nil
				newNode.Right = nil
				newNode.Parent = nil
				newNode.BalanceFactor = 0

				newNode.Parent = tem
				tem.Lift = newNode

				break
			}
		} else if insertValue == tem.V {
			ErrorEqual = errors.New("this is no equal value in AVL Tree")
			break
		}
	}
	//更新所有节点的平衡因素
	temTwo.SetBalanceFactor()

	if temTwo.BalanceFactor == -2 {
		var leftNode = root.Lift
		if leftNode.BalanceFactor == -1 {
			//如果是-1这个就是左边节点偏左,直接左旋
			NewRoot = rotateToTheLeft(temTwo)
		} else if leftNode.BalanceFactor == 1 {
			//这个就是左边节点偏右,需要先变成偏左,先这个节点进行右旋,然后再左旋
			var newLeftNode = rotateToTheRight(leftNode)

			temTwo.Lift = newLeftNode
			newLeftNode.Parent = temTwo

			NewRoot = rotateToTheLeft(temTwo)
		}
	} else if temTwo.BalanceFactor == 2 {
		var RightNode = root.Right

		if RightNode.BalanceFactor == 1 {
			//如果是1这个就是右边偏右直接右旋
			NewRoot = rotateToTheRight(temTwo)
		} else if RightNode.BalanceFactor == -1 {
			//如果是-1这个就是右边偏左,先左旋,再右旋
			var newRightNode = rotateToTheLeft(RightNode)

			temTwo.Right = newRightNode
			newRightNode.Parent = temTwo

			NewRoot = rotateToTheRight(temTwo)
		}
	} else {
		NewRoot = temTwo
	}
	return NewRoot, ErrorEqual
}

如果是删除节点
对于非叶子节点的删除,最终都将转化为对叶子节点的删除。

  1. 中序遍历
  2. 找到删除节点的前驱或者后驱节点
  3. 值进行替换
  4. 重新设置平衡因子
  5. 进行左右旋转
    首先先中序遍历
func PrintTree(root *NodeOfAVL) {
	if root == nil {
		return
	}
	PrintTree(root.Lift)
	fmt.Printf("%v\n", root.V)
	PrintTree(root.Right)
}

然后是找到删除节点的前驱或者后驱节点
就是小一个或者大一个的节点位置

func (root *NodeOfAVL) find(findValue Value) (NodeLocation *NodeOfAVL, ErrorFind error) {
	var tem = root
	for {
		if findValue > tem.V {
			if tem.Right != nil {
				tem = tem.Right
			} else if tem.Right == nil {
				ErrorFind = errors.New("this is no findValue")
				NodeLocation = nil
				break
			}
		} else if findValue < tem.V {
			if tem.Lift != nil {
				tem = tem.Lift
			} else if tem.Lift == nil {
				ErrorFind = errors.New("this is no findValue")
				NodeLocation = nil
				break
			}
		} else if findValue == tem.V {
			NodeLocation = tem
			ErrorFind = nil
			break
		}
	}
	return NodeLocation, ErrorFind
}

然后将值进行替换
移除前驱节点的情况或者后驱节点的情况都是在叶子上
比较好处理
然后就是和插入同样的步骤
重新看平衡因子
进行左右旋转

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GO中树的实现与学习 的相关文章

随机推荐

  • GPT模型系列

    文章目录 1 Mask Multi head Attentiion 2 Generative Pre Traning GPT 3 GPT2 4 GPT3 1 Mask Multi head Attentiion Mask Multi hea
  • php数据库判断登录用户,【判断用户登录】PHP这样判断流程是否正确?每次都查询数据库 存COOKIE...

    我自己来做的PHP判断用户是否登录 流程 1 先判断有没有cookie uid cookie uid 如果没有跳出循环检测 2 如果有 连接数据库查询该uid对应的记录 如果没有改记录则跳出循环检测并且注销所有用户cookie 3 如果有
  • 前k个高频单词

    不要害怕前方的未知和困难 因为它们都是你成长的机会 不要过于在意别人的眼光和评价 因为唯有你的内心才知道自己真正的价值 珍惜当下 享受生活的点滴 让自己变得更加坚强 自信 成熟 作者 不能再留遗憾了 专栏 Java学习 本文章主要内容 前k
  • 星星之火-52:6G十大领域关键技术

    目录 1 6G超宽带通信系统的网络架构 2 6G超宽带通信系统的软件架构 3 太赫兹通信技术 4 6G 信道仿真技术及射线跟踪 5 超大带宽与全频谱协作 6 轨道角动量调制技术 7 宽带太赫兹硬件元器件技术 8 太赫兹天线技术 9 太赫兹射
  • 国产系统有了,芯片有了,编译器有了,那编程语言呢?

    国产操作系统一直在发展 市面上也早有了多款基于Linux内核的操作系统 各大OS厂商也都有自己的市场和拥趸 而芯片这块 虽然我们起步晚 商业市场也显得浮躁纷繁 但依旧有务实的IT科研工作者 工程师为主的企业或团队默默无闻十年磨一剑 苦心孤诣
  • (十三)MySQL数据库安装——从0开始大数据开发实战:电影推荐系统(scala版)

    执行一下命令 安装MySQL sudo apt get update sudo apt get install mysql server 安装过程中会提示设置MySQL数据库root用户的密码 本案例设置密码为hadoop 安装完成后默认启
  • 常用英语缩写

    Abandon简写为ABAN Abandoned简写为ABD Abbreviate简写为ABBR Abbreviated简写为ABR Abbreviation简写为ABR Ability简写为ABL Abundance简写为ABUND Ac
  • 虚幻4学习笔记(4)光照、游戏角色、上下车、冲刺瞬移多段跳、打包

    光照 光照 光照分类 光的移动性 自动曝光 指数级高度雾 生成光束 使用体积雾创建光束 使用天空球制造夜晚 设置玩家角色 设置玩家切换 镜头过度 上下车 上车 下车 下车减速 人物冲刺和瞬移 冲刺 瞬移 多段跳设置 打包 B站UP谌嘉诚课程
  • LinkedList工作原理及实现

    以双向链表实现 链表无容量限制 但双向链表本身使用了更多空间 也需要额外的链表指针操作 按下标访问元素 get i set i e 要悲剧的遍历链表将指针移动到位 如果i gt 数组大小的一半 会从末尾移起 插入 删除元素时修改前后节点的指
  • Qt编写自定义控件大全

    最新版可执行文件 https pan baidu com s 1Y z4GT4kslgsb4f46yLILA 不定期增加控件及修正BUG和改进算法 目前已超过90个控件 总图 总图 1 动画按钮 1 可设置显示的图像和底部的文字 2 可设置
  • jenkins部署jeecg-boot3.1(前后端)自动化

    tip 我是使用我的腾讯云轻量应用服务器做的本次实战 操作系统 CentOS 7 6 64bit 主机规格 CPU 4核 内存 4GB 这个配置起前端有点带不动 建议8G内存 这个问题我反复测试很多次 一跑npm 就开始疯狂占资源 然后操作
  • 联想服务器开机引导,联想服务器怎么进入bios

    联想电脑设置起来比较麻烦 除了快捷启动菜单比较方便 如果要用传统的方式进行设置 会有很多项要设置 那么你知道联想服务器怎么进入bios吗 接下来 学习啦小编跟你分享联想服务器进入bios的设置步骤图解 联想服务器进入bios的设置步骤图解
  • word文档 文字变网址 解决办法

    word文档中文字变网址解决办法 问题描述 打开word文档发现其中有些文字变成了网址 解决办法 Alt F9 联想键盘 Alt Fn F9 这是因为直接按F9是功能键
  • n对括号的匹配方式(卡特兰数)

    4对 括号有多少种可能的合法 匹配方式 n对 括号呢 此题是卡特兰数的一个通常应用 相似的还有出栈顺序等 关于卡特兰数的具体内容 请参阅百度百科或Wiki http baike baidu com view 2499752 htm 网络上可
  • 泛型与正则表达式在Java编程中扮演着重要的角色

    泛型与正则表达式在Java编程中扮演着重要的角色 泛型提供了一种在编译时期检查类型安全性的机制 而正则表达式则允许我们以模式匹配的方式进行字符串的处理 本文将详细介绍Java中泛型和正则表达式的基础知识 并提供相应的源代码示例 泛型 什么是
  • 笔记:springboot-admin 整合spring security应用注册失败问题

    问题描述 原本正常的springboot admin 在整合了spring security之后就无法正常注册应用了 查看打印 Failed to register application as Application name admin
  • Android 中Activity和Fragment生命周期的具体变化详解

    博主前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住也分享一下给大家 点击跳转到网站 前言 一个MainActivity中装载了两个Fragment 在MainActivity中的onCreate 方法中进行了Fragme
  • ​ 输入一个字符串,将其中所有的大写和小写字母改成该字母的下一个字母,z 的下一个字母为 a。JAVA

    import java util Scanner public class Main public static void main String args Scanner scanner new Scanner System in Str
  • 【Unity】四叉树/八叉树管理和动态加载场景物件

    一 引言 场景的组织与管理是3d游戏开发中重要的一环 unity3d引擎中 只提供了最基本的场景组织 当我们加载场景时 会将场景中的物件及其依赖的资源全部加载出来 这对于较为庞大的场景显然是不合理的 可以考虑在进入场景时预先将可见范围内的物
  • GO中树的实现与学习

    go 的书写很像C 然后以前没有弄明白的事情终于弄明白了 这一次是树的重新学习和深入学习 树的学习 基本二叉树 树的基本创建 方法一 方法二 树的遍历 先序遍历非递归 中序遍历非递归 后序遍历非递归 层序遍历 树的深度 DFS BFS 搜索