08.树和图的遍历

一、树的基本概念

1.树的定义:

形式化定义:(二编组,结点组D和边组R)

树:T={D,R}。D是包含n个节点的有限集合(n>=0)。当n=0时为空树,否则关系R满足以下条件:

  • 有且有一个结点 d 0 d_0 d0,对于关系R来说没有前驱结点,结点 d 0 d_0 d0称作根结点。
  • 除了根节点外,每个结点有且仅有一个前驱结点。
  • D中每个结点可以有0个或多个后继结点。

递归定义:

树是由n(n>=0)个结点组组成的有限集合(记为T)。其中:

  • 如果n=0,它是一棵空树,这是树的特例
  • 如果n>0,这n个结点中存在一个唯一的节点作为树的根结点。
  • 其余节点,本身又是一颗树。称为根节点root的子树。
  • 树中所有节点构成一种层次关系

特性描述定义:

  • 树是一个无环的,无向连同图
  • 有n个结点,和n-1条边
  • 有一个根节点,通常需要一个引用
  • 任意点条连接,且只有条路径
2.常用术语

结点的度和数的度:节点的度,节点拥有的子树的个数;树的度,树中所有节点的度,中的最大值。

分支节点与叶子结点:度不为零的结点称为非终端结点(又叫分支节点)。度为0的节点称为叶节点(叶子节点)。

路径:两个节点,和连接这两个节点的结点序列,称为这两个结点之间的路径。

路径长度:路径中结点的数目称为路径长度减1,(也就是分支数目)

根节点:没有父节点,有的实现中,根节点的父节点,指向自己。

叶子节点:没有子节点的节点。(度为0的节点)

非叶子节点:度不为0的节点。

子树:包含再一棵树中的小数 用 Δ \Delta Δ表示

父节点:直接相连的两个节点,上面的叫父节点。

子节点:下面的叫子节点。

节点的层次和数的高度:根结点在1层,它孩子节点再2层,以此类推;树中结点的最大层称为树的高度。

有序树和无序树:若树中各结点的子树是按照一定的次序从左向右安排的,且相对次序不能随意变换的,则称为有序树,否则称为无序树。

森林:n(n>0)个互不相交的数的集合称为森林。

只要把树的根节点删除,就成了森林。

反正,只要给n棵独立的数加上一个节点,并吧这n棵树作为该节点的子树,则森林就变成了一棵树。

3.二叉树的定义

二叉树不是一种特殊的树,和树是平行的概念。当二叉树只有一个子结点时,也有左节点和右结点之分。而树,没有这样的区别。

  • 二叉树是有限的结点集合
  • 这个集合可能为空
  • 或者由一个根节点和两棵互不相交的称为左子树和右子树的二叉树组成,

二叉树有5种基本形态

二叉树和度为二的树,是两种完全不同的结构(左右子树)

4.两种特殊的二叉树

1.满二叉树:

  • 如果所有的结点都是双分支节点
  • 并且叶节点集中再二叉树的最下一层。

可以按层遍历,并且给每个节点编号。

2.完全二叉树:

  • 最多只有下面两层的结点的度小于2
  • 并且最下面一层的叶节点都依次排在最左边的位置上
  • 完全二叉树实际上是对应的满二叉树删除叶子节点最左边的若干个结点得到的
5.二叉树的性质

(1). n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1

  • 度之和=分支数
  • 分支数=n -1
    n = n 0 + n 2 + n 1 n = n_0+n_2+n_1 n=n0+n2+n1
  • 度之和= n 1 + n 2 n_1+n_2 n1+n2

(2) 非空二叉树上第i层上至多有 2 i − 1 2^{i-1} 2i1 个结点

(3) 高度为h的二叉树最多有 2 h − 1 2^h-1 2h1个结点

(4) 完全二叉树有:

  • $n_1=$0或1,可以由n的奇偶行确定。
  • 若 i <=n/2,则编号为i的结点为分支结点,否则为叶结点。
  • 除树根结点外,若一个结点的编号为i,则它的双亲结点的编号为i/2
  • 若编号为i的结点有左孩子结点,则左孩子结点的编号为2i;若编号为i的结点,有右孩子则结点编号为2i+1
  • 具有n个结点的完全二叉树的深度为 ∣ l o g 2 n ∣ + 1 |log_2n|+1 log2n+1

二、二叉树的遍历

1.先序遍历

所谓的前序遍历就是先访问根节点,再访问左节点,最后访问右节点

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lCj3xmom-1643101381576)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

如上图所示,前序遍历结果为:ABDFECGHI

用递归实现比较简单,直接上代码:

//一、二叉树的遍历,递归实现

type BTNode struct {
	data       interface{}
	leftChild  *BTNode
	rightChild *BTNode
}

// InitTestTree 初始化测试用树结构
func InitTestTree() *BTNode {
	A := BTNode{data: "A"}
	B := BTNode{data: "B"}
	C := BTNode{data: "C"}
	D := BTNode{data: "D"}
	E := BTNode{data: "E"}
	F := BTNode{data: "F"}
	G := BTNode{data: "G"}
	H := BTNode{data: "H"}
	I := BTNode{data: "I"}

	A.leftChild = &B
	A.rightChild = &C
	B.leftChild = &D
	B.rightChild = &F
	F.leftChild = &E
	C.leftChild = &G
	C.rightChild = &I
	G.rightChild = &H
	return &A
}

//PreOrderTraverse 1.递归方式实现先序遍历
func PreOrderTraverse(root *BTNode) string {
	//递归返回逻辑
	if root == nil {
		return ""
	}
	// 执行访问逻辑,演示中只是打印数据
	fmt.Print(" ", root.data, " ")
	res := root.data.(string)
	res += PreOrderTraverse(root.leftChild)
	res += PreOrderTraverse(root.rightChild)
	return res
}
2.中序遍历

中序遍历:所谓的中序遍历就是先访问左节点,再访问根节点,最后访问右节点

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1z55zwR3-1643101381576)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

如上图所示,中序遍历结果为:DBEFAGHCI

//一、二叉树的遍历,递归实现

type BTNode struct {
	data       interface{}
	leftChild  *BTNode
	rightChild *BTNode
}

// InitTestTree 初始化测试用树结构
func InitTestTree() *BTNode {
	A := BTNode{data: "A"}
	B := BTNode{data: "B"}
	C := BTNode{data: "C"}
	D := BTNode{data: "D"}
	E := BTNode{data: "E"}
	F := BTNode{data: "F"}
	G := BTNode{data: "G"}
	H := BTNode{data: "H"}
	I := BTNode{data: "I"}

	A.leftChild = &B
	A.rightChild = &C
	B.leftChild = &D
	B.rightChild = &F
	F.leftChild = &E
	C.leftChild = &G
	C.rightChild = &I
	G.rightChild = &H
	return &A
}

//MidOrderTraverse 2.递归方式实现中序遍历
func MidOrderTraverse(root *BTNode) string {
	//递归返回逻辑
	if root == nil {
		return ""
	}
	// 执行访问逻辑,演示中只是打印数据
	res := MidOrderTraverse(root.leftChild)
	fmt.Print(" ", root.data, " ")
	res += root.data.(string)
	res += MidOrderTraverse(root.rightChild)
	return res
}
3.后序遍历

后序遍历:所谓的后序遍历就是先访问左节点,再访问右节点,最后访问根节点。

img[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KSOFJitR-1643101381577)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

如上图所示,后序遍历结果为:DEFBHGICA

//一、二叉树的遍历,递归实现

type BTNode struct {
	data       interface{}
	leftChild  *BTNode
	rightChild *BTNode
}

// InitTestTree 初始化测试用树结构
func InitTestTree() *BTNode {
	A := BTNode{data: "A"}
	B := BTNode{data: "B"}
	C := BTNode{data: "C"}
	D := BTNode{data: "D"}
	E := BTNode{data: "E"}
	F := BTNode{data: "F"}
	G := BTNode{data: "G"}
	H := BTNode{data: "H"}
	I := BTNode{data: "I"}

	A.leftChild = &B
	A.rightChild = &C
	B.leftChild = &D
	B.rightChild = &F
	F.leftChild = &E
	C.leftChild = &G
	C.rightChild = &I
	G.rightChild = &H
	return &A
}

//AfterOrderTraverse 3.递归方式实现中序遍历
func AfterOrderTraverse(root *BTNode) string {
	//递归返回逻辑
	if root == nil {
		return ""
	}
	// 执行访问逻辑,演示中只是打印数据
	res := AfterOrderTraverse(root.leftChild)
	res += AfterOrderTraverse(root.rightChild)
	fmt.Print(" ", root.data, " ")
	res += root.data.(string)
	return res
}
4.非递归实现

注意:非递归实现中,前序遍历和中序遍历的代码结构是一样的,只不过前序是结点入栈前访问,中序是出栈后打印。

//PreOrderTraverseStack 非递归方式实现 先序遍历
func PreOrderTraverseStack(root *BTNode) string {
	stack := arraystack.New()
	travalNode := root
	res := ""

	for travalNode != nil {
		fmt.Print(" ", travalNode.data, " ")
		res += travalNode.data.(string)
		stack.Push(travalNode)
		travalNode = travalNode.leftChild
		for travalNode == nil && !stack.Empty() {
			tmp, _ := stack.Pop()
			travalNode = tmp.(*BTNode)
			travalNode = travalNode.rightChild
		}

	}
	return res

}

//MidOrderTraverseStack 非递归方式实现 先序遍历
func MidOrderTraverseStack(root *BTNode) string {
	S := arraystack.New()
	cur := root
	res := ""

	for cur != nil {
		S.Push(cur)
		cur = cur.leftChild

		for !S.Empty() && cur == nil{
			tmp, _ := S.Pop()
			cur = tmp.(*BTNode)
			fmt.Print(" ", cur.data, " ")
			res += cur.data.(string)
			cur = cur.rightChild
		}
	}
	return res
}

git 代码:

https://gitee.com/gudongkun/datestruct/tree/master/dataStructures/tree

三、树的层序遍历

1.队列实现:

仔细看看层序遍历过程,其实就是从上到下,从左到右依次将每个数放入到队列中,然后按顺序依次打印就是想要的结果。

实现过程
1、首先将二叉树的根节点push到队列中,判断队列不为NULL,就输出队头的元素,
2、判断节点如果有孩子,就将孩子push到队列中,
3、遍历过的节点出队列,
4、循环以上操作,直到Tree == NULL。

代码实现:

func LevelTraverse() string {
	tree := InitTestTree()
	var list []BTNode
	var res string
	list = append(list, *tree)

	for len(list) != 0 {
		ele := list[0]
		// 访问结点
		fmt.Println(ele.data)
		res =  res + ele.data.(string)

		list = list[1:]
		if ele.leftChild != nil {
			list = append(list, *ele.leftChild)
		}
		if ele.rightChild != nil {
			list = append(list, *ele.rightChild)
		}
	}
	return res
}

单元测试;

func TestMidOrderTraverseStack(t *testing.T) {
	tree := InitTestTree()
	str := MidOrderTraverseStack(tree)
	if str != "DBEFAGHCI" {
		t.Log("\nexpect:","DBEFAGHCI",",give:",str)
		t.Fail()
	}
}

func TestLevelTraverse(t *testing.T) {
	res :=  LevelTraverse()
	if res != "ABCDFGIEH" {
		t.Fail()
	}

}
2.数组实现
func LevelTraverseByArr() string {
	tree := InitTestTree()
	var list []BTNode
	var res string
	index := 0

	list = append(list, *tree)

	for index < len(list) {
		ele := list[index]
		// 访问结点
		fmt.Println(ele.data)
		res = res + ele.data.(string)

		if ele.leftChild != nil {
			list = append(list, *ele.leftChild)
		}
		if ele.rightChild != nil {
			list = append(list, *ele.rightChild)
		}

		index++
	}
	return res
}

单元测试:

func TestLevelTraverseByArr(t *testing.T) {
	res :=  LevelTraverseByArr()
	if res != "ABCDFGIEH" {
		t.Fail()
	}
}

四、图的遍历

1.深度优先遍历(DFS)

基本思想:

  1. 首先访问出发点v,并且标记为访问过

  2. 然后选取与v邻接的未被访问的任意一个顶点w,并访问他

  3. 重复这个过程,当一个顶点的所有邻接点都被访问过时,依次回退。

代码实现:

package graph

import "fmt"

func GraphdfsMain() {

	g := NewMGraph()

	g.AddNode("A")
	g.AddNode("B")
	g.AddNode("C")
	g.AddNode("D")
	g.AddNode("E")
	g.AddNode("F")
	g.AddNode("G")
	g.AddNode("H")
	g.AddNode("I")
	g.AddNode("J")

	g.AddEdge("A", "B", 5)
	g.AddEdge("A", "D", 9)
	g.AddEdge("A", "E", 1)
	g.AddEdge("B", "D", 2)
	g.AddEdge("B", "C", 4)
	g.AddEdge("C", "H", 4)
	g.AddEdge("C", "I", 1)
	g.AddEdge("C", "J", 8)
	g.AddEdge("D", "H", 2)
	g.AddEdge("D", "G", 11)
	g.AddEdge("D", "F", 5)
	g.AddEdge("D", "E", 2)
	g.AddEdge("E", "F", 1)
	g.AddEdge("F", "G", 7)
	g.AddEdge("G", "H", 1)
	g.AddEdge("G", "I", 4)
	g.AddEdge("H", "I", 6)
	g.AddEdge("I", "J", 1)

	visited := make(map[string]bool)
	for _, v := range g.Nodes {
		visited[v] = false
	}

	Graphdfs("A", g, visited)

}

func Graphdfs(s string, g MGraph, visited map[string]bool) {
	visited[s] = true
	// 执行访问逻辑
	fmt.Println(s)
	list := g.GetConnectedNode(s)
	for _, v := range list {
		if visited[v] == false {
			Graphdfs(v,g,visited)
		}
	}
}

2.广度优先遍历(bfs)

算法思想:

  1. 任取图中一点,入队列,并将这个顶点标记为已访问。
  2. 当队列不空时循环:出队列,依次检查出队列的所有邻接顶点,访问没有被访问过的邻接顶点,并把他如队列。
  3. 当队列为空时,跳出循环,广度优先搜索完成。

注意广度优先需要用到队列

代码实现:

package graph

import "fmt"

func Graphbfs() {

	g := NewMGraph()

	g.AddNode("A")
	g.AddNode("B")
	g.AddNode("C")
	g.AddNode("D")
	g.AddNode("E")
	g.AddNode("F")
	g.AddNode("G")
	g.AddNode("H")
	g.AddNode("I")
	g.AddNode("J")

	g.AddEdge("A", "B", 5)
	g.AddEdge("A", "D", 9)
	g.AddEdge("A", "E", 1)
	g.AddEdge("B", "D", 2)
	g.AddEdge("B", "C", 4)
	g.AddEdge("C", "H", 4)
	g.AddEdge("C", "I", 1)
	g.AddEdge("C", "J", 8)
	g.AddEdge("D", "H", 2)
	g.AddEdge("D", "G", 11)
	g.AddEdge("D", "F", 5)
	g.AddEdge("D", "E", 2)
	g.AddEdge("E", "F", 1)
	g.AddEdge("F", "G", 7)
	g.AddEdge("G", "H", 1)
	g.AddEdge("G", "I", 4)
	g.AddEdge("H", "I", 6)
	g.AddEdge("I", "J", 1)

	visited := make(map[string]bool)
	for _, v := range g.Nodes {
		visited[v] = false
	}

	var list []string
	index := 0
	visited["A"] = true
	//执行访问逻辑
	fmt.Println("A")
	list = append(list,"A")

	for index < len(list) {
		ele := list[index]

		nodeList := g.GetConnectedNode(ele)
		for _,vv :=range nodeList {
			if visited[vv] == false {
				visited[vv] = true
				//执行访问逻辑
				fmt.Println(vv)
				list = append(list,vv)
			}
		}

		index ++
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值