一:选择排序的算法思想是
每一次在一组数中选最大的放到最后,然后再在剩余的数中选次大的数放到倒数第二个位置,直到这组数选完为止;(以升序为例)
二:优化:
传统的简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。
思路:
1:首先标记最大和最小的位置的下标
2:判断最左位置下标的数和最小值下标的数比较,如果大则交换,否则向后移动,判断最右位置下标的数和最大值下标的数比较,如果小则交换.
实现:
void SelectSort(int *a, int n)
{
assert(a);
int left = 0;
int right = n - 1;
while (left<right)
{
int MinIndex = left;//最小值的下标
int MaxIndex = right;//最大值的下标
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]);
//修正,如果最左边的元素就是最大的元素,那么就要把最小值的下标给最大值的下标
if (MaxIndex == left)
{
MaxIndex = MinIndex;
}
swap(a[right], a[MaxIndex]);
++left, --right;
}
}
选择排序的时间复杂度是O(n^2),当原数组有序排序后的时间复杂度仍然是O(n^2);
三:堆排序
我们知道堆其实就是一颗完全二叉树或者我们可以看成是近似完全二叉树:
1:父节点的值大于或者等于任何一个子节点的值
2:左右字数都满足一个堆;
前面我们也了解堆排序就是一种选择排序,我们以大堆为例,我们把堆顶与最后的位置交换,然后其余的元素继续调整建堆,再把堆顶的元素和倒数第二个位置的元素进行交换,直到所有的数有序;
关于建堆是找到非叶节点,然后向下调整建堆:
我么通过动图来感受下堆排序的过程:
实现:
void _AjustDown(int *a, size_t n, int root)
{
assert(a);
int parent = root;
int child = parent * 2 + 1;
//找到第一个非叶节点
while (child<n)
{
//找较大的孩子
if (child + 1 < n && a[child +1] >a[child])
{
++child;
}
//比较孩子和父亲谁大,如果孩子大就和父亲交换,更新孩子和父亲的位置
if (child+1<n && a[child] > a[parent])
{
swap(a[child], a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int *a, int n)
{
assert(a);
//我们知道堆是从根节点开始向调整
for (int i = (n - 2) / 2; i >= 0; --i)
{
_AjustDown(a, n,i);
}
//选择最大的和最后一个数交换
int end = n-1;
while (end>0)
{
swap(a[0], a[end]);
_AjustDown(a, end, 0);
--end;
}
}
堆排序相比选择排序它的效率更高,时间复杂度o(N^lgN).