zoukankan      html  css  js  c++  java
  • 树算法的GO语言实现

    树的遍历

    递推公式

    X序遍历,就先打印X,再打印左右
    前序遍历的递推公式:
    preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)
    中序遍历的递推公式(有序):
    inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)
    后序遍历的递推公式:
    postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r

    package algo
    
    import (
    	"fmt"
    	"testing"
    )
    
    type Node struct {
    	data int
    	left *Node
    	right *Node
    }
    
    func PreOrder(node *Node){
    	if node == nil{
    		return
    	}
    	fmt.Println(node.data)
    	PreOrder(node.left)
    	PreOrder(node.right)
    }
    
    //从小到大有序
    func InOrder(node *Node){
    	if node == nil {
    		return
    	}
    	//递归找到最左(最小的点)
    	InOrder(node.left)
    	fmt.Println(node.data)
    	InOrder(node.right)
    }
    
    func PostOrder(node *Node){
    	if node == nil{
    		return
    	}
    	PostOrder(node.left)
    	PostOrder(node.right)
    	fmt.Println(node.data)
    }
    
    func TestTree(t *testing.T){
    	tree := &Node{data:5}
    	tree.left = &Node{data:4}
    	tree.right = &Node{data:7}
    	tree.left.left = &Node{data:3}
    	PostOrder(tree)
    }
    
    func FindValue(root *Node,value int) *Node{
    	//以根节点为起点
    	currentNode := root
    	//叶节点指针为空
    	for currentNode != nil{
    		//数值相同,直接返回
    		if currentNode.data == value{
    			return currentNode
    			//数值比当前节点大,节点偏移到当前节点的右节点
    		}else if value > currentNode.data {
    			currentNode = currentNode.right
    		}else if value < currentNode.data {
    			currentNode = currentNode.left
    		}
    	}
    	//没有找到返回空指针
    	return nil
    }
    
    
    /**
    关键:
    1. 树是空的判断
    2. 插入位置节点是空的判断
     */
    func insert(tree *Node,value int){
    	//树是空的,直接把根节点指向新插入数据
    	if tree == nil{
    		tree = &Node{data:value}
    		return
    	}
    	currentNode := tree
    
    	//遍历到叶子节点
    	for currentNode != nil{
    		//如果新数据比当前节点大,并且右节点为空,则插入到右节点
    		if value > currentNode.data{
    			if currentNode.right == nil{
    				currentNode.right = &Node{data:value}
    				return
    			}
    			//右节点不为空,继续从右节点找插入位置
    			currentNode = currentNode.right
    		}else{
    			if currentNode.left == nil{
    				currentNode.left = &Node{data:value}
    				return
    			}
    			currentNode = currentNode.left
    		}
    	}
    }
    
    

    BFS

    • 图其实是树的一个变种
    • 从A出发,一层一层从左到右,从上往下
    1. 先遍历与A相邻的节点(B & C,顺序无要求,可以多种结果)
    2. 从左到右看和B & C相邻的节点
    3. 由于先遍历的是B,先求B相邻的节点(A C D),由于A C已经遍历过,只需要遍历D

    数据结构

    • 队列(A入队,A出队,并且把A相邻节点B C塞入队列。把B拿出来同时把B的相邻节点D塞入队列...可以保证先遍历B再遍历C的节点),用队列保证下一个遍历的元素是前面元素的顺序

    DFS

    • 从A出发,一条路走到底,再往回走
    1. 先找一个与A相邻的点(B or C)B
    2. 再找与B相邻的点(C or D)D
    3. 找与D相邻的点(C or E or F)F
    4. 发现F没有路,往回挑到前面一个点(D)
    5. 找D没有走过的点(C or E)E
    6. 从E找没有做过的点C
    7. C发现所有点已经走完(回跳)

    数据结构

    • 栈(A入栈,把栈里清空一个元素并把邻节点放入,C和B入栈,B出栈,把B的邻节点,D入栈...F没有邻节点,也要从栈拿出一个元素),用栈保证下一个拿的是上一个元素的邻节点
    package algo
    
    import (
    	"testing"
    )
    
    
    func BFS(graph map[string][]string,start string)[]string{
    	//初始化队列,起点入队
    	queue := []string{}
    	queue = append(queue, start)
    	//初始化访问集合,起点加入集合
    	visitedSet := map[string]bool{}
    	visitedSet[start] = true
    	//用一个切片记录走过的路径
    	bfs := []string{}
    	//循环,直到队列无元素(最后一个点)
    	for len(queue) > 0 {
    		//取出队头
    		vertex := queue[0]
    		queue = queue[1:]
    		
    		//记录路径
    		bfs = append(bfs, vertex)
    		
    		//取出队头的邻节点
    		neighbor := graph[vertex]
    		//队头的未访问的邻节点全部入队,并且放到访问集合
    		for _,node := range(neighbor){
    			if !visitedSet[node]{
    				queue = append(queue,node)
    				visitedSet[node] = true
    			}
    		}
    	}
    	return bfs
    }
    
    
    func DFS(graph map[string][]string,start string){
    	//建立栈,并且起点入栈
    	stack := []string{}
    	stack = append(stack,start)
    	//建立已访问集合,并且标记起点已访问
    	visitedSet := map[string]bool{}
    	visitedSet[start] = true
    	//循环,结束点是栈中没有元素
    	for len(stack) > 0 {
    		//取出栈顶(数组最后一个元素)
    		vertex := stack[len(stack)-1]
    		stack = stack[:len(stack)-1]
    		//取出栈顶元素的邻节点
    		neighbors := graph[vertex]
    		fmt.Println(vertex)
    		//循环邻节点的所有元素
    		for _,node := range(neighbors){
    			//判断元素,如果没有被访问,则入栈并标记为已访问
    			if !visitedSet[node]{
    				stack = append(stack,node)
    				visitedSet[node] = true
    
    			}
    		}
    	}
    }
    
    func TestBFS(t *testing.T){
    	graph := map[string][]string{}
    	graph["A"] = []string{"B","C"}
    	graph["B"] = []string{"A","C","D"}
    	graph["C"] = []string{"A","B","D","E"}
    	graph["D"] = []string{"B","C","E","F"}
    	graph["E"] = []string{"C","D"}
    	graph["F"] = []string{"D"}
    	bfs := BFS(graph,"A")
    	t.Log(bfs)
    }
    

    特点

    • 是完全二叉树,除了最后一层,数据都是满的,最后一层靠左
    • 节点的值大于或小于左右节点
    • 最大值在顶点的是大顶堆,最小值在顶点的是最小堆
    • 数组存储,节点i,左节点i2,右节点i2+1

    建堆和移除堆顶

    建堆:把节点插入到末尾,从下向上,每个节点与父节点比较,不满足大小关系就交换(从下往上是指节点主动与父节点比较),直到节点循环到1为止
    移除堆顶:用节点最后一个值覆盖堆顶的值,然后进行堆化,从堆顶开始,从上往下,每个父节点与左边节点比较,如果不满足大小关系,则把最大标记置为左节点,右节点同理。通过两个比较后,如果最大标记仍然是父节点,表示满足了大小关系,循环结束,否则父节点与最大标记交换。父节点更新为最大标记的位置,开始下一轮比较。

    type Heap struct {
    	arr []int
    	count int //已使用的容量
    	length int //总容量
    }
    
    //模拟构造函数
    func NewHeap(length int)*Heap{
    	heap := &Heap{}
    	heap.arr = make([]int,length+1) //初始化,从arr[1]开始使用
    	heap.count = 0 //默认已使用容量为0
    	heap.length = length+1
    	return heap
    }
    
    func (this *Heap)insert(value int){
    	//已使用容量 >= 总容量 表示容量已满
    	if this.count >= this.length{
    		return
    	}
    	//标记已使用容量+1
    	this.count++
    	//最后一个元素放入value
    	this.arr[this.count] = value
    	//从下往上堆化(子节点主动跟父节点比较)
    	i := this.count
    	//循环结束条件:i/2>0表示最后一直比较到1
    	//子节点与父节点比较
    	for i/2 > 0 && this.arr[i] > this.arr[i/2]{
    			this.arr[i], this.arr[i/2] = this.arr[i/2], this.arr[i]
    			//i偏移到父节点,如果比较不成立,表示不需要移动数据,数据已经到了合适的位置
    			i = i / 2
    	}
    }
    
    func (this *Heap)removeMax(){
    	if this.count == 0{
    		return
    	}
    	//用最后一个值覆盖第一个值
    	this.arr[1] = this.arr[this.count]
    	//总数减少
    	this.count--
    	//堆化
    	this.heapify()
    }
    
    func (this *Heap)heapify(){
    	//从第一个节点开始比较
    	i := 1
    	for {
    		//假设父节点是最大的
    		maxPos := i
    		//当左节点存在(小于和等于都是存在的),比较大小,如果左节点更大,maxPos更新为左节点的值
    		if i*2 <= this.count && this.arr[i] < this.arr[i*2]{
    			maxPos = i*2
    		}
    		//同理,如果右节点更大,更新为右节点的值
    		if i*2+1 <= this.count && this.arr[i] < this.arr[i*2+1]{
    			maxPos = i*2+1
    		}
    		//如果左右比价后,父节点仍然是最大的,表示已经不需要比较,数据都是正常的
    		if maxPos == i {
    			break
    		}
    		//否则,用父节点跟左右节点中最大的值交换
    		this.arr[i],this.arr[maxPos] = this.arr[maxPos],this.arr[i]
    		//父节点更新为最大标记的位置,以进行下一轮比较的起点
    		i = maxPos
    	}
    }
    
    func TestHeap(t *testing.T){
    	head := NewHeap(5)
    	head.insert(4)
    	head.insert(6)
    	head.insert(1)
    	head.insert(3)
    	head.insert(7)
    	head.removeMax()
    	head.removeMax()
    	for i:=1; i<=head.count;i++{
    		t.Log(head.arr[i])
    	}
    }
    

    堆排序

    步骤

    1. 建堆:从n/2开始到1(非叶子节点的范围),从后往前推进,逐渐建堆(假设开始堆只有3个元素,逐渐往上加)
    2. 堆化:传入一个父节点以及数组结束位置,父节点与左右节点对比,如果没有交换,则父节点是最大的,否则,标记左/右节点为下一个父节点,并交换元素,并继续向下堆化
    3. 排序:先进行第一次建堆,建完后最大/小值在堆顶,把堆顶放到数组末尾,并且砍掉(i--),重复此过程,直到堆顶(i)
    package algo
    
    import (
    	"testing"
    )
    
    //堆化
    func heapify(arr []int,start int,end int){
    	i := start
    	//用一个变量记录最大的位置
    	maxPos := i
    	for{
    		//找出父左右中最大的节点
    		//当左节点没有超过数组的边界并且左节点更大,标记最大位置
    		if i*2 <= end && arr[i*2] > arr[i]{
    			maxPos = i*2
    		}
    		if i*2+1 <= end && arr[i*2+1] > arr[maxPos]{
    			maxPos = i*2+1
    		}
    		//父节点已经是最大的时候,结束循环
    		if maxPos == i {
    			break
    		}
    		//父节点不是最大的时候,跟最大的节点交换,并且标记下一轮循环的父节点为该节点
    		//如果不用循环继续往下堆化,依然可找出最大的元素在父节点,但是无法保证子节点的堆性质成立
    		arr[maxPos],arr[i] = arr[i],arr[maxPos]
    		i = maxPos
    	}
    }
    
    func buildHead(arr []int,start int,end int){
    	//从最后一个非叶子节点开始,逐渐加入元素,建堆
    	for i:=end/2; i>=start; i--{
    		heapify(arr,i,end)
    	}
    }
    
    func heapSort(arr []int){
    	//从后往前堆化,完成后堆顶是最大的元素
    	//把堆顶的元素跟最后一个元素交换,并且往前推移,直到推进到堆顶
    	for i := len(arr)-1;i>=1;i--{
    		buildHead(arr,1,i)
    		arr[i],arr[1] = arr[1],arr[i]
    	}
    }
    
    func TestHeadSort(t *testing.T){
    	arr := []int{0,5,7,2,4,1}
    	heapSort(arr)
    	for i:=1; i<len(arr);i++{
    		t.Log(arr[i])
    	}
    }
    
  • 相关阅读:
    JavaScript对原始数据类型的拆装箱操作
    Javascript继承(原始写法,非es6 class)
    动态作用域与词法作用域
    自行车的保养
    探索JS引擎工作原理 (转)
    C语言提高 (7) 第七天 回调函数 预处理函数DEBUG 动态链接库
    C语言提高 (6) 第六天 文件(续) 链表的操作
    C语言提高 (5) 第五天 结构体,结构体对齐 文件
    C语言提高 (4) 第四天 数组与数组作为参数时的数组指针
    C语言提高 (3) 第三天 二级指针的三种模型 栈上指针数组、栈上二维数组、堆上开辟空间
  • 原文地址:https://www.cnblogs.com/jaychan/p/12782744.html
Copyright © 2011-2022 走看看