选择排序
选择排序的基本思想是:每一趟(如第 i i i 趟)在后面 n − i + 1 ( i = 1 , 2 , … , n − 1 ) n-i+1 \quad (i=1,2,\dots,n-1) n−i+1(i=1,2,…,n−1) 个待排序元素中选取关键字最小的元素,作为有序子序列的第 i i i 个元素,直到第 n − 1 n-1 n−1 趟做完,待排序元素只剩下1个,就不用再选了。
简单选择排序
根据上面选择排序的思想,可以很直观地得岀简单选择排序算法的思想:假设排序表为L[1..n]
,第
i
i
i 趟排序即从L[i..n]
中选择关键字最小的元素与L(i)
交换,每一趟排序可以确定一个元素的最终位置,这样经过
n
−
1
n-1
n−1 趟排序就可使得整个排序表有序。
简单选择排序算法的代码如下:
void SelectSort(int *A, int n) {
for (int i = 0; i < n - 1; i++) { //一共进行n-1趟
int min = i; //记录最小元素位置
for (int k = i + 1; k < n; k++) //在A[i...n-1]中选择最小的元素
if (A[k] < A[min])
min = k; //更新最小元素位置
if (min != i) { //交换
int temp = A[min];
A[min] = A[i];
A[i] = temp;
}
}
}
简单选择排序算法的性能分析如下:
空间效率:仅使用常数个辅助单元,故空间效率为 O ( 1 ) O(1) O(1)。
时间效率:从上述伪码中不难看出,在简单选择排序过程中,元素移动的操作次数很少,不会超过 3 ( n − 1 ) 3(n-1) 3(n−1) 次,最好的情况是移动0次,此时对应的表已经有序;但元素间比较的次数与序列的初始状态无关,始终是 n ( n − 1 ) / 2 n(n-1)/2 n(n−1)/2 次,因此时间复杂度始终是 O ( n 2 ) O(n^2) O(n2) 。
稳定性:在第 i i i 趟找到最小元素后,和第 i i i 个元素交换,可能会导致第 i i i 个元素与其含有相同关键字元素的相对位置发生改变。例如,表L={2, 2, 1},经过一趟排序后L={1, 2, 2},最终排序序列也是L={1, 2, 2},显然,2与2的相对次序已发生变化。因此,简单选择排序是一种不稳定的排序方法。
堆排序
堆的定义如下,
n
n
n 个关键字序列L[1...n]
称为堆,当且仅当该序列满足:
1)L(i)>=L(2i)
且L(i)>=L(2i+1)
或
2)L(i)<=L(2i)
且L(i)<=L(2i+1)
(
1
≤
i
≤
⌊
n
/
2
⌋
)
(1 \le i \le \left \lfloor n/2 \right \rfloor )
(1≤i≤⌊n/2⌋)
可以将该一维数组视为一棵完全二叉树,满足条件1的堆称为大根堆(大顶堆),大根堆的最大元素存放在根结点,且其任一非根结点的值小于等于其双亲结点值。满足条件2的堆称为小根堆(小顶堆),小根堆的定义刚好相反,根结点是最小元素。下图所示为一个大根堆。
堆排序的思路很简单:首先将存放在L[1...n]
中的
n
n
n 个元素建成初始堆,由于堆本身的特点(以大顶堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中仅剩一个元素为止。
可见堆排序需要解决两个问题:
1)如何将无序序列构造成初始堆
2)输出堆顶元素后,如何将剩余元素调整成新的堆?
堆排序的关键是构造初始堆。 n n n 个结点的完全二叉树,最后一个结点是第 ⌊ n / 2 ⌋ \left \lfloor n/2 \right \rfloor ⌊n/2⌋ 个结点的孩子。对第 ⌊ n / 2 ⌋ \left \lfloor n/2 \right \rfloor ⌊n/2⌋ 个结点为根的子树筛选(对于大根堆,若根结点的关键字小于左右孩子中关键字较大者,则交换),使该子树成为堆。之后向前依次对各结点( ⌊ n / 2 ⌋ − 1 ∼ 1 \left \lfloor n/2 \right \rfloor -1 \sim 1 ⌊n/2⌋−1∼1 )为根的子树进行筛选,看该结点值是否大于其左右子结点的值,若不大于,则将左右子结点中的较大值与之交换,交换后可能会破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止。反复利用上述调整堆的方法建堆,直到根结点。
如下图所示:
1)初始时调整L(4)
子树,09<32,交换,交换后满足堆的定义;
2)向前继续调整L(3)
子树,78 <左右孩子的较大者87,交换,交换后满足堆的定义;
3)向前调整L(2)
子树,17 <左右孩子的较大者45,交换后满足堆的定义;
4)向前调整至根结点L(1)
,53 <左右孩子的较大者87,交换,交换后破坏了L(3)
子树的堆,采用上述方法对L(3)
进行调整,53<左右孩子的较大者78,交换,至此该完全二叉树满足堆的定义。
输出堆顶元素后,将堆的最后一个元素与堆顶元素交换,此时堆的性质被破坏,需要向下进行筛选。将09和左右孩子的较大者78交换,交换后破坏了L(3)
子树的堆,继续对L(3)
子树向下筛选,将09和左右孩子的较大者65交换,交换后得到了新堆,调整过程如下图所示。
下面是建立大根堆的算法:
void HeadAdjust(int *A, int k, int len) {
//函数HeadAdjust将元素k为根的子树进行调整
A[0] = A[k]; //A[0]暂存子树的根结点
for (int i = 2 * k; i < len; i *= 2) { //沿key较大的子结点向下筛选
if (i < len && A[i] < A[i + 1])
i++; //取key较大的子结点的下标
if (A[0] > A[i])
break; //筛选结束
else {
A[k] = A[i]; //将A[i]调整到双亲结点上
k = i; //修改k值,以便继续向下筛选
}
}
A[k] = A[0]; //被筛选结点的值放入最终位置
}
void BuildMaxHeap(int *A, int len) {
for (int i = len / 2; i > 0; i--)
HeadAdjust(A, i, len); //从i=[n/2]〜1,反复调整堆
}
调整的时间与树高有关,为 O ( h ) O(h) O(h) 。在建含 n n n 个元素的堆时,关键字的比较总次数不超过 4 n 4n 4n,时间复杂度为 O ( n ) O(n) O(n),这说明可以在线性时间内将一个无序数组建成一个堆。
下面是堆排序算法:
void HeapSort(int *A, int len) {
BuildMaxHeap(A, len); //初始建堆
for (int i = len; i > 1; i--) { //n-1趟的交换和建堆过程
cout << A[1] << "\t"; //输出堆顶元素
int temp = A[1]; //堆顶堆底交换
A[1] = A[i];
A[i] = temp;
HeadAdjust(A, 1, i - 1); //调整,把剩余的i-1个元素整理成堆
}
cout << A[1] << "\t"; //输出最后一个堆顶元素
}
同时,堆也支持插入操作。对堆进行插入操作时,先将新结点放在堆的末端,再对这个新结点向上执行调整操作。大根堆的插入操作示例如下图所示。
堆的插入操作代码如下:
void HeapInsert(int *A, int len, int element) {
A[len + 1] = element; //将新结点放在堆的末端
//从这个新结点开始向上调整堆
int i = len + 1;
while (i > 1) {
if (A[i] > A[i / 2]) { //和双亲结点交换
int temp = A[i];
A[i] = A[i / 2];
A[i / 2] = temp;
} else
break; //筛选结束
i /= 2;
}
}
堆排序适合关键字较多的情况。例如,在1亿个数中选出前100个最大值?首先使用一个大小为100的数组,读入前100个数,建立小顶堆,而后依次读入余下的数,若小于堆顶则舍弃, 否则用该数取代堆顶并重新调整堆,待数据读取完毕,堆中100个数即为所求。
堆排序算法的性能分析如下:
空间效率:仅使用了常数个辅助单元,所以空间复杂度为 O ( 1 ) O(1) O(1) 。
时间效率:建堆时间为 O ( n ) O(n) O(n) ,之后有 n − 1 n-1 n−1 次向下调整操作,每次调整的时间复杂度为 O ( h ) O(h) O(h) ,故在最好、最坏和平均情况下,堆排序的时间复杂度为 O ( n log 2 n ) O(n\log_{2}{n}) O(nlog2n) 。
稳定性:进行筛选时,有可能把后面相同关键字的元素调整到前面,所以堆排序算法是一种不稳定的排序方法。例如,表L={1, 2, 2},构造初始堆时可能将2交换到堆顶,此时L={2, 1, 2},最终排序序列为L={1, 2, 2},显然,2与2的相对次序已发生变化。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。