diff --git a/src/assets/js/algorithm/sort.js b/src/assets/js/algorithm/sort.js index 2d3cc1f..36e0a2e 100644 --- a/src/assets/js/algorithm/sort.js +++ b/src/assets/js/algorithm/sort.js @@ -1,509 +1,560 @@ -/** - * 排序算法父类 Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-16 - */ - -function getSortClassList() { - return [ - QuickSort, - BubbleSort, - SelectionSort, - InsertionSort, - MergeSort, - RandomQuickSort, - CountingSort, - RadixSort, - ] -} - -class Sort { - static animation = null - constructor(animation) { - this.animation = animation - console.log(`初始化 ${this.info()['name']}`) - } - - info() { - return { - name: "未命名算法", - available: false - } - } - - sort(array) { - console.log("this method was not implement.") - return array - } - - doSortWithAnimation(elementId) { - let customAttr = document.getElementById(elementId).customAttr - let array = JSON.parse(JSON.stringify(customAttr.nodes)) // 深拷贝一个数组 - console.log(customAttr, array) - this.sortWithAnimation(customAttr, array) - } - - sortWithAnimation(array) { - console.log("this method was not implement.") - return array - } - - /** - * 交换数组中的两个元素 - * @param {*} array 数组 - * @param {*} index1 元素1索引 - * @param {*} index2 元素2索引 - */ - swap(array, index1, index2) { - if (index1 < 0 || index1 >= array.length) - throw new Error("index1索引超限") - if (index2 < 0 || index2 >= array.length) - throw new Error("index2索引超限") - if (index1 == index2) - return - - let tmp = array[index1] - array[index1] = array[index2] - array[index2] = tmp - - // only for test - // console.log(Object.keys(array)) - // console.log(array, index1, index2) - } - swapAnimation(id, index1, index2) { - if (index1 == index2) - return - animation.swapLinkedListItems(id, [index1, index2]) - } -} - - -/** - * 快速排序算法 Qucik Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-16 - */ -class QuickSort extends Sort { - /** - * step - * - * 1. 首先设定一个分界值,通过该分界值将数组分成左右两部分 - * 2. 将大于或等于分界值的数据集中到数组右边,小于的集中到左边 - * 3. 左右两侧的数组分别重复步骤 2. - * 4. 完成排序 - */ - - info() { - return { - name: "快速排序算法", - enName: "Quick Sort", - available: true - } - } - - /* - 每个(未排序)的部分 - 将第一个元素设为 pivot - 存储索引 = pivot索引 +1 - 从 i=pivot指数 +1 到 最右索引 的遍历 - 如果 a[i] < a[pivot] - 交换 (i, 存储索引); 存储索引++; - 交换(pivot, 存储索引 - 1) - */ - sort(array, left, right) { - if (typeof (left) === "undefined") left = 0 - if (typeof (right) === "undefined") right = array.length - 1 - - if (right <= left) - return array // 递归出口 - - const pivot = left // 第一个元素的索引 - let p = left + 1 - for (let i = left + 1; i <= right; i++) { - if (array[i] < array[pivot]) { - this.swap(array, i, p) - p++ - } - } - this.swap(array, pivot, p - 1) - - // 左、右分别排序(索引超限在 sort 内部进行处理,此处无需判断) - this.sort(array, pivot, p - 2) - this.sort(array, p, right) - return array - } - - sortWithAnimation(customAttr, array, left, right) { - if (typeof (left) === "undefined") left = 0 - if (typeof (right) === "undefined") right = array.length - 1 - - if (right <= left) - return array // 递归出口 - - const pivot = left // 第一个元素的索引 - let p = left + 1 - for (let i = left + 1; i <= right; i++) { - animation.compareLinkedListItems(customAttr.id, i, pivot) - if (array[i] < array[pivot]) { - this.swap(array, i, p) - this.swapAnimation(customAttr.id, i, p) - p++ - } - } - this.swap(array, pivot, p - 1) - this.swapAnimation(customAttr.id, pivot, p - 1) - - // 左、右分别排序(索引超限在 sort 内部进行处理,此处无需判断) - this.sortWithAnimation(customAttr, array, pivot, p - 2) - this.sortWithAnimation(customAttr, array, p, right) - return array - } -} - - -/** - * 冒泡排序算法 Bubble Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-16 - */ -class BubbleSort extends Sort { - - info() { - return { - name: "冒泡排序算法", - enName: "Bubble Sort", - available: true - } - } - - /* - 做 - swapped = false - 从 i = 1 到 最后一个没有排序过元素的索引-1 - 如果 左边元素 > 右边元素 - 交换(左边元素,右边元素) - swapped = true; ++swapCounter(交换计数器) - while swapped - */ - sort(array) { - let sortTime = 0 - let swapped - do { - swapped = false - for (let i = 1; i < array.length - sortTime; i++) { - if (array[i - 1] > array[i]) { - this.swap(array, i - 1, i) - swapped = true - } - } - sortTime++ - } while (swapped) - - return array - } - - sortWithAnimation(customAttr, array) { - let sortTime = 0 - let swapped - do { - swapped = false - for (let i = 1; i < array.length - sortTime; i++) { - animation.compareLinkedListItems(customAttr.id, i - 1, i) - if (array[i - 1] > array[i]) { - this.swap(array, i - 1, i) - this.swapAnimation(customAttr.id, i - 1, i) - swapped = true - } - } - sortTime++ - } while (swapped) - - return array - } -} - - -/** - * 选择排序算法 Selection Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-16 - */ -class SelectionSort extends Sort { - - info() { - return { - name: "选择排序算法", - enName: "Selection Sort", - available: true - } - } - - /* - 重复(元素个数-1)次 - 把第一个没有排序过的元素设置为最小值 - 遍历每个没有排序过的元素 - 如果元素 < 现在的最小值 - 将此元素设置成为新的最小值 - 将最小值和第一个没有排序过的位置交换 - */ - sort(array) { - let minIndex - for (let i = 0; i < array.length; i++) { - minIndex = i - for (let j = i + 1; j < array.length; j++) { - if (array[minIndex] > array[j]) { - this.swap(array, minIndex, j) - } - } - } - return array - } - - sortWithAnimation(customAttr, array) { - let minIndex - for (let i = 0; i < array.length; i++) { - // console.log(array) - minIndex = i - for (let j = i + 1; j < array.length; j++) { - animation.compareLinkedListItems(customAttr.id, minIndex, j) - if (array[minIndex] > array[j]) { - this.swap(array, minIndex, j) - this.swapAnimation(customAttr.id, minIndex, j) - } - } - } - return array - } -} - - -/** - * 插入排序算法 Insertion Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-18 - */ -class InsertionSort extends Sort { - - info() { - return { - name: "插入排序算法", - enName: "Insertion Sort", - available: true - } - } - - /* - 将第一个元素标记为已排序 - 对于每一个未排序的元素 X - “提取” 元素 X - i = 最后排序过元素的索引 到 0 的遍历 - 如果当前元素 j > X - 将排序过的元素向右移一格 - 跳出循环并在此插入 X - */ - sort(array) { - if (array.length == 0) - return array - for (let i = 1; i < array.length; i++) { - let X = array[i] - let j = i - 1 - while (array[j] >= 0 && array[j] > X) { - array[j + 1] = array[j] - j-- - } - array[j + 1] = X - } - return array - } - - sortWithAnimation(customAttr, array) { - if (array.length == 0) - return array - for (let i = 1; i < array.length; i++) { - let X = array[i] - animation.popupLinkedListItems(customAttr.id, i, {}) - let j = i - 1 - while (array[j] >= 0 && array[j] > X) { - array[j + 1] = array[j] - animation.exchangeLinkedListItems(customAttr.id, j + 1, j) - j-- - } - array[j + 1] = X - animation.popupLinkedListItems(customAttr.id, j + 1, { popBack: true }) - } - return array - } -} - - -/** - * 归并排序算法 Merge Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-19 - */ -class MergeSort extends Sort { - - info() { - return { - name: "归并排序算法", - enName: "Merge Sort", - } - } - - /* - 将每个元素拆分成大小为1的分区 - 递归地合并相邻的分区 - 遍历 i = 左侧首项位置 到 右侧末项位置 - 如果左侧首项的值 <= 右侧首项的值 - 拷贝左侧首项的值 - 否则: 拷贝右侧首项的值; 增加逆序数 - 将元素拷贝进原来的数组中 - */ - sort(array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } - - sortWithAnimation(customAttr, array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } -} - - -/** - * 随机快速排序算法 Random Quick Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-19 - */ -class RandomQuickSort extends Sort { - - info() { - return { - name: "随机快速排序算法", - enName: "Random Quick Sort", - } - } - - /* - 每个(未排序)的部分 - 随机选取 pivot,和第一个元素交换 - 存储索引 = pivot索引 +1 - 从 i=pivot指数 +1 到 最右索引 的遍历 - 如果 a[i] < a[pivot] - 交换 (i, 存储索引); 存储索引++; - 交换(pivot, 存储索引 - 1) - */ - sort(array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } - - sortWithAnimation(customAttr, array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } -} - - -/** - * 计数排序算法 Counting Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-19 - */ -class CountingSort extends Sort { - - info() { - return { - name: "计数排序算法", - enName: "Counting Sort", - } - } - - /* - 创建关键值(计数)数组 - 遍历数列中的每个元素 - 相应的计数器增加 1 - 每轮计数,都从最小的值开始 - 当计数为非零数时 - 重新将元素存储于列表 - 将计数减1 - */ - sort(array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } - - sortWithAnimation(customAttr, array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } -} - - -/** - * 基数排序算法 Radix Sort v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-19 - */ -class RadixSort extends Sort { - - info() { - return { - name: "基数排序算法", - enName: "Radix Sort", - } - } - - /* - 分别给每个数位(0到9)创造1个桶(数列),共计10个 - 遍历每个数位 - 遍历数列中的每个元素 - 将元素移至相应的桶中 - 在每个桶中,从最小的数位开始 - 当桶不是空的 - 将元素恢复至数列中 - */ - sort(array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } - - sortWithAnimation(customAttr, array) { - if (array.length == 0) - return array - // todo - console.log("尚未实现") - return array - } -} +/** + * 排序算法父类 Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-16 + */ + +function getSortClassList() { + return [ + QuickSort, + BubbleSort, + SelectionSort, + InsertionSort, + MergeSort, + RandomQuickSort, + CountingSort, + RadixSort, + ] +} + +class Sort { + static animation = null + constructor(animation) { + this.animation = animation + console.log(`初始化 ${this.info()['name']}`) + } + + info() { + return { + name: "未命名算法", + available: false + } + } + + sort(array) { + console.log("this method was not implement.") + return array + } + + doSortWithAnimation(elementId) { + let customAttr = document.getElementById(elementId).customAttr + let array = JSON.parse(JSON.stringify(customAttr.nodes)) // 深拷贝一个数组 + console.log(customAttr, array) + this.sortWithAnimation(customAttr, array) + } + + sortWithAnimation(array) { + console.log("this method was not implement.") + return array + } + + /** + * 交换数组中的两个元素 + * @param {*} array 数组 + * @param {*} index1 元素1索引 + * @param {*} index2 元素2索引 + */ + swap(array, index1, index2) { + if (index1 < 0 || index1 >= array.length) + throw new Error("index1索引超限") + if (index2 < 0 || index2 >= array.length) + throw new Error("index2索引超限") + if (index1 == index2) + return + + let tmp = array[index1] + array[index1] = array[index2] + array[index2] = tmp + + // only for test + // console.log(Object.keys(array)) + // console.log(array, index1, index2) + } + swapAnimation(id, index1, index2) { + if (index1 == index2) + return + animation.swapLinkedListItems(id, [index1, index2]) + } +} + + +/** + * 快速排序算法 Qucik Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-16 + */ +class QuickSort extends Sort { + /** + * step + * + * 1. 首先设定一个分界值,通过该分界值将数组分成左右两部分 + * 2. 将大于或等于分界值的数据集中到数组右边,小于的集中到左边 + * 3. 左右两侧的数组分别重复步骤 2. + * 4. 完成排序 + */ + + info() { + return { + name: "快速排序算法", + enName: "Quick Sort", + available: true + } + } + + /* + 每个(未排序)的部分 + 将第一个元素设为 pivot + 存储索引 = pivot索引 +1 + 从 i=pivot指数 +1 到 最右索引 的遍历 + 如果 a[i] < a[pivot] + 交换 (i, 存储索引); 存储索引++; + 交换(pivot, 存储索引 - 1) + */ + sort(array, left, right) { + if (typeof (left) === "undefined") left = 0 + if (typeof (right) === "undefined") right = array.length - 1 + + if (right <= left) + return array // 递归出口 + + const pivot = left // 第一个元素的索引 + let p = left + 1 + for (let i = left + 1; i <= right; i++) { + if (array[i] < array[pivot]) { + this.swap(array, i, p) + p++ + } + } + this.swap(array, pivot, p - 1) + + // 左、右分别排序(索引超限在 sort 内部进行处理,此处无需判断) + this.sort(array, pivot, p - 2) + this.sort(array, p, right) + return array + } + + sortWithAnimation(customAttr, array, left, right) { + if (typeof (left) === "undefined") left = 0 + if (typeof (right) === "undefined") right = array.length - 1 + + if (right <= left) + return array // 递归出口 + + const pivot = left // 第一个元素的索引 + let p = left + 1 + for (let i = left + 1; i <= right; i++) { + animation.compareLinkedListItems(customAttr.id, i, pivot) + if (array[i] < array[pivot]) { + this.swap(array, i, p) + this.swapAnimation(customAttr.id, i, p) + p++ + } + } + this.swap(array, pivot, p - 1) + this.swapAnimation(customAttr.id, pivot, p - 1) + + // 左、右分别排序(索引超限在 sort 内部进行处理,此处无需判断) + this.sortWithAnimation(customAttr, array, pivot, p - 2) + this.sortWithAnimation(customAttr, array, p, right) + return array + } +} + + +/** + * 冒泡排序算法 Bubble Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-16 + */ +class BubbleSort extends Sort { + + info() { + return { + name: "冒泡排序算法", + enName: "Bubble Sort", + available: true + } + } + + /* + 做 + swapped = false + 从 i = 1 到 最后一个没有排序过元素的索引-1 + 如果 左边元素 > 右边元素 + 交换(左边元素,右边元素) + swapped = true; ++swapCounter(交换计数器) + while swapped + */ + sort(array) { + let sortTime = 0 + let swapped + do { + swapped = false + for (let i = 1; i < array.length - sortTime; i++) { + if (array[i - 1] > array[i]) { + this.swap(array, i - 1, i) + swapped = true + } + } + sortTime++ + } while (swapped) + + return array + } + + sortWithAnimation(customAttr, array) { + let sortTime = 0 + let swapped + do { + swapped = false + for (let i = 1; i < array.length - sortTime; i++) { + animation.compareLinkedListItems(customAttr.id, i - 1, i) + if (array[i - 1] > array[i]) { + this.swap(array, i - 1, i) + this.swapAnimation(customAttr.id, i - 1, i) + swapped = true + } + } + sortTime++ + } while (swapped) + + return array + } +} + + +/** + * 选择排序算法 Selection Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-16 + */ +class SelectionSort extends Sort { + + info() { + return { + name: "选择排序算法", + enName: "Selection Sort", + available: true + } + } + + /* + 重复(元素个数-1)次 + 把第一个没有排序过的元素设置为最小值 + 遍历每个没有排序过的元素 + 如果元素 < 现在的最小值 + 将此元素设置成为新的最小值 + 将最小值和第一个没有排序过的位置交换 + */ + sort(array) { + let minIndex + for (let i = 0; i < array.length; i++) { + minIndex = i + for (let j = i + 1; j < array.length; j++) { + if (array[minIndex] > array[j]) { + this.swap(array, minIndex, j) + } + } + } + return array + } + + sortWithAnimation(customAttr, array) { + let minIndex + for (let i = 0; i < array.length; i++) { + // console.log(array) + minIndex = i + for (let j = i + 1; j < array.length; j++) { + animation.compareLinkedListItems(customAttr.id, minIndex, j) + if (array[minIndex] > array[j]) { + this.swap(array, minIndex, j) + this.swapAnimation(customAttr.id, minIndex, j) + } + } + } + return array + } +} + + +/** + * 插入排序算法 Insertion Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-18 + */ +class InsertionSort extends Sort { + + info() { + return { + name: "插入排序算法", + enName: "Insertion Sort", + available: true + } + } + + /* + 将第一个元素标记为已排序 + 对于每一个未排序的元素 X + “提取” 元素 X + i = 最后排序过元素的索引 到 0 的遍历 + 如果当前元素 j > X + 将排序过的元素向右移一格 + 跳出循环并在此插入 X + */ + sort(array) { + if (array.length == 0) + return array + for (let i = 1; i < array.length; i++) { + let X = array[i] + let j = i - 1 + while (array[j] >= 0 && array[j] > X) { + array[j + 1] = array[j] + j-- + } + array[j + 1] = X + } + return array + } + + sortWithAnimation(customAttr, array) { + if (array.length == 0) + return array + for (let i = 1; i < array.length; i++) { + let X = array[i] + animation.popupLinkedListItems(customAttr.id, i, {}) + let j = i - 1 + while (array[j] >= 0 && array[j] > X) { + array[j + 1] = array[j] + animation.exchangeLinkedListItems(customAttr.id, j + 1, j) + j-- + } + array[j + 1] = X + animation.popupLinkedListItems(customAttr.id, j + 1, { popBack: true }) + } + return array + } +} + + +/** + * 归并排序算法 Merge Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-19 + */ +class MergeSort extends Sort { + + info() { + return { + name: "归并排序算法", + enName: "Merge Sort", + available: true + } + } + + /* + 将每个元素拆分成大小为1的分区 + 递归地合并相邻的分区 + 遍历 i = 左侧首项位置 到 右侧末项位置 + 如果左侧首项的值 <= 右侧首项的值 + 拷贝左侧首项的值 + 否则: 拷贝右侧首项的值; 增加逆序数 + 将元素拷贝进原来的数组中 + */ + sort(array) { + if (array.length <= 1) + return array + let middle = Math.floor(array.length / 2) + let left = this.sort(array.slice(0, middle)) + let right = this.sort(array.slice(middle)) + let result = [] + let i = 0, j = 0 + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) { + result.push(left[i]) + i++ + } else { + result.push(right[j]) + j++ + // this.reverseCount++ + } + } + while (i < left.length) { + result.push(left[i]) + i++ + } + while (j < right.length) { + result.push(right[j]) + j++ + } + array = result + return array + } + + sortWithAnimation(customAttr, array, arrayFirstElementIndex = 0) { + if (array.length <= 1) { + return array + } + console.log("array", array); + + let middle = Math.floor(array.length / 2) + let left = this.sortWithAnimation(customAttr, array.slice(0, middle), arrayFirstElementIndex) + let right = this.sortWithAnimation(customAttr, array.slice(middle), arrayFirstElementIndex + middle) + let result = [] + let i = 0, j = 0 + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) { + result.push(left[i]) + animation.popupLinkedListItemToNewPosition(customAttr.id, arrayFirstElementIndex + i) + i++ + } else { + result.push(right[j]) + animation.popupLinkedListItemToNewPosition(customAttr.id, arrayFirstElementIndex + j) + j++ + // this.reverseCount++ + } + } + while (i < left.length) { + result.push(left[i]) + animation.popupLinkedListItemToNewPosition(customAttr.id, arrayFirstElementIndex + i) + i++ + } + while (j < right.length) { + result.push(right[j]) + animation.popupLinkedListItemToNewPosition(customAttr.id, arrayFirstElementIndex + j) + j++ + } + return result + } +} + + +/** + * 随机快速排序算法 Random Quick Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-19 + */ +class RandomQuickSort extends Sort { + + info() { + return { + name: "随机快速排序算法", + enName: "Random Quick Sort", + } + } + + /* + 每个(未排序)的部分 + 随机选取 pivot,和第一个元素交换 + 存储索引 = pivot索引 +1 + 从 i=pivot指数 +1 到 最右索引 的遍历 + 如果 a[i] < a[pivot] + 交换 (i, 存储索引); 存储索引++; + 交换(pivot, 存储索引 - 1) + */ + sort(array) { + if (array.length == 0) + return array + // todo + console.log("尚未实现") + return array + } + + sortWithAnimation(customAttr, array) { + if (array.length == 0) + return array + // todo + console.log("尚未实现") + return array + } +} + + +/** + * 计数排序算法 Counting Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-19 + */ +class CountingSort extends Sort { + + info() { + return { + name: "计数排序算法", + enName: "Counting Sort", + } + } + + /* + 创建关键值(计数)数组 + 遍历数列中的每个元素 + 相应的计数器增加 1 + 每轮计数,都从最小的值开始 + 当计数为非零数时 + 重新将元素存储于列表 + 将计数减1 + */ + sort(array) { + if (array.length == 0) + return array + // todo + console.log("尚未实现") + return array + } + + sortWithAnimation(customAttr, array) { + if (array.length == 0) + return array + // todo + console.log("尚未实现") + return array + } +} + + +/** + * 基数排序算法 Radix Sort v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-19 + */ +class RadixSort extends Sort { + + info() { + return { + name: "基数排序算法", + enName: "Radix Sort", + } + } + + /* + 分别给每个数位(0到9)创造1个桶(数列),共计10个 + 遍历每个数位 + 遍历数列中的每个元素 + 将元素移至相应的桶中 + 在每个桶中,从最小的数位开始 + 当桶不是空的 + 将元素恢复至数列中 + */ + sort(array) { + if (array.length == 0) + return array + // todo + console.log("尚未实现") + return array + } + + sortWithAnimation(customAttr, array) { + if (array.length == 0) + return array + // todo + console.log("尚未实现") + return array + } +} diff --git a/src/assets/js/algorithm/test.js b/src/assets/js/algorithm/test.js index fff7d0e..96f2fe4 100644 --- a/src/assets/js/algorithm/test.js +++ b/src/assets/js/algorithm/test.js @@ -1,72 +1,73 @@ - -/** - * 排序算法测试 - */ - -/** - * 创建一个随机数数组 - * @returns - */ -function getRandomData(count = 20) { - var randomData = [] - for (let i = 0; i < count; i++) // 生成随机数 - randomData.push(Math.ceil(Math.random() * 100 - 50)) - return randomData -} -var data = [ - [], - [-1], - [1, 2], - [1, 2, 3, 4, 5, 6, 7, 8], - [8, 7, 6, 5, 4, 3, 2, 1], - [1, 1, 1, 1, 1, 1, 1], - [5, 8, 7, 4, 3, 1, 6, 2, 6, 5], - [47, 11, 50, 13, 16, 49, 8, 9, 38, 27, 20], - // getRandomData(6), -] -var check = [ - [], - [-1], - [1, 2], - [1, 2, 3, 4, 5, 6, 7, 8], - [1, 2, 3, 4, 5, 6, 7, 8], - [1, 1, 1, 1, 1, 1, 1], - [1, 2, 3, 4, 5, 5, 6, 6, 7, 8], - [8, 9, 11, 13, 16, 20, 27, 38, 47, 49, 50], - // null -] - -var sortAlgorithm = { - quicksort: new QuickSort(animation), - bubblesort: new BubbleSort(animation), - selectionSort: new SelectionSort(animation), - insertionSort: new InsertionSort(animation), -} - -// 遍历每一种算法 -Object.values(sortAlgorithm).forEach(sortAlgo => { - // 输出调试信息 - console.log("算法信息", sortAlgo.info()) - data.forEach(element => { - // 将数组元素进行深拷贝 - var input = JSON.parse(JSON.stringify(element)) - // 进行排序 - var result = sortAlgo.sort(input) - console.log("before", element, "after", result) - // 与结果进行对比,判断是否正确 - if (data.indexOf(element) > -1) { - var rightSortResult = check[data.indexOf(element)]; - if (rightSortResult) { - if (JSON.stringify(rightSortResult) !== JSON.stringify(result)) { - console.warn("👆结果不匹配!正确结果为", rightSortResult) - } else { - console.info("👆正确") - } - } else { - console.log("👆缺少正确答案,跳过") - } - } - }); - // 输出一个空行,便于观察 - console.log("-----------------------------------------------") -}); + +/** + * 排序算法测试 + */ + +/** + * 创建一个随机数数组 + * @returns + */ +function getRandomData(count = 20) { + var randomData = [] + for (let i = 0; i < count; i++) // 生成随机数 + randomData.push(Math.ceil(Math.random() * 100 - 50)) + return randomData +} +var data = [ + [], + [-1], + [1, 2], + [1, 2, 3, 4, 5, 6, 7, 8], + [8, 7, 6, 5, 4, 3, 2, 1], + [1, 1, 1, 1, 1, 1, 1], + [5, 8, 7, 4, 3, 1, 6, 2, 6, 5], + [47, 11, 50, 13, 16, 49, 8, 9, 38, 27, 20], + // getRandomData(6), +] +var check = [ + [], + [-1], + [1, 2], + [1, 2, 3, 4, 5, 6, 7, 8], + [1, 2, 3, 4, 5, 6, 7, 8], + [1, 1, 1, 1, 1, 1, 1], + [1, 2, 3, 4, 5, 5, 6, 6, 7, 8], + [8, 9, 11, 13, 16, 20, 27, 38, 47, 49, 50], + // null +] + +var sortAlgorithm = { + quicksort: new QuickSort(animation), + bubblesort: new BubbleSort(animation), + selectionSort: new SelectionSort(animation), + insertionSort: new InsertionSort(animation), + mergeSort: new MergeSort(animation), +} + +// 遍历每一种算法 +Object.values(sortAlgorithm).forEach(sortAlgo => { + // 输出调试信息 + console.log("算法信息", sortAlgo.info()) + data.forEach(element => { + // 将数组元素进行深拷贝 + var input = JSON.parse(JSON.stringify(element)) + // 进行排序 + var result = sortAlgo.sort(input) + console.log("before", element, "after", result) + // 与结果进行对比,判断是否正确 + if (data.indexOf(element) > -1) { + var rightSortResult = check[data.indexOf(element)]; + if (rightSortResult) { + if (JSON.stringify(rightSortResult) !== JSON.stringify(result)) { + console.warn("👆结果不匹配!正确结果为", rightSortResult) + } else { + console.info("👆正确") + } + } else { + console.log("👆缺少正确答案,跳过") + } + } + }); + // 输出一个空行,便于观察 + console.log("-----------------------------------------------") +}); diff --git a/src/assets/js/class.js b/src/assets/js/class.js index cbe7d51..268f70d 100644 --- a/src/assets/js/class.js +++ b/src/assets/js/class.js @@ -1,647 +1,695 @@ -/** - * WorkSpace class v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-15 - * - * Released under the MIT license - */ -class WorkSpace { - static settings = null; - static primaryCanvas = null; - - constructor(settings) { - this.settings = settings - settings.workSpace = this - - // 清除原有内容 - settings.container - .style("width", settings.width) - .style("height", settings.height) - // .attr("width", settings.width) - // .attr("height", settings.height) - .html("") - - // 创建工作区SVG - this.primaryCanvas = settings.container.append("svg") - // 添加id - .attr("id", "primaryCanvas") - // 设置 SVG 宽高 - .attr("width", "100%") - .attr("height", "100%") - // 背景色 - .style("background-color", settings.colorMap["background"]) - } -} - -class ViBase { - static workSpace = null; - constructor(workSpace) { - this.workSpace = workSpace - } -} -class ArrayVi extends ViBase { - // [47, 11, 50, 13, 16, 49, 8, 9, 38, 27, 20] - // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] - static listData - - // 网页加载完毕初始化事件 - initialize({ elementId }) { - let controlDiv = document.getElementById("control-div") - controlDiv.style.textAlign = "center" - - var sortClassList = getSortClassList(); - console.log(sortClassList); - var DOMFragment = document.createDocumentFragment() - - var selector = document.createElement("select") - for (let i = 0; i < sortClassList.length; i++) { - const sortClass = new sortClassList[i](animation) - const sortClassInfo = sortClass.info() - - // 跳过未完成的算法 - if (!sortClassInfo['available']) - continue - - let option = document.createElement("option") - option.value = sortClassInfo['name'] - option.innerText = sortClassInfo['name'] - selector.appendChild(option) - } - DOMFragment.appendChild(selector) - - let ctrlBtn = document.createElement("button") - ctrlBtn.innerHTML = "开始排序" - let that = this - ctrlBtn.onclick = function () { - // 点击排序算法按钮 - // if (!that.updateListDataArray(elementId, { doNotAlert: false })) - // return - if (!that.listData || that.listData.length == 0) { - alert("数组为空") - return - } - - // 隐藏一些东西,显示一些东西 - controlDiv.style.display = 'none' - d3.select("#console-div") - .style("display", "") - d3.select("#console-current-algorithm") - .style("text-align", "center") - .html(selector.value/*sortClassInfo['name']*/) - - // 找到对应的算法,然后开始排序 - for (let i = 0; i < sortClassList.length; i++) { - const sortClass = new sortClassList[i](animation) - const sortClassInfo = sortClass.info() - - if (sortClassInfo['name'] === selector.value) { - sortClass.doSortWithAnimation(elementId) - break - } - } - } - DOMFragment.appendChild(ctrlBtn) - - // 页面最后更新时间 - var lastModifiedTime = (new Date(document.lastModified).getTime() / 1000).toFixed(0) - let lastModifiedDiv = document.createElement("div") - lastModifiedDiv.style.fontSize = "x-small" - lastModifiedDiv.style.opacity = ".5" - lastModifiedDiv.innerHTML = "页面版本戳: " + lastModifiedTime - DOMFragment.appendChild(lastModifiedDiv) - - controlDiv.appendChild(DOMFragment) - - // 生成一个随机数组 - this.randomListDataArray(elementId) - - // 显示 siderbar - d3.select("#sidebar").style("display", "") - } - - // 将数组显示到输入框中 - updateListDataInput() { - document.getElementById("array-input").value = this.listData.join(",") - } - - // Array 内容改变时 - updateListDataArray(elementId, { doNotAlert = false }) { - var val = document.getElementById("array-input").value - try { - var preList = val.replaceAll(",", ",").split(",") - this.listData = [] - preList.forEach(element => { - if (element.trim() === "" || isNaN(element)) - return - this.listData.push(Number(element)) - }); - arrayVi.initArray(elementId, this.listData) - } catch (err) { - console.log(err) - if (!doNotAlert) - alert("输入不正确,请检查!") - return false - } - return true - } - - // 随机 Array - randomListDataArray(elementId) { - function getRandom(length) { - return Math.floor(Math.random() * length); // 可均衡获取 0 到 length-1 的随机整数。 - } - // 获取一个 6 - 12 以内的随机数 - var len = 6 + getRandom(13 - 6) - - this.listData = [] - for (let i = 0; i < len; i++) { - this.listData.push(getRandom(51)) - } - this.updateListDataInput() - this.updateListDataArray(elementId, { doNotAlert: false }) - } - - // 绘制数组 - initArray(elementId, listData) { - console.log("initArray") - let fragment = shape.getLinkedListFragment(elementId, listData, { - x: 100, - y: 100, - width: "100px", - height: "100px", - }) - - // console.log(fragment) - workSpace.primaryCanvas.html("") - - // 添加水印 居中 - var watermarkWidth = settings.outerSize.height * 0.65 - shape.addWatermark(elementId, { - imageSrc: "./assets/image/logo-small.svg", - }) - .attr('id', 'watermark-c-c') - .attr('x', settings.outerSize.width / 2) - .attr('y', settings.outerSize.height / 2) - .style('width', watermarkWidth + 'px') - .style('height', watermarkWidth + 'px') - .style('transform', `translate(-${watermarkWidth / 2}px, -${watermarkWidth / 2}px)`) - .style('opacity', '0.015') - .style('transition', '0.2s') - - // 添加水印 右下角 - var watermarkWidth = 60 - shape.addWatermark(elementId, { - imageSrc: "./assets/image/logo-small.svg", - }) - .attr('id', 'watermark-r-b') - .attr('x', settings.outerSize.width) - .attr('y', settings.outerSize.height) - .style('width', watermarkWidth + 'px') - .style('height', watermarkWidth + 'px') - .style('transform', 'translate(-80px, -80px)') - - workSpace.primaryCanvas.node().appendChild(fragment) - document.getElementById(elementId).customAttr = fragment.customAttr - } -} - -/** - * Shape class v0.1.0 - * - * @author coder-xiaomo - * @date 2022-05-15 - * - * Released under the MIT license - */ -class Shape { - static workSpace = null; - constructor(workSpace) { - this.workSpace = workSpace - } - - addShape(shape, id) { - var settings = this.workSpace.settings - return workSpace.primaryCanvas.append(shape) - .style("transform", `translate(${settings.margin.left}px, ${settings.margin.top}px)`) - .attr("id", id) - } - addShape_NoTransform(shape, id) { - return workSpace.primaryCanvas.append(shape) - .attr("id", id) - } - - // 添加矩形 - rectangle(id, { x, y, width, height, fillColor = "white", strokeColor = "black" }) { - return this.addShape("rect", id) - .attr("x", x) - .attr("y", y) - .attr("width", width) - .attr("height", height) - .style("fill", fillColor) - .style("stroke", strokeColor) - } - - // 添加圆形 - circle(id, { x, y, radius, fillColor = "white", strokeColor = "black" }) { - return this.addShape("circle", id) - .attr("cx", x) - .attr("cy", y) - .attr("r", radius) - .style("fill", fillColor) - .style("stroke", strokeColor) - } - - // 添加文本 - text(id, { x, y, text, fillColor }) { - return this.addShape("text", id) - .attr("x", x) - .attr("y", y) - .text(text) - .style("fill", fillColor) - } - - // 添加线 - line(id, { x1, y1, x2, y2, strokeColor }) { - return this.addShape("line", id) - .attr("x1", x1) - .attr("y1", y1) - .attr("x2", x2) - .attr("y2", y2) - .style("stroke", strokeColor) - } - - // 添加路径 - path(id, d, { fillColor = "white", strokeColor = "black" }) { - return this.addShape("path", id) - .attr("d", d) - .style("fill", fillColor) - .style("stroke", strokeColor) - } - - // 添加坐标轴 - axis(id, { transform, axis }) { - return this.addShape_NoTransform("g", id) - .attr("transform", transform) - .call(axis) - } - - // 添加一个链表节点 - addNode(id, x, y, width, height, text) { - var primaryCanvas = workSpace.primaryCanvas - primaryCanvas.append("rect", id) - .attr("x", x) - .attr("y", y) - .attr("width", width) - .attr("height", height) - .style("fill", workSpace.settings.colorMap["fill"]) - .style("stroke", workSpace.settings.colorMap["stroke"]) - primaryCanvas.append("text", id + "_text") - .attr("x", x) - .attr("y", y) - // .attr("x", x + width / 2) - // .attr("y", y + height / 2) - .text(text) - .style("fill", workSpace.settings.colorMap["text"]) - } - - - // 添加一个图片水印 - addWatermark(id, { imageSrc }) { - var primaryCanvas = workSpace.primaryCanvas - return primaryCanvas.append("image", id) - .attr("xlink:href", imageSrc) - } - - // 绘制一个链表 - getLinkedListFragment(id, nodes) { - var settings = this.workSpace.settings - - let displayMaxWidth = 1 * settings.innerSize.width - let displayMaxHeight = 1 * settings.innerSize.height - let areaWidth = 1 * nodes.length // 按照1个Unit来计算 - let areaHeight = 1 // 按照1个Unit来计算 - - let oneUnit = 100 - // 可以假设高度相等比较宽度,这样好理解 - if (displayMaxWidth / displayMaxHeight > areaWidth / areaHeight) { - // 展示区域左右有多余空间 - oneUnit = displayMaxHeight / areaHeight - } else { - // 展示区域上下有空间(或刚刚好) - oneUnit = displayMaxWidth / areaWidth - } - - // 定义最大值 - if (oneUnit > 120) oneUnit = 120 - - let fragment = document.createDocumentFragment() - fragment.customAttr = { - id: id, - nodes: nodes, - type: "linkedList", - oneUnit: oneUnit, - gsapTimeline: gsap.timeline({ - onStart: function () { - consoleClear() - }, - onComplete: function () { - consoleLog(`排序完成`) - console.log("all done") - // this.seek(0) - } - }) - } - // console.log(fragment.customAttr) - - // 元素不能设置 width 和 height - - let g = d3.select(fragment) - .append("svg:g") - .attr("id", id) - .attr("fill", "white") - .attr("transform", `translate(${settings.margin.left}, ${settings.margin.top})`) - - for (let i = 0; i < nodes.length; i++) { - let node = nodes[i] - let _g = g.append("svg:g") - - _g.append("svg:rect") - .attr("x", i * oneUnit) - .attr("y", 0) - .attr("width", oneUnit) - .attr("height", oneUnit) - .attr("fill", settings.colorMap["fill"]) - .attr("stroke", settings.colorMap["stroke"]) - - _g.append("svg:text") - .text(node) - .attr("x", i * oneUnit + oneUnit / 2) - .attr("y", oneUnit / 2) - .attr("width", oneUnit) - .attr("height", oneUnit) - .attr("fill", settings.colorMap["text"]) - - // 调试用 - if (settings.debugMode) - _g.append("svg:text") - .text(i) - .attr("x", i * oneUnit) - .attr("y", oneUnit) - .attr("width", oneUnit) - .attr("height", oneUnit) - .attr("fill", "black") - - // console.log(text.node().getBBox()) - // console.log(text.node().getBoundingClientRect()) - } - - // g.append("svg:rect") - // .attr("width", oneUnit * nodes.length) - // .attr("height", oneUnit) - // .style("fill", "none") - // .style("stroke", "green") - return fragment - } -} - -class VectorAnimation { - constructor(workSpace) { - this.workSpace = workSpace - } - - swapElementAttr(attrName/* or attrNameList */, element1, element2) { - function exchange(attrName) { - // 保存 element1 的属性,将 element2 的属性赋值给 element1, 再将保存的属性赋值给 element2 - var tmp = element1.getAttribute(attrName) - element1.setAttribute(attrName, element2.getAttribute(attrName)) - element2.setAttribute(attrName, tmp) - } - if (typeof attrName === "string") { - exchange(attrName) - } else if (typeof attrName === "object") { - for (let i = 0; i < attrName.length; i++) { - exchange(attrName[i]) - } - } - } - - swapElementInnerHTML(element1, element2) { - var tmp = element1.innerHTML - element1.innerHTML = element2.innerHTML - element2.innerHTML = tmp - } - - swapElementsAttr(elementPairList) { - // [ - // [attrName or attrNameList, element1, element2], - // [attrName or attrNameList, element1, element2], - // ... - // ] - for (let i = 0; i < elementPairList.length; i++) { - let elementPair = elementPairList[i] - this.swapElementAttr(elementPair[0], elementPair[1], elementPair[2]) - } - } - - // 交换数组元素 - swapLinkedListItems(id, [fromIndex, toIndex]) { - if (fromIndex < toIndex) - [fromIndex, toIndex] = [toIndex, fromIndex] - - var settings = this.workSpace.settings - let linkedList = document.getElementById(id) - let customAttr = linkedList.customAttr - // console.log(customAttr) - - var gList = linkedList.childNodes - - let from = gList[fromIndex] - let to = gList[toIndex] - - var deltaX = customAttr.oneUnit * (fromIndex - toIndex); - var deltaY = customAttr.oneUnit * 1.08 - - var animateSettings = this.workSpace.settings.animation.getConf() - - // 如果是相邻的两个元素交换,或者要交换的两个元素是同一个元素 - if (Math.abs(fromIndex - toIndex) <= 1) { - deltaY /= 2 - animateSettings.duration *= 0.6 - } - - var that = this - let timeline = gsap.timeline({ - onStart: function () { - displayCurrentArray(customAttr.nodes) - - consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`) - }, - onComplete: function () { - // 交换DOM元素中的值 - that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1]) - - // 交换 node 列表中的值 - // console.log(customAttr.nodes) - let tmp = customAttr.nodes[fromIndex] - customAttr.nodes[fromIndex] = customAttr.nodes[toIndex] - customAttr.nodes[toIndex] = tmp - - // console.log(customAttr.nodes) - displayCurrentArray(customAttr.nodes) - - console.log("animation done (swap)") - } - }).add([ - gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }), - gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }), - gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }), - gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }), - gsap.to(from.childNodes, { ...animateSettings, y: -deltaY }), - gsap.to(to.childNodes, { ...animateSettings, y: deltaY }), - ]).add([ - gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }), - gsap.to(to.childNodes, { ...animateSettings, x: deltaX }), - ]).add([ - gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }), - gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }), - gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }), - gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }), - gsap.to(from.childNodes, { ...animateSettings, y: 0 }), - gsap.to(to.childNodes, { ...animateSettings, y: 0 }), - ]).add([ - // 恢复到动画之前的状态 - gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }), - gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }), - ]) - - customAttr.gsapTimeline.add(timeline) - } - - // 高亮数组元素 - highlightLinkedListItems(id, indexList, onStartCallback) { - let linkedList = document.getElementById(id) - let customAttr = linkedList.customAttr - var gList = linkedList.childNodes - - if (typeof (indexList) === "number") - indexList = [indexList] - - var hightlightElementList_fill = [] - var hightlightElementList_text = [] - for (let i = 0; i < indexList.length; i++) { - const index = indexList[i]; - hightlightElementList_fill.push(gList[index].childNodes[0]) - hightlightElementList_text.push(gList[index].childNodes[1]) - } - var animateSettings = this.workSpace.settings.animation.getConf() - - let timeline = gsap.timeline({ - onStart: function () { - displayCurrentArray(customAttr.nodes) - - if (onStartCallback) - onStartCallback() - }, - onComplete: function () { - console.log("animation done (hightlight)") - } - }).add([ - gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill_focus"] }), - gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text_focus"] }), - ]).add([ - gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill"] }), - gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text"] }), - ]) - - customAttr.gsapTimeline.add(timeline) - } - - // 比较数组元素 - compareLinkedListItems(id, index1, index2) { - this.highlightLinkedListItems(id, [index1, index2], function () { - consoleLog(`比较索引为 ${index1} 和 ${index2} 的两个元素`) - }) - } - - // 弹出/弹回 数组元素 - popupLinkedListItems(id, index, { popBack = false }) { - let linkedList = document.getElementById(id) - let customAttr = linkedList.customAttr - var gList = linkedList.childNodes - - var deltaY = customAttr.oneUnit * 1.08 - if (popBack) deltaY = 0 - - let timeline = gsap.timeline({ - onStart: function () { - if (typeof (onStartCallback) === "function") - onStartCallback() - }, - onComplete: function () { - console.log("animation done (popup)") - } - }).add([ - gsap.to(gList[index], { ...this.workSpace.settings.animation.getConf(), y: deltaY }), - ]) - - customAttr.gsapTimeline.add(timeline) - } - - // 交换相邻数组元素位置(仅在水平方向呼唤,垂直方向不做调整) 【fromIndex 是y方向上突出的元素】 - exchangeLinkedListItems(id, fromIndex, toIndex) { - if (fromIndex < toIndex) - [fromIndex, toIndex] = [toIndex, fromIndex] - - var settings = this.workSpace.settings - let linkedList = document.getElementById(id) - let customAttr = linkedList.customAttr - // console.log(customAttr) - - var gList = linkedList.childNodes - - let from = gList[fromIndex] - let to = gList[toIndex] - - var deltaX = customAttr.oneUnit * (fromIndex - toIndex); - - var deltaY = customAttr.oneUnit * 1.08 // 要跟 popupLinkedListItems 函数中的 deltaY 完全一致 - var animateSettings = settings.animation.getConf() - - var that = this - let timeline = gsap.timeline({ - onStart: function () { - displayCurrentArray(customAttr.nodes) - - consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`) - }, - onComplete: function () { - // 交换DOM元素中的值 - that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1]) - - // 交换 node 列表中的值 - // console.log(customAttr.nodes) - let tmp = customAttr.nodes[fromIndex] - customAttr.nodes[fromIndex] = customAttr.nodes[toIndex] - customAttr.nodes[toIndex] = tmp - - // console.log(customAttr.nodes) - displayCurrentArray(customAttr.nodes) - - console.log("animation done (exchange)") - } - }).add([ - gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }), - gsap.to(to.childNodes, { ...animateSettings, x: deltaX }), - ]).add([ - // 恢复到动画之前的状态 - gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0 }), - gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0 }), - // 由于两个元素一个突出,一个不突出,所以还要交换两个y - gsap.to(from, { ...animateSettings, duration: 0, y: 0 }), - gsap.to(to, { ...animateSettings, duration: 0, y: deltaY }), - ]) - - customAttr.gsapTimeline.add(timeline) - } +/** + * WorkSpace class v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-15 + * + * Released under the MIT license + */ +class WorkSpace { + static settings = null; + static primaryCanvas = null; + + constructor(settings) { + this.settings = settings + settings.workSpace = this + + // 清除原有内容 + settings.container + .style("width", settings.width) + .style("height", settings.height) + // .attr("width", settings.width) + // .attr("height", settings.height) + .html("") + + // 创建工作区SVG + this.primaryCanvas = settings.container.append("svg") + // 添加id + .attr("id", "primaryCanvas") + // 设置 SVG 宽高 + .attr("width", "100%") + .attr("height", "100%") + // 背景色 + .style("background-color", settings.colorMap["background"]) + } +} + +class ViBase { + static workSpace = null; + constructor(workSpace) { + this.workSpace = workSpace + } +} +class ArrayVi extends ViBase { + // [47, 11, 50, 13, 16, 49, 8, 9, 38, 27, 20] + // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + static listData + + // 网页加载完毕初始化事件 + initialize({ elementId }) { + let controlDiv = document.getElementById("control-div") + controlDiv.style.textAlign = "center" + + var sortClassList = getSortClassList(); + console.log(sortClassList); + var DOMFragment = document.createDocumentFragment() + + var selector = document.createElement("select") + for (let i = 0; i < sortClassList.length; i++) { + const sortClass = new sortClassList[i](animation) + const sortClassInfo = sortClass.info() + + // 跳过未完成的算法 + if (!sortClassInfo['available']) + continue + + let option = document.createElement("option") + option.value = sortClassInfo['name'] + option.innerText = sortClassInfo['name'] + selector.appendChild(option) + } + DOMFragment.appendChild(selector) + + let ctrlBtn = document.createElement("button") + ctrlBtn.innerHTML = "开始排序" + let that = this + ctrlBtn.onclick = function () { + // 点击排序算法按钮 + // if (!that.updateListDataArray(elementId, { doNotAlert: false })) + // return + if (!that.listData || that.listData.length == 0) { + alert("数组为空") + return + } + + // 隐藏一些东西,显示一些东西 + controlDiv.style.display = 'none' + d3.select("#console-div") + .style("display", "") + d3.select("#console-current-algorithm") + .style("text-align", "center") + .html(selector.value/*sortClassInfo['name']*/) + + // 找到对应的算法,然后开始排序 + for (let i = 0; i < sortClassList.length; i++) { + const sortClass = new sortClassList[i](animation) + const sortClassInfo = sortClass.info() + + if (sortClassInfo['name'] === selector.value) { + sortClass.doSortWithAnimation(elementId) + break + } + } + } + DOMFragment.appendChild(ctrlBtn) + + // 页面最后更新时间 + var lastModifiedTime = (new Date(document.lastModified).getTime() / 1000).toFixed(0) + let lastModifiedDiv = document.createElement("div") + lastModifiedDiv.style.fontSize = "x-small" + lastModifiedDiv.style.opacity = ".5" + lastModifiedDiv.innerHTML = "页面版本戳: " + lastModifiedTime + DOMFragment.appendChild(lastModifiedDiv) + + controlDiv.appendChild(DOMFragment) + + // 生成一个随机数组 + this.randomListDataArray(elementId) + + // 显示 siderbar + d3.select("#sidebar").style("display", "") + } + + // 将数组显示到输入框中 + updateListDataInput() { + document.getElementById("array-input").value = this.listData.join(",") + } + + // Array 内容改变时 + updateListDataArray(elementId, { doNotAlert = false }) { + var val = document.getElementById("array-input").value + try { + var preList = val.replaceAll(",", ",").split(",") + this.listData = [] + preList.forEach(element => { + if (element.trim() === "" || isNaN(element)) + return + this.listData.push(Number(element)) + }); + arrayVi.initArray(elementId, this.listData) + } catch (err) { + console.log(err) + if (!doNotAlert) + alert("输入不正确,请检查!") + return false + } + return true + } + + // 随机 Array + randomListDataArray(elementId) { + function getRandom(length) { + return Math.floor(Math.random() * length); // 可均衡获取 0 到 length-1 的随机整数。 + } + // 获取一个 6 - 12 以内的随机数 + var len = 6 + getRandom(13 - 6) + + this.listData = [] + for (let i = 0; i < len; i++) { + this.listData.push(getRandom(51)) + } + this.updateListDataInput() + this.updateListDataArray(elementId, { doNotAlert: false }) + } + + // 绘制数组 + initArray(elementId, listData) { + console.log("initArray") + let fragment = shape.getLinkedListFragment(elementId, listData, { + x: 100, + y: 100, + width: "100px", + height: "100px", + }) + + // console.log(fragment) + workSpace.primaryCanvas.html("") + + // 添加水印 居中 + var watermarkWidth = settings.outerSize.height * 0.65 + shape.addWatermark(elementId, { + imageSrc: "./assets/image/logo-small.svg", + }) + .attr('id', 'watermark-c-c') + .attr('x', settings.outerSize.width / 2) + .attr('y', settings.outerSize.height / 2) + .style('width', watermarkWidth + 'px') + .style('height', watermarkWidth + 'px') + .style('transform', `translate(-${watermarkWidth / 2}px, -${watermarkWidth / 2}px)`) + .style('opacity', '0.015') + .style('transition', '0.2s') + + // 添加水印 右下角 + var watermarkWidth = 60 + shape.addWatermark(elementId, { + imageSrc: "./assets/image/logo-small.svg", + }) + .attr('id', 'watermark-r-b') + .attr('x', settings.outerSize.width) + .attr('y', settings.outerSize.height) + .style('width', watermarkWidth + 'px') + .style('height', watermarkWidth + 'px') + .style('transform', 'translate(-80px, -80px)') + + workSpace.primaryCanvas.node().appendChild(fragment) + document.getElementById(elementId).customAttr = fragment.customAttr + } +} + +/** + * Shape class v0.1.0 + * + * @author coder-xiaomo + * @date 2022-05-15 + * + * Released under the MIT license + */ +class Shape { + static workSpace = null; + constructor(workSpace) { + this.workSpace = workSpace + } + + addShape(shape, id) { + var settings = this.workSpace.settings + return workSpace.primaryCanvas.append(shape) + .style("transform", `translate(${settings.margin.left}px, ${settings.margin.top}px)`) + .attr("id", id) + } + addShape_NoTransform(shape, id) { + return workSpace.primaryCanvas.append(shape) + .attr("id", id) + } + + // 添加矩形 + rectangle(id, { x, y, width, height, fillColor = "white", strokeColor = "black" }) { + return this.addShape("rect", id) + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height) + .style("fill", fillColor) + .style("stroke", strokeColor) + } + + // 添加圆形 + circle(id, { x, y, radius, fillColor = "white", strokeColor = "black" }) { + return this.addShape("circle", id) + .attr("cx", x) + .attr("cy", y) + .attr("r", radius) + .style("fill", fillColor) + .style("stroke", strokeColor) + } + + // 添加文本 + text(id, { x, y, text, fillColor }) { + return this.addShape("text", id) + .attr("x", x) + .attr("y", y) + .text(text) + .style("fill", fillColor) + } + + // 添加线 + line(id, { x1, y1, x2, y2, strokeColor }) { + return this.addShape("line", id) + .attr("x1", x1) + .attr("y1", y1) + .attr("x2", x2) + .attr("y2", y2) + .style("stroke", strokeColor) + } + + // 添加路径 + path(id, d, { fillColor = "white", strokeColor = "black" }) { + return this.addShape("path", id) + .attr("d", d) + .style("fill", fillColor) + .style("stroke", strokeColor) + } + + // 添加坐标轴 + axis(id, { transform, axis }) { + return this.addShape_NoTransform("g", id) + .attr("transform", transform) + .call(axis) + } + + // 添加一个链表节点 + addNode(id, x, y, width, height, text) { + var primaryCanvas = workSpace.primaryCanvas + primaryCanvas.append("rect", id) + .attr("x", x) + .attr("y", y) + .attr("width", width) + .attr("height", height) + .style("fill", workSpace.settings.colorMap["fill"]) + .style("stroke", workSpace.settings.colorMap["stroke"]) + primaryCanvas.append("text", id + "_text") + .attr("x", x) + .attr("y", y) + // .attr("x", x + width / 2) + // .attr("y", y + height / 2) + .text(text) + .style("fill", workSpace.settings.colorMap["text"]) + } + + + // 添加一个图片水印 + addWatermark(id, { imageSrc }) { + var primaryCanvas = workSpace.primaryCanvas + return primaryCanvas.append("image", id) + .attr("xlink:href", imageSrc) + } + + // 绘制一个链表 + getLinkedListFragment(id, nodes) { + var settings = this.workSpace.settings + + let displayMaxWidth = 1 * settings.innerSize.width + let displayMaxHeight = 1 * settings.innerSize.height + let areaWidth = 1 * nodes.length // 按照1个Unit来计算 + let areaHeight = 1 // 按照1个Unit来计算 + + let oneUnit = 100 + // 可以假设高度相等比较宽度,这样好理解 + if (displayMaxWidth / displayMaxHeight > areaWidth / areaHeight) { + // 展示区域左右有多余空间 + oneUnit = displayMaxHeight / areaHeight + } else { + // 展示区域上下有空间(或刚刚好) + oneUnit = displayMaxWidth / areaWidth + } + + // 定义最大值 + if (oneUnit > 120) oneUnit = 120 + + let fragment = document.createDocumentFragment() + fragment.customAttr = { + id: id, + nodes: nodes, + type: "linkedList", + oneUnit: oneUnit, + gsapTimeline: gsap.timeline({ + onStart: function () { + consoleClear() + }, + onComplete: function () { + consoleLog(`排序完成`) + console.log("all done") + // this.seek(0) + } + }) + } + // console.log(fragment.customAttr) + + // 元素不能设置 width 和 height + + let g = d3.select(fragment) + .append("svg:g") + .attr("id", id) + .attr("fill", "white") + .attr("transform", `translate(${settings.margin.left}, ${settings.margin.top})`) + + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i] + let _g = g.append("svg:g") + + _g.append("svg:rect") + .attr("x", i * oneUnit) + .attr("y", 0) + .attr("width", oneUnit) + .attr("height", oneUnit) + .attr("fill", settings.colorMap["fill"]) + .attr("stroke", settings.colorMap["stroke"]) + + _g.append("svg:text") + .text(node) + .attr("x", i * oneUnit + oneUnit / 2) + .attr("y", oneUnit / 2) + .attr("width", oneUnit) + .attr("height", oneUnit) + .attr("fill", settings.colorMap["text"]) + + // 调试用 + if (settings.debugMode) + _g.append("svg:text") + .text(i) + .attr("x", i * oneUnit) + .attr("y", oneUnit) + .attr("width", oneUnit) + .attr("height", oneUnit) + .attr("fill", "black") + + // console.log(text.node().getBBox()) + // console.log(text.node().getBoundingClientRect()) + } + + // g.append("svg:rect") + // .attr("width", oneUnit * nodes.length) + // .attr("height", oneUnit) + // .style("fill", "none") + // .style("stroke", "green") + return fragment + } +} + +class VectorAnimation { + constructor(workSpace) { + this.workSpace = workSpace + } + + swapElementAttr(attrName/* or attrNameList */, element1, element2) { + function exchange(attrName) { + // 保存 element1 的属性,将 element2 的属性赋值给 element1, 再将保存的属性赋值给 element2 + var tmp = element1.getAttribute(attrName) + element1.setAttribute(attrName, element2.getAttribute(attrName)) + element2.setAttribute(attrName, tmp) + } + if (typeof attrName === "string") { + exchange(attrName) + } else if (typeof attrName === "object") { + for (let i = 0; i < attrName.length; i++) { + exchange(attrName[i]) + } + } + } + + swapElementInnerHTML(element1, element2) { + var tmp = element1.innerHTML + element1.innerHTML = element2.innerHTML + element2.innerHTML = tmp + } + + swapElementsAttr(elementPairList) { + // [ + // [attrName or attrNameList, element1, element2], + // [attrName or attrNameList, element1, element2], + // ... + // ] + for (let i = 0; i < elementPairList.length; i++) { + let elementPair = elementPairList[i] + this.swapElementAttr(elementPair[0], elementPair[1], elementPair[2]) + } + } + + // 交换数组元素 + swapLinkedListItems(id, [fromIndex, toIndex]) { + if (fromIndex < toIndex) + [fromIndex, toIndex] = [toIndex, fromIndex] + + var settings = this.workSpace.settings + let linkedList = document.getElementById(id) + let customAttr = linkedList.customAttr + // console.log(customAttr) + + var gList = linkedList.childNodes + + let from = gList[fromIndex] + let to = gList[toIndex] + + var deltaX = customAttr.oneUnit * (fromIndex - toIndex); + var deltaY = customAttr.oneUnit * 1.08 + + var animateSettings = this.workSpace.settings.animation.getConf() + + // 如果是相邻的两个元素交换,或者要交换的两个元素是同一个元素 + if (Math.abs(fromIndex - toIndex) <= 1) { + deltaY /= 2 + animateSettings.duration *= 0.6 + } + + var that = this + let timeline = gsap.timeline({ + onStart: function () { + displayCurrentArray(customAttr.nodes) + + consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`) + }, + onComplete: function () { + // 交换DOM元素中的值 + that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1]) + + // 交换 node 列表中的值 + // console.log(customAttr.nodes) + let tmp = customAttr.nodes[fromIndex] + customAttr.nodes[fromIndex] = customAttr.nodes[toIndex] + customAttr.nodes[toIndex] = tmp + + // console.log(customAttr.nodes) + displayCurrentArray(customAttr.nodes) + + console.log("animation done (swap)") + } + }).add([ + gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }), + gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }), + gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }), + gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }), + gsap.to(from.childNodes, { ...animateSettings, y: -deltaY }), + gsap.to(to.childNodes, { ...animateSettings, y: deltaY }), + ]).add([ + gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }), + gsap.to(to.childNodes, { ...animateSettings, x: deltaX }), + ]).add([ + gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }), + gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }), + gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }), + gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }), + gsap.to(from.childNodes, { ...animateSettings, y: 0 }), + gsap.to(to.childNodes, { ...animateSettings, y: 0 }), + ]).add([ + // 恢复到动画之前的状态 + gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }), + gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }), + ]) + + customAttr.gsapTimeline.add(timeline) + } + + // 高亮数组元素 + highlightLinkedListItems(id, indexList, onStartCallback) { + let linkedList = document.getElementById(id) + let customAttr = linkedList.customAttr + var gList = linkedList.childNodes + + if (typeof (indexList) === "number") + indexList = [indexList] + + var hightlightElementList_fill = [] + var hightlightElementList_text = [] + for (let i = 0; i < indexList.length; i++) { + const index = indexList[i]; + hightlightElementList_fill.push(gList[index].childNodes[0]) + hightlightElementList_text.push(gList[index].childNodes[1]) + } + var animateSettings = this.workSpace.settings.animation.getConf() + + let timeline = gsap.timeline({ + onStart: function () { + displayCurrentArray(customAttr.nodes) + + if (onStartCallback) + onStartCallback() + }, + onComplete: function () { + console.log("animation done (hightlight)") + } + }).add([ + gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill_focus"] }), + gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text_focus"] }), + ]).add([ + gsap.to(hightlightElementList_fill, { ...animateSettings, fill: settings.colorMap["fill"] }), + gsap.to(hightlightElementList_text, { ...animateSettings, fill: settings.colorMap["text"] }), + ]) + + customAttr.gsapTimeline.add(timeline) + } + + // 比较数组元素 + compareLinkedListItems(id, index1, index2) { + this.highlightLinkedListItems(id, [index1, index2], function () { + consoleLog(`比较索引为 ${index1} 和 ${index2} 的两个元素`) + }) + } + + // 弹出/弹回 数组元素 + popupLinkedListItems(id, index, { popBack = false }) { + let linkedList = document.getElementById(id) + let customAttr = linkedList.customAttr + var gList = linkedList.childNodes + + var deltaY = customAttr.oneUnit * 1.08 + if (popBack) deltaY = 0 + + let timeline = gsap.timeline({ + onStart: function () { + if (typeof (onStartCallback) === "function") + onStartCallback() + }, + onComplete: function () { + console.log("animation done (popup)") + } + }).add([ + gsap.to(gList[index], { ...this.workSpace.settings.animation.getConf(), y: deltaY }), + ]) + + customAttr.gsapTimeline.add(timeline) + } + + // 交换相邻数组元素位置(仅在水平方向呼唤,垂直方向不做调整) 【fromIndex 是y方向上突出的元素】 + exchangeLinkedListItems(id, fromIndex, toIndex) { + if (fromIndex < toIndex) + [fromIndex, toIndex] = [toIndex, fromIndex] + + var settings = this.workSpace.settings + let linkedList = document.getElementById(id) + let customAttr = linkedList.customAttr + // console.log(customAttr) + + var gList = linkedList.childNodes + + let from = gList[fromIndex] + let to = gList[toIndex] + + var deltaX = customAttr.oneUnit * (fromIndex - toIndex); + + var deltaY = customAttr.oneUnit * 1.08 // 要跟 popupLinkedListItems 函数中的 deltaY 完全一致 + var animateSettings = settings.animation.getConf() + + var that = this + let timeline = gsap.timeline({ + onStart: function () { + displayCurrentArray(customAttr.nodes) + + consoleLog(`交换索引为 ${fromIndex} 和 ${toIndex} 的两个元素`) + }, + onComplete: function () { + // 交换DOM元素中的值 + that.swapElementInnerHTML(from.childNodes[1], to.childNodes[1]) + + // 交换 node 列表中的值 + // console.log(customAttr.nodes) + let tmp = customAttr.nodes[fromIndex] + customAttr.nodes[fromIndex] = customAttr.nodes[toIndex] + customAttr.nodes[toIndex] = tmp + + // console.log(customAttr.nodes) + displayCurrentArray(customAttr.nodes) + + console.log("animation done (exchange)") + } + }).add([ + gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }), + gsap.to(to.childNodes, { ...animateSettings, x: deltaX }), + ]).add([ + // 恢复到动画之前的状态 + gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0 }), + gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0 }), + // 由于两个元素一个突出,一个不突出,所以还要交换两个y + gsap.to(from, { ...animateSettings, duration: 0, y: 0 }), + gsap.to(to, { ...animateSettings, duration: 0, y: deltaY }), + ]) + + customAttr.gsapTimeline.add(timeline) + } + + // 弹出元素到新的数组 + popupLinkedListItemToNewPosition(id, index) { + let linkedList = document.getElementById(id) + let customAttr = linkedList.customAttr + console.log("customAttr", customAttr); + + var gList = linkedList.childNodes + + let newIndex = 0; + + var deltaX = customAttr.oneUnit * (newIndex - index); + var deltaY = customAttr.oneUnit * 1.08 + var animateSettings = this.workSpace.settings.animation.getConf() + + var that = this + let timeline = gsap.timeline({ + onStart: function () { + consoleLog(`弹出索引为 ${index} 的元素到索引为 ${newIndex} 位置`) + }, + onComplete: function () { + console.log("animation done (popup to new position)") + } + }).add([ + gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }), + gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill_focus"] }), + gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }), + gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text_focus"] }), + gsap.to(from.childNodes, { ...animateSettings, y: -deltaY }), + gsap.to(to.childNodes, { ...animateSettings, y: deltaY }), + ]).add([ + gsap.to(from.childNodes, { ...animateSettings, x: -deltaX }), + gsap.to(to.childNodes, { ...animateSettings, x: deltaX }), + ]).add([ + gsap.to(from.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }), + gsap.to(to.childNodes[0], { ...animateSettings, fill: settings.colorMap["fill"] }), + gsap.to(from.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }), + gsap.to(to.childNodes[1], { ...animateSettings, fill: settings.colorMap["text"] }), + gsap.to(from.childNodes, { ...animateSettings, y: 0 }), + gsap.to(to.childNodes, { ...animateSettings, y: 0 }), + ]).add([ + // 恢复到动画之前的状态 + gsap.to(from.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }), + gsap.to(to.childNodes, { ...animateSettings, duration: 0, x: 0, y: 0 }), + ]) + + customAttr.gsapTimeline.add(timeline) + } } \ No newline at end of file diff --git a/src/index.html b/src/index.html index 3e8cde5..5611a20 100644 --- a/src/index.html +++ b/src/index.html @@ -1,85 +1,85 @@ - - - - - - - - 算法可视化 | 小墨AlGO - - - - - - - - - - - - - - - - -
- -
- 正在加载中,请稍候... -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + 算法可视化 | 小墨AlGO + + + + + + + + + + + + + + + + +
+ +
+ 正在加载中,请稍候... +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file