选择排序
基本思想:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的最后,直到全部记录排序完毕
直接选择排序:
1)基本思想:是一种简单直观的排序,从待排序的记录序列中选择关键码的最小(或最大)的记录并将它与序列中的第一个记录交换位置;然后从不包括第一个位置上的记录序列中选择关键码最小(或最大)的记录并将它与序列中的第2个记录交换位置;如此往复,知道序列中只剩下一个记录为止。
2)代码实现:
public static int[] directSelectSort(int[]arr){
for (int i = 0; i < arr.Length;i++)
{
int index = i;
for (int j = index + 1; j <arr.Length; j++)
{
if (arr[j] < arr[index])
{
index = j;
}
}
int tmp = arr[i];
arr[i] = arr[index];
arr[index] = tmp;
}
return arr;
}
3)时间复杂度:O(n2)
2.堆排序
堆排序是在直接排序的基础上借助于完全二叉树结构而形成的一种排序方法。从数据结构的观点看,堆排序是完全二叉树的顺序存储结构的应用。
在直接排序中,为找出关键字最小的记录需要进行n-1次比较,然后为寻找关键字次小的记录要对剩下的n-1个记录进行n-2次比较。在这n-2次比较中,有许多比较在第一次排序的n-1次比较中已经做了。事实上,直接选择排序的每次排序排序除了找到当前关键字最小的记录外,还长生了许多比较结果的信息,这些信息在以后各次排序中还有用,但由于没有保存这些信息,所以每次排序都要对剩余的全部记录的关键字重新进行一遍比较,这样就大大增加了时间的开销。
堆排序是针对直接排序所存在的上述问题的一种改进方法。它在寻找当前关键字最小记录的同时,还保存了本次排序过程中所产生的其他比较信息。
设有n个元素组成的序列{a0,a1,…an-1},若满足下面的条件:
1) 这些元素是一颗完全二叉树的结点,且对于i=0,1,…,n-1,ai是该完全二叉树编号为i的结点;
2) 满足下列不等式:
则称该序列为一个堆。堆分为最大堆和最小堆两种。满足不等式(a)的是最小堆,满足不等式(b)的是最大堆,如下图所示:
3) 堆的两条性质
最大堆的根结点是堆中关键码最大的结点,最小堆的根结点是堆关键码最小的结点,我们称堆的根结点记录为堆顶记录
对于最大堆,从根结点到每个叶子结点的路径上,结点组成的序列都是递减有序的;对于最小堆,从根结点到每个叶子结点的路径上,结点组成的序列都是递增有序的。
将待排序的记录序列建成一个堆,并借助于堆的性质进行排序的方法称为堆排序。堆排序的基本思想是:设有n个记录,首先将这n个记录按关键码建成堆,将堆记录输出,得到n个记录中关键码最大(或最小)的记录;调整剩余的n-1个记录,使之称为一个新堆,再输出堆记录;如此反复,当堆中只有一个元素时,整个序列的排序结束,得到的序列便是原始序列的非递减或非递增序列。
从堆排序的的基本思想中可以看出,在对排序的过程中,主要包括两方面的工作
1) 如何将原始的记录序列按关键码建成堆
2) 输出堆顶记录后,怎样调整剩下记录,使其关键码称为一个新堆
首先,以最大堆为例,讨论第一个问题:如何将n个记录的序列按关键码建成堆,如图所示
根据前面的定义,将n个记录构成一棵完全二叉树,所有的叶子结点都妈祖最大堆的定义,对于第1个非叶子结点(通常从i=((n-1)-1)/2,i的最小值为0开始),找出第2i+1个记录和2i+2个记录中关键码的较大者,然后与i记录的关键码进行比较,如果第i个记录的关键码大于或等于第2i+1个记录和第2i+1个记录的关键码,则以第i个记录为根结点的完全二叉树已满足最大堆的定义;否则,对换第i条记录和关键码较大的记录,兑换后以第i条记录为根结点的完全二叉树满足最大堆的定义。按照这种方法,再调用第2个非叶子结点(i=(n-1)/2-1),第三个叶子结点,……,直到根结点。当根结点调整完后,则这课树就是一个最大堆了。
构建最大堆的过程如下:
第一步:从i=((n-1)-1)/2=(10-1)/2=4开始,所对应的关键码80 不小于i=90所对应的关键码60和i=10所对应的关键码45,所以不需要调整,如图(b)所示
第二步:当i=3时,所对应的关键码10小于i=7所对应的关键码100,交换他们的位置,如图(b)所示
第三步:当i=2时,所对应的关键码40小于i=6所对应的关键码90,交换他们的位置,如图(d)所示
第四步:当i=10时,对堆顶结点记录进行调整,所对应的关键码30小于i=3岁对应的关键码100,交换他们的位置,这导致i=3所对应的关键码30小于i=8所对应的关键码75,交换他们的位置,如图(e)所示
第五步:当i=0时,对堆顶结点记录进行调整,所对应的关键码70小于i=1所对应的关键码100,交换他们的位置,这导致i=1所对应的关键码70小于i=3和i=4所对应的关键码75和80,将关键码70与关键码80所对应的位置交换,就建立了以关键码100为根结点的完全二叉树是一个最大堆,如图(f)所示,整个堆建立的过程就完成了。
最坏情况下,堆的时间复杂度为O(nlog2n),这是堆的最大优点。堆排序的方法在记录较少的情况下不提倡,但对于记录较多的数据列表还是很有效的。因为其运行时间主要消耗在建初始堆和调整新建堆时进行的反复筛选上。
代码实现:
/// <summary>
/// 堆排
/// </summary>
/// <paramname="arr"></param>
/// <returns></returns>
public static int[]heapSort(int[] arr)
{
for (int i = 0; i< arr.Length; i++)
{
//构建最小堆
int count =arr.Length - i;
int now =(count - 2) / 2 ;
while (now>= 0)
{
int k =now;
int min =-1;
if (2 * k+ 1 > count - 1)//无孩子结点
{
break;
}
else if (2 * k + 2 > count - 1)
{
min =2 * k + 1;
}
else
{
min =arr[2 * k + 1 + i] < arr[2 * k + 2 + i] ? 2 * k + 1 : 2 * k + 2;
}
//int min= arr[2 * k + 1+i] < arr[2 * k + 2+i] ? 2 * k + 1 : 2 * k + 2;
while(min!=-1&&arr[min+i] < arr[k+i])
{
inttmp = arr[k+i];
arr[k+i] = arr[min+i];
arr[min+i] = tmp;
k =min;
if (2* k + 1 > count - 1)//无孩子结点
{
break;
}
elseif (2 * k + 2 > count - 1)
{
min = 2 * k + 1;
}
else
{
min = arr[2 * k + 1+i] < arr[2 * k +2+i] ? 2 * k + 1 : 2 * k + 2;
}
}
now--;
}
}
return arr;
}