cdn change to local and use markdown and style change

This commit is contained in:
Cai
2025-11-05 19:26:02 +08:00
parent 33844786a1
commit 6b9c8e9e0c
39 changed files with 3885 additions and 720 deletions

1884
Html/.log

File diff suppressed because one or more lines are too long

View File

@@ -12,7 +12,7 @@ if __name__ == "__main__":
app, app,
host="0.0.0.0", host="0.0.0.0",
port=5551, port=5551,
debug=True, # 开发期打开 debug=False, # 开发期打开
# allow_unsafe_werkzeug=True, # 避免 dev server 的安全限制提示 # allow_unsafe_werkzeug=True, # 避免 dev server 的安全限制提示
# ssl_context=('server.crt', 'server.key') # ssl_context=('server.crt', 'server.key')
) )

View File

@@ -95,39 +95,52 @@ def add_material_chapter(material_id, chapter_name, teacher_id):
def rename_material_structure(material_id, is_chapter, chapter_name, lesson_name, new_name): def rename_material_structure(material_id, is_chapter, chapter_name, lesson_name, new_name):
mongo = current_app.extensions["mongo"] mongo = current_app.extensions["mongo"]
material = mongo.db.materials.find_one({'_id': ObjectId(material_id)}) material = mongo.db.materials.find_one({'_id': ObjectId(material_id)})
# 先检查是否存在 if not material:
return False # 材料不存在
# 检查名称是否存在于列表中的辅助函数
def name_exists_in_list(item_list, name_key, target_name):
return any(item[name_key] == target_name for item in item_list)
# 查找包含指定章节名的章节
def find_chapter_by_name(chapters, target_name):
for chapter in chapters:
if chapter['chapter_name'] == target_name:
return chapter
return None
# 检查章节是否存在
target_chapter = find_chapter_by_name(material['chapters'], chapter_name)
if not target_chapter:
return False
if is_chapter: if is_chapter:
for chapter in material['chapters']: # 检查新章节名是否已存在
if chapter['chapter_name'] == chapter_name: if name_exists_in_list(material['chapters'], 'chapter_name', new_name):
break
return False return False
for chapter in material['chapters']:
if chapter['chapter_name'] == new_name: # 更新章节名
return False
else:
for chapter in material['chapters']:
if chapter['chapter_name'] == chapter_name:
break
return False
for lesson in chapter['lessons']:
if lesson['lesson_name'] == lesson_name:
break
for lesson in chapter['lessons']:
if lesson['lesson_name'] == new_name:
return False
if is_chapter:
mongo.db.materials.update_one( mongo.db.materials.update_one(
{'_id': ObjectId(material_id)}, {'_id': ObjectId(material_id)},
{'$set': {'chapters.$[c].chapter_name': new_name}}, {'$set': {'chapters.$[c].chapter_name': new_name}},
array_filters=[{'c.chapter_name': chapter_name}] array_filters=[{'c.chapter_name': chapter_name}]
) )
else: else:
# 检查课时是否存在
if not name_exists_in_list(target_chapter['lessons'], 'lesson_name', lesson_name):
return False
# 检查新课时名是否已存在
if name_exists_in_list(target_chapter['lessons'], 'lesson_name', new_name):
return False
# 更新课时名
mongo.db.materials.update_one( mongo.db.materials.update_one(
{'_id': ObjectId(material_id)}, {'_id': ObjectId(material_id)},
{'$set': {'chapters.$[c].lessons.$[l].lesson_name': new_name}}, {'$set': {'chapters.$[c].lessons.$[l].lesson_name': new_name}},
array_filters=[{'c.chapter_name': chapter_name}, {'l.lesson_name': lesson_name}] array_filters=[{'c.chapter_name': chapter_name}, {'l.lesson_name': lesson_name}]
) )
return True return True
def delete_material_chapter_lesson(material_id, chapter_name, lesson_name, teacher_id): def delete_material_chapter_lesson(material_id, chapter_name, lesson_name, teacher_id):
@@ -178,33 +191,65 @@ def reorder_material_structure(material_id, order):
material = mongo.db.materials.find_one({'_id': ObjectId(material_id)}) material = mongo.db.materials.find_one({'_id': ObjectId(material_id)})
if not material: if not material:
return False return False
# 1. 构建原章节映射(保留章节元信息)和原章节的课时映射
chapter_map = {ch["chapter_name"]: ch for ch in material["chapters"]} chapter_map = {ch["chapter_name"]: ch for ch in material["chapters"]}
# 每个章节的课时映射:{章节名: {课时名: 课时对象}}
original_chapter_lessons = {
ch["chapter_name"]: {ls["lesson_name"]: ls for ls in ch["lessons"]}
for ch in material["chapters"]
}
# 2. 收集所有章节“少掉的课时”到全局池(仅包含原章节有但新排序没包含的课时)
# 用列表存储,避免同名课时被覆盖(处理可能的重名场景)
global_extra_lessons = []
for ch_name in chapter_map:
original_lessons = original_chapter_lessons[ch_name] # 原章节的所有课时
# 找到当前章节在order中的配置如果没有默认该章节所有课时都是“少掉的”
chapter_order = next((item for item in order if item["chapter_name"] == ch_name), None)
if not chapter_order:
# 章节未在order中出现所有原课时都是“少掉的”
global_extra_lessons.extend(original_lessons.values())
continue
# 章节在order中出现找出原课时中不在新排序里的即“少掉的”
new_lesson_names = chapter_order["lessons"]
for ls_name, ls in original_lessons.items():
if ls_name not in new_lesson_names:
global_extra_lessons.append(ls)
print("global_extra_lessons:", global_extra_lessons)
new_chapters = [] new_chapters = []
for chapter_order in order: for chapter_order in order:
ch_name = chapter_order['chapter_name'] ch_name = chapter_order["chapter_name"]
if ch_name not in chapter_map: if ch_name not in chapter_map:
continue # 如果前端传了不存在的章节名,可以选择跳过或报错 continue # 跳过不存在的章节
original_ch = chapter_map[ch_name] original_ch = chapter_map[ch_name]
original_lessons = original_chapter_lessons[ch_name] # 本章节原课时
# 建立 lesson_name -> lesson dict
lesson_map = {ls["lesson_name"]: ls for ls in original_ch["lessons"]}
# 按顺序重排 lessons
new_lessons = [] new_lessons = []
for ls_name in chapter_order['lessons']:
if ls_name in lesson_map:
new_lessons.append(lesson_map[ls_name])
# 如果还有遗漏(前端没传的),可以追加到最后,避免丢数据 # 3. 构建当前章节的新课时列表
for ls_name, ls in lesson_map.items(): for ls_name in chapter_order["lessons"]:
if ls not in new_lessons: # 优先使用本章节原有的课时
new_lessons.append(ls) if ls_name in original_lessons:
new_lessons.append(original_lessons[ls_name])
continue
# 本章节没有,从全局池(其他章节少掉的课时)中找
# 处理可能的重名:找到第一个匹配的课时
for i, extra_ls in enumerate(global_extra_lessons):
if extra_ls["lesson_name"] == ls_name:
new_lessons.append(extra_ls)
del global_extra_lessons[i] # 从全局池移除,避免重复
break
# 构造新的 chapter # 4. 构造新章节(保留原章节元信息,替换课时列表)
new_chapter = {**original_ch, "lessons": new_lessons} new_chapter = {**original_ch, "lessons": new_lessons}
new_chapters.append(new_chapter) new_chapters.append(new_chapter)
# 更新 material # 5. 处理全局池中剩余的课时(未被任何章节引用的,追加到最后一个章节)
if global_extra_lessons and new_chapters:
new_chapters[-1]["lessons"].extend(global_extra_lessons)
print("new_chapters:", new_chapters)
# 更新数据库
mongo.db.materials.update_one( mongo.db.materials.update_one(
{"_id": ObjectId(material_id)}, {"_id": ObjectId(material_id)},
{ {
@@ -216,6 +261,7 @@ def reorder_material_structure(material_id, order):
) )
return True return True
def save_materials_markdown_cos( def save_materials_markdown_cos(
material_id: str, material_id: str,
chapter_name: str, chapter_name: str,

View File

@@ -0,0 +1,193 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
</head>
<body>
<h1>算法设计范式:分而治之与归并排序</h1>
<blockquote>
<p>Auto-generated at 2025-09-22 15:38</p>
</blockquote>
<h2>算法设计范式:分而治之与归并排序</h2>
<h3>分治法:分而治之</h3>
<h4>引入</h4>
<p>在算法分析与设计中,有一些设计算法解决问题的通用方法可供大家选取尝试,其中一个很常用的方法就叫做“分治法”,字如其意,
分治法就是将大问题分解为小问题(或说子问题),并将子问题持续分解下去,直到子问题可以用常数时间进行求解。
最基础的问题:何时可以用分治法?</p>
<ol>
<li>找最值问题</li>
<li>回文字符串</li>
</ol>
<h4>分治法的组成</h4>
<p>分治法有3个重要组成分解、解决、合并。</p>
<p>有一些帮助我们进行这些操作的分析与设计原则:</p>
<ol>
<li><strong>子问题相似</strong>:分解出的子问题与原问题类型一致,可复用相同解法,保证递归或分治逻辑的一致性。</li>
<li><strong>子问题互不干扰</strong>:子问题边界清晰、数据独立,避免相互影响,简化问题拆解与求解逻辑。</li>
<li><strong>合并代价可控</strong>:子问题解合并为原问题解的时间 / 空间开销在可接受范围,避免抵消分治带来的效率提升。</li>
</ol>
<h4>分治法的时间复杂度迭代公式</h4>
<p>Tn= 子问题数量 * T 子问题规模 + 合并这些子问题的解的用时</p>
<h3>归并算法理论分析</h3>
<p><strong>案例背景:智慧城市交通优化系统</strong></p>
<p>在上周的工作中你已经实现并分析了插入排序在交通数据处理中的性能。虽然它在数据量较小或基本有序的情况下表现不错但在应对大规模拥堵逆序数据其性能瓶颈非常明显。本周你的项目经理Agent将向你介绍一种更高效的算法设计思想——“分而治之”并要求你掌握其代表算法归并排序。</p>
<h4>任务:理解与分析</h4>
<h5>场景介绍</h5>
<p>归并排序Merge Sort是解决大规模排序问题的利器。在投入编码之前你必须先从理论上理解它为何如此高效。</p>
<h5>思考题</h5>
<p>请与右侧的Agent你的项目经理对话清晰地回答以下问题以证明你已理解核心思想</p>
<ol>
<li>
<p><strong>分治思想</strong>根据课程资料请解释什么是“分而治之”Divide-and-Conquer设计范式它包含哪三个基本步骤</p>
</li>
<li>
<p><strong>递推关系</strong>:归并排序的时间复杂度可以用递推式 T(n)=2T(n/2)+Θ(n) 来表示。请解释这个式子各部分的含义:</p>
<ul>
<li><code>2</code> 代表什么?</li>
<li><code>T(n/2)</code> 代表什么?</li>
<li><code>Θ(n)</code> 代表什么?</li>
</ul>
</li>
<li>
<p><strong>效率对比</strong>你的同事认为插入排序经过极致优化后处理n个数据的耗时为 <eq>0.1n^2</eq>;而归并排序由于递归和合并开销,耗时为 1000nlog_2n。在处理超大规模城市交通数据时即n趋于无穷大时哪个算法最终会胜出请从渐进增长的角度解释你的理由。</p>
</li>
<li>
<p><strong>增长排序</strong>:将下列函数的渐进增长率从低到高排序,并将它们划分到等价类(即 Θ 关系相同的函数)。这有助于你理解不同算法效率的所在层级。</p>
<ul>
<li><eq>n^2</eq> (插入排序最坏情况)</li>
<li>nlogn (归并排序)</li>
<li>n (一种非常低效的蛮力算法)</li>
<li><eq>2^n</eq> (另一种指数级算法)</li>
<li>logn (类似二分查找的效率)</li>
</ul>
</li>
</ol>
<h3>归并排序的编程实现</h3>
<h4>任务:编码与对比</h4>
<h5>场景介绍</h5>
<p>现在你已经从理论上理解了归并排序的威力。是时候亲手实现它,并与上周的插入排序进行一次性能上的正面较量了!</p>
<h5>题目:实现归并排序并对比性能</h5>
<p>你需要实现归并排序的核心函数,并利用上周的测试框架来直观对比它与插入排序在处理不同交通数据时的表现。</p>
<h5>代码框架</h5>
<p>在下方代码编辑区,完成 <code>merge_sort(arr)</code><code>merge(left, right)</code> 两个函数的实现。</p>
<pre><code class="language-python">import random
import time
#上周实现的插入排序(用于对比)
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j &gt;= 0 and arr[j] &gt; key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
#--- 本周任务:请在下方实现归并排序 ---
def merge_sort(arr):
&quot;&quot;&quot;
实现归并排序算法
参数arr: 待排序的交通数据数组
返回: 一个新的、排序好的数组
&quot;&quot;&quot;
# TODO: 实现归并排序的递归逻辑
# 提示当数组元素个数小于等于1时递归终止
def merge(left, right):
&quot;&quot;&quot;
合并两个已排序的子数组
参数left: 左侧已排序数组
参数right: 右侧已排序数组
返回: 合并后的一个有序数组
&quot;&quot;&quot;
#--- 测试与对比部分 ---
def measure_performance(sort_func, data):
start_time = time.perf_counter_ns()
sort_func(data.copy())
end_time = time.perf_counter_ns()
return (end_time - start_time) / 10**6
network_sizes = [1000, 5000, 10000, 50000]
print(&quot;算法性能对比测试:&quot;)
for size in network_sizes:
# 生成逆序数据,这是插入排序的最坏情况
worst_case_data = list(range(size, 0, -1))
time_insertion = measure_performance(insertion_sort, worst_case_data)
time_merge = measure_performance(merge_sort, worst_case_data)
print(f&quot;--- 网络规模 n={size} (最坏情况) ---&quot;)
print(f&quot; - 插入排序: {time_insertion:.2f} ms&quot;)
print(f&quot; - 归并排序: {time_merge:.2f} ms&quot;)
</code></pre>
<h5>分析与讨论</h5>
<p>完成编程并得到输出后请与右侧Agent讨论以下问题</p>
<ol>
<li>
<p><strong>性能验证</strong>:观察“交通大堵塞”(最坏情况)的测试结果,归并排序的性能表现与插入排序有何天壤之别?这是否验证了你在第一关的理论分析?</p>
</li>
<li>
<p><strong>性能稳定性</strong>:如果我们将测试数据换成“畅通无阻”(最好情况)或“随机车流”(平均情况),你认为归并排序的运行时间会发生巨大变化吗?为什么?</p>
</li>
<li>
<p><strong>空间成本</strong>:在实现 <code>merge</code> 函数时,你可能创建了一个新的数组 <code>merged</code> 来存放结果。这在算法分析中被称为“空间复杂度”。与在原数组上进行交换的插入排序相比,归并排序的这个特点是优点还是缺点?</p>
</li>
<li>
<p><strong>最终决策</strong>:作为项目工程师,你会选择哪种排序算法用于我们的大规模交通调度系统?请综合考虑<strong>时间效率</strong><strong>空间成本</strong>来陈述你的理由。</p>
</li>
</ol>
<h3>进一步研究分治问题的时间复杂度分析</h3>
<p>分治问题的两种时间复杂度分析方法:主方法和递归树法。</p>
<h4>主方法</h4>
<p>主方法本质是一种速算公式针对于分治迭代式是这样的形式的T(n)=aT(n/b)+f(n)a&gt;=1b&gt;1。
主方法概念上理解,就是比较分治法的“子问题总数”和“合并总数”,在问题复杂度上,哪个为“主”,就按照这个主部分计算时间复杂度。
(因为时间复杂度公式上,我们只会保留增长最快的那一项,也就是“主项”)</p>
<p>严格定义这里就略了,直接看口诀:</p>
<ol>
<li>算log_b (a)得到n^log_b (a)</li>
<li>比f(n)和n^log_b (a)的增长速度:</li>
</ol>
<ul>
<li>前者慢 → O(n^log_b (a))</li>
<li>差不多 → O(n^log_b (a) · log n)k=0 时);</li>
<li>前者快且满足正则条件 → O(f(n))。</li>
<li>其中正则条件是存在0&lt;c&lt;1使得a·f(n/b)≤ c·n²</li>
</ul>
<p>试着计算一下T(n) = 3T(n/2) + O(n²) 这个迭代式的时间复杂度</p>
<h4>递归树法</h4>
<p>递归树相对更容易理解(可能之前的问题你已经在不知觉中使用递归树法),因为这种方法完全就是对分治方法本身的全流程模拟。</p>
<p>步骤 1画树 —— 把问题拆解成 “节点”,标记每个节点的分解与合并耗时(父节点的解决耗时其实就是自己的所有子节点的分解与合并耗时)
a. 根节点:写原问题的规模 n 和分解 / 合并耗时 f (n)
b. 子节点:根节点拆成几个子问题,就画几个子节点,每个子节点写 “子问题规模 + 子问题的分解 / 合并耗时”
c. 叶子节点:当子问题规模小到能直接解决(比如 n=1就停止拆解叶子节点写 “规模 1耗时 Θ(1)”
步骤 2算层 —— 求每一层的总耗时
同一层的节点,代表 “同一轮拆解的所有问题”,它们的耗时总和就是这一层的总代价
步骤 3求和 —— 总耗时 = 所有层的代价之和
先确定树有多少层,再把每层的总代价加起来</p>
<p>试着计算:无法用主方法解决的问题: T (n)=T (n/3)+T (2n/3)+2*n</p>
</body>
</html>

View File

@@ -0,0 +1,159 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<!-- 你的其他样式 -->
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
MathJax.config = {
tex: {
inlineMath: [['$', '$'], ['\(', '\)']]
},
svg: {
fontCache: 'global'
}
};
</script>
</head>
<body>
<h1>基于比较的排序</h1>
<h2>基于比较的排序</h2>
<h3>优先队列的原理</h3>
<p><strong>优先队列Priority Queue</strong> 是一种抽象数据结构,它支持以最高(或最低)优先级为先进行元素的插入和取出操作。
<br>
在优先队列中,每次检索得到的都是当前权重最大(或最小)的元素。
<br><br>
为了实现优先队列,最直接的思路是维护一个内部元素有序的数组:每次取数就是第一个数,存数时将元素放入合适位置,并调整序列。
<br><br>
<strong>Heap结构</strong>是实现优先队列的首选。</p>
<h3>数组模拟堆实现的优先队列</h3>
<h4>堆结构与取存原理</h4>
<p>Heap本质上是一个完全二叉树结构
(当然也可以是多叉树,但没有必要)
<img src="https://hsamooc-cdn-1374354408.cos.ap-guangzhou.myqcloud.com/image_b73d7df5-018e-4542-b891-b3711c42c56a" alt="图1大根堆的二叉树结构图示" /></p>
<p>这里用“大根堆”为例,从图中可以看到,每一个节点都比它的子节点更大。
既然“优先队列”可以理解为一种特殊的“队列”,那么我们先用堆实现这个队列的出和入:</p>
<h5>取数</h5>
<p>从优先队列中取数,显然堆顶的数就是要的那个最大者。
但是将这个数取出后还不能结束,因为需要维护堆的性质。
<br>
为了维护堆性质,一般通过将末尾的元素放到堆顶,然后将其不断与左右儿子进行替换,直到他比两个儿子都大或儿子不存在为止,称为“下滤”。
<br></p>
<h5>存数</h5>
<p>与取数类似,重要的是维护堆的性质。将新数放到最后(上图中的第一个黑色节点中)后,将这个数进行“上浮”。</p>
<h4>数组模拟堆</h4>
<p>为了实现堆,其实不需要真的写一个二叉树,用数组就可以做到。
<img src="https://hsamooc-cdn-1374354408.cos.ap-guangzhou.myqcloud.com/image_b4142d35-630c-4d12-a147-d514cca9e0d5" alt="图2左边是堆的数组表示右边是其对应的二叉树" />
如上图,左边是堆的数组表示,右边是其对应的二叉树。</p>
<p>数组下标从1开始任意一个下标i其左右儿子下标刚好就是&quot;i*2&quot;和“i*2+1”。这样在后续代码实现时代码写起来就会简单很多。</p>
<h6>建堆</h6>
<p>用数组模拟堆还有一个好处,就是可以“原地建堆”。
对于一个元素随机的数组只需要O(n)的时间复杂度就可以完成随机数组向堆的转化。</p>
<p>具体做法为“从后向前”遍历,对于每一个非叶子节点,就将其进行“下滤”,这样以它为根的子树就变成一个小堆。往前遍历即可。</p>
<h3>优先队列的算法实现</h3>
<h4>练习:堆操作</h4>
<p>在进行代码实现之前,做一个问题练习:
对于一个随机队列&quot;[3, 32, 6, 43, 5, 8, 0, 9]&quot;,经过一轮反向扫描下滤建大根堆操作,得到的堆的序列是什么?
将答案告知AI教师。</p>
<h4>任务:城市事件优先调度</h4>
<p>现在,让我们通过编程实践来掌握堆排序的实现。假设我们需要对城市中发生的一系列事件按照紧急程度(以数值大小表示优先级)排序,从而依次处理最高优先级的事件。这相当于将一组数字按从大到小排序的过程,与堆排序的机制完全一致。</p>
<p><br><br></p>
<h4>题目:实现堆排序</h4>
<p>请你实现一个堆排序算法 heap_sort(arr),将传入的数组利用堆排序方法排序(从大到小)。
<br>
你可以通过实现max_heapify和build_max_heap等函数来完成这一任务。
<br>
完成编码后,我们将对算法的性能进行测试,比较不同规模输入下堆排序运行时间的增长情况。
<br></p>
<h5>代码框架</h5>
<p>请在下方代码编辑区完成 max_heapify、build_max_heap 和 heap_sort 函数的实现。</p>
<pre><code class="language-python">import random
import time
def max_heapify(arr, n, i):
&quot;&quot;&quot;
维护最大堆性质:假设结点 i 的左右子树已经是最大堆,
调整结点 i 使以其为根的子树成为最大堆
参数:
arr: 存储堆的数组
n: 堆的有效大小(长度)
i: 需要下滤调整的节点索引
&quot;&quot;&quot;
# TODO: 在此处实现 &quot;下滤&quot; 操作,将 arr[i] 下沉到正确位置
def build_max_heap(arr):
&quot;&quot;&quot;
将无序数组原地建成最大堆,从后往前进行下滤
参数:
arr: 待调整的数组
&quot;&quot;&quot;
# TODO: 调用 max_heapify 将 arr 调整为堆
def heap_sort(arr):
&quot;&quot;&quot;
利用堆排序算法排序数组(降序)
参数:
arr: 待排序数组
返回:
排序后的数组(从大到小)
&quot;&quot;&quot;
# TODO: 完成堆排序的实现
n = len(arr)
# 1. 原地建堆
build_max_heap(arr)
# 2. 依次将当前堆顶(最大值)交换到数组末尾,并缩小堆的范围,然后下滤
#性能测试:对比不同规模输入的堆排序用时
def measure(sort_func, data):
start = time.perf_counter_ns()
sort_func(data.copy())
end = time.perf_counter_ns()
return (end - start) / 10**6 # 毫秒
sizes = [1000, 5000, 10000]
print(&quot;堆排序性能测试:&quot;)
for n in sizes:
data = [random.randint(0, 1000000) for _ in range(n)]
t = measure(heap_sort, data)
print(f&quot;数据规模 n={n}: 排序耗时 {t:.2f} ms&quot;)
</code></pre>
<h5>实验结果分析</h5>
<p>请完成并运行上述代码,观察不同输入规模下算法的执行时间。理论上,堆排序的时间复杂度为<eq>O(n \log n)</eq>当输入规模增大时运行时间应呈现近似线性乘以对数的增长趋势。具体来说若将输入规模扩大10倍运行时间将增加约<eq>10 \times \log_2(10) \approx 10 \times 3.3 \approx 33</eq>倍左右。
<br>
相比之下,简单的<eq>O(n^2)</eq>排序算法在相同扩大量级下耗时会增加约100倍。通过与之前插入排序实验的对比你会发现堆排序对规模扩大的响应增长显著缓慢得多。
<br>
这印证了堆排序的效率优势:在最坏情况和平均情况下它都能维持<eq>O(n \log n)</eq>的性能,不会出现如快速排序在极端情况下退化为<eq>O(n^2)</eq>的尴尬局面。
<br>
此外,堆排序是一种原地排序(只需要常数级别的额外空间),这也是相对于归并排序的一个优势。综合来看,利用优先队列实现的堆排序在效率和空间上都表现出色,是一种成熟可靠的排序方法。</p>
<h3>比较排序的决策树模型</h3>
<p>前面的内容介绍了多种基于元素比较的排序算法(比较排序),包括快速排序、堆排序等。接下来,我们讨论一个重要的理论结果:
<br>
在比较模型下,任意排序算法的最优时间复杂度下界为<eq>Ω(n \log n)</eq>
这个结论意味着,无论设计何种巧妙的比较排序算法,都无法突破这一定义上的效率极限。证明这一点的经典工具是决策树模型。</p>
<p>决策树是描述比较排序过程的一种抽象模型。
在排序过程中,每进行一次比较(例如“<eq>A[i] \le A[j]</eq>?”)就相当于根据结果(二叉决策:是/否)将可能的输入情况划分到两个分支。
<br>
整个排序算法的运行过程可以被看作是在这样一棵决策树上从根节点走向某个叶节点的过程。决策树的每个叶节点对应一种可能的输入集合及其确定的输出顺序。
<br>
当有<eq>n</eq>个待排序元素时,假设它们两两各不相同,则可能的输入排列情况共有<eq>n!种(所有元素的全排列)。为了正确地将每种输入排列映射到唯一的输出(即排好序的有序序列),排序算法的决策树必须至少具备</eq>n!个叶节点——每个叶子对应一种输入排列的判别结果。</p>
<p>对于一棵二叉决策树,若包含<eq>L</eq>个叶节点,其高度<eq>h</eq>满足<eq>L \le 2^h</eq>,因此<eq>h \ge \lceil \log_2 L \rceil</eq>。在排序问题中,<eq>L</eq>最少取<eq>n!</eq>,因此最优情况下决策树高度也满足:
<br>
<eq>$h_{\min} \geq \lceil \log_2(n!) \rceil.</eq>$
利用对数运算的性质,可以估计<eq>\log_2(n!)</eq>的数量级。根据斯特林公式近似,<eq>n!</eq>大约为<eq>(n/e)^n</eq>的数量级,那么:
<br>
<eq>$\log_2(n!) \approx \log_2\left((n/e)^n\sqrt{2\pi n}\right) = n\log_2 n - n\log_2 e + O(\log n).</eq>$
<br>
可以看出,当<eq>n</eq>较大时,<eq>\log_2(n!) = Θ(n \log n)</eq>。这意味着决策树的高度下界<eq>h_{\min} = Ω(n \log n)</eq>。换言之,任何基于比较的排序算法在最理想情况下也需要执行与<eq>n \log n</eq>同数量级的比较操作。
<br>
例如,对于$ n=3$的简单情况,<eq>3! = 6</eq>,满足<eq>2^2 < 6 < 2^3</eq>因此判定3个元素的任意排列需要至少3次比较。这与我们已知的事实相符对三个无任何特殊性质的数进行排序最少需要3次比较才能确定它们的正确顺序。</p>
</body>
</html>

6
Html/apps/static/cdnback/all.min.css vendored Normal file

File diff suppressed because one or more lines are too long

3
Html/apps/static/cdnback/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
Html/apps/static/cdnback/split.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,30 +1,37 @@
/* 左右分栏:左=编辑,右=预览 */ /* 让左侧编辑区真正占满并允许内部滚动 */
.editor-pane { .tab-content {
grid-template-columns: 1fr 1fr; /* 左右各一半 */ height: calc(100vh - 220px);
gap: 12px; /* 不要隐藏滚动:*/
align-items: start; overflow: visible; /* 或者干脆删掉 overflow 这行 */
} }
/* 兼容你页面现有布局:让编辑区域(左半)占满左侧 */
.tab-content { /* EasyMDE 容器按列铺开,编辑器本体撑满剩余空间 */
height: calc(100vh - 220px); /* 视口高度自适应,按需调整 */ .EasyMDEContainer {
overflow: hidden; /* 内部由 EasyMDE 自己滚动 */ height: 100%;
display: flex; display: flex;
} flex-direction: column;
.tab { }
height: 100%;
} /* 关键:让 CodeMirror 吃满容器高度 */
.tab textarea { .EasyMDEContainer .CodeMirror {
height: 100%; height: 100% !important;
} }
/* 右侧预览 */
.md-preview-pane { /* 关键:滚动发生在 CodeMirror-scroll 里 */
border: 1px solid #e5e7eb; .EasyMDEContainer .CodeMirror-scroll {
border-radius: 8px; height: 100% !important;
padding: 16px; overflow: auto !important; /* 启用内部滚动条(也支持鼠标滚轮) */
background: #fff; }
height: calc(100vh - 220px);
overflow: auto; /* 右侧预览保持原样独立滚动 */
} .md-preview-pane {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
background: #fff;
height: calc(100vh - 220px);
overflow: auto;
}
.md-preview-pane h1, .md-preview-pane h2, .md-preview-pane h3 { .md-preview-pane h1, .md-preview-pane h2, .md-preview-pane h3 {
border-bottom: 1px solid #f1f5f9; border-bottom: 1px solid #f1f5f9;
padding-bottom: .3em; padding-bottom: .3em;

View File

@@ -13,7 +13,39 @@
font-size: 32px; font-size: 32px;
margin-bottom: 20px; margin-bottom: 20px;
} }
#chapter-list {
/* 设置固定高度,超出范围将出现滚动 */
max-height: 75vh;
/* 超出高度时显示垂直滚动条 */
overflow-y: auto;
/* 隐藏水平滚动条(如果不需要) */
overflow-x: hidden;
/* 可选:添加一些内边距和边框增强视觉效果 */
padding: 10px;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
/* 可选美化滚动条样式仅WebKit浏览器有效 */
#chapter-list::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}
#chapter-list::-webkit-scrollbar-track {
background: #f1f1f1; /* 滚动条轨道背景 */
border-radius: 3px;
}
#chapter-list::-webkit-scrollbar-thumb {
background: #c1c1c1; /* 滚动条滑块颜色 */
border-radius: 3px;
}
#chapter-list::-webkit-scrollbar-thumb:hover {
background: #a8a8a8; /* 鼠标悬停时的滑块颜色 */
}
.course-cards { .course-cards {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -450,6 +450,7 @@ async function persistOrderNow(chapters) {
}); });
if (!res.ok) throw new Error('保存新顺序失败'); if (!res.ok) throw new Error('保存新顺序失败');
console.log('已保存顺序') console.log('已保存顺序')
console.log(payload);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }

Binary file not shown.

Binary file not shown.

View File

@@ -11,12 +11,18 @@
<script> <script>
document.cookie = "vscode-tkn=44edc269-6f88-46ab-9790-dc253f66ac36; path=/; SameSite=None; Secure"; document.cookie = "vscode-tkn=44edc269-6f88-46ab-9790-dc253f66ac36; path=/; SameSite=None; Secure";
</script> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/1.1.1/marked.min.js"></script> <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/1.1.1/"></script> -->
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script> <script src="/static/cdnback/marked.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- <script src="https://cdn.socket.io/4.0.0/"></script> -->
<script src="https://unpkg.com/split.js/dist/split.min.js"></script> <script src="/static/cdnback/socket.io.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <!-- <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/" rel="stylesheet"> -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> <link rel="stylesheet" href="/static/cdnback/bootstrap.min.css">
<!-- <script src="https://unpkg.com/split.js/dist/"></script> -->
<script src="/static/cdnback/split.min.js"></script>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/"></script> -->
<script src="/static/cdnback/jquery.min.js"></script>
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> -->
<link rel="stylesheet" href="/static/cdnback/bootstrap-icons.css">
</head> </head>
<body> <body>
<div class="maxcontainer" id="maxcontainer" style="height: 100%;"> <div class="maxcontainer" id="maxcontainer" style="height: 100%;">

View File

@@ -5,9 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>华实学伴 - 专业的在线学习平台</title> <title>华实学伴 - 专业的在线学习平台</title>
<!-- Bootstrap 5 CSS --> <!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> -->
<link href="/static/cdnback/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome 图标 --> <!-- Font Awesome 图标 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> -->
<link rel="stylesheet" href="/static/cdnback/all.min.css">
<link rel="stylesheet" href="/static/css/dashboard.css"> <link rel="stylesheet" href="/static/css/dashboard.css">
</head> </head>
<body> <body>

View File

@@ -5,9 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>华实学伴 - 专业的在线学习平台</title> <title>华实学伴 - 专业的在线学习平台</title>
<!-- Bootstrap 5 CSS --> <!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> -->
<link href="/static/cdnback/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome 图标 --> <!-- Font Awesome 图标 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> -->
<link rel="stylesheet" href="/static/cdnback/all.min.css">
<link rel="stylesheet" href="/static/css/dashboard.css"> <link rel="stylesheet" href="/static/css/dashboard.css">
</head> </head>
<body> <body>

View File

@@ -12,12 +12,18 @@
<script> <script>
document.cookie = "vscode-tkn=44edc269-6f88-46ab-9790-dc253f66ac36; path=/; SameSite=None; Secure"; document.cookie = "vscode-tkn=44edc269-6f88-46ab-9790-dc253f66ac36; path=/; SameSite=None; Secure";
</script> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/1.1.1/marked.min.js"></script> <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/1.1.1/"></script> -->
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script> <script src="/static/cdnback/marked.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <!-- <script src="https://cdn.socket.io/4.0.0/"></script> -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css" rel="stylesheet"> <script src="/static/cdnback/socket.io.min.js"></script>
<script src="https://unpkg.com/split.js/dist/split.min.js"></script> <!-- <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/" rel="stylesheet"> -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <link rel="stylesheet" href="/static/cdnback/bootstrap.min.css">
<!-- <script src="https://unpkg.com/split.js/dist/"></script> -->
<script src="/static/cdnback/split.min.js"></script>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/"></script> -->
<script src="/static/cdnback/jquery.min.js"></script>
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> -->
<link rel="stylesheet" href="/static/cdnback/bootstrap-icons.css">
</head> </head>
<body> <body>

View File

@@ -12,16 +12,22 @@
<link rel="stylesheet" href="/static/css/lesson_setting.css"> <link rel="stylesheet" href="/static/css/lesson_setting.css">
<link rel="stylesheet" href="/static/css/modelinput.css"> <link rel="stylesheet" href="/static/css/modelinput.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <link rel="stylesheet" href="/static/cdnback/all.min.css">
<!-- <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> -->
<script src="/static/cdnback/axios.min.js"></script>
<script src="/static/js/modelinput.js"></script> <script src="/static/js/modelinput.js"></script>
<!-- Markdown Editor (EasyMDE) --> <!-- Markdown Editor (EasyMDE) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css" /> <!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css" /> -->
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script> <link rel="stylesheet" href="/static/cdnback/easymde.min.css" />
<!-- <script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script> -->
<script src="/static/cdnback/easymde.min.js"></script>
<!-- Markdown 渲染与安全 --> <!-- Markdown 渲染与安全 -->
<script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script> <!-- <script src="https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.7/dist/purify.min.js"></script> <script src="/static/cdnback/markdown-it.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.7/dist/purify.min.js"></script> -->
<script src="/static/cdnback/purify.min.js"></script>
<link rel="stylesheet" href="/static/css/lesson_markdown.css"> <link rel="stylesheet" href="/static/css/lesson_markdown.css">
</head> </head>

View File

@@ -2,7 +2,8 @@
<!-- Bootstrap 5 CSS --> <!-- Bootstrap 5 CSS -->
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> --> <!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> -->
<!-- Font Awesome 图标 --> <!-- Font Awesome 图标 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> -->
<link rel="stylesheet" href="/static/cdnback/all.min.css">
<link rel="stylesheet" href="/static/css/dashboard.css"> <link rel="stylesheet" href="/static/css/dashboard.css">
<header class="navbar"> <header class="navbar">
<div class="logo"> <div class="logo">

View File

@@ -10,9 +10,12 @@
<script> <script>
document.cookie = "vscode-tkn=44edc269-6f88-46ab-9790-dc253f66ac36; path=/; SameSite=None; Secure"; document.cookie = "vscode-tkn=44edc269-6f88-46ab-9790-dc253f66ac36; path=/; SameSite=None; Secure";
</script> </script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="/static/cdnback/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.min.js"></script> <!-- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css" rel="stylesheet"> <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.1/socket.io.min.js"></script> -->
<script src="/static/cdnback/socket.io.min.js"></script>
<!-- <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css" rel="stylesheet"> -->
<link href="/static/cdnback/bootstrap.min.css" rel="stylesheet">

View File

@@ -9,7 +9,8 @@
<link rel="stylesheet" href="/static/css/teacher_course_setting.css"> <link rel="stylesheet" href="/static/css/teacher_course_setting.css">
<link rel="stylesheet" href="/static/css/modelinput.css"> <link rel="stylesheet" href="/static/css/modelinput.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> -->
<link rel="stylesheet" href="/static/cdnback/all.min.css">
<script src="/static/js/modelinput.js"></script> <script src="/static/js/modelinput.js"></script>
</head> </head>

View File

@@ -0,0 +1 @@
{"username": "_10086_"}

View File

@@ -0,0 +1 @@
{"username": "chenjianfei"}

View File

@@ -0,0 +1 @@
{"username": "cn0101"}

View File

@@ -0,0 +1 @@
{"username": "dhj"}

View File

@@ -0,0 +1 @@
{"username": "dhj_teacher"}

View File

@@ -0,0 +1 @@
{"username": "not_shy"}

View File

@@ -0,0 +1 @@
{"username": "ryh_"}

View File

@@ -0,0 +1 @@
{"username": "univero"}

View File

@@ -0,0 +1 @@
{"username": "univero1"}