1、直接选择排序(Straight Select Sorting)
(1)描述
从无序区选出一个最小的数,与第1
个数交换;
再从剩下无序数据中找出最小的数,与第2
个数交换,总共选择n-1
次。
(2)实现
两层循环,外层循环控制当前轮最小值应该存放的数组下标i
,
内层循环从i
到最后选出最小值的下标min
。
如果i
和min
不是同一个值,则交换。
void straight_select(int data[], int length)
{
int i, k;
int min;
for (i = 0; i < length-1; ++i)
{
min = i;
for (k = i+1; k < length; ++k)
if (data[k] < data[min])
min = k;
if (i != min)
SWAP_INT(data[i], data[min])
}
}
(3) 时间复杂度
因为每次只能选出1
个元素,每次选择需要比较无序区的所有元素,所以
时间复杂度固定为 O(n2) 。
当然也可以优化,比如每次选出2
个元素,可以是1
个max
和1
个min
,也可以是min
的top2
,但也只是把外层循环由n-1
变成了n/2
,最终只是无关紧要的项有变化,复杂度仍然是 O(n2) 。
(4)空间复杂度 O(1)
(5)稳定性:不稳定
比如 2, 2, 1,第1轮选择把第1个2交换到了最后。
(2)堆排序(Heapsort)
(1)描述
这里所说的堆并不是内存的堆,而是数据结构的二叉堆。
二叉堆是一棵完全二叉树,分为两种:最大堆和最小堆。
最大堆的父结点的值总是大于或等于它的子节点,
最小堆的父结点的值总是小于或等于它的子节点,
左右子节点之间并无固定的大小关系。
满二叉树的第1层有1个节点,第2层有2个,第3层有4个,
第 h 层有 2h-1 个节点,是一个公比为 2
的等比数列。
根据等比数列求和公式:
如果从堆顶往下只遍历某一个子树就可以选择出最值,就可以把直接选择排序的选择最值部分的时间复杂度从 O(n) 降到了 O(logn) 。
2、实现
1.构建一个二叉堆。
2.交换堆顶(第1个数据)与最后一个数据。
3.利用堆的便利,轻松将前n-1个数据重新构建堆。重复2和3步骤,一共选择n-1次。
void build_heap(int data[], int father, int length)
{
int child;
for(child = 2 * father + 1; child < length; child = 2 * father + 1)
{
if (child+1 < length && data[child+1] > data[child])
++child;
if (data[child] > data[father])
{
SWAP_INT(data[child], data[father])
father = child;
continue;
}
break;
}
}
void heap_sort(int data[], int length)
{
for(int i = length/2 -1; i >=0; --i)
{
build_heap(data, i, length);
}
for(int i = 1; i < length; ++i)
{
SWAP_INT(data[0], data[length-i])
build_heap(data, 0, length-i);
}
}
用数组实现是很方便的,因为数组可以随机访问,根据父节点,访问子节点的复杂度是O(1)。
如果用链表实现,则需要把每个节点的父节点和左右子节点记录下来,需要自定义结构体,如果用STL中的list<int>这样的链表,父子节点访问对方的复杂度是O(n),这样一来整个排序的时间复杂度上升了,与直接选择排序相比也丧失了优势。
STL的algorithm有提供堆排序的算法,只需要提供一个比较元素大小的函数即可。代码链接
(3) 时间复杂度
代码大致分为2个部分:1是初始构建堆,2是选出最值+构建堆;
1)初始构建堆
设节点总数为 n
, 设堆的高度为 h
,
设最大的非叶子节点所在的层为第k
层,则k = h - 1
。
初始构建堆时,
第 h
层,因为全是叶子节点,不需要构建堆,即构建0
次。
第 h-1
层,构建1
次堆。
所以第 i
层需要构建 h-i
次堆。
且第i
层最多有 2(i-1) 个节点。
所以第 i
层一共需要构建堆的次数为
前 i
层一共是
初始构建堆的过程是前 k
层,一共最多需要构建堆多少次,接下来就是化简 Si ,再把 i = k
代入计算即可。
等式两边同时乘以 2
得:
两式相减(幂相同的项相减)得:
除了首尾的两个项,中间是等比数列可以用等比数列求和公式计算:
因为 k = h - 1 ,所以:
根据上面算过的 h 的取值范围:
去掉低阶项,初始构建堆的时间复杂度是 O(n) 。
2)从堆顶往下构建堆的时间复杂度是O(logn),一共选择(n-1)次,时间复杂度是O(nlogn) 。
两项相加,初始构建堆的时间复杂度是低阶项可以忽略不计,所以:
时间复杂度 O(nlogn) 。
网友评论