常用算法的时间复杂度和空间复杂度
所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。时间复杂度来说:
(1)平方阶(O(n2))排序
各类简单排序:直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlog2n))排序
快速排序、堆排序和归并排序;
(3)O(n1+§))排序,§是介于0和1之间的常数。 希尔排序
(4)线性阶(O(n))排序
基数排序,此外还有桶、箱排序。
说明:当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n);
而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O(n2);
原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。
稳定性:排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。
稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序不是,稳定的排序算法:选择排序、快速排序、希尔排序、堆排序选择排序算法准则:每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。选择排序算法的依据影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
1.待排序的记录数目n的大小;
2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
3.关键字的结构及其分布情况;
4.对排序稳定性的要求。设待排序元素的个数为n.
1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序 : 如果内存空间允许且要求稳定性的,
归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高.
2) 当n较大,内存空间允许,且要求稳定性 =》归并排序
3)当n较小,可采用直接插入或直接选择排序。
直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。
直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序
4)一般不使用或不直接使用传统的冒泡排序。
5)基数排序
它是一种稳定的排序算法,但有一定的局限性:
1、关键字可分解。
2、记录的关键字位数较少,如果密集更好
3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
————————————————
版权声明:本文为CSDN博主「Sampson Kang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Theoneky/article/details/85201345
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SortType : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
int[] array = new int[]{ 4,7,0,8,2,9,7};
// InsertSort( array);
// ShellSort( array);
// BubbleHighSort( array);
// SelectSort(array);
// SelectHighSort(array);
// QuickSort(array,0,array.Length-1);//快速排序
// BuildingHeap(array,array.Length);
// HeapAdjust(array,0, array.Length-1);
HeapSort(array,array.Length);
DebugArray(array ,"array:");
}
void DebugArray(int[] array,string reason)
{
string content=reason;
for(int i = 0;i<array.Length;i++)
{
content = content +"," +array[i];
}
Debug.Log(content);
}
// Update is called once per frame
void Update()
{
}
//插入排序
#region 直接插入排序
//1—直接插入排序(Straight Insertion Sort),在插入以前,排过的都是有序的
// 如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。
// 所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
// 效率:时间复杂度:O(n^2).
// 其他的插入排序有二分插入排序,2-路插入排序。
private void InsertSort( int [] array)
{
if(array.Length < 2)
{
return;
}
for(int i = 1;i< array.Length;i++)// 在插入以前,排过的都是有序的
{
if(array[i] < array[i -1])//找到一个 位于 i-1 的 ,从这个位置往前遍历,并进行移动
{
int j = i-1;
int x = array[i];
while(j >=0 && x < array[j]) //由if 条件可知,至少while可执行一次
{
array[j+1] = array[j];
j--;
}
array[j +1] = x;//因为while肯定执行了,所以j肯定小 1.所以加 1
}
}
}
#endregion 希尔排序
#region 2. 插入排序—希尔排序(Shell`s Sort)
/** * 直接插入排序的一般形式 * * @param int dk 缩小增量,如果是直接插入排序,dk=1 ,就相当于上面的直接插入排序* */
void ShellInsertSort( int[] array, int dk)
{
if (array.Length< 2)
{
return;
}
for(int i = dk; i< array.Length;i++)
{
if (array[i] < array[i-dk])
{
int j = i - dk;
int x = array[i];
while(j >= 0 && x < array[j])
{
array[j +dk] = array[j];
j -= dk;
}
array[j+dk] = x;
}
}
}
// 我们简单处理增量序列:增量序列d = {n/2 ,n/4, n/8 …1} n为要排序数的个数即:先将要排序的一组记录按某个增量d(n/2,n为要排序数的个数)分成若干组子序列,
// 每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。
// 希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。
// 增量因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:增量因子中除1 外没有公因子,且最后一个增量因子必须为1。希尔排序方法是一个不稳定的排序方法。
// ————————————————
// 原文链接:https://blog.csdn.net/Theoneky/article/details/85201345
void ShellSort( int[] array)
{
if(array.Length < 2)
{
return;
}
int dk = array.Length/2;
// int dk = 1;//相当于直接插入排序
while(dk >=1)
{
ShellInsertSort( array,dk);
DebugArray(array ,"dk:"+ dk + "---");
dk = dk/2;
}
}
#endregion
#region 3. 冒泡排序
//普通冒泡
void BubbleSimpleSort( int [] array)
{
if (array.Length <2)
{
return;
}
for(int i = 0; i < array.Length -1;i ++)
{
for(int j = i +1;j < array.Length;j++)
{
if (array[i] > array[j])
{
int temp = array[i];
array[i] = array[j] ;
array[j] = temp ;
}
}
}
}
//冒泡升级版,遍历一次获得最大和最小的两个
//传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,
//我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。
void BubbleHighSort( int [] array)
{
if (array.Length <2)
{
return;
}
for(int i = 0; i < array.Length/2;i ++)
{
int tempMaxIndex = array.Length -i -1;
for(int j = i +1;j < array.Length -i;j++)
{
if (array[i] > array[j])
{
int temp = array[i];
array[i] = array[j] ;
array[j] = temp ;
}
if (array[tempMaxIndex] < array[j])
{
int temp = array[tempMaxIndex];
array[tempMaxIndex] = array[j] ;
array[j] = temp ;
}
}
}
}
#endregion
#region 选择排序
//简单选择排序 在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;从前往后每次确定一个位置
private int SelectSortPos(int []array, int startIndex)
{
int pos = startIndex;
for(int i = startIndex + 1;i<array.Length;i++)
{
if(array[pos] > array[i])
{
pos = i;
}
}
return pos;
}
private void SelectSort(int[] array)
{
for(int i=0;i<array.Length -1;i++)
{
int pos = SelectSortPos(array,i);//找到位置后互换位置。确定了最小的一个 。与冒泡的区别是,冒泡移动了所有比他大的数据的位置
// Debug.Log("pos "+ array[pos] + ", i"+ i);
int tempValue = array[i];
array[i] = array[pos];
array[pos] = tempValue;
}
}
//简单选择排序的改进——二元选择排序简单选择排序,每趟循环只能确定一个元素排序后的定位。
//可以改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。
private void SelectHighSort(int[] array)
{
for(int i=0;i<array.Length/2 ;i++) //遍历一次找到最大和最小
{
int min = i;
int max = i;
for(int j = i +1;j<array.Length -i;j++)
{
if(array[j] < array[min])
{
min = j;
}
if(array[j] > array[max])
{
max = j;
}
}
// Debug.Log("min ="+ min+ ", max ="+ max);
int tempMin = array[min];
int tempMax = array[max];
array[min] = array[i];
array[i] = tempMin;
array[max] = array[array.Length - i -1];
array[array.Length - i -1] = tempMax;
}
}
#endregion
#region 选择排序—堆排序(Heap Sort)
// 堆排序是一种树形选择排序,是对直接选择排序的有效改进。
// 从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。
// 所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
// 设树深度为k, 从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:
// 而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。
//参考 https://blog.csdn.net/Theoneky/article/details/85201345?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param
/** * 已知array[s…m]除了 array[s] 外均满足堆的定义
* 调整array[s],使其成为大顶堆.即将对第s个结点为根的子树筛选, *
* @param array是待调整的堆数组 * @param s是待调整的数组元素的位置 * @param length是数组的长度 * */
// array 按二叉树规则排列,把从s到length-1 的数据进行排序得到顶部属于大顶的,注意如果不属于 s 节点的子孙节点,则不参与排序。
private void HeapAdjust(int [] array,int s, int length)
{
int temp = array[s];
int child = 2*s +1;//左子节点
while(child < length)
{
while(child < length -1 && array[child] < array[child+1])
{
// 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
child++;
}
// 如果较大的子结点大于父结点
if(temp < array[child])
{
array[s] = array[child]; // 那么把较大的子结点往上移动,替换它的父结点
s = child;// 重新设置s ,即待调整的下一个结点的位置 ,继续对s进行大顶排序
child = 2*s +1;
}
else
{
// 如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
break;
}
}
// 当前待调整的结点放到比其大的孩子结点位置上
array[s] = temp;
// string tip = "HeapAdjust, s "+ s+ ", length "+ length+ " ---";
// DebugArray(array,tip);
}
private void HeapAdjustV2(int[] array,int i,int length)
{
int temp = array[i];
for(int k = 2*i+1;k < length;k = 2*k+1)//k 的初始遍历从2*i +1开始,即 i 的左子节点;递增单位是 2*k +1, 即从 k 的左子节点 2*k+1 开始遍历
{
// string str = string.Format("i ={0}, k={1}",i,k);
// Debug.Log(str);
if(k+1 < length && array[k] < array[k+1])
{
k++;
}
if(temp < array[k] )
{
array[i] = array[k];
i = k;
}
else
{
break;
}
}
array[i] = temp;
}
/** * 初始堆进行调整 * 将H[0..length-1]建成堆 * 调整完之后第一个元素是序列的最大的元素 */
void BuildingHeap(int [] H, int length)
{
//最后一个有孩子的节点的位置
int lastParent= length/2 -1;
for(int i = lastParent; i>= 0;i--) //开始从最低有子节点的开始进行堆排序,一级一级 建堆,最终能得到的就是一个大顶堆,即最顶部是最大值,而其他位置的则不确定。根据二叉树可以理解,最顶上的两个一级子节点,左右两个子节点的树没有进行过对比。
{
HeapAdjustV2(H,i,length);
}
}
void HeapSort(int[] H,int length)
{
//初始堆
BuildingHeap(H,length);
//从最后一个元素开始对序列进行调整
for(int i = length-1;i>0 ;i--)
{
//交换堆顶元素H[0]和堆中最后一个元素 。 因为HeapAdjust得到的是大顶,H[0] 是最大值 ,把0与i 互换后,保证了 i 是参与二叉树排序中的最大的
int temp = H[i];
H[i] = H[0];
H[0] = temp;
HeapAdjustV2(H,0,i);//继续对 0~i中按二叉树规则选出大顶。
}
}
#endregion
#region 交换排序—快速排序(Quick Sort) 特点:每次选取的基准数据通过排序后可以确定此元素的正确位置。
//参考链接 https://blog.csdn.net/weixin_42109012/article/details/91645051
// 1)选择一个基准数据,通常选择第一个元素或者最后一个元素,
// 2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准数据值小。另一部分记录的 元素值比基准值大。
//* 3)此时基准数据在其排好序后的正确位置
// 4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。快速排序的示例
private void QuickSort(int [] array,int low,int high)
{
// 开始默认基准为 low
if(low < high)
{
// 分段位置下标
int stand = GetStandard(array,low,high);
// 递归调用排序
// 左边排序
QuickSort(array,low, stand-1);
// 右边排序
QuickSort(array,stand+1, high);
}
}
private void Swap(int[] array, int i,int j)
{
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
//进行一次,确定一次low与 high 中间的 基准数据 的准确位置
private int GetStandard(int[] array,int low,int high)
{
// 基准数据
int baseData = array[low];
while(low < high)
{
// 因为默认基准是从左边开始,所以从右边开始比较
// 当队尾的元素大于等于基准数据 时,就一直向前挪动 high 指针
while(low< high && array[high] >= baseData)
{
high--;
}
// 当找到比 array[low] 小的时,就把后面的值 array[high] 赋给它
if (low < high)
{
Swap(array,low,high);
}
//交换后 array[high] = baseData
// 当队首元素小于等于基准数据 时,就一直向后挪动 low 指针
while(low < high && array[low] <= baseData)
{
low ++;
}
// 当找到比 array[high] 大的时,就把后面的值 array[low] 赋给它
if (low < high)
{
Swap(array,low,high);
}
}
// 跳出循环时 low 和 high 相等,此时的 low 或 high 就是 key 的正确索引位置
// 把基准数据赋给正确位置
array[low] = baseData;
DebugArray(array ,"GetStandard:"+ "low "+low + "high "+ high + "----");
return low;
}
#endregion
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SortTrain : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
int [] array = new int[]{5,4,8,5,9,3,2};
// InsertSort(array);
// ShellInsertSort(array);
// SelectSort(array);
// HighSelectSort(array);
// BubleSimpleSort(array);
// BubleHighSort(array);
// QuickSort(array,0,array.Length -1);
// HeapSort(array);
DebugArray(array,"HeapSort");
}
void DebugArray(int[] array, string reason)
{
var tip = reason + ": ";
for(int i = 0; i<array.Length;i++)
{
tip = tip + array[i] + ",";
}
Debug.Log(tip);
}
// Update is called once per frame
void Update()
{
}
#region //插入排序
void InsertSort(int[] array)
{
if (array.Length < 2)
{
return;
}
for(int i = 1;i< array.Length;i++)
{
if (array[i] < array[i - 1])
{
int temp = array[i];
for(int j = i -1;j >=0;j--)
{
if(temp < array[j])
{
array[j +1] = array[j];
i = j;
}
}
array[i] = temp;
}
}
}
#endregion
#region 希尔排序
void ShellSort(int[] array, int dk)
{
for(int i = dk; i <array.Length; i = i +dk)
{
if(array[i] < array[i -dk])
{
int temp = array[i];
for(int j = i -dk;j >0;j = j -dk)
{
if(array[j] > temp)
{
array[j+dk] = array[j];
i = j;
}
}
array[i]= temp;
}
}
}
void ShellInsertSort(int[] array)
{
if(array.Length < 2)
{
return;
}
int s = array.Length >> 1;
for(int i =s; i>0; i = i >>1)
{
ShellSort(array,i);
}
}
#endregion
#region 选择排序
int GetSelectPos(int[] array,int start)
{
int tempMin = array[start];
for(int i = start +1;i<array.Length;i ++)
{
if (array[i] < tempMin)
{
tempMin = array[i];
start = i;
}
}
return start;
}
void SelectSort(int[] array)
{
for(int i = 0; i<array.Length -1; i++)
{
int pos = GetSelectPos(array,i);
Swap(array,pos,i);
}
}
void HighSelectSort(int[] array)
{
for(int i = 0; i<array.Length/2; i++)
{
int min = i;
int max = i;
for(int j = i + 1;j< array.Length -i;j ++)
{
if (array[j] < array[min])
{
min = j;
}
if (array[j] > array[max])
{
max = j;
}
}
Swap(array,i,min);
Swap(array,array.Length -i-1,max);
}
}
#endregion
#region 冒泡排序
void BubleSimpleSort(int[] array)
{
for(int i = 0; i< array.Length -1;i++)
{
for(int j = i + 1; j< array.Length;j++)
{
if(array[i] > array[j])
{
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
//冒泡排序升级版
void BubleHighSort(int[] array)
{
for(int i = 0; i< array.Length/2 ;i++)
{
int curMaxIndex = array.Length-i -1;
for(int j = i + 1; j < array.Length -i;j++)
{
if(array[i] > array[j])
{
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
else if (array[curMaxIndex] < array[j] )
{
int temp = array[curMaxIndex];
array[curMaxIndex] = array[j];
array[j] = temp;
}
}
}
}
#endregion
#region 快速排序
int GetStandard(int[] array, int low , int high)
{
int baseData = array[low];
while(low < high)
{
while(low < high && array[high] >= baseData)
{
high --;
}
if (low < high )
{
Swap(array, low,high);
}
while(low < high && array[low] <= baseData)
{
low++;
}
if (low < high)
{
Swap(array, low,high);
}
}
array[low] = baseData;
return low;
}
void QuickSort(int [] array, int low,int high)
{
if(low < high)
{
int standardIndex = GetStandard(array,low,high);
QuickSort(array,low,standardIndex -1);
QuickSort(array,standardIndex +1,high);
}
}
#endregion
#region 堆排序
//对堆中,以start 为父节点的二叉树进行排序
void HeapAdjust(int[] array,int start, int length)
{
int temp = array[start];
int s = start;
int child = 2 *s +1;
while(child < length)
{
while(child + 1 < length && array[child] < array[child +1])
{
child ++;
}
if(temp < array[child])
{
array[s] = array[child];
//继续遍历以child为父节点的二叉树
s = child;
child = 2*s +1;
}
else
{
break;
}
}
array[s] = temp;
}
void BuildingHeap(int[] array)
{
for(int i = array.Length/2 -1;i >=0; i --)
{
HeapAdjust(array,i,array.Length);
}
}
void HeapSort(int [] array)
{
BuildingHeap(array);
for(int i = array.Length -1;i>0;i--)
{
Swap(array,i,0);
HeapAdjust(array,0, i);
}
}
#endregion
public void Swap(int[] array , int i,int j)
{
if(i < 0 || j < 0 || array.Length <=i || array.Length <= j )
{
return;
}
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
参考 算法的时间复杂度和空间复杂度: