上篇数据结构系列:树?二叉树?有一说一,这俩货你不会真的不行 ,我们简单的分享了树结构的一般特点和二叉树结构相关的特性和实现,本篇将基于二叉树分享相关的遍历方法(前序,中序,后序,层序),以及部分应用的算法。




jquery 递归树节点 拼接 js递归树结构每一层_怎样将树的中序遍历的数输入到一个数组中


了解二叉树的遍历

我们知道,线性表的遍历,我们可以通过for循环或者迭代器。而树的遍历,明显不太可能以相同的方式进行,毕竟树结构抽象的是层级结构

二叉树的遍历从宏观的大角度看,分为深度优先遍历(前序,中序,后序),广度优先遍历(层级),如图


jquery 递归树节点 拼接 js递归树结构每一层_怎样将树的中序遍历的数输入到一个数组中_02


概括地说:深度优先遍历主要包括前序遍历,中序遍历,以及后序遍历,广度优先遍历主要是指层级遍历(每遍历完整层再遍历下一层,这里都是以从左往右的顺序进行元素的遍历)。

对于3种深度优先遍历来讲,最简单的理解方法就是:将当前节点,当前节点的父节点,当前节点的子节点当作一个整体看,分别判断左中右的顺序,这样其实就很好理解。

比如说:中序遍历(左中右的顺序),也就是先从最左边的最左节点开始,然后此子树的中间节点(当前树的根节点),然后此子树的右节点,此时这棵左子树已经遍历完成,把当前遍历完成的左子树看作一个整体,找到这棵树是哪个节点的左子节点,然后遍历此左子节点的中间节点,以此类推。

下面分享这几种不同的遍历方法及实现

前序遍历(根->左->右)

前序理解

若二叉树为空,则直接返回;否则按照根节点,然后左子树,右子树的顺序依次访问(解释:每访问一个节点,就把当前节点当作根节点,然后找左右子节点,如果有左节点,则把左节点当作当前树的根结点,继续找左右子节点……)


jquery 递归树节点 拼接 js递归树结构每一层_怎样将树的中序遍历的数输入到一个数组中_03


前序实现

# 递归实现def pre_order(tree):    if tree:        print(tree.get_root())        pre_order(tree.get_left_child())        pre_order(tree.get_right_child())        # 利用栈的思想def pre_order(tree_node):    s = Stack()    while tree_node or (not s.is_empty):        while tree_node != None:            print(tree_node.get_root())            s.push(tree_node)            tree_node = tree_node.get_left_child()        if not s.is_empty():            tree_node = s.pop()            tree_node = tree_node.get_right_child()

中序遍历(左->根->右)

中序理解

树为空,直接返回;否则就从根节点开始(但并不是以根节点开始访问),找到最最最左子节点开始,然后当前左子节点的父节点,然后右节点,结果如下


jquery 递归树节点 拼接 js递归树结构每一层_jquery 递归树节点 拼接_04


中序实现(基于递归)

def in_order(tree):    if tree != None:        in_order(tree.get_left_child())        print(tree.get_root())        in_order(tree.get_right_child())

后序遍历

后序理解

若树为空,则直接返回;否则先遍历左子树,然后右子树,最后访问根节点,结果如下


jquery 递归树节点 拼接 js递归树结构每一层_js递归遍历树结构_05


后序实现(基于递归)

def post_order(tree):    if tree != None:    post_order(tree.get_left_child())        post_order(tree.get_right_child())        print(tree.get_root())

层序遍历

层序理解

这个最简单:从每一层开始,按照从左往右的顺序遍历结点


jquery 递归树节点 拼接 js递归树结构每一层_怎样将树的中序遍历的数输入到一个数组中_06


层序实现

# 利用队列的数据结构def level_order(root):    q = queue()    q.enqueue(root)    while not q.is_empty():        current_root = q.dequeue()        print(current_root.get_root())        if current_root.left_child != None:            q.enqueue(current.left)        if current_root.right_child != None:            q.enqueue(current.right)

说到这,二叉树的几种遍历就差不多结束了,接下来我们分享几个重要的基于二叉树的应用

堆(heap)

概念

堆排序是高效排序算法的一个解决方案,它的主要优点是,无论输入数据如何,它的最坏情况运行时间都是O(n*logn)。

堆排序是优先队列最主要的实现方法

特点

有这样一个特点(堆次序):任何一条路径都是已经排好序的有序数列

  • 最小堆: 每个节点的数据项都小于或等于其两个子节点数据,最小的项位于根节点
  • 最大堆: 每个节点的数据项都大于等于其两个子节点的数据,最大的项位于根节点

堆结构一般有如下几个接口

heap.is_empty() - 堆是否为空堆,返回布尔值

heap.heappush(item) - 增加数据到堆结构

heap.heappop() - 返回heap最顶部的数据,并删除

heap.peek() - 返回heap最顶端的项

我们这里都基于数组实现堆结构,因为对于数组(这里引申为python的列表)来讲,在一个列表中,父节点和子节点的索引存在着一定的关系(如果专门保存一个空元素给列表的第一个位置,那么子节点的索引永远等于父节点地板除2的值)


jquery 递归树节点 拼接 js递归树结构每一层_jquery 递归树节点 拼接_07


实现

# 最小堆实现(python标准库也自带heapq模块,这里为自己实现的逻辑)class BinHeap(object):    def __init__(self):        # 这里给一个初始元素占位,是为了下面的计算方便(parent_index = 2 * left_child_index = 2 * right_child_index + 1)        self._heap = [0]        self.current_size = 0    # 有序的添加数据    def heappush(self, data):        self._heap.append(data)        self.current_size += 1        # 避免current_size的变化,用新的变量引用        child_index = self.current_size        while child_index // 2 > 0:            if self._heap[child_index] < self._heap[child_index // 2]:                self._heap[child_index], self._heap[child_index // 2] = self._heap[child_index // 2], self._heap[child_index]            child_index = child_index // 2        # 删除堆中最小元素并返回    def heappop(self):        # 将最小值抛出,并将当前最后的一个值填充给抛出的位置        rm_data = self._heap[1]        self._heap[1] = self._heap[self.current_size]        self.current_size -= 1        self._heap.pop(1)        current_index = 1        while current_index * 2 <= self.current_size:            min_index = self.min_child(current_index)            if self._heap[current_index] > self._heap[min_index]:                self._heap[current_index], self._heap[min_index] = self._heap[min_index], self._heap[current_index]                current_index = min_index        return rm_data    # 辅助函数,帮助pop_data选择子结点的最小索引并返回最小索引    def min_child(self, current_index):        # 如果只有一个子节点的情况,因为是完全二叉树,必然有左子节点的存在        if 2 * current_index + 1 > self.current_size:            return 2 * current_index        if self._heap[2 * current_index] > self._heap[2 * current_index + 1]:            return 2 * current_index + 1        return 2 * current_index

表达式树

表达式树是包含了表达式的运算数和运算符的二叉树。这里主要是将中缀表达式转换为解析树结构

大概的思路

  1. 将中缀转换为全括号表达式来进行操作 ,exp: 3+5*3-2 -> ( ( 3 + ( 5 * 3 ) ) - 2 )
  2. 将所有数据存入列表,准备利用栈结构
  3. 从左到右扫描全括号表达式的每个单词
  4. 创建一个空树,当前结点就为根结点
  5. 如果是"(":为当前结点添加一个新的左结点,当前结点下降为这个新结点
  6. 如果是"±*/",将当前结点的值赋值为此符号,同时为当前结点添加一个新结点作为其右子结点,当前结点下降为这个新结点
  7. 如果当前是操作数,将当前结点的值设为此数,当前结点上升到父结点
  8. 如果当前结点是")",则当前结点上升到父结点

具体实现

# 将中缀表达式转换为解析树结构表示def build_parse_tree(tokens):    token_list = tokens.split(" ")    # 栈用来保存当前结点    s = Stack()    # 最开始构建一个空树    root = BinaryTree("")    s.push(root)    current_root = root    for i in token_list():        if i == "(":            current_root.insert_left("")            s.push(current_root)            current_root = current_root.get_left_child()        elif isinstance(i, int):            current_root.set_root(int(i))            parent = s.pop()            current_root = parent        elif i in ["+", "-", "*", "/"]:            current_root.set_root(i)            current_root.insert_right("")            s.push(current_root)            current_root = current_root.get_right_child()        elif i == ")":            current_root = s.pop()        else:            raise ValueError("wrong")    return root

我是一名奋战在编程界的pythoner,工作中既要和数据打交道,也要和erp系统,web网站保持友好的沟通……时不时的会分享一些提高效率的编程小技巧,在实际应用中遇到的问题以及解决方案,或者源码的阅读等等,欢迎大家一起来讨论!如果觉得写得还不错,欢迎关注点赞,谢谢。