直接移动 ( 交换数据位置 )
逻辑移动 ( 改变指针,更快 )
-
冒泡排序法 / 交换排序法
从第一个元素开始,比较两个相邻元素的大小,若大小顺序有误,则对调后再进行下一次元素比较,确定最后一个元素位于正确的顺序。接着再进行第二次扫描,直到完成所有元素的排序。for i in range(len(data)-1, 0, -1): for j in range(i): if data[j] > data[j+1]: data[j], data[j+1] = data[j+1], data[j]
-
选择排序法( Selection Sort )
算是枚举法的应用
反复从未排序的数列中取出最小的元素,加入到另一个数列中。for i in range(len(data)-1): for j in range(i+1, len(data)): if data[i] > data[j]: data[i], data[j] = data[j], data[i]
-
插入排序法( Insert Sort )
将数组中的元素逐一与 已排好序 的数据进行比较,前两个元素先排好,再将第三个元素插入合适的位置,重复直到完成。for i in range(1, len(data)): cur = data[i] pre = i - 1 while pre >= 0 and cur < data[pre]: data[pre + 1] = data[pre] # 把数后置一位,给小数腾地儿 pre -= 1 # pre = -1,说明该值是目前最小的,把他放在第一个,即 pre = 0 data[pre + 1] = cur
-
希尔排序法( Shell Sort )
可以减少插入排序搬移数据的次数。
将数据区分成 特定间隔 的几个小区快,以插入排序法排完区块内的数据后再渐渐减少隔离的距离。- 划分数,不一定是 2,质数最好。间隔为 8/2
8,7,6,5,4,3,2,1
(8, 4),(7, 3),(6, 2),(5, 1) # 并不是真的把两个数搬到一起,只是在形式上
插入排序后
(4, 8),(3, 7),(2, 6),(1, 5)
再缩小间隔 (8/2)/2
(4, 3, 2, 1),(8, 7, 6, 5)
插入排序后
(1, 2, 3, 4),(5, 6, 7, 8)
jmp = len(data) // 2 while jmp != 0: # 插入排序 for i in range(jmp, len(data)): tmp = data[i] j = i - jmp while j >= 0 and tmp < data[j]: data[j + jmp] = data[j] j -= jmp data[j + jmp] = tmp jmp = jmp // 2
- 划分数,不一定是 2,质数最好。间隔为 8/2
-
合并排序法( Merge Sort )
针对以排好序的两个或两个以上的数列(或数据文件),通过合并的方式,将其组合成一个大的且已排好序的数列(或数据文件)。
2 路( 2-way )合并排序:
① 将 N 个长度为 1 的键值,成对的合并成 N/2 个长度为 2 的有序键值组
② 将 N/2 个长度为 2 的键值组,成对的合并成 N/4 个长度为 4 的有序键值组
③ 不断合并,直到键值组长度为 N,且已排好序l3 = [] idx1 = idx2 = 0 for i in range(len(l1) + len(l2)-2): if l1[idx1] < l2[idx2]: l3.append(l1[idx1]) idx1 += 1 if idx1 == len(l1): l3.extend(l2[idx2:]) break else: l3.append(l2[idx2]) idx2 += 1 if idx2 == len(l2): l3.extend(l1[idx1:]) break
-
快速排序法( Quick Sort ) / 分割交换排序法
目前公认最佳。使用了分而治之( Divide and Conquer )。
先在数据中找到一个虚拟的中间值,并按此中间值将所有打算排序的数据分为两部分。小于中间值的再左边,大于的再右边。再以同样的方式分别处理左、右两边数据,直到排完序为止。
假设有 n 项记录 R1,R2,R3,...,Rn,其键值为 k1, k2,k3,...,kn:
① 先假设 K 的值为第一个键值
② 从左向右找出键值 Ki,使得 Ki > K
③ 从右向左找出键值 Kj,是的 Kj < K
④ 如果 i < j,那么 Ki 与 Kj 互换,并回到步骤 ②
⑤ 若 i ≥ j,则将 K 与 Kj 互换,并以 j 为基准点分割成左右两部分。然后针对左右两部分进行步骤 ① 至 ⑤,直到左半边键值等于右半边键值def quick(d, size, lf, rg): # 第一项键值为 d[lf] # size = len(d) if lf < rg: # 左右两边索引值 # ② lf_idx = lf + 1 while d[lf_idx] < d[lf]: if lf_idx + 1 == size: break lf_idx += 1 # ③ rg_idx = rg while d[rg_idx] > d[lf]: rg_idx -= 1 # ④ while lf_idx < rg_idx: d[lf_idx], d[rg_idx] = d[rg_idx], d[lf_idx] lf_idx += 1 while d[lf_idx] < d[lf]: lf_idx += 1 rg_idx -= 1 while d[rg_idx] > d[lf]: rg_idx -= 1 # ⑤ d[lf], d[rg_idx] = d[rg_idx], d[lf] quick(d, size, lf, rg_idx-1) # 以rg_idx为基准点分成左右两半,以递归方式分别为左右两边进行排序直至完成排序 quick(d, size, rg_idx+1, rg)
-
基数排序法
并不需要元素间进行比较,属于一种分配模式排序方式。
按比较的方向可分为最高位优先 ( Most Significant Digit First , MSD ) 和最低位优先 ( Least Significant Digit First , LSD ) 两种。
MSD 是从最左边的位数开始比较,LSD 是从最右边的位数开始比较。def radix(data): """0~999 整数比较""" n = 1 # n 为基数,从个位开始排序 while n <= 100: tmp = [[None] * 100 for row in range(10)] # 设置暂存数组 [[n位为0的所有元素], [?1], [?2], ... , [?9]]。 for i in range(len(data)): m = (data[i] // n) % 10 # n 位的数值,n=1为个位,n=10为10位 tmp[m][i] = data[i] # 放入棋盘对应位置 k = 0 for i in range(10): for j in range(len(data)): if tmp[i][j] is not None: data[k] = tmp[i][j] # 读出棋盘里的值,重构数组 k += 1 n *= 10
原数组 [3, 5, 32, 74, 169, 2, 111, 0]
过程:
比较个位 [0, 111, 32, 2, 3, 74, 5, 169]
比较十位 [0, 2, 3, 5, 111, 32, 169, 74]
比较百位 [0, 2, 3, 5, 32, 74, 111, 169]
结果:
[0, 2, 3, 5, 32, 74, 111, 169]
冒泡、选择、插入排序三种数据搬移量最大的是 插入排序。
合并排序法是稳定的。不会改变两个相同数字的原有顺序。