打印一颗基于数组的完全二叉树——Python3实现

news/2024/5/20 8:37:30 标签: , 完全二叉树, 数组

前言

最近在复习排序的内容,发现基于数组虽然用起来很方便,但打印不方便。所以本文实现了一个简单美观的打印一颗基于数组完全二叉树的算法(就是一种完全二叉树嘛,但实现最小一般是基于数组的)。

算法思路

打印的层分为数字层和指针层:

  • 指针层就是/ \ / \
  • 数字层就是5 43 7 n。(PS:n代表无节点)

示例打印:

      1       
   /      \   
  22       11 
 / \      / \ 
5   43   7   n

先构建最后一层(数字层)的字符串,因为它的宽度是整个打印二叉树的最大宽度,需要最先得到最大宽度。构建最后一层时,不考虑每个数字是否为多位,直接每个数字之间间隔3个空格(注意,3这个数字是可以调节的)。而且,如果考虑多位数字从而减少后面的空格,反而不好控制也不美观,最关键的是,如果是4位数字,那这个4位数字与后面的数字就没有间隔了。

构建好最后一层数字层后,从下往上接着构建。接下来的每层的字符串的长度也都是之前得到的最大宽度,初始化时,每层字符串都是长度为最大宽度的全是空格的字符串。

构建指针层时,总是以下面最接近的数字层为标准。比如示例打印的倒数第二层。

  • 它的第一个/是根据5来的,具体的说,因为5是一个左孩子,我们获得5在该层字符串中的索引index,然后令本层的index+1的字符变成/
  • 同理,它的第一个\是根据43来的,但这里有所不同,我们关注的是43的第一个字符的索引index。然后令本层的index-1的字符变成\,因为43是一个右孩子。

构建数字层(这里指除了最后一层的数字层)时,也总是以下面最接近的数字层为标准。比如示例打印的倒数第三层。

  • 它的22的第一个2的位置是根据它的左孩子和右孩子的位置来的。它的左孩子5在字符串中的索引为0,右孩子43在字符串中的索引为4(同样,只看第一个字符的位置)。那么22的位置就是(0+4)/2得到一个整数index,然后将22放置到字符串的index位置(注意,就算是多位数字,也是把多位数字的第一位放到index,多位数字的其他位也依次放置)。

最重要在于,总是以下面最接近的数字层为标准来构建每层的字符串,这样指针总是刚好靠近孩子,父节点总是在孩子节点的中间位置(因为是整除,可能有时稍有点偏,但完全不影响美观)。

算法实现

data = [1,22,11,5,43,7,1,36,42,43,1,6,14,22333,3,4,5,6,7,81]

def replace(s, rep, index):
    #替换s的从index开始的部分字符串,替换长度为len(rep)
    prev = s[0:index]
    next = s[index+len(rep):len(s)]
    return prev + rep + next

def getLevelAssign(List, limit):
    #观察范围是[0, limit]的完全二叉树tree,
	#然后得到tree对应的满二叉树的每层的信息
	#包括 每层开始节点的所在数组索引levelStart
	#     每层结束节点的所在数组索引+1 levelLimit
	#     每层节点个数(虽然没用到)
    assign = []
    levelStart = 0
    levelCount = 1  #每层节点的个数,2 的幂
    levelLimit = 1  #每层节点索引的限制
    while(levelStart < limit):
        #基本信息弄成一维数组,放到一个二维数组中,以栈的方式
        assign.insert(0, [levelStart, levelLimit, levelCount])
        
        levelCount = levelCount << 1
        levelStart = levelCount - 1
        levelLimit += levelCount
    # 返回时,assign[0]是满二叉树最后一层的信息,assign[1]是满二叉树倒数第二层的信息
    return assign

def printHeap(List, limit):
    assign = getLevelAssign(List,limit)#得到了每个数字层的信息
    maxLevelLen = 0
    maxLevelBlankNumber = 3  #此处可调节。代表最后一层的数字之间的空格数
    printLi = []
    lastLevel = True
    for ass in assign: 
        #构建数字层
        levelStr = ""  #代表数字层
        if lastLevel:  #如果是最后一层(第一次循环当然是最后一层)
            for i in range(ass[0],ass[1]):
                if i < limit:
                    levelStr += str(List[i])+" "*maxLevelBlankNumber
                else:
                    levelStr += "n"+" "*maxLevelBlankNumber
            levelStr = levelStr[:-maxLevelBlankNumber]
            maxLevelLen = len(levelStr)  #得到最大宽度
            lastLevel = False
        else:  # 如果不是最后一层的数字层
            #下面最接近的数字层在printLi[1],因为隔了一个指针层
            levelStr = " "*maxLevelLen
            takeIndex = ass[0] #从原始数组中取数字的开始索引
            # count代表在最接近的数字层遇到的孩子节点的次数
            # left 代表某连续两个左右孩子的左孩子的索引 (指左孩子字符串在它所在层的构建字符串中的索引)
            # right代表某连续两个左右孩子的右孩子的索引
            count = left = right = 0
            for index in range(len(printLi[1])):
                #当遇到连续数字字符串中第一个数字时
                if printLi[1][index] != " " and (index == 0 or printLi[1][index-1] == " "):
                    #左移存储left right
                    left = right
                    right = index
                    count += 1
                    #当遇到了偶数次孩子时,我们就可以为本层数字层加入数字字符串了
                    if count != 0 and count%2 == 0:
                        middle = int((left + right)/2)#取得一个中间位置
                        levelStr = replace(levelStr, str(List[takeIndex]), middle)
                        takeIndex += 1 
        printLi.insert(0, levelStr)
        
        #构建完数字层,需要构建数字层之上的指针层,第一层数字层不需要,因为它是根节点
        if ass[0] != 0:#不是第一层
            pointerStr = " "*maxLevelLen
            left = True#先遇到的一定是左孩子
            # printLi[0]是下面最接近的数字层
            for charIndex in range(len(printLi[0])):
                #当遇到连续数字字符串中第一个数字时,添加本层的指针
                if printLi[0][charIndex] != ' ' and (charIndex == 0 or printLi[0][charIndex-1] == " "):
                    if left == True:#左孩子,那么本层添加一个/
                        pointerStr = replace(pointerStr, "/", charIndex+1)
                    else:           #右孩子,那么本层添加一个\
                        pointerStr = replace(pointerStr, "\\", charIndex-1)
                    left = not left  #置反
                    
            printLi.insert(0, pointerStr)  #栈的方式添加

    for i in printLi:
        print(i)
        
printHeap(data,len(data))

打印效果如下:

                              1                               
               /                              \               
              22                               11             
       /             \                  /             \       
      5               43               7               1      
   /     \         /      \         /     \         /     \   
  36      42      43       1       6       14      22333   3  
 / \     / \     /  \     / \     / \     / \     / \     / \ 
4   5   6   7   81   n   n   n   n   n   n   n   n   n   n   n

其他

printHeap的第二个参数可以随意调整,只要在[0, len(data)]的范围内就行。

如果有位数很多的数字时,则可能需要把maxLevelBlankNumber调大。


http://www.niftyadmin.cn/n/768178.html

相关文章

表单设计器资料收集

由于公司一些需求&#xff0c;收集了一些有关表单设计器&#xff0c;主要是一些技术特点、各自的优缺点记录&#xff0c;以此记录一下。 FormMaking http://form.making.link/ FormMaking 表单设计器是一个基于 Vue 和 ElementUI 的可视化表单设计器&#xff0c;支持 i18n 国…

从小顶堆到堆排序——超详细图解——Python3实现

文章目录前言最小堆实现已知所有节点&#xff0c;原地构建最小堆最小堆删除顶点最小堆添加节点实时插入删除堆排序总结前言 在简单选择排序中&#xff0c;每次选择会从待排序元素中找到最小值&#xff0c;但每次选择都需要遍历完剩余所有元素&#xff0c;而且在遍历时没有记录…

JUC集合类 PriorityBlockingQueue源码解析 JDK8

文章目录前言成员构造器原地建堆入队offer扩容出队poll获取堆顶方法peek内部删除迭代器总结前言 PriorityBlockingQueue是一个无界阻塞队列&#xff0c;它的出队方式不再是FIFO&#xff0c;而是优先级高的先出队。其内部实现是最小堆&#xff0c;即堆顶元素是逻辑上最小的那个…

JDK8 PriorityBlockingQueue(Collection<? extends E> c)构造器 源码解析

文章目录前言if (pq.getClass() PriorityBlockingQueue.class)if (a.getClass() ! Object[].class)if (screen && (n 1 || this.comparator ! null))heapify()PriorityBlockingQueue的BUG&#xff1f;&#xff01;总结前言 PriorityBlockingQueue的这个(Collection&…

JUC集合类 DelayQueue源码解析 JDK8

前言 DelayQueue是一个无界阻塞队列&#xff0c;它和PriorityBlockingQueue一样是一个优先队列&#xff0c;但区别在于队列元素只能放置Delayed对象&#xff0c;而且只有元素到期后才能将其出队。 内部是一个最小堆&#xff0c;堆顶永远是最先“到期”的那个元素。如果堆顶元…

JUC集合类 LinkedTransferQueue源码解析 JDK8

文章目录前言LinkedTransferQueue概述术语解释xfer交易后来的一方交易先来的一方tryAppendtryMatchDataunsplice为什么是普通语义而不是CAS内部删除 remove迭代器总结前言 LinkedTransferQueue是一种特殊的无界阻塞队列&#xff0c;它提供一种Transfer的功能&#xff0c;用以保…

JUC集合类 SynchronousQueue源码解析 JDK8

文章目录前言Transferer抽象类TransferStack节点成员节点类型TransferStack成员transfer方法awaitFulfillcleanTransferQueue节点成员节点类型TransferQueue成员transfer方法awaitFulfillclean无效操作总结前言 SynchronousQueue其实就是LinkedTransferQueue的升级版&#xff…

JUC框架 从Runnable到Callable到FutureTask 使用浅析

前言 本文旨在简单讲解Runnable、Callable、FutureTask这几个线程执行相关的接口和类。为后面FutureTask源码讲解作铺垫。 JUC框架 系列文章目录 Runnable FunctionalInterface public interface Runnable {public abstract void run(); }我们知道创建线程有两种方式&#…