顺序插入排序:
--- 顺序插入排序
---@param arr 需要排序的表
local InsertSort = function(arr)
for i = 2, #arr do
local j = i - 1
local tmp = arr[i]
--采用顺序查找法查找插入位置
if arr[i] < arr[j] then
while j >= 1 and tmp < arr[j] do
arr[j + 1] = arr[j]--移动元素
j = j - 1
end
--插入正确位置
arr[j + 1] = tmp
end
end
return arr
end
折半插入排序:
--- 折半插入排序
---@param arr 需要排序的表
local BLinsertSort = function(arr)
for i = 2, #arr do
local tmp = arr[i]
local low, hight, mid = 1, i - 1, 0
--采用二分查找法查找插入位置
while low <= hight do
mid = math.floor((low+hight)/2)
if tmp < arr[mid] then
hight = mid - 1
else
low = mid + 1
end
end--循环结束arr[hight + 1]则为插入位置
--移动元素
local j = i - 1
while j > 0 and j > hight do
arr[j+1] = arr[j]
j = j - 1
end
--插入正确位置
arr[hight + 1] = tmp
end
return arr
end
希尔排序:
--- 希尔排序算法
---@param arr 需要排序的表
---@param dlta 增量因子表
local ShellSort = function(arr, dlta)
for k = 1, #dlta do
--对arr进行一趟增量为dk的Shell排序,dk为步长因子
local dk = dlta[k]
for i = dk+1, #arr, 1 do
if arr[i] < arr[i - dk] then
local tmp = arr[i]
local j = i - dk
--顺序查找 间隔为dk
while j > 0 and tmp < arr[j] do
arr[j + dk] = arr[j]
j = j - dk
end
--插入正确位置
arr[j + dk] = tmp
end
end
end
return arr
end
冒泡排序:
--- 冒泡排序
---@param arr 需要排序的表
local BubbleSort = function(arr)
--flag 作为是否有交换的标记
local flag = 1
for i = 1, #arr - 1 do
if flag == 0 then return arr end
flag = 0
for j = 1, #arr - i do
if arr[j] > arr[j+1] then
--发生交换,flag置为1,若本趟没发送交换,说明排序完成,flag保持为0
flag = 1
arr[j], arr[j+1] = arr[j+1], arr[j]
end
end
end
return arr
end
快速排序:
local Sort, Partition
--- 快速排序
---@param arr 需要排序的表
---@param low 表的最低索引
---@param hight 表的最高索引
Sort = function(arr, low, hight)
--长度大于1
if low < hight then
--取arr表中心位置,用于将表分为两个子表
local pivot = Partition(arr, low, hight)
--将arr[low...hight] 一分为二,pivot为中心枢轴元素排好序的位置
Sort(arr, low, pivot - 1) --对小于pivot子表递归排序
Sort(arr, pivot + 1, hight) --对大于pivot子表递归排序
end
return arr
end
--- 将枢轴移动到表(arr)中心位置
---@param arr 子表
---@param low 表的最低索引
---@param hight 表的最高索引
Partition = function(arr, low, hight)
--取low为枢轴(中心)
local pivotKey = arr[low]
--子表长度大于1
while low < hight do
--从后往前遍历,小于枢轴的值则--hight
while low < hight and arr[hight] >= pivotKey do
hight = hight - 1
end
--移动到子表低位
arr[low] = arr[hight]
--从前往后遍历,大于枢轴的值则++low
while low < hight and arr[low] <= pivotKey do
low = low + 1
end
--移动到子表高位
arr[hight] = arr[low]
end--遍历后得到low == hight的位置,就是此子表的中心位置
--赋值中心位置
arr[low] = pivotKey
return low
end
--调用
Sort(arr, 1, #arr)
简单选择排序:
--- 简单选择排序
---@param arr 需要排序的表
local SelectSort = function(arr)
for i = 1, #arr do
--选择每一趟中最小值的下标
local idx = i
for j = i + 1, #arr do
--记录最小值下标
idx = arr[idx] > arr[j] and j or idx
end
--交换
if i ~= idx then
arr[i],arr[idx] = arr[idx],arr[i]
end
end
return arr
end
堆排序:
--- 堆排序
---@param arr 需要排序的表
local HeapSort = function(arr)
local len = #arr
--建初始堆(升序一般用大根堆,降序一般用小根堆)
local i = math.floor(len/2)
while i >= 1 do
HeapAdjust(arr, i, len)
i = i - 1
end
--进行n - 1趟排序
for j = len, 1, -1 do
--跟与最后一个元素交换
arr[1], arr[j] = arr[j], arr[1]
--对arr[1]到arr[i - 1]重新堆排序
HeapAdjust(arr, 1, j - 1)
end
return arr
end
--- 堆筛选
---@param arr 需要筛选的表
---@param s 根结点
---@param m 最后一个叶子节点
local HeapAdjust = function(arr, s, m)
--已知arr[s..m]中记录的关键字除arr[s]之外均满足堆的定义,本函数调整arr[s]的关键字
--使得arr[s..m]成为一个大根堆
local tmp = arr[s]
local j = 2*s
--沿着值较大的孩子结点向下筛选
while j <= m do
--记录值较大的孩子下标
if j < m and arr[j] < arr[j+1] then j = j + 1; end
--根结点大于最大孩子值,则无需变换
if tmp >= arr[j] then break end
arr[s] = arr[j]
--tmp应插入在s位置上
s = j
j = 2 * j
end
--插入正确位置
arr[s] = tmp
end
各排序方法的综合比较:
一、时间性能
1、按平均的时间性能来分,有三类排序方法:
- 时间复杂度为O(nlogn)的有:快速排序、堆排序和归并排序,其中以快速排序为最好
- 时间复杂度为O(n²)的有:直接插入排序、冒泡排序和简单排序,其中以直接插入为最好,特别是对那些对关键字近似的记录序列尤为如此
- 时间复杂度为O(n)的有:基数排序
2、当待排记录按关键字顺序有序时,直接插入排序和冒泡排序能达到O(n)的时间复杂度;而对于快速排序而言。这是最不好的情况,此时的时间性能退化为O(n²),因此是应该尽量的情况
3、简单选择排序、堆排序和归并排序的时间性能不随记录序列中关键字的发布而改变
二、空间性能
1、所有的简单排序方法(直接插入、冒泡和简单选择)和堆排序的空间复杂度为O(1)
2、快速排序为O(logn),为栈所需的辅助空间
3、归并排序所需辅助空间最多,其空间复杂度为O(n)
4、链式基数排序需附设队列收尾指针,则空间复杂度为O(rd)
三、排序方法的稳定性能
1、稳定的排序方法指的是,对于两个关键字相等的记录,他们在序列中的相对位置,在排序之前和经过排序之后,没有改变
2、当对多关键字的记录序列进行LSD方法排序,必须采用稳定的排序方法
3、对于不稳定的排序方法,只要能举例出一个实例即可说明
4、快速排序和堆排序是不稳定的排序方法
四、关于排序方法的时间复杂度的下限
1、初基数排序外,其他方法都是基于”比较关键字“进行排序的排序方法,可以证明,折类排序方法可能达到最快的时间复杂度为O(nlogn),基数排序不是基于”比较关键字“的排序方法,所以它不受这个限制
2、可以用一个判定树来描述这类基于”比较关键字“进行排序的排序方法
五、排序方法比较
排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 |
直接插入排序 | O(n²) | O(1) | 稳定 |
希尔排序 | ~O(n^1.3) | O(1) | 不稳定 |
冒泡排序 | O(n²) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | 不稳定 |
直接选择排序 | O(n²) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(n²) | 不稳定 |
选择排序 | O(nlogn) | O(n) | 稳定 |
基数排序 | O(k*(n+m)) | O(n+m) | 稳定 |
- 基数排序中k代表待排元素的维数,m为基数的个数