| 用“归并”改进“快速排序” [ 时间:2003-11-2 来源:plainsong ] | |||
排序和搜索是我们编程时最常用到的两种算法了,C++程序员们幸运一些,因为在C++标准库中就有各种通用的排序函数;而Delphi程序员就只有TList.Sort和TStringList.Sort可用了,所以Delphi程序员通常都把排序函数加入到自己的常用函数单元中。 作为“通用排序函数”,自然要在“基于比较”的排序算法中选择,而其中的“快速排序”以其效率优势常使它成为程序员的首选。但快速排序也有一个常常另人担心的问题……它在最差情况下的时间复杂度是O(N2)。因此,悲观的程序员们通常会改用“堆排序”(在《C++标准程序库》一书中作者就推荐使用partial_sort,因为sort可能是用“快速排序”实现的而partial_sort则通常是用“堆排序实现的”)。 为了能“既吃到鱼又不丢了熊掌”,很多程序员都在想办法改良“快速排序”,在不影响它的优秀的平均性能的前提下使它在最差情况下也可以获得可以忍受的性能,其中最成功的应该算是“Intro Sort”,而我在这里谈的是另一种方法。 我们知道,“快速排序”的主要思想就是递归地对序列进行划分,用一个枢值把序列分成“大序列”和“小序列”。如果这个划分每次都是平均地“一分为二”,这时它就获得了最佳的性能;而如果每次都是“畸形划分”——把序列分成长度为1和N-1的两部分,它就退化成了递归实现的“冒泡排序”,性能可想而知。要想对它进行改良,就要针对这种情况进行处理。一种思路是对划分进行改良,让它不产生“畸形划分”;另一种思路是对划分结果进行检查,如果是畸形划分则进行特殊处理。我这里的方法是第二种。 基本思路就是,对划分结果产生的两个子序列的长度进行检查,如果其中一个与另一个的长度比超过某一界限,则认为这是一个“畸形划分”,对较短的子序列继续使用“快速排序”,而把较长的子序列平分为两个子序列分别排序,然后再进行一次合并。两个有序序列的合并是可以实现为线性的时间复杂度的,因此可以在每次都是畸形划分时仍然获得O(N*LogN)的时间复杂度。 基本算法描述如下: procedure Sort(Data, Size); begin M := Partition(Data, Size); MoreData := @Data[M + 1]; MoreSize := Size - M - 1; Size := M; if Size > M then begin Swap(MoreData, Data); Swap(MoreSize, Size); end; Sort(Data, Size); if MoreSize div MAX_RATIO > Size then begin Sort(MoreData, MoreSize div 2); Sort(@MoreData[MoreSize div 2], MoreSize - MoreSize div 2); Merge(MoreData, MoreSize div 2, MoreSize); end else Sort(MoreData, MoreSize); end; 其中Partition就是众所周知的用于“快速排序”的划分子程序,Merge(Data, First,Size)把Data中[0,First)和[First, Size)两个有序列合并为一个有序序列并存放在Data中。上面的算法认为Partition划分的位置M处的值就是划分的枢值,也就是说序列可以分成[0,M-1]、[M,M]和[M+1,Size-1]三部分。如果Partition的实现不能保证这一点,则MoreData应为Data[M],而MoreSize也应为Size - M。 现在简单分析一下这个排序,最好情况是在Partition每次划分都是平均划分时,这时合并不会发生,它等同于“快速排序”。而在最差情况下,每次Partition都会把序列分成长度为1和N-1的两部分,这时它就变成了递归实现的“二路归并排序”,但在每次合并前都进行了约N次比较,作用只是分出一个元素不参与合并。因此这个排序的时间复杂度仍然是O(N*LogN),比较次数大约是归并排序的二倍。 关于MAX_RATIO的选择,根据我的经验,应该是选择4-16之间的数字比较好,我选择的是8。 我实际上实现时比上面说的要复杂一些,一是在序列长度不大于16时改用插入排序,二是去掉了一个递归。然后作了一些效率上的测试,数据是500,000个长度为10的字符串,大致如下: 随机分布,500000个元素: Sort:时间:3.31;比较次数:10843175。 QuickSort:时间:3.28;比较次数:10936357。 IntroSort:时间:3.35;比较次数:10958355。 MergeSort:时间:4.20;比较次数:13502620。 正序分布,500000个元素: Sort:时间:1.71;比较次数:8401712。 QuickSort:时间:1.91;比较次数:9262161。 IntroSort:时间:1.80;比较次数:8401712。 MergeSort:时间:1.72;比较次数:4766525。 逆序分布,500000个元素: Sort:时间:2.38;比较次数:11737937。 QuickSort:时间:2.54;比较次数:12619014。 IntroSort:时间:2.38;比较次数:11293745。 MergeSort:时间:1.69;比较次数:4192495。 相同值,500000个元素: Sort:时间:1.41;比较次数:8401712。 QuickSort:时间:1.47;比较次数:9262161。 IntroSort:时间:1.40;比较次数:8401712。 MergeSort:时间:1.43;比较次数:4766525。 波形分布(波长1000),500000个元素: Sort:时间:2.52;比较次数:10658948。 QuickSort:时间:2.97;比较次数:12971845。 IntroSort:时间:3.02;比较次数:12672744。 MergeSort:时间:2.71;比较次数:7978745。 峰形分布(前半段为正序,后半段为前半段的逆转),500000个元素: Sort:时间:2.42;比较次数:10401407。 IntroSort:时间:5.13;比较次数:19211813。 MergeSort:时间:1.88;比较次数:5176855。 谷形分布(峰形分布的逆转),500000个元素: Sort:时间:2.29;比较次数:10944792。 IntroSort:时间:5.29;比较次数:17898801。 MergeSort:时间:1.90;比较次数:5282136。 由于这个排序的最差分布不是很好寻找,我修改了一下Partition函数,使正序分成成为它的不利分布,然后在同一台机器上测了一下: 正序分布,500000个元素: Sort:时间:2.77;比较次数:12011738。 IntroSort:时间:4.31;比较次数:19212426。 MergeSort:时间:1.73;比较次数:4766525。 据我分析,这时排序可以分成QuickSort和MergeSort两部分,两部分花的时间相互独立,所以我把这个时间与MergeSort的时间相减,然后加上随机分布情况的MergeSort的时间(取4.20),结果为5.24,应该是差不多的。 从这个结果来看,和IntroSort的最差情况基本上是差不多的。 在这里感谢优快云的LeeMaRS和ZhanYv,他们在论坛上和我对排序的改进进行了很多讨论,并给了我很大帮助。LeeMaRS为我提供了一个优秀的Partition函数。 下面给出实现的完整代码: type TPointerList = array[0..32767] of Pointer; PPointerList = ^TPointerList; PPointer = ^ Pointer; TLessThenProc = function(Left, Right: Pointer): Boolean; const SORT_MAX = 16; MAX_RATIO = 8; {************************************************************************** 函数:Partition 功能:将一个序列划分成两个子序列,后一子序列所有值都不大于前一子序列任意值。 返回子序列分割处索引。 参数: Data: PPointerList,源序列。 Size: Integer, 序列长度。 LessThen: TLessThenProc,用于定义顺序的比较函数 说明: 用于“快速排序” 枢值策略:选择0、0.5、1三处的值的中间值 返回值保证: A < Result 则必然 not LessThen(Data[Result], Data[A]); A > Result 则必然 not LessThen(Data[A], Data[Result]); **************************************************************************} function Partition(Data: PPointerList; Size: Integer;LessThen: TLessThenProc): Integer; var M: Integer; Value: Pointer; begin M := Size div 2; Dec(Size); if LessThen(Data[0], Data[M]) then Swap(Data[M], Data[0]); if LessThen(Data[Size], Data[0]) then Swap(Data[Size], Data[0]); if LessThen(Data[0], Data[M]) then Swap(Data[M], Data[0]); Value := Data[0]; Result := 0; while Result < Size do begin while (Result < Size) and LessThen(Value, Data[Size]) do Dec(Size); If Result < Size then begin Data[Result] := Data[Size]; Inc(Result); end; while (Result < Size) and LessThen(Data[Result], Value) do Inc(Result); If Result < Size then begin Data[Size] := Data[Result]; Dec(Size); end; end; Data[Result] := Value; end; {************************************************************************** 函数:Merge 功能:将两个有序序列合并为一个有序列序。 参数: SrcFirst, SrcSecond: PPointerList,两个源序列。合并时如果有相同的值, SrcSecond的值将排在SrcFirst的值的后面。 Dest:PPointerList,存放合并结果的序列,必须有足够的空间。 SizeFirst, SizeSecond: Integer, 两个源序列长度 LessThen: TLessThenProc,用于定义顺序的比较函数 **************************************************************************} procedure Merge(SrcFirst, SrcSecond, Dest: PPointerList; SizeFirst, SizeSecond: Integer; LessThen: TLessThenProc); var I: Integer; IsFirst: Boolean; begin IsFirst := True; if (SizeFirst = 0) or (LessThen(SrcSecond[0], SrcFirst[0])) then begin Swap(Pointer(SrcFirst), Pointer(SrcSecond)); Swap(SizeFirst, SizeSecond); IsFirst := not IsFirst; end; while SizeFirst > 0 do begin if SizeSecond = 0 then I := SizeFirst else begin I := 0; while (I < SizeFirst) and ((IsFirst and not LessThen(SrcSecond[0], SrcFirst[I])) or (not IsFirst and LessThen(SrcFirst[I], SrcSecond[0]))) do Inc(I); end; Move(SrcFirst^, Dest^, Sizeof(Pointer) * I); Dec(SizeFirst, I); SrcFirst := @SrcFirst[I]; Dest := @Dest[I]; Swap(Pointer(SrcFirst), Pointer(SrcSecond)); Swap(SizeFirst, SizeSecond); IsFirst := not IsFirst; end; end; {************************************************************************** 函数:SortInsert 功能:向有序序列中插入一个值,保证插入后仍然有序。 参数: Data: PPointerList,有序序列,必须可容纳Size + 1个元素 Size: Integer, 原序列长度 Value: 新插入的值 LessThen: TLessThenProc,用于定义顺序的比较函数 **************************************************************************} procedure SortInsert(Data: PPointerList; Size: Integer; Value: Pointer; LessThen: TLessThenProc); var J: Integer; begin if LessThen(Value, Data[0]) then J := 0 else begin J := Size; while (J > 0) and LessThen(Value, Data[J - 1]) do Dec(J); end; Move(Data[J], Data[J +1], Sizeof(Pointer) * (Size - J)); Data[J] := Value; end; {************************************************************************** 函数:MergePart 功能:将序列中的两个邻近有序子序列合并为一个有序子序列并存放在原处。 参数: Data: PPointerList,源序列。 PartSize: Integer, 第一个有序子序列长度。 Size: Integer, 序列总长度。 LessThen: TLessThenProc,用于定义顺序的比较函数 说明: 如果自由空间足够,调用Merge实现,否则调用SortInsert。 **************************************************************************} procedure MergePart(Data: PPointerList; First: Integer; Size: Integer;LessThen: TLessThenProc); var Buffer: PPointerList; I: Integer; begin Buffer := AllocMem(Size * Sizeof(Pointer)); if Buffer <> nil then begin Move(Data^, Buffer^, Size * Sizeof(Pointer)); Merge(@Buffer[0], @Buffer[First], Data, First, Size-First, LessThen); FreeMem(Buffer); end else begin Dec(Size); for I := PartSize to Size do SortInsert(Data, I, Data[I], LessThen); end; end; {************************************************************************** 函数:InsertionSort 功能:简单插入排序 参数: Data: PPointerList,源序列。 Size: Integer, 序列长度。 LessThen: TLessThenProc,用于定义顺序的比较函数 **************************************************************************} procedure InsertionSort(Data: PPointerList; Size: Integer; LessThen: TLessThenProc); var I: Integer; begin Dec(Size); for I := 1 to Size do SortInsert(Data, I, Data[I], LessThen); end; {************************************************************************** 函数:Sort 功能:排序 参数: Data: PPointerList,源序列。 Size: Integer, 序列长度。 LessThen: TLessThenProc,用于定义顺序的比较函数 说明: 使用快速排序。 当子序列长度不大于SORT_MAX时使用插入排序。 当子序列长度比大于MAX_RATIO时将长子序列一分为二分别排序,然后合并。 **************************************************************************} procedure Sort(Data: PPointerList; Size: Integer; LessThen: TLessThenProc); var M: Integer; OtherData: PPointerList; OtherSize: Integer; begin Assert(Data <> nil); while Size > SORT_MAX do begin M := Partition(Data, Size, LessThen); if (M <= Size div 2) then begin OtherData := @Data[M + 1]; OtherSize := Size - M - 1; Size := M; end else begin OtherData := Data; OtherSize := M; Data := @OtherData[M + 1]; Size := Size - M - 1; end; if (OtherSize div MAX_RATIO > Size) then begin M := OtherSize div 2; Sort(OtherData, M, LessThen, MaxRatio); Sort(@OtherData[M], OtherSize - M, LessThen, MaxRatio); MergePart(OtherData, M, OtherSize, LessThen); end else Sort(OtherData, OtherSize, LessThen, MaxRatio); end; InsertionSort(Data, Size, LessThen); end; | |||
|
| |||
相关推荐
链表排序——归并排序、快速排序
目录 1 归并排序 1.1 实现思路 1.2 代码实现 2 快速排序 2.1 实现思路 2.2 代码实现 1 归并排序 归并排序基本原理https://blog.youkuaiyun.com/qq_28114615/article/details/86345717 1.1 实现思路 与数组归并排序相同,主要分为以下三个步骤: ①找到链表中点。最常用的方法是双指针...
必学算法之归并排序及其改进方案
在之前的文章中学习了必学算法之快速排序及其改进方案面试高频考点,今天我们来探讨一下另一个经典排序算法——归并排序。归并排序作为十大经典排序算法之一,主要采用分而治之的思想来实现排序,这个思想非常有助于我们解决问题,尤其是无序大数据在一定内存容量下转换成排序数据(面试高频考点,具体答案在文末给出)的时候经常会使用到,现在我们来学习一下这个算法及其改进版本。归并排序最早于1945年由约翰·冯·诺依曼(John von Neumann)提出,是创建在归并操作上的一种有效的排序算法。
排序——归并与快速排序
I. 归并排序 归并排序思想 将数组一分为二(折半); 分别将两部分数组进行排序; 将排序好的两部分数组进行合并成新的有序数组。 动态图演示 算法实现 利用分治算法,自顶向下的进行递归排序。动态图演示则为自下往上的排序演示。 public static void main(String[] args) { int[] array = {53, 34, 32, ...
基础算法改进——归并排序的改进
基础算法改进——归并排序 改进方案: 1.小区间采用插入排序(原因:小区间内元素有序的概率较大,) 2.待比较两区间元素间已经有序,直接返回(区间内部肯定是已经有的,这是归并的原理) 2.1 正序:前子序列的最大值<=后子序列的最小值,直接返回 2.2 逆序:后子序列的最大值<=前子序列的最小值,先复制后子序列,再复制前子序列。 3.临时数组的有效利用 4:不递归: 4.1:用栈模拟递归,即用自定义的栈来模拟系统栈的递归过程从而避免递归 4.2:递推,即把通常的归并过程反过来实现,自下而上不断合
归并排序比快速排序慢吗?只是没有写对罢了
归并排序比快速排序慢吗?只是没有写对罢了 众所周知,归并 排序比快速排序慢,虽然它稳定,而且时间上也稳定,但是就是慢20%~100%,今天,我经过优化,使其达到了与快速排序相同的水平,对于1W-1000W的完全随机生成的小于1亿的int数据,其与普通快速排序的差距在5%以内,而且有时是它比普通快速排序快5%。对于经过随机数优化的快速排序,我的归并排序比其快10%左右,对于经典的归并排序,我的比它快20%左右。(1W以下时间几乎都是0没法比哈哈) 先上写法 对于一般的没有研究过的人来说,归并就是借用一个数组,
改进的归并排序算法
改进的归并排序算法,两种方式 1 是不回写, 2是 非递归
改进归并排序算法
一、针对经典归并排序算法问题的改进措施 (1)针对递归层次问题 采用能在小规模集合上有效工作的其它算法,直接对小规模集合处理。 如INSERTIONSORT算法 (2)针对元素频繁移动问题 采用一个称为链接信息数组LINK(1:n)的数据结构,记录归并过程中A中的元素相对于其排序后在分类表中位置坐标的链接关系。 LINK(i)取值于[1,n],是指向A的元素的指针:在分类表中它指向下一个元素在A中的位置坐标。 二、具体实现 m_improved.h #pragma once //直接插入排序 void i
归并分类算法、改进的归并分类算法和快速分类算法.zip
编程实现归并分类算法、改进的归并分类算法和快速分类算法代码,并能使用3000,5000,10000,30000,50000五种不同数据规模进行算法运算时间的对比分析。
数据结构与算法之归并排序及其改进(多路归并,可视化)
归并排序概念及步骤 归并排序:归并排序是利用分治思想进行排序的算法。 步骤: 1、分解——将序列进行多次递归的分解,分解为多个小序列,最终的结果为每个小序列仅仅含有1个元素 2、合并——将相邻的小序列进行排序合并之后多次递归为最终的有序序列。演示图 因为我们排序的元素本身为线性顺序表,每个元素可以看作单个的个体因此相关的分解操作就不需要进行直接进行合并排序即可,演示图以
归并算法的改进算法
对于归并算法的四点改进 1.不回写,可以减少一半的数组移动 2.不递归 ,可以加快执行速度 3.无逆序,分段的时候递增的为一段,段数不确定 4.与插入相结合,因为数列个数在16以内的话插入排序会比归并排序要快
基于IC555的PWM信号发生器设计:从原理到电机调速与LED调光应用
脉冲宽度调制(PWM)是一种通过调节数字脉冲宽度来控制模拟信号电平的核心技术,其原理在于固定频率下改变高电平占空比,从而等效调节平均电压或功率。这项技术因其高效率、低损耗的特点,在电机调速、LED调光、开关电源等工程领域具有重要价值。本文聚焦于利用经典IC555定时器芯片构建PWM信号发生器,通过分析其无稳态工作模式及改进型电路设计,实现了占空比独立可调且频率稳定的信号输出。文中结合电机驱动与LED调光等具体应用场景,详细阐述了电路参数计算、元件选型、PCB设计及常见问题排查等实践要点,为电子爱好者与工程师
用“归并”改进“快速排序” (转)
用“归并”改进“快速排序” (转)[@more@] 排序和搜索是我们编程时最常用到的两种算法了,C++程序员们幸运一些,因为在C++标准库中就有各种通用的排序函数;而Delphi程序员就只有TList.Sort和TString...
告别卡顿!用U盘给你的MacBook Pro 2015+来一次macOS Monterey的‘大扫除’式安装
每次系统升级后,老款MacBook Pro用户最常抱怨的就是"怎么比之前更卡了?"——这不是错觉。系统迭代过程中积累的缓存文件、残留配置和隐形服务,就像房间角落堆积的灰尘,拖慢了整台机器的响应速度。而通过U盘进行的macOS Monterey全新安装,本质上是一次彻底的"数字大扫除":它不仅替换系统文件,还会重建所有索引、清理磁盘碎片,甚至能解决某些玄学般的卡顿问题。对于2015款及之后的MacBook Pro用户,这种安装方式能让老设备重获新生。
堆排序、快速排序和归并排序
快排的思想、Hoare版分割法、挖坑法分割法、如何优化快速排序。
本地部署大模型:dgx-spark-ai项目硬件选型、部署与私有化AI构建指南
大语言模型(LLM)的本地部署是当前AI工程实践中的重要方向,其核心原理在于将模型权重与计算过程完全置于用户可控的硬件环境中。这项技术的核心价值在于实现了数据主权与隐私安全,避免了云端服务的延迟、持续订阅成本及数据外泄风险。从技术实现看,本地部署的关键在于硬件选型与软件环境适配,尤其是NVIDIA GPU的CUDA生态、显存容量与内存带宽的匹配。在应用场景上,本地化部署特别适合对数据敏感性要求高的领域,如企业内部代码助手、金融分析或医疗研究,能够支持私有化模型微调与定制化工作流构建。本文以dgx-spark
AMS TMF8820/21/28 SPAD 掩码和模式选择
为了实现最快的测距周期,将报告周期设置在测距周期以下的毫秒,以确保不会有等待时间。除了8X8固定了pixel的位置意外,其他模式客户可以设计自己的spad掩码,并将spad单独分配给通道。对于这些spad掩码,有一些约束。左下角提示红色代表光学中心,绿色方框表示单个spad,每个MP里可能有2X3个spad,也有2X5个spad,也有3X6+3个spad,这部分适配光学,最大的程度的利用spad区域。以下是几种预定义的spad掩码,非常灵活的配置,来适配各种3X3,4X4,8X8的模式选择。
快速排序和归并排序中一趟的理解(递归和非递归)
引:2019年408中数据结构一道考察快速排序的选择题 定位:这道题在考察快速排序中一趟的概念。 思路: 回想教材(《数据结构》严)里对一趟的定义 算法描述: 可见第一张图的过程实际上是递归形式实现的快排第一次调用Partition函数产生的结果。 然而,我们知道,如果第一次选中的pivot处在了待排序元素最终结果中的中间位置。那么接下来的处理也是递归进行的, 如图(a)所示,在第一次调用Patition函数后,元素49被放到了最终位置,之后对49左侧位置元素调用Qsort进行处理
从源码编译CesiumForUnreal
从源码编译CesiumForUnreal插件
快速排序,归并排序,改进后的排序
快速排序,归并排序,改进后的排序 快速排序&&归并排序 #include <iostream> #include <ctime> using namespace std; //快速排序 int Split(int a[], int low, int high) { int i = low, temp; if (a[low] < a[(l...
CANoe如何实现TLS协议
TLS,Transport Layer Security,传输层安全协议。是在传输层和应用层之间,为了保证应用层数据能够安全可靠地通过传输层传输且不会泄露的安全防护。
ROS2 CANopen 使用教程
第一步是定义一个结构,用于保存有关接头和相关 CANopen 数据的信息。该结构是 CANopen 网络的基础,可确保存储所有相关数据并在需要时进行访问。
快速排序和归并排序
一篇文章带你学会快排和归并排序。
使用 MATLAB/Simulink 对基于同步发电机的双机系统进行建模,并对其在短路故障条件下的暂态稳定性进行仿真和分析
双机系统(Two-Machine System)是电力系统中用于研究动态稳定性和控制策略的一个典型模型。它由两台同步发电机通过输电网络相互连接,模拟实际电网中的互联操作。当发生短路故障时,系统的暂态稳定性会受到挑战,可能导致发电机失步或电压崩溃。本教程将带你使用 MATLAB/Simulink 对基于同步发电机的双机系统进行建模,并对其在短路故障条件下的暂态稳定性进行仿真和分析。
归并排序与快速排序
快速排序与归并排序快速排序1、原理与实现(1)分治法(2)轴点(3)构造轴点——partition算法(4)实例(5)性能分析(6)改进(7)变种选取(selection)k-selection与中位数选取:通用算法众数减而治之linearSelect() 最开始的几章我们分别介绍了冒泡排序,插入排序,选择排序与归并排序。在介绍散列技术时也介绍了桶排序,基数排序,计数排序。在讨论优先级队列时也介绍了堆排序,以及更为常用的锦标赛排序。 归并排序和快速排序都使用了分治法,但二者又有区别。 归并排序的计算量和难点
LinePhone3.2.7(android端最新版本) sip客户端
SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/
温度PID控制PLC程序
温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值
归并排序和快速排序
一,归并排序 归并排序算法实现 算法思路: 如果要排序一个数组,我们先从数组中间把数组分成左数组和右数组两部分,分别对左右数组进行排序,然后将排序好的数组合并成结果数组,排序就完成了,最后只需将结果数组复制回原数组即可。 核心思想 ----- 分治思想 分治也即是分而治之,将一个大问题分解为小的子问题来解决。分治算法一般都是用递归来实现的。分治是一种解决问题的处理思想,递归是一种编程技巧。 归并...
复分析 阿尔福斯(Ahlfors) 第三版答案 英文
答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案
【排序算法】归并排序与快速排序
一篇文章教你彻底理解快速排序和归并排序
大中型超市收银系统(可直接运行)
光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开
1890



被折叠的 条评论
为什么被折叠?



