直接选择排序
基本思想
在一个待排数组中找到最小或者最大的元素,然后把他放在最左边或者最右边,再以剩下的数组作为待排数组,依次排序,直到排完每个数组。
下图就是一个排列升序的过程。
特性
插入排序的过程是非常好理解的,但是这种方法效率低,排序时在遍历数组的时候还要再遍历后面的部分寻找最大或最小值,时间复杂度为O(N2)。
是在原数组上进行比较和交换,不创建其他的空间,所以空间复杂度为O(1)。
稳定性的话在排序过程中肯定是不能保证稳定的,前面的相同元素完全可能在交换过程中被换到与他相同元素的后面,所以该算法是不稳定的。
优化与实现
对于直接选择排序,我们还是可以对它进行一点小小的优化:
原本的方法是找最大或最小交换到头或尾,每次寻找一个元素,那我们其实也可以一次就找到最大和最小的两个元素,然后把他俩放到头和尾,再排序数组中间的部分,这样就相当于一次排序了两个数,效率比之前增加了一倍。
虽然这样的方法使得直接选择排序得到了一定的优化,但是其时间复杂度O(N2/2)==O(N2)。这任然无法改变它是一个比较低效的算法。
下面我们就来用第二种方法实现一下直接插入排序。
上图就是这个排序的实现过程,其中的Swap是一个交换元素的函数,下面是实现代码
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
直接插入排序的代码我放在下面,有需要可以下来拷贝研究研究
void SelectSort(int* a, int n)
{
int left = 0;
int right = n - 1;
while (left < right)
{
int minIndex = left; int maxIndex = left;
for (int i = left; i <= right; i++)
{
if (a[i] < a[minIndex])
minIndex = i;
if (a[i] > a[maxIndex])
{
maxIndex = i;
}
}
Swap(&a[left], &a[minIndex]);
//如果max和left位置重叠,max被换走了,要修正一下max的位置
if (left == maxIndex)
{
maxIndex = minIndex;
}
Swap(&a[right], &a[maxIndex]);
left++;
right--;
}
}
堆排序
堆排序我曾经在介绍二叉树的顺序存储时介绍过,其原理与数据结构中的二叉树和堆有关,具体的堆的原理和排序的过程大家可以看我的这篇文章:数据结构-二叉树的顺序存储与堆(堆排序),这里面对堆和堆排序进行了比较详细的介绍。
这里我就简单的介绍一下,首先二叉树就是一个节点最多只有两个子节点的树,有一种特殊的二叉树叫做完全二叉树,完全二叉树除了最后一层节点可能不满,其他层的节点数都必须是满的,而且最后一层如果不满,那也只能是从右向左不满,中间不能右空。而堆就是一个特殊的完全二叉树,它的特殊之处就在于堆的每一个父节点都比它的两个子节点大或者小,如果父节点都比子节点大,那就是大堆,如果父节点都比子节点小,那就是小堆。
在堆中有一种叫做向下调整的算法,即从堆的根节点开始,向下调整,通过这个调整可以使一个左右子树都是堆,而节点自身与它的两个子节点不一定是堆的二叉树调整成为一个堆。这种调整方式非常的高效,堆排序就是利用了这种特性。
下面是堆排序的实现过程
下面是向下调整算法和堆排序的代码
void AdjustDwon(int* a, int n, int root)
{
int parent = root;
int child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])//要先检查,再访问
{
child++;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else//如果小,符合情况,所以一定要出去
{
break;
}
}
}
void HeapSort(int* a, int n)
{
//建大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//O(N)
{
AdjustDwon(a, n, i);
}
int end = n - 1;
while (end > 0) //O(N*logN)
{
Swap(&a[0], &a[end]);
AdjustDwon(a, end, 0);
end--;
}
}
以上就是本篇的全部内容