数据结构-树

一.概述

1.简介

树是计算机中非常重要的一种数据结构 ,同时使用树这种数据结构,可以描述现实生活中的很多事物,例如家谱、单位的组织架构等。树是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做"树"是因为它看起来像一棵倒挂的树,也就是说它是根朝上 ,而叶朝下的。

2.树的特点

  • 1.每个结点有零个或多个子结点
  • 2.没有父结点的结点为根结点
  • 3.每一个非根结点只有一个父结点;
  • 4.每个结点及其后代结点整体上可以看做是一棵树,称为当前结点的父结点的一个子树;

3.树的相关术语

  • 结点的度
    • 一个结点含有的子树的个数称为该结点的度;
  • 叶结点
    • 度为0的结点称为叶结点,也可以叫做终端结点
  • 分支结点
    • 度不为0的结点称为分支结点,也可以叫做非终端结点
  • 结点的层次
    • 从根结点开始,根结点的层次为1,根的直接后继层次为2,以此类推结点的层序编号:
    • 将树中的结点,按照从上层到下层,同层从左到右的次序排成一个线性序列,把他们编成连续的自然数。
  • 树的度
    • 树中所有结点的度的最大值
  • 树的高度(深度)
    • 树中结点的最大层次
  • 森林:
    • m(m>=0)个互不相交的树的集合,将一颗非空树的根结点删去,树就变成一个森林;给森林增加一个统一的根结点,森林就变成一棵树
  • 孩子结点
    • 一个结点的直接后继结点称为该结点的孩子结点
  • 双亲结点(父结点)
    • 一个结点的直接前驱称为该结点的双亲结点
  • 兄弟结点
    • 同一双亲结点的孩子结点间互称兄弟结点

二.二叉树

1.二叉树的基本定义

二叉树就是度不超过2的树(每个结点最多有两个子结点)

满二叉树

一个二叉树,如果每一个层的结点树都达到最大值,则这个二叉树就是满二叉树

完全二叉树

叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树

2.二叉树创建,删除,get等功能举例

Go 复制代码
package main

import "fmt"

// TreeNode 定义二叉树的节点结构
type TreeNode struct {
	Key   int         // 节点的键
	Value interface{} // 节点的值
	Left  *TreeNode   // 指向左子节点
	Right *TreeNode   // 指向右子节点
}

// BinarySearchTree 定义二叉搜索树结构
type BinarySearchTree struct {
	Root *TreeNode
}

// NewTreeNode 创建一个新的树节点
func NewTreeNode(key int, value interface{}) *TreeNode {
	return &TreeNode{Key: key, Value: value}
}

// Put 插入一个键值对到二叉搜索树中
func (tree *BinarySearchTree) Put(key int, value interface{}) {
	if tree.Root == nil {
		tree.Root = NewTreeNode(key, value)
	} else {
		tree.Root.put(key, value)
	}
}

// put 是 TreeNode 的一个方法,用于递归插入节点
func (node *TreeNode) put(key int, value interface{}) {
	if key < node.Key {
		if node.Left == nil {
			node.Left = NewTreeNode(key, value)
		} else {
			node.Left.put(key, value)
		}
	} else if key > node.Key {
		if node.Right == nil {
			node.Right = NewTreeNode(key, value)
		} else {
			node.Right.put(key, value)
		}
	} else {
		// 更新值
		node.Value = value
	}
}

// Get 根据键查找值
func (tree *BinarySearchTree) Get(key int) (interface{}, bool) {
	if tree.Root == nil {
		return nil, false
	}
	return tree.Root.get(key)
}

// get 是 TreeNode 的一个方法,用于递归查找值
func (node *TreeNode) get(key int) (interface{}, bool) {
	if node == nil {
		return nil, false
	}
	if key == node.Key {
		return node.Value, true
	} else if key < node.Key {
		return node.Left.get(key)
	} else {
		return node.Right.get(key)
	}
}

// Delete 删除指定的键
func (tree *BinarySearchTree) Delete(key int) {
	tree.Root = tree.Root.delete(key)
}

// delete 是 TreeNode 的一个方法,用于递归删除节点
func (node *TreeNode) delete(key int) *TreeNode {
	if node == nil {
		return nil
	}
	if key < node.Key {
		node.Left = node.Left.delete(key)
	} else if key > node.Key {
		node.Right = node.Right.delete(key)
	} else {
		// 找到节点
		if node.Left == nil {
			return node.Right
		}
		if node.Right == nil {
			return node.Left
		}
		// 找到右子树中的最小节点
		minRight := node.Right.min()
		node.Key = minRight.Key
		node.Value = minRight.Value
		node.Right = node.Right.delete(minRight.Key)
	}
	return node
}

// Min 查找二叉树中的最小键
func (tree *BinarySearchTree) Min() (int, bool) {
	if tree.Root == nil {
		return 0, false
	}
	minNode := tree.Root.min()
	return minNode.Key, true
}

// min 是 TreeNode 的一个方法,用于查找最小节点
func (node *TreeNode) min() *TreeNode {
	if node.Left == nil {
		return node
	}
	return node.Left.min()
}

// Max 查找二叉树中的最大键
func (tree *BinarySearchTree) Max() (int, bool) {
	if tree.Root == nil {
		return 0, false
	}
	maxNode := tree.Root.max()
	return maxNode.Key, true
}

// max 是 TreeNode 的一个方法,用于查找最大节点
func (node *TreeNode) max() *TreeNode {
	if node.Right == nil {
		return node
	}
	return node.Right.max()
}

// 主函数
func main() {
	tree := &BinarySearchTree{}

	// 插入节点
	tree.Put(5, "five")
	tree.Put(3, "three")
	tree.Put(7, "seven")
	tree.Put(2, "two")
	tree.Put(4, "four")

	// 获取节点
	if value, found := tree.Get(3); found {
		fmt.Printf("找到节点: %d -> %v\n", 3, value)
        } else {
		fmt.Printf("节点 %d 不存在\n", 3)
	}

	// 查找最小和最大键
	minKey, minFound := tree.Min()
	if minFound {
		fmt.Printf("最小键: %d\n", minKey)
	} else {
		fmt.Println("树为空,无法找到最小键")
	}

	maxKey, maxFound := tree.Max()
	if maxFound {
		fmt.Printf("最大键: %d\n", maxKey)
	} else {
		fmt.Println("树为空,无法找到最大键")
	}

	// 删除节点
	tree.Delete(3)
	if value, found := tree.Get(3); found {
		fmt.Printf("删除后找到节点: %d -> %v\n", 3, value)
	} else {
		fmt.Printf("节点 %d 已被删除\n", 3)
	}

	// 再次查找最小和最大键
	minKey, minFound = tree.Min()
	if minFound {
		fmt.Printf("删除后最小键: %d\n", minKey)
	} else {
		fmt.Println("树为空,无法找到最小键")
	}

	maxKey, maxFound = tree.Max()
	if maxFound {
		fmt.Printf("删除后最大键: %d\n", maxKey)
	} else {
		fmt.Println("树为空,无法找到最大键")
	}
}

代码解析

  1. TreeNode 结构体

    • 每个节点包含一个整数键 (Key)、一个值 (Value)、一个指向左子节点的指针 (Left) 和一个指向右子节点的指针 (Right)。
  2. BinarySearchTree 结构体

    • 包含一个根节点 (Root)。
  3. Put 方法

    • 用于插入新的键值对。如果树为空,将创建根节点;如果树非空,则递归调用 put 方法。
  4. Get 方法

    • 根据键查找对应的值,使用递归在树中查找。
  5. Delete 方法

    • 删除指定键的节点,并调整树的结构以保持二叉搜索树的特性。
  6. Min 和 Max 方法

    • 查找树中的最小和最大键,使用 minmax 方法遍历左子树和右子树。
  7. 主函数

    • 创建一棵二叉搜索树,插入了一些节点,演示了查找、最小和最大键的查找、以及节点的删除。

注意事项

  • 本示例中的键是整数,如果需要支持其他类型的键(如字符串),需要对 TreeNode 进行相应的修改。
  • 键的唯一性是二叉搜索树的基本要求,因此在插入时,如果存在相同的键,将更新该键的值。

3.二叉树的基础遍历

很多情况下,可能需要像遍历数组数组一样,遍历树,从而拿出树中存储的每一个元素,由于树状结构和线性结构不一样,它没有办法从头开始依次向后遍历,所以存在如何遍历,也就是按照什么样的搜索路径进行遍历的问题。

把树简单的画作上图中的样子,由一个根节点、一个左子树、一个右子树组成,那么按照根节点什么时候被访问,可以把二叉树的遍历分为以下三种方式:

  • 1.前序遍历: 先访问根结点,然后再访问左子树,最后访问右子树
  • 2.中序遍历: 先访问左子树,中间访问根节点,最后访问右子树
  • 3.后序遍历: 先访问左子树,再访问右子树,最后访问根节点

如果分别对下面的树使用三种遍历方式进行遍历,得到的结果如下:


举个例子说明:

Go 复制代码
package main
 
import(
    "fmt"
)
 
type Hero struct {
    No int
    Name string
    Left *Hero
    Right *Hero
}
 
//前序遍历: 先输出root根节点,然后输出左子树,然后输出右子树
func PreOrder(node *Hero)  {
    if node != nil {
        fmt.Printf("no=%d,name=%v\n", node.No, node.Name)
        PreOrder(node.Left)
        PreOrder(node.Right)
    }
}
 
//中序遍历: 先输出root的左子树,然后输出右子树,然后输出root根节点
func InfixOrder(node *Hero)  {
    if node != nil {
        InfixOrder(node.Left)
        fmt.Printf("no=%d,name=%v\n", node.No, node.Name)
        InfixOrder(node.Right)
    }
}
 
//后序遍历: 先输出root的右子树,然后输出root根节点,然后输出左子树
func PostOrder(node *Hero)  {
    if node != nil {
        InfixOrder(node.Left)
        InfixOrder(node.Right)
        fmt.Printf("no=%d,name=%v\n", node.No, node.Name)
    }
}
 
func main(){
    //构建二叉树
    root := &Hero {
        No: 1,
        Name: "及时雨",
    }
    left1 := &Hero {
        No: 2,
        Name: "智多星",
    }
    right1 := &Hero {
        No: 3,
        Name: "玉麒麟",
    }
    root.Left = left1
    root.Right = right1
 
    right2 := &Hero {
        No: 4,
        Name: "豹子头",
    }
    right1.Right = right2
 
    PreOrder(root)
    InfixOrder(root)
}

4.二叉树的层序遍历

层序遍历(Level Order Traversal)是二叉树的一种遍历方式,其特点是按层从上到下从左到右依次访问树中的每个节点。通常使用队列(Queue)来实现层序遍历.代码如下:

Go 复制代码
package main

import (
	"fmt"
)

// TreeNode 定义二叉树的节点结构
type TreeNode struct {
	Value int         // 节点的值
	Left  *TreeNode   // 左子节点
	Right *TreeNode   // 右子节点
}

// BinaryTree 定义二叉树结构
type BinaryTree struct {
	Root *TreeNode
}

// NewTreeNode 创建一个新的树节点
func NewTreeNode(value int) *TreeNode {
	return &TreeNode{Value: value}
}

// LevelOrderTraversal 层序遍历
func (tree *BinaryTree) LevelOrderTraversal() [][]int {
	if tree.Root == nil {
		return [][]int{}
	}

	var result [][]int
	queue := []*TreeNode{tree.Root}

	for len(queue) > 0 {
		levelSize := len(queue)
		var currentLevel []int

		for i := 0; i < levelSize; i++ {
			node := queue[0]
			queue = queue[1:] // 移除队列的第一个元素
			currentLevel = append(currentLevel, node.Value)

			if node.Left != nil {
				queue = append(queue, node.Left) // 将左子节点加入队列
			}
			if node.Right != nil {
				queue = append(queue, node.Right) // 将右子节点加入队列
			}
		}
		result = append(result, currentLevel) // 将当前层的结果加入结果集
	}

	return result
}

// 主函数
func main() {
	// 创建一个二叉树
	tree := &BinaryTree{
		Root: NewTreeNode(1),
	}
	tree.Root.Left = NewTreeNode(2)
	tree.Root.Right = NewTreeNode(3)
	tree.Root.Left.Left = NewTreeNode(4)
	tree.Root.Left.Right = NewTreeNode(5)
	tree.Root.Right.Left = NewTreeNode(6)
	tree.Root.Right.Right = NewTreeNode(7)

	// 层序遍历
	result := tree.LevelOrderTraversal()

	// 输出结果
	for i, level := range result {
		fmt.Printf("Level %d: %v\n", i, level)
	}
}

代码解析

  1. TreeNode 结构体

    • 每个节点包含一个整数值 (Value)、一个指向左子节点的指针 (Left) 和一个指向右子节点的指针 (Right)。
  2. BinaryTree 结构体

    • 包含一个根节点 (Root)。
  3. NewTreeNode 函数

    • 用于创建新的树节点。
  4. LevelOrderTraversal 方法

    • 返回一个二维切片,表示每层节点的值。
    • 使用队列来存储当前层的节点,直到队列为空。
    • 每次处理完一层后,将当前层的值加入结果集。
  5. 主函数

    • 创建一个示例二叉树,并调用层序遍历方法。
    • 输出每层的节点值。

注意事项

  • 层序遍历常用于查找树的深度、打印树的结构以及在某些情况下处理树的节点。
  • 如果二叉树为空,返回的结果将是一个空的二维切片

5.二叉树的最大深度问题

给定一棵树,请计算树的最大深度 (树的根节点最远叶子结点最长路径 上的结点数

上面这棵树的最大深度为4。

实现:

递归方法是最直接的方式,基本思路是:

  1. 如果节点为空,深度为 0。
  2. 如果节点不为空,深度为 1 加上其左右子树的最大深度

代码如下:

Go 复制代码
package main

import (
	"fmt"
)

// TreeNode 定义二叉树的节点结构
type TreeNode struct {
	Value int
	Left  *TreeNode
	Right *TreeNode
}

// MaxDepth 递归计算二叉树的最大深度
func MaxDepth(node *TreeNode) int {
	if node == nil {
		return 0
	}
	leftDepth := MaxDepth(node.Left)
	rightDepth := MaxDepth(node.Right)
	return max(leftDepth, rightDepth) + 1
}

// 辅助函数:返回两个数中的较大者
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

// 主函数
func main() {
	// 创建一个示例二叉树
	root := &TreeNode{Value: 1}
	root.Left = &TreeNode{Value: 2}
	root.Right = &TreeNode{Value: 3}
	root.Left.Left = &TreeNode{Value: 4}
	root.Left.Right = &TreeNode{Value: 5}

	// 计算最大深度
	depth := MaxDepth(root)
	fmt.Printf("The maximum depth of the binary tree is: %d\n", depth)
}

迭代解法

也可以使用队列(广度优先搜索,BFS)来迭代地计算二叉树的最大深度。基本思路是:

  1. 使用队列来存储每一层的节点。
  2. 每处理完一层,深度加 1

代码如下:

Go 复制代码
package main

import (
	"fmt"
)

// TreeNode 定义二叉树的节点结构
type TreeNode struct {
	Value int
	Left  *TreeNode
	Right *TreeNode
}

// MaxDepthIterative 迭代计算二叉树的最大深度
func MaxDepthIterative(root *TreeNode) int {
	if root == nil {
		return 0
	}

	queue := []*TreeNode{root}
	depth := 0

	for len(queue) > 0 {
		levelSize := len(queue)
		for i := 0; i < levelSize; i++ {
			node := queue[0]
			queue = queue[1:]

			if node.Left != nil {
				queue = append(queue, node.Left)
			}
			if node.Right != nil {
				queue = append(queue, node.Right)
			}
		}
		depth++
	}

	return depth
}

// 主函数
func main() {
	// 创建一个示例二叉树
	root := &TreeNode{Value: 1}
	root.Left = &TreeNode{Value: 2}
	root.Right = &TreeNode{Value: 3}
	root.Left.Left = &TreeNode{Value: 4}
	root.Left.Right = &TreeNode{Value: 5}

	// 计算最大深度
	depth := MaxDepthIterative(root)
	fmt.Printf("The maximum depth of the binary tree is: %d\n", depth)
}
  • 递归方法:实现简单,直观,但在深度较大的树上可能会导致栈溢出
  • 迭代方法:使用队列实现,适合处理较大深度的树,同时避免了栈溢出的问题

6.折纸问题

需求:

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时 折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连

续对折2 次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。给定一 个输入参数N,代表纸条都从下边向上方连续对折N次,请从上到下打印所有折痕的方向 例如:N=1时,打印: down;N=2时,打印: down down up

解法:

把对折后的纸张翻过来,让粉色朝下,这时把第一次对折产生的折痕看做是根结点,那第二次对折产生的下折痕就是该结点的左子结点,而第二次对折产生的上折痕就是该结点的右子结点,这样我们就可以使用树型数据结构来描述对折后产生的折痕。

这棵树有这样的特点:

  • 1.根结点为下折痕;
  • 2.每一个结点的左子结点为下折痕;
  • 3.每一个结点的右子结点为上折痕;

实现步骤:

  • 1.定义结点类
  • 2.构建深度为N的折痕树;
  • 3.使用中序遍历,打印出树中所有结点的内容;

构建深度为N的折痕树:

  • 1.第一次对折,只有一条折痕,创建根结点;
  • 2.如果不是第一次对折,则使用队列保存根结点;
  • 3.循环遍历队列:
    • 3.1从队列中拿出一个结点;
    • 3.2如果这个结点的左子结点不为空,则把这个左子结点添加到队列中;
    • 3.3如果这个结点的右子结点不为空,则把这个右子结点添加到队列中;
    • 3.4判断当前结点的左子结点和右子结点都不为空,如果是,则需要为当前结点创建一个值为down的左子结点,一个值为up的右子结点。

代码如下:

Go 复制代码
package main

import (
	"fmt"
)

// TreeNode 定义二叉树的节点结构
type TreeNode struct {
	Value       string
	Left, Right *TreeNode
}

// BuildCreaseTree 构建深度为N的折痕树
func BuildCreaseTree(depth int) *TreeNode {
	if depth <= 0 {
		return nil
	}

	// 创建根节点
	root := &TreeNode{Value: "Root"}
	if depth == 1 {
		return root
	}

	// 使用队列来构建树
	queue := []*TreeNode{root}

	for i := 2; i <= depth; i++ {
		levelSize := len(queue)
		for j := 0; j < levelSize; j++ {
			current := queue[j]
			queue = queue[1:] // 从队列中取出当前节点

			// 如果当前节点的左右子节点都不为空,则创建新的子节点
			if current.Left == nil && current.Right == nil {
				current.Left = &TreeNode{Value: "down"}
				current.Right = &TreeNode{Value: "up"}
			}

			// 将新创建的子节点加入队列
			if current.Left != nil {
				queue = append(queue, current.Left)
			}
			if current.Right != nil {
				queue = append(queue, current.Right)
			}
		}
	}

	return root
}

// InOrderTraversal 中序遍历树并打印节点内容
func InOrderTraversal(node *TreeNode) {
	if node == nil {
		return
	}
	InOrderTraversal(node.Left)      // 访问左子树
	fmt.Println(node.Value)           // 访问当前节点
	InOrderTraversal(node.Right)     // 访问右子树
}

// 主函数
func main() {
	depth := 3  // 设置树的深度为3
	root := BuildCreaseTree(depth)

	fmt.Println("In-order traversal of the crease tree:")
	InOrderTraversal(root)
}

三.堆

1.堆的定义

堆是计算机科学中一类特殊的数据结构的统称,堆通常可以被看做是一棵完全二叉树的数组对象

2.堆的特性:

  • 1.它是完全二叉树,除了树的最后一层结点不需要是满的,其它的每一层从左到右都是满的,如果最后一层结点不是满的,那么要求左满右不满。
  • 2.它通常用数组来实现,具体方法就是将二叉树的结点按照层级顺序放入数组中,根结点在位置1,它的子结点在位置2和3,而子结点的子结点则分别在位置4,5,6和7,以此类推。

如果一个结点的位置为k,则它的父结点的位置为[k/2],而它的两个子结点的位置则分别为2k和2k+1。这样,在不使用指针的情况下,我们也可以通过计算数组的索引在树中

上下移动:从a[k]向上一层,就令k等于k/2,向下一层就令k等于2k或2k+1。

  • 3.每个结点都大于等于它的两个子结点。这里要注意堆中仅仅规定了每个结点大于等于它的两个子结点,但这两个子结点的顺序并没有做规定,跟我们之前学习的二叉查找树是有区别的。

2.堆的实现

2.1insert插入方法的实现

堆是用数组完成数据元素的存储的,由于数组的底层是一串连续的内存地址,所以我们要往堆中插入数据,我们只能往数组中从索引0处开始,依次往后存放数据,但是堆中对元素的顺序是有要求的,每一个结点的数据要大于等于它的两个子结点的数据,所以每次插入一个元素,都会使得堆中的数据顺序变乱,这个时候我们就需要通过一些方法让刚才插入的这个数据放入到合适的位置。

所以,如果往堆中新插入元素,只需要不断的比较新结点a[k]和它的父结点a[k/2]的大小,然后根据结果完成数据元素的交换,就可以完成堆的有序调整。

2.2 delMax删除最大元素方法的实现

由堆的特性可以知道,索引1处的元素,也就是根结点就是最大的元素,当把根结点的元素删除后,需要有一个新的根结点出现,这时可以暂时把堆中最后一个元素放到索引1处,充当根结点,但是它有可能不满足堆的有序性需求,这个时候我们就需要通过一些方法,让这个新的根结点放入到合适的位置。

所以,当删除掉最大元素后,只需要将最后一个元素放到索引1处,并不断的拿着当前结点a[k]与它的子结点a[2k]和a[2k+1]中的较大者交换位置,即可完成堆的有序调整。

代码如下:

Go 复制代码
package main

import (
	"fmt"
)

// MaxHeap 定义最大堆结构
type MaxHeap struct {
	data []int
}

// NewMaxHeap 创建一个新的最大堆
func NewMaxHeap() *MaxHeap {
	return &MaxHeap{data: []int{}}
}

// Insert 插入一个新元素
func (h *MaxHeap) Insert(val int) {
	h.data = append(h.data, val)
	h.heapifyUp(len(h.data) - 1)
}

// Delete 删除最大元素(堆顶元素)
func (h *MaxHeap) Delete() (int, bool) {
	if len(h.data) == 0 {
		return 0, false
	}
	
	max := h.data[0] // 获取堆顶元素
	h.data[0] = h.data[len(h.data)-1] // 将最后一个元素放到根节点
	h.data = h.data[:len(h.data)-1]   // 删除最后一个元素
	h.heapifyDown(0)                   // 下沉调整堆
	return max, true
}

// heapifyUp 上浮调整堆
func (h *MaxHeap) heapifyUp(index int) {
	for index > 0 {
		parent := (index - 1) / 2
		if h.data[index] > h.data[parent] {
			// 交换
			h.data[index], h.data[parent] = h.data[parent], h.data[index]
			index = parent
		} else {
			break
		}
	}
}

// heapifyDown 下沉调整堆
func (h *MaxHeap) heapifyDown(index int) {
	for {
		left := 2*index + 1
		right := 2*index + 2
		largest := index

		if left < len(h.data) && h.data[left] > h.data[largest] {
			largest = left
		}
		if right < len(h.data) && h.data[right] > h.data[largest] {
			largest = right
		}
		if largest != index {
			// 交换
			h.data[index], h.data[largest] = h.data[largest], h.data[index]
			index = largest
		} else {
			break
		}
	}
}

// Print 打印堆的内容
func (h *MaxHeap) Print() {
	for _, val := range h.data {
		fmt.Print(val, " ")
	}
	fmt.Println()
}

// 主函数
func main() {
	heap := NewMaxHeap()

	heap.Insert(10)
	heap.Insert(20)
	heap.Insert(5)
	heap.Insert(30)
	heap.Insert(15)

	fmt.Println("Heap after inserts:")
	heap.Print() // 输出堆的内容

	max, _ := heap.Delete()
	fmt.Println("Deleted max element:", max)

	fmt.Println("Heap after deletion:")
	heap.Print() // 输出堆的内容
}

代码说明

  1. MaxHeap 结构体data 是一个切片,用于存储堆中的元素。

  2. NewMaxHeap 函数:创建一个新的最大堆实例。

  3. Insert 方法 :将新元素添加到堆的末尾,然后调用 heapifyUp 方法调整堆。

  4. Delete 方法:删除堆顶元素,返回其值,并对堆进行调整。

  5. heapifyUp 方法:通过不断上浮新插入的元素,确保堆的性质得到维护。

  6. heapifyDown 方法:删除堆顶元素后,将最后一个元素放到根节点,并通过下沉调整堆。

  7. Print 方法:打印堆中的所有元素。

  8. main 函数:演示如何使用最大堆的插入和删除操作。

3.堆排序

3.1引入

给定一个数组:

String[] arr = {"S","O","R","T","E","X","A","M","P","L","E"}

上述数组是无序的,请对数组中的字符按从小到大排序(使用堆排序)。

实现步骤:

  • 1.构造堆
  • 2.得到堆顶元素,这个值就是最大值;
  • 3.交换堆顶元素和数组中的最后一个元素,此时所有元素中的最大元素已经放到合适的位置;
  • 4.对堆进行调整,重新让除了最后一个元素的剩余元素中的最大值放到堆顶;
  • 5.重复2~4这个步骤,直到堆中剩一个元素为止。

代码如下:

Go 复制代码
实现步骤
构造最大堆:将数组转换为最大堆。最大堆的性质是每个父节点的值都大于或等于其子节点的值。
获取堆顶元素:堆顶元素是当前的最大值。
交换堆顶元素和数组最后一个元素:将最大元素放到数组的正确位置。
调整堆:对堆进行调整,使得剩余元素重新构成最大堆。
重复步骤2到4,直到堆中只剩一个元素

package main

import (
	"fmt"
)

// 堆的结构体
type MaxHeap struct {
	arr []string
}

// 创建最大堆
func (h *MaxHeap) BuildHeap() {
	// 从最后一个非叶子节点开始调整堆
	for i := len(h.arr)/2 - 1; i >= 0; i-- {
		h.heapify(len(h.arr), i)
	}
}

// 堆调整
func (h *MaxHeap) heapify(n int, i int) {
	largest := i          // 初始化最大值为根
	left := 2*i + 1      // 左子节点
	right := 2*i + 2     // 右子节点

	// 如果左子节点大于根
	if left < n && h.arr[left] > h.arr[largest] {
		largest = left
	}
	// 如果右子节点大于当前最大值
	if right < n && h.arr[right] > h.arr[largest] {
		largest = right
	}
	// 如果最大值不是根
	if largest != i {
		h.arr[i], h.arr[largest] = h.arr[largest], h.arr[i] // 交换
		h.heapify(n, largest) // 递归调整
	}
}

// 堆排序
func (h *MaxHeap) Sort() {
	n := len(h.arr)
	h.BuildHeap() // 构建最大堆

	for i := n - 1; i > 0; i-- {
		h.arr[0], h.arr[i] = h.arr[i], h.arr[0] // 交换堆顶和最后一个元素
		h.heapify(i, 0) // 调整堆
	}
}

// 主函数
func main() {
	arr := []string{"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"}
	heap := MaxHeap{arr: arr}

	heap.Sort() // 执行堆排序

	fmt.Println("Sorted array:", heap.arr)
}


代码说明
MaxHeap 结构体:包含一个字符串切片 arr,表示堆中的元素。
BuildHeap 方法:构建最大堆,从最后一个非叶子节点开始调用 heapify 方法调整堆。
heapify 方法:用于调整堆的结构,确保父节点大于或等于其子节点。
Sort 方法:执行堆排序。首先构建最大堆,然后不断交换堆顶和最后一个元素,并调整堆。
main 函数:创建字符串数组,构造堆并执行排序,最后打印排序后的数组

3.1堆构造过程

堆的构造,最直观的想法就是另外再创建一个和数组一样的新数组,然后从左往右遍历原数组,每得到一个元素后,添加到新数组中,并通过上浮,对堆进行调整,最后新的数组就是一个堆。

上述的方式虽然很直观,也很简单,但是可以用更聪明一点的办法完成它。创建一个新数组,把原数组0~length-1的数据拷贝到新数组的1~length处,再从**新数组长度的一半(因为堆数据结构特性-数组,堆数组一半下标后的是叶子节点,具体看堆的定义)**处开始往1索引处扫描(从右往左),然后对扫描到的每一个元素做下沉调整即可

3.2堆排序过程

对构造好的堆,只需要做类似于堆的删除操作,就可以完成排序。

  • 1.将堆顶元素和堆中最后一个元素交换位置;
  • 2.通过对堆顶元素下沉调整堆,把最大的元素放到堆顶(此时最后一个元素不参与堆的调整,因为最大的数据已经到了数组的最右边)
  • 3.重复1~2步骤,直到堆中剩最后一个元素。
Go 复制代码
实现步骤概述
交换堆顶元素和堆中最后一个元素:将当前最大元素(堆顶)移动到数组的末尾。
调整堆:通过将新的堆顶元素下沉到合适的位置,以恢复堆的性质。
重复以上步骤,直到堆中只剩下一个元素
package main

import (
	"fmt"
)

// MaxHeap 结构体表示最大堆
type MaxHeap struct {
	arr []string
}

// BuildHeap 构建最大堆
func (h *MaxHeap) BuildHeap() {
	n := len(h.arr)
	for i := n/2 - 1; i >= 0; i-- {
		h.heapify(n, i)
	}
}

// heapify 调整堆的结构
func (h *MaxHeap) heapify(n int, i int) {
	largest := i
	left := 2*i + 1
	right := 2*i + 2

	if left < n && h.arr[left] > h.arr[largest] {
		largest = left
	}
	if right < n && h.arr[right] > h.arr[largest] {
		largest = right
	}
	if largest != i {
		h.arr[i], h.arr[largest] = h.arr[largest], h.arr[i] // 交换
		h.heapify(n, largest) // 递归调整
	}
}

// Sort 执行堆排序
func (h *MaxHeap) Sort() {
	n := len(h.arr)
	h.BuildHeap() // 构建最大堆

	for i := n - 1; i > 0; i-- {
		h.arr[0], h.arr[i] = h.arr[i], h.arr[0] // 交换堆顶和最后一个元素
		h.heapify(i, 0) // 调整堆,排除已经排序的元素
	}
}

// 主函数
func main() {
	arr := []string{"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"}
	heap := MaxHeap{arr: arr}

	heap.Sort() // 执行堆排序

	fmt.Println("Sorted array:", heap.arr)
}

代码说明
MaxHeap 结构体:表示一个最大堆,包含一个字符串切片 arr。
BuildHeap 方法:从最后一个非叶子节点开始,构建最大堆。
heapify 方法:负责调整堆的结构,确保父节点的值大于或等于子节点的值。
Sort 方法:实现堆排序的核心逻辑:
首先构建最大堆。
然后通过循环,交换堆顶元素和当前最后一个元素,并调整堆。
main 函数:创建字符串数组,构造堆并执行排序,最后输出排序结果。
运行结果
运行以上程序,输出将是:
Sorted array: [A E L M O P R S T X]
相关推荐
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
weixin_432702262 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
passer__jw7673 小时前
【LeetCode】【算法】283. 移动零
数据结构·算法·leetcode
爱吃生蚝的于勒3 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~3 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
脉牛杂德4 小时前
多项式加法——C语言
数据结构·c++·算法
一直学习永不止步4 小时前
LeetCode题练习与总结:赎金信--383
java·数据结构·算法·leetcode·字符串·哈希表·计数
wheeldown12 小时前
【数据结构】选择排序
数据结构·算法·排序算法
躺不平的理查德16 小时前
数据结构-链表【chapter1】【c语言版】
c语言·开发语言·数据结构·链表·visual studio
阿洵Rain16 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法