顺序插入排序:

--- 顺序插入排序
---@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为基数的个数