1. 树状图求概率
    • 功能:动画生成两次或三次事件的树状图,自动计算所有可能结果数和符合条件的结果数。
      支持生成2次或3次事件的树状图
      动画展示树状图的生成过程
      自动计算所有可能结果数
      自动计算符合条件的结果数
      自动计算概率值
      支持自定义事件结果(如A、B、C等)
      支持自定义条件(如"AA"、"AB"等)
      显示所有可能结果和符合条件的结果
      在这里插入图片描述
// 树状图求概率
// 功能:动画生成两次或三次事件的树状图,自动计算所有可能结果数和符合条件的结果数

// 事件配置接口
interface EventConfig {
  eventCount: 2 | 3;
  events: string[];
  probabilities: number[];
  condition: string;
}

// 结果节点接口
interface ResultNode {
  id: string;
  value: string;
  probability: number;
  level: number;
  parentId: string | null;
  path: string;
}



@Entry
@Component
struct TreeDiagramProbability {
  @State canvasWidth: number = 350;
  @State canvasHeight: number = 350;
  @State eventConfig: EventConfig = {
    eventCount: 2,
    events: ['A', 'B'],
    probabilities: [0.5, 0.5],
    condition: 'A'
  };
  @State resultNodes: ResultNode[] = [];
  @State allResults: string[] = [];
  @State conditionResults: string[] = [];
  @State showAnimation: boolean = false;
  @State animationProgress: number = 0;
  @State selectedEvents: string[] = ['A', 'B'];
  @State inputEvent: string = '';
  @State isCalculated: boolean = false;
  
  build() {
    Column({ space: 15 }) {
      Text('树状图求概率')
        .fontSize(26)
        .fontWeight(FontWeight.Bold)
        .textAlign(TextAlign.Center)

      Column() {
        Text('功能介绍')
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
        Text('动画生成两次或三次事件的树状图,自动计算所有可能结果数和符合条件的结果数,帮助理解概率计算')
          .fontSize(14)
          .fontColor('#666666')
          .textAlign(TextAlign.Center)
      }
      .width('100%')
      .backgroundColor('#E3F2FD')
      .borderRadius(10)
      .padding(15)

      Column({ space: 10 }) {
        Text('事件配置')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
        
        Column({ space: 8 }) {
          Text('事件次数')
            .fontSize(14)
            .fontColor('#666666')
          
          Row({ space: 10 }) {
            Button('2次')
              .width('48%')
              .height(40)
              .fontSize(14)
              .backgroundColor(this.eventConfig.eventCount === 2 ? '#2196F3' : '#E0E0E0')
              .onClick(() => {
                this.eventConfig.eventCount = 2
              })
            
            Button('3次')
              .width('48%')
              .height(40)
              .fontSize(14)
              .backgroundColor(this.eventConfig.eventCount === 3 ? '#2196F3' : '#E0E0E0')
              .onClick(() => {
                this.eventConfig.eventCount = 3
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
        }
        .width('100%')
        .padding(8)
        .backgroundColor('#F5F5F5')
        .borderRadius(6)

        Column({ space: 8 }) {
          Text('事件结果')
            .fontSize(14)
            .fontColor('#666666')
          
          Row({ space: 10 }) {
            TextInput({ placeholder: '输入事件' })
              .width('60%')
              .height(40)
              .fontSize(14)
              .onChange((value: string) => {
                this.inputEvent = value
              })
            
            Button('添加')
              .width('35%')
              .height(40)
              .fontSize(14)
              .backgroundColor('#4CAF50')
              .onClick(() => {
                this.addEvent()
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          
          if (this.selectedEvents.length > 0) {
            Flex({
              direction: FlexDirection.Row,
              wrap: FlexWrap.Wrap,
              justifyContent: FlexAlign.Start
            }) {
              ForEach(this.selectedEvents, (event: string, index: number) => {
                Row({ space: 5 }) {
                  Text(event)
                    .fontSize(14)
                    .fontColor('#2196F3')
                  
                  Button('×')
                    .width(20)
                    .height(20)
                    .fontSize(12)
                    .backgroundColor('#F44336')
                    .onClick(() => {
                      this.removeEvent(index)
                    })
                }
                .padding(5)
                .backgroundColor('#E3F2FD')
                .borderRadius(4)
              })
            }
            .width('100%')
          }
        }
        .width('100%')
        .padding(8)
        .backgroundColor('#F5F5F5')
        .borderRadius(6)

        Column({ space: 8 }) {
          Text('条件')
            .fontSize(14)
            .fontColor('#666666')
          
          TextInput({ placeholder: '输入条件,如 "AA"' })
            .width('100%')
            .height(40)
            .fontSize(14)
            .onChange((value: string) => {
              this.eventConfig.condition = value
            })
        }
        .width('100%')
        .padding(8)
        .backgroundColor('#F5F5F5')
        .borderRadius(6)

        Row({ space: 10 }) {
          Button('生成树状图')
            .width('48%')
            .height(50)
            .fontSize(16)
            .backgroundColor('#4CAF50')
            .onClick(() => {
              this.generateTree()
            })
          
          Button('重置')
            .width('48%')
            .height(50)
            .fontSize(16)
            .backgroundColor('#FF9800')
            .onClick(() => {
              this.reset()
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .width('90%')
      .padding(10)
      .backgroundColor('#FAFAFA')
      .borderRadius(10)

      Column({ space: 10 }) {
        Text('树状图')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
        
        Canvas(this.canvasContext)
          .width(this.canvasWidth)
          .height(this.canvasHeight)
          .backgroundColor('#FFFFFF')
          .border({ width: 2, color: '#333' })
          .borderRadius(10)
          .onReady(() => {
            this.drawTree()
          })
      }
      .width('100%')
      .padding(10)
      .backgroundColor('#FAFAFA')
      .borderRadius(10)

      Column({ space: 10 }) {
        Text('计算结果')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
        
        if (this.isCalculated) {
          Column({ space: 8 }) {
            Text(`所有可能结果数: ${this.allResults.length}`)
              .fontSize(16)
              .fontColor('#2196F3')
              .fontWeight(FontWeight.Bold)
            
            Text(`符合条件的结果数: ${this.conditionResults.length}`)
              .fontSize(16)
              .fontColor('#4CAF50')
              .fontWeight(FontWeight.Bold)
            
            Text(`概率: ${this.calculateProbability().toFixed(4)}`)
              .fontSize(18)
              .fontColor('#FF9800')
              .fontWeight(FontWeight.Bold)
            
            Text('所有可能结果:')
              .fontSize(14)
              .fontColor('#666666')
            
            Text(this.allResults.join(', '))
              .fontSize(14)
              .fontColor('#333333')
              .textAlign(TextAlign.Center)
            
            Text('符合条件的结果:')
              .fontSize(14)
              .fontColor('#666666')
            
            Text(this.conditionResults.join(', '))
              .fontSize(14)
              .fontColor('#4CAF50')
              .textAlign(TextAlign.Center)
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#E8F5E9')
          .borderRadius(10)
        } else {
          Column() {
            Text('请先生成树状图')
              .fontSize(14)
              .fontColor('#999999')
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#F5F5F5')
          .borderRadius(10)
        }
      }
      .width('100%')
      .padding(10)
      .backgroundColor('#FAFAFA')
      .borderRadius(10)

      Column({ space: 8 }) {
        Text('使用说明')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
        
        Text('• 选择事件次数(2次或3次)')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 输入事件结果(如 A, B)')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 输入条件(如 AA)')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 点击"生成树状图"')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 观察树状图和计算结果')
          .fontSize(14)
          .fontColor('#666666')
        
        Text('• 理解概率的计算方法')
          .fontSize(14)
          .fontColor('#666666')
      }
      .width('95%')
      .padding(12)
      .backgroundColor('#FFF3E0')
      .borderRadius(10)
    }
    .width('100%')
    .height('100%')
    .padding(10)
    .justifyContent(FlexAlign.Start)
  }

  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D();

  private addEvent() {
    if (this.inputEvent.trim() !== '' && this.selectedEvents.length < 4) {
      this.selectedEvents.push(this.inputEvent.trim())
      this.inputEvent = ''
    }
  }

  private removeEvent(index: number) {
    this.selectedEvents.splice(index, 1)
  }

  private generateTree() {
    if (this.selectedEvents.length < 2) {
      return
    }
    
    this.resultNodes = []
    this.allResults = []
    this.conditionResults = []
    
    const eventCount = this.eventConfig.eventCount
    const events = this.selectedEvents
    const probability = 1 / events.length
    
    this.generateNodes(events, eventCount, probability)
    this.calculateResults()
    this.isCalculated = true
    this.showAnimation = true
    this.animationProgress = 0
    
    const totalSteps = 100
    let step = 0
    const animate = () => {
      if (step <= totalSteps) {
        this.animationProgress = step / totalSteps
        this.drawTree()
        step++
        setTimeout(animate, 30)
      } else {
        this.showAnimation = false
      }
    }
    animate()
  }

  private generateNodes(events: string[], eventCount: number, probability: number) {
    const generateLevel = (level: number, parentId: string | null, path: string) => {
      if (level > eventCount) {
        return
      }
      
      events.forEach((event: string, index: number) => {
        const id = `${parentId ? parentId + '-' : ''}${event}${index}`
        const newPath = path + event
        
        const node: ResultNode = {
          id,
          value: event,
          probability,
          level,
          parentId,
          path: newPath
        }
        
        this.resultNodes.push(node)
        generateLevel(level + 1, id, newPath)
      })
    }
    
    generateLevel(1, null, '')
  }

  private calculateResults() {
    const leafNodes = this.resultNodes.filter((node: ResultNode) => {
      return node.level === this.eventConfig.eventCount
    })
    
    leafNodes.forEach((node: ResultNode) => {
      this.allResults.push(node.path)
      if (node.path === this.eventConfig.condition) {
        this.conditionResults.push(node.path)
      }
    })
  }

  private calculateProbability(): number {
    if (this.allResults.length === 0) {
      return 0
    }
    return this.conditionResults.length / this.allResults.length
  }

  private drawTree() {
    const ctx = this.canvasContext
    ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
    
    if (this.resultNodes.length === 0) {
      ctx.fillStyle = '#999999'
      ctx.font = '16px sans-serif'
      ctx.fillText('请先生成树状图', this.canvasWidth / 2 - 80, this.canvasHeight / 2)
      return
    }
    
    const padding = 40
    const treeWidth = this.canvasWidth - 2 * padding
    const treeHeight = this.canvasHeight - 2 * padding
    const levelHeight = treeHeight / (this.eventConfig.eventCount + 1)
    
    const levelNodes = new Map<number, ResultNode[]>()
    this.resultNodes.forEach((node: ResultNode) => {
      let nodes = levelNodes.get(node.level)
      if (!nodes) {
        nodes = []
        levelNodes.set(node.level, nodes)
      }
      nodes.push(node)
    })
    
    levelNodes.forEach((nodes: ResultNode[], level: number) => {
      const levelWidth = treeWidth / (nodes.length + 1)
      
      nodes.forEach((node: ResultNode, index: number) => {
        const x = padding + (index + 1) * levelWidth
        const y = padding + level * levelHeight
        
        if (this.showAnimation) {
          const progress = Math.min(1, this.animationProgress * 2 - (level - 1) * 0.3)
          if (progress > 0) {
            ctx.globalAlpha = progress
            this.drawNode(ctx, x, y, node)
            this.drawEdges(ctx, node, levelNodes, levelWidth, levelHeight, padding, progress)
            ctx.globalAlpha = 1
          }
        } else {
          this.drawNode(ctx, x, y, node)
          this.drawEdges(ctx, node, levelNodes, levelWidth, levelHeight, padding, 1)
        }
      })
    })
  }

  private drawNode(ctx: CanvasRenderingContext2D, x: number, y: number, node: ResultNode) {
    ctx.fillStyle = '#2196F3'
    ctx.beginPath()
    ctx.arc(x, y, 15, 0, 2 * Math.PI)
    ctx.fill()
    
    ctx.strokeStyle = '#333'
    ctx.lineWidth = 2
    ctx.stroke()
    
    ctx.fillStyle = '#FFFFFF'
    ctx.font = '12px sans-serif'
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'
    ctx.fillText(node.value, x, y)
    
    if (node.level === this.eventConfig.eventCount) {
      ctx.fillStyle = '#333'
      ctx.font = '10px sans-serif'
      ctx.fillText(node.path, x, y + 25)
    }
  }

  private drawEdges(ctx: CanvasRenderingContext2D, node: ResultNode, levelNodes: Map<number, ResultNode[]>, levelWidth: number, levelHeight: number, padding: number, progress: number) {
    if (node.parentId) {
      const parentLevel = node.level - 1
      const parentNodes = levelNodes.get(parentLevel)
      
      if (parentNodes) {
        parentNodes.forEach((parentNode: ResultNode) => {
          if (parentNode.id === node.parentId) {
            const parentX = padding + (parentNodes.indexOf(parentNode) + 1) * levelWidth
            const parentY = padding + parentLevel * levelHeight
            const currentNodes = levelNodes.get(node.level)
            if (currentNodes) {
              const currentX = padding + (currentNodes.indexOf(node) + 1) * levelWidth
              const currentY = padding + node.level * levelHeight
              
              ctx.strokeStyle = '#333'
              ctx.lineWidth = 2
              ctx.beginPath()
              ctx.moveTo(parentX, parentY + 15)
              ctx.lineTo(currentX, parentY + 15 + (currentY - parentY - 30) * progress)
              ctx.stroke()
            }
          }
        })
      }
    }
  }

  private reset() {
    this.resultNodes = []
    this.allResults = []
    this.conditionResults = []
    this.selectedEvents = ['A', 'B']
    this.inputEvent = ''
    this.eventConfig = {
      eventCount: 2,
      events: ['A', 'B'],
      probabilities: [0.5, 0.5],
      condition: 'A'
    }
    this.isCalculated = false
    this.showAnimation = false
    this.animationProgress = 0
    this.drawTree()
  }
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐