树算法的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])
	}
}
原文地址:https://www.cnblogs.com/jaychan/p/12782744.html