接上文 简单选择排序 简单选择排序很容易理解,代码也很容易实现。但毕竟比较次数太多。树形选择排序则对这个问题进行了改进。

原理 简单来说,树形选择排序(Tree selection sort)就是在选择完一轮找出最小值后,直接在与最小值比较中稍大的元素里筛选出最小的。这样避免了简单选择查询那种,抛弃了之前比较过的结果,每次都全部重新比较的情况。

流程举例

  • 先列出所有待排序的元素如:8、4、12、7、35、9、22,并用他们组成满二叉树的叶子元素,不足的位置以∞作为补充。 将元素两两相比较,分别得到较小值:4,7,9,22。再次两两比较,得到4,9。最终比较一次得到最小值4。由此构建出一个完整的满二叉树:

  • 完成一轮比较后,将胜出者4的叶子节点改成∞,然后由它的兄弟节点8继续参加下一轮比较。从这次开始,元素8仅需按构建好的树结构一步步向上与其他胜出的非父节点仅需比较即可,比如这里只需要在和7,9比较,就能得到最小元素是7

  • 然后将元素7的叶子节点改成∞,其兄弟节点12与8、9节点比较,即可得到8:

  • 以此类推,最终得到最后一个元素35:

时间复杂度 由于每次仅需与胜出的其他节点仅需比较,所以时间复杂度相较简单选择排序的O(n^2)降低到O(nlogn)。 但是由于储存了每次各胜出节点的数据,所以需要更多的储存空间,而且其中n-1次的与∞的比较行为较为多余。

代码实现

package main

import (
	"fmt"
	"math"
)

// 声明一个节点的结构体,包含节点数值大小和是否需要参与比较
type node struct {
	// 数值大小
	value int
	// 叶子节点状态
	available bool
	// 叶子中的排序,方便失效
	rank int
}

func main() {
	var length = 10
	var mm = make(map[int]int, length)
	var o []int

	// 先准备一个顺序随机的数(qie)组(pian)
	for i := 0; i < length; i++ {
		mm[i] = i
	}
	for k, _ := range mm {
		o = append(o, k)
	}

	fmt.Println(o)
	treeSelectionSort(o)
}

func treeSelectionSort(origin []int) []int {
	// 树的层数
	var level int
	var result = make([]int, 0, len(origin))
	for pow(2, level) < len(origin) {
		level++
	}
	// 叶子节点数
	var leaf = pow(2, level)
	var tree = make([]node, leaf*2-1)

	// 先填充叶子节点的数据
	for i := 0; i < len(origin); i++ {
		tree[leaf+i-1] = node{origin[i], true, i}
	}
	// 每层都比较叶子兄弟大小,选出较大值作为父节点
	for i := 0; i < level; i++ {
		// 当前层节点数
		nodeCount := pow(2, level-i)
		// 每组兄弟间比较
		for j := 0; j < nodeCount/2; j++ {
			compareAndUp(&tree, nodeCount-1+j*2)
		}
	}



	// 这个时候树顶端的就是最小的元素了
	result = append(result, tree[0].value)
	fmt.Println(result)

	// 选出最小的元素后,还剩n-1个需要排序
	for t := 0; t < len(origin) - 1; t++ {
		// 赢球的节点
		winNode := tree[0].rank + leaf - 1
		// 把赢球的叶子节点状态改为失效
		tree[winNode].available = false

		// 从下一轮开始,只需与每次胜出节点的兄弟节点进行比较
		for i := 0; i < level; i ++ {
			leftNode := winNode
			if winNode%2 == 0 {
				leftNode = winNode - 1
			}
			
			// 比较兄弟节点间大小,并将胜出的节点向上传递
			compareAndUp(&tree, leftNode)
			winNode = (leftNode - 1) / 2
		}

		// 每轮都会吧最小的推到树顶端
		result = append(result, tree[0].value)
		fmt.Println(result)
	}

	return origin
}

func compareAndUp(tree *[]node, leftNode int) {
	rightNode := leftNode + 1

	// 除非左节点无效或者右节点有效并且比左节点大,否则就无脑选左节点
	if !(*tree)[leftNode].available || ((*tree)[rightNode].available && (*tree)[leftNode].value > (*tree)[rightNode].value) {
		(*tree)[(leftNode-1)/2] = (*tree)[rightNode]
	} else {
		(*tree)[(leftNode-1)/2] = (*tree)[leftNode]
	}
}

func pow(x, y int) int {
	return int(math.Pow(float64(x), float64(y)))
}

运行结果