简单选择排序
算法思想:每一次找到序列中最大值的位置,然后将其与最后一个交换,长度为n的序列完成n-1趟就能够实现从小到大排序。
空间复杂度:O(1)
时间复杂度:稳定O(n2)
稳定性:不稳定
可以用于链表
void selectSort(int a[], int length){
for (int i = length -1 ; i >= 0 ; i--){
int maxPos = 0;
for (int j = 1 ; j <= i ; j ++)
if (a[j] > a[maxPos]){
maxPos = j;
}
int tem = a[i];
a[i] = a[maxPos];
a[maxPos] = tem;
}
return ;
}
堆排序
堆:
满足根大于左右子树,称大根堆,完成递增排序;
满足根小于左右子树,称小根堆,完成递减排序。
堆可以看作是完全二叉树,同时算法代码实现用的也是二叉树顺序存储结构。
即根a[i],对应左孩子a[2i],右孩子a[2i+1]
堆排序思想就是通过堆来获取最大或者最小值,然后放在最后,视作已经排序元素,不计入堆中,然后再进行堆获取最值。最后就能够完成排序。
所以堆排序最重要的问题就有三个:
- 怎么通过堆获取最值,这个简单,根据定义,根结点就是最值,拿出来就行。
- 怎么构造堆?
- 与平衡二叉树类似,如果进行结点删除,怎么维护堆?
构造堆,以大根堆为例。
从定义出发只要保证根这个位置,比他的子树都大就行。完全二叉树可以知道如果位置<=n/2向下取整那么这个结点一定不是叶子结点,所以就从这里开始判断。
如果这个根比他的孩子小,就需要将他的孩子换上来。当然这里得选择两个孩子最大的那个换上来。换完之后还并没有结束,因为这个根也许比他的孙子还要小,所以需要一直往下判断,直到这个根比他的左右孩子都要大为止。这个过程是堆排序中最重要的过程。
这个过程还有一个优化的点,类似插入排序不断后移覆盖的思想,可以将根结点放在一个变量里,然后每一次如果需要将根结点替换,直接将其覆盖,而不是进行交换,需要跟根结点判断改为跟变量判断。然后最后将变量覆盖到最后根所在位置即可。
对于每一个非叶结点进行上述操作那么就可以获得一个堆。
构造完堆,就需要将其放到最后,也就是可以直接将对顶与堆的最后一个结点交换,然后视最后一个结点已经不在堆的考虑范围之内就行。然后对根再进行一个上述的一步维护操作就可以的到n-1个结点的堆。然后循环你懂的。
空间复杂度:O(1)
时间复杂度:O(nlogn)。其中建堆操作需要O(n)。维护堆的操作最多4logn也就是在O(logn)。
稳定性:不稳定
算法优点,每一次获取最值时间复杂度为O(logn)
额外补充:
如果在堆中插入一个元素,以大根堆为例,可以将插入元素放在最后,然后判断他的父结点是否比他小,如果比他小他俩换位,一直向上这样判断。
如果需要在堆中删除一个元素,让最后一个结点代替他的位置,然后对这个位置做一次维护操作就行。
void opt(int a[], int length, int pos){
int tem = pos;
int j;
for (int i = pos ; i < length; i >>= 1){
j = pos << 1;
if (j < length-1 && a[j+1] > a[j])
j++;
if (tem >= a[j])
break;
a[i] = a[j];
}
a[j] = tem;
return ;
}
void buildHeap(int a[], int length){
for (int i = (length-1)/2 ; i > 0 ; i--){
opt(a, length, i);
}
}
void HeapSort(int a[], int length){
buildHeap(a, length);
for (int i = length-1 ; i > 0; i--){
int a[0] = a[1];
a[1] = a[i];
a[i] = a[0];
opt(a, length, 0);
}
}