驴友花雕 发表于 昨天 07:03

【花雕动手做】基于 Kitronik 可编程开发板之排序算法

Kitronik ARCADE 是一款由英国教育科技公司 Kitronik 精心打造的可编程游戏机开发板,专为编程教学与创客实践而设计。该设备原生支持微软的 MakeCode Arcade 平台,用户可通过图形化或 JavaScript 编程方式,轻松创建、下载并运行复古风格的街机游戏。

它集成了彩色 LCD 显示屏、方向控制键、功能按键、蜂鸣器和震动马达等交互组件,提供完整的游戏输入输出体验。无论是初学者进行编程启蒙,还是创客群体开发交互式作品,Kitronik ARCADE 都能作为理想的硬件载体,助力创意实现。

凭借其开源友好、易于上手、兼容性强等特点,该开发板广泛应用于中小学编程课程、创客工作坊、游戏开发教学以及个人项目原型设计,深受教育者与技术爱好者的喜爱。







驴友花雕 发表于 昨天 07:05

【花雕动手做】基于 Kitronik 可编程开发板之排序算法

作为学习、练习与尝试,这里创建一个排序算法的小游戏。
打开网页版:https://arcade.makecode.com/,设置项目名称:排序算法

JavaScript 实验代码

const pauseDuration = 10;

interface SortingAlgorithm {
    title: string;
    algorithm: (values: number[]) => number[];
    a?: number[];
    place?: string;
}

let currCount = 26;
let currentRun = 0;

let ySegment: number;
let currentPlace: number;

let running: SortingAlgorithm[] = [];
let notRunning: SortingAlgorithm[] = [];

addExample("selection sort", sorts.selectionSort);
addExample("insertion sort", sorts.insertionSort);
addExample("bubble sort", sorts.bubbleSort);
addExample("shell sort", sorts.shellSort);
addExample("heap sort", sorts.heapSort);
addExample("quick sort", sorts.quickSort);
addExample("merge sort", sorts.mergeSort);

// Start off with two random algorithms running
for (let i = 0; i < 2; i++) {
    moveRandom(notRunning, running);
}

start();

game.onPaint(() => {
    running.forEach(function (value: SortingAlgorithm, index: number) {
      drawCurrentState(value, currCount, ySegment, index * ySegment);
    });
});

// start over with a new seed
controller.A.onEvent(ControllerButtonEvent.Pressed, () => {
    start();
});

// remove a sorting algorithm from the group of running sorts
controller.left.onEvent(ControllerButtonEvent.Pressed, () => {
    if (running.length > 1) {
      moveRandom(running, notRunning)
      start();
    }
});

// display a new sorting algorithm if possible
controller.right.onEvent(ControllerButtonEvent.Pressed, () => {
    if (notRunning.length > 0) {
      moveRandom(notRunning, running)
      start();
    }
});

// increase the number of elements to sort if possible
controller.up.onEvent(ControllerButtonEvent.Pressed, function () {
    if (currCount + 6 < screen.width / 2) {
      currCount += 6;
      start();
    }
});

// decrease the number of elements to sort if possible
controller.down.onEvent(ControllerButtonEvent.Pressed, function () {
    if (currCount > 6) {
      currCount -= 6;
      start();
    }
})

function moveRandom<T>(a: T[], b: T[]) {
    if (a.length > 0) {
      const i = randint(0, a.length - 1);
      b.push(a.removeAt(i));
    }
}

function addExample(title: string, sortAlgorithm: (values: number[]) => number[]) {
    let output: SortingAlgorithm = {
      title: title,
      algorithm: sortAlgorithm
    }
    notRunning.push(output);
}

function start() {
    const r = new Math.FastRandom();
    ++currentRun;
    currentPlace = 1;

    ySegment = Math.floor(screen.height / running.length);

    // clear old arrays to quickly cull other threads as much as possible;
    // only merge sort will survive as it is not in place
    running.forEach(v => {
      while (v.a && v.a.length != 0)
            v.a.pop();
    });

    // create a new starting array for each of the algorithms
    running.forEach(v => v.a = fillWithDefault(r, currCount, ySegment - (image.font5.charHeight + 2)));

    // run the comparison
    running.forEach(v => control.runInParallel(() => {
      const run = currentRun;
      v.place = undefined;
      v.algorithm(v.a);
      if (run === currentRun) {
            const place = currentPlace++;
            if (place === 1)
                music.powerUp.play();
            else if (place === running.length)
                music.wawawawaa.play();

            // ordinal indicator is 'st', 'nd', 'rd', or 'th'
            v.place = place + ordinalIndicator(place);
      }
    }));
}

function fillWithDefault(r: Math.FastRandom, count: number, maxHeight: number): number[] {
    // reset seed so that output is consistent
    r.reset();
    let output: number[] = [];

    for (let i = 0; i < count; ++i) {
      output.push(r.randomRange(0, maxHeight));
    }

    return output;
}

function drawCurrentState(s: SortingAlgorithm, count: number, height: number, yOffset: number) {
    const a = s.a
    const title = s.title;
    const lineWidth = Math.floor(screen.width / count) - 1;
    const borderWidth = (screen.width - (count * (lineWidth + 1))) / 2;

    for (let i = 0; i < a.length; ++i) {
      if (a > 0) {
            const maxValue = ySegment - (image.font5.charHeight + 2);
            // pick color between 0x1 and 0xE based on value
            let c = Math.clamp(0x1, 0xE, Math.floor(a * 14 / maxValue));
            screen.fillRect(borderWidth + i * (lineWidth + 1), height + yOffset - a, lineWidth, a, c);
      }
    }

    screen.print(title, borderWidth, yOffset + 1, 0x2, image.font5);
    if (s.place)
      screen.print(s.place, borderWidth, yOffset + 3 + image.font5.charHeight, 0x2, image.font5);
}

function ordinalIndicator(input: number) {
    const lastDigit = input % 10;
    if (lastDigit === 1)
      return "st";
    else if (lastDigit === 2)
      return "nd";
    else if (lastDigit === 3)
      return "rd";
    else
      return "th";
}

/**
* Sorting Algorithm Implementations
*/
namespace sorts {
    function swap(a: number[], i: number, j: number) {
      let tmp = a;
      a = a;
      a = tmp;
      pause(pauseDuration);
    }

    function compare(a: number, b: number) {
      pause(pauseDuration)
      return a < b;
    }

    export function insertionSort(a: number[]) {
      for (let i = 0; i < a.length; i++) {
            let value = a
            let j: number;
            for (j = i - 1; j > -1 && compare(value, a); --j) {
                a = a;
                pause(pauseDuration);
            }
            a = value;
      }

      return a;
    }

    export function selectionSort(a: number[]) {
      for (let i = 0; i < a.length; i++) {
            let min = i;
            for (let j = i + 1; j < a.length; j++) {
                if (compare(a, a)) {
                  min = j;
                  pause(pauseDuration);
                }
            }
            if (i !== min) {
                swap(a, i, min);
            }
      }

      return a;
    }

    export function bubbleSort(a: number[]) {
      for (let i = 0; i < a.length; ++i) {
            for (let j = 0; j < i; ++j) {

                if (compare(a, a)) {
                  swap(a, i, j);
                }
            }
      }

      return a;
    }

    export function shellSort(a: number[]) {
      let increment = a.length / 2;
      while (increment > 0) {
            for (let i = increment; i < a.length; ++i) {
                let j = i;
                let t = a;

                while (j >= increment && compare(t, a)) {
                  a = a;
                  j = j - increment;
                  pause(pauseDuration);
                }
                a = t;
            }

            if (increment == 2) {
                increment = 1;
            } else {
                increment = Math.floor(increment * 5 / 11);
            }
      }

      return a;
    }

    export function quickSort(a: number[]) {
      qsort(a, 0, a.length - 1);
      return a;

      function qsort(a: number[], lo: number, hi: number) {
            if (lo < hi) {
                let p = partition(a, lo, hi);
                qsort(a, lo, p - 1);
                qsort(a, p + 1, hi);
            }
      }

      function partition(a: number[], lo: number, hi: number) {
            let pivot = a;
            let i = lo - 1;

            for (let j = lo; compare(j, hi); ++j) {
                if (a < pivot) {
                  i++;
                  swap(a, i, j);
                }
            }

            swap(a, i + 1, hi);
            return i + 1;
      }
    }

    export function heapSort(a: number[]) {
      function buildMaxHeap(a: number[]) {
            let i = Math.floor(a.length / 2 - 1);

            while (i >= 0) {
                heapify(a, i, a.length);
                i -= 1;
            }
      }

      function heapify(heap: number[], i: number, max: number) {
            while (i < max) {
                const left = 2 * i + 1;
                const right = left + 1;
                let curr = i;

                if (left < max && compare(heap, heap)) {
                  curr = left;
                }

                if (right < max && compare(heap, heap)) {
                  curr = right;
                }

                if (curr == i) return;

                swap(heap, i, curr);
                i = curr;
            }
      }
      buildMaxHeap(a);

      for (let i = a.length - 1; i > 0; --i) {
            swap(a, 0, i);
            heapify(a, 0, i);
      }

      return a;
    }

    export function mergeSort(a: number[]) {
      // Typically, you wouldn't keep an 'offset' or a link to the 'original' array,
      // as the sort works by returning a new, sorted array as output - not by modifying
      // the one passed as input. Here, though, it is needed so that the preview on the
      // screen can be updated
      function msort(a: number[], offset: number, original: number[]): number[] {
            if (a.length < 2) {
                return a;
            }

            const middle = Math.floor(a.length / 2);

            let left = a.slice(0, middle);
            let right = a.slice(middle, a.length);

            left = msort(left, offset, original);
            right = msort(right, offset + middle, original);

            const merged = merge(left, right);

            // Update preview on screen
            merged.forEach(function (value: number, index: number) {
                original = value;
                pause(pauseDuration);
            });

            return merged;
      }

      function merge(left: number[], right: number[]) {
            let result = [];
            let lIndex = 0;
            let rIndex = 0;

            while (lIndex < left.length && rIndex < right.length) {
                if (compare(left, right)) {
                  result.push(left);
                  ++lIndex;
                } else {
                  result.push(right);
                  ++rIndex;
                }
            }
            while (lIndex < left.length) {
                result.push(left);
                ++lIndex;
            }
            while (rIndex < right.length) {
                result.push(right);
                ++rIndex;
            }
            return result;
      }

      return msort(a, 0, a);
    }

    export function isSorted(a: number[]) {
      for (let i = 1; i < a.length; ++i) {
            if (a > a) {
                return false;
            }
      }
      return true;
    };
}

驴友花雕 发表于 昨天 07:11

【花雕动手做】基于 Kitronik 可编程开发板之排序算法

ARCADE MakeCode 排序算法可视化工具代码解读
这是一个基于ARCADE MakeCode的排序算法可视化工具,可以同时比较多种排序算法的性能。

1. 基本设置和数据结构
javascript
const pauseDuration = 10;// 每次操作后的暂停时间,用于可视化



interface SortingAlgorithm {

    title: string;      // 算法名称

    algorithm: (values: number[]) => number[];// 排序函数

    a?: number[];         // 待排序的数组

    place?: string;       // 排名(如"1st")

}



let currCount = 26;       // 当前数组元素数量

let currentRun = 0;       // 当前运行次数标识



let ySegment: number;   // 每个算法在屏幕上的垂直分段高度

let currentPlace: number; // 当前排名



let running: SortingAlgorithm[] = [];    // 正在运行的算法

let notRunning: SortingAlgorithm[] = []; // 未运行的算法

2. 添加排序算法示例
javascript
addExample("selection sort", sorts.selectionSort);

addExample("insertion sort", sorts.insertionSort);

addExample("bubble sort", sorts.bubbleSort);

addExample("shell sort", sorts.shellSort);

addExample("heap sort", sorts.heapSort);

addExample("quick sort", sorts.quickSort);

addExample("merge sort", sorts.mergeSort);

3. 初始化运行算法
javascript
// 开始时随机选择两个算法运行

for (let i = 0; i < 2; i++) {

    moveRandom(notRunning, running);

}

4. 绘制函数
javascript
game.onPaint(() => {

    running.forEach(function (value: SortingAlgorithm, index: number) {

      drawCurrentState(value, currCount, ySegment, index * ySegment);

    });

});

5. 控制器事件处理
javascript
// A按钮:重新开始

controller.A.onEvent(ControllerButtonEvent.Pressed, () => {

    start();

});



// 左键:移除一个排序算法

controller.left.onEvent(ControllerButtonEvent.Pressed, () => {

    if (running.length > 1) {

      moveRandom(running, notRunning)

      start();

    }

});



// 右键:添加一个排序算法

controller.right.onEvent(ControllerButtonEvent.Pressed, () => {

    if (notRunning.length > 0) {

      moveRandom(notRunning, running)

      start();

    }

});



// 上键:增加元素数量

controller.up.onEvent(ControllerButtonEvent.Pressed, function () {

    if (currCount + 6 < screen.width / 2) {

      currCount += 6;

      start();

    }

});



// 下键:减少元素数量

controller.down.onEvent(ControllerButtonEvent.Pressed, function () {

    if (currCount > 6) {

      currCount -= 6;

      start();

    }

})

6. 核心函数
移动随机元素函数
javascript
function moveRandom<T>(a: T[], b: T[]) {

    if (a.length > 0) {

      const i = randint(0, a.length - 1);

      b.push(a.removeAt(i));

    }

}
添加示例函数
javascript

<span style="background-color: rgb(255, 255, 255);">f</span>unction addExample(title: string, sortAlgorithm: (values: number[]) => number[]) {

    let output: SortingAlgorithm = {

      title: title,

      algorithm: sortAlgorithm

    }

    notRunning.push(output);

}
启动函数
javascript
function start() {
    const r = new Math.FastRandom();
    ++currentRun;// 增加运行次数标识
    currentPlace = 1;

    ySegment = Math.floor(screen.height / running.length);

    // 清除旧数组
    running.forEach(v => {
      while (v.a && v.a.length != 0)
            v.a.pop();
    });

    // 创建新数组
    running.forEach(v => v.a = fillWithDefault(r, currCount, ySegment - (image.font5.charHeight + 2)));

    // 并行运行所有算法
    running.forEach(v => control.runInParallel(() => {
      const run = currentRun;
      v.place = undefined;
      v.algorithm(v.a);// 执行排序算法
      if (run === currentRun) {// 确保是当前运行
            const place = currentPlace++;
            if (place === 1)
                music.powerUp.play();// 第一名音效
            else if (place === running.length)
                music.wawawawaa.play();// 最后一名音效

            v.place = place + ordinalIndicator(place);// 设置排名
      }
    }));
}

驴友花雕 发表于 昨天 07:17

【花雕动手做】基于 Kitronik 可编程开发板之排序算法

排序算法解读
1、插入排序 (Insertion Sort)
javascript
export function insertionSort(a: number[]) {

    for (let i = 0; i < a.length; i++) {

      let value = a

      let j: number;

      // 将当前元素与已排序部分比较,找到合适位置插入

      for (j = i - 1; j > -1 && compare(value, a); --j) {

            a = a;// 向后移动元素

            pause(pauseDuration);

      }

      a = value;// 插入到正确位置

    }

    return a;

}
工作原理:将数组分为已排序和未排序两部分,逐个将未排序元素插入到已排序部分的正确位置

时间复杂度:最好O(n),平均O(n²),最坏O(n²)

空间复杂度:O(1)

特点:对小规模数据高效,稳定排序算法

2、选择排序 (Selection Sort)
javascript
export function selectionSort(a: number[]) {

    for (let i = 0; i < a.length; i++) {

      let min = i;// 假设当前位置是最小值

      // 在未排序部分寻找最小值

      for (let j = i + 1; j < a.length; j++) {

            if (compare(a, a)) {

                min = j;// 更新最小值位置

                pause(pauseDuration);

            }

      }

      if (i !== min) {

            swap(a, i, min);// 将最小值放到已排序末尾

      }

    }

    return a;

}
工作原理:每次从未排序部分选择最小元素,放到已排序部分的末尾

时间复杂度:始终为O(n²)

空间复杂度:O(1)

特点:简单但效率低,不稳定排序算法

3、冒泡排序 (Bubble Sort)
javascript
export function bubbleSort(a: number[]) {

    for (let i = 0; i < a.length; ++i) {

      for (let j = 0; j < i; ++j) {

            // 比较相邻元素,如果顺序错误则交换

            if (compare(a, a)) {

                swap(a, i, j);

            }

      }

    }

    return a;

}
工作原理:重复遍历数组,比较相邻元素并交换,直到没有需要交换的元素

时间复杂度:最好O(n),平均O(n²),最坏O(n²)

空间复杂度:O(1)

特点:简单但效率低,稳定排序算法

4、希尔排序 (Shell Sort)
javascript
export function shellSort(a: number[]) {

    let increment = a.length / 2;// 初始间隔

    while (increment > 0) {

      // 对各个子数组进行插入排序

      for (let i = increment; i < a.length; ++i) {

            let j = i;

            let t = a;



            // 对子数组进行插入排序

            while (j >= increment && compare(t, a)) {

                a = a;

                j = j - increment;

                pause(pauseDuration);

            }

            a = t;

      }



      // 减小间隔

      if (increment == 2) {

            increment = 1;

      } else {

            increment = Math.floor(increment * 5 / 11);// 使用特定序列减少间隔

      }

    }

    return a;

}
工作原理:是插入排序的改进版,通过将数组分成多个子数组进行排序,逐渐减少子数组的间隔

时间复杂度:取决于间隔序列,最好O(n log n),最坏O(n²)

空间复杂度:O(1)

特点:比插入排序高效,不稳定排序算法

5、快速排序 (Quick Sort)
javascript
export function quickSort(a: number[]) {

    qsort(a, 0, a.length - 1);// 递归排序

    return a;



    function qsort(a: number[], lo: number, hi: number) {

      if (lo < hi) {

            let p = partition(a, lo, hi);// 分区操作

            qsort(a, lo, p - 1);// 递归排序左半部分

            qsort(a, p + 1, hi);// 递归排序右半部分

      }

    }



    function partition(a: number[], lo: number, hi: number) {

      let pivot = a;// 选择最后一个元素作为基准

      let i = lo - 1;



      for (let j = lo; compare(j, hi); ++j) {

            if (a < pivot) {

                i++;

                swap(a, i, j);// 将小于基准的元素移到左边

            }

      }



      swap(a, i + 1, hi);// 将基准放到正确位置

      return i + 1;

    }

}
工作原理:分治算法,选择一个基准元素,将数组分成两部分,一部分小于基准,一部分大于基准,然后递归排序

时间复杂度:最好O(n log n),平均O(n log n),最坏O(n²)

空间复杂度:O(log n)

特点:通常是最快的排序算法,但不稳定

6、堆排序 (Heap Sort)
javascript
export function heapSort(a: number[]) {

    function buildMaxHeap(a: number[]) {

      let i = Math.floor(a.length / 2 - 1);

      // 从最后一个非叶子节点开始构建最大堆

      while (i >= 0) {

            heapify(a, i, a.length);

            i -= 1;

      }

    }



    function heapify(heap: number[], i: number, max: number) {

      while (i < max) {

            const left = 2 * i + 1;// 左子节点

            const right = left + 1;// 右子节点

            let curr = i;



            // 找出当前节点、左子节点、右子节点中的最大值

            if (left < max && compare(heap, heap)) {

                curr = left;

            }



            if (right < max && compare(heap, heap)) {

                curr = right;

            }



            if (curr == i) return;// 如果当前节点已经是最大值,则返回



            swap(heap, i, curr);// 交换当前节点与最大值节点

            i = curr;// 继续向下调整

      }

    }

   

    buildMaxHeap(a);// 构建最大堆



    // 逐个提取最大值并放到数组末尾

    for (let i = a.length - 1; i > 0; --i) {

      swap(a, 0, i);// 将最大值(堆顶)与末尾元素交换

      heapify(a, 0, i);// 调整堆

    }



    return a;

}
工作原理:利用堆数据结构,首先构建最大堆,然后逐个提取最大值并放到数组末尾

时间复杂度:始终为O(n log n)

空间复杂度:O(1)

特点:时间复杂度稳定,但不稳定排序算法

7、归并排序 (Merge Sort)
javascript
export function mergeSort(a: number[]) {

    function msort(a: number[], offset: number, original: number[]): number[] {

      if (a.length < 2) {

            return a;// 基线条件:数组长度为1或0时已排序

      }



      const middle = Math.floor(a.length / 2);// 找到中间位置



      let left = a.slice(0, middle);// 分割左半部分

      let right = a.slice(middle, a.length);// 分割右半部分



      left = msort(left, offset, original);// 递归排序左半部分

      right = msort(right, offset + middle, original);// 递归排序右半部分



      const merged = merge(left, right);// 合并两个已排序数组



      // 更新屏幕上的可视化效果

      merged.forEach(function (value: number, index: number) {

            original = value;

            pause(pauseDuration);

      });



      return merged;

    }



    function merge(left: number[], right: number[]) {

      let result = [];

      let lIndex = 0;

      let rIndex = 0;



      // 比较两个数组的元素,按顺序合并

      while (lIndex < left.length && rIndex < right.length) {

            if (compare(left, right)) {

                result.push(left);

                ++lIndex;

            } else {

                result.push(right);

                ++rIndex;

            }

      }

      // 将剩余元素添加到结果中

      while (lIndex < left.length) {

            result.push(left);

            ++lIndex;

      }

      while (rIndex < right.length) {

            result.push(right);

            ++rIndex;

      }

      return result;

    }



    return msort(a, 0, a);

}
工作原理:分治算法,将数组分成两半,递归排序,然后合并两个已排序的子数组

时间复杂度:始终为O(n log n)

空间复杂度:O(n)

特点:稳定排序算法,适合大数据集,但需要额外空间

驴友花雕 发表于 昨天 07:22

【花雕动手做】基于 Kitronik 可编程开发板之排序算法

通过模拟器,调试与模拟运行



实验场景记录













驴友花雕 发表于 昨天 07:25

【花雕动手做】基于 Kitronik 可编程开发板之排序算法


页: [1]
查看完整版本: 【花雕动手做】基于 Kitronik 可编程开发板之排序算法