内部排序 (四):选择排序 Selection Sorting (简单选择排序、堆排序)

作为数据结构的课程笔记,以便查阅。如有出错的地方,还请多多指正!

简单选择排序 Simple Selection Sort

排序过程

思路:

  • 首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
  • 再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
  • 重复上述操作,共进行n-1趟排序后,排序结束
    在这里插入图片描述

算法实现

void Simple_selection_sort(SqList_t* list)
{
	for (int i = 1; i < list->len; ++i)
	{
		int min = i;

		for (int j = i + 1; j <= list->len; ++j)
		{
			if (list->rec[min].key > list->rec[j].key)
			{
				min = j;
			}
		}
		if (min != i)
		{
			list->rec[0] = list->rec[i];
			list->rec[i] = list->rec[min];
			list->rec[min] = list->rec[0];
		}
	}
}

算法评价

T(n)

选择排序需执行 n − 1 n-1 n1趟。第 i i i趟排序需要比较 n − i n-i ni

  • 比较次数: ∑ i = 1 n − 1 ( n − i ) = n 2 − n 2 \sum_{i=1}^{n-1} (n-i) = \frac {n^2-n}{2} i=1n1(ni)=2n2n

若待排记录为从小到大排列(正序)

  • 移动次数: 0 0 0

若待排记录为从大到小排列

  • 移动次数: 3 ( n − 1 ) 3(n-1) 3(n1)

∴ T ( n ) = O ( n 2 ) \therefore T(n)=O(n^2) T(n)=O(n2)

S(n)

S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)

是否稳定

  • 不稳定

堆排序 Heap Sort

堆 (heap)

  • 堆采用完全二叉树结构,使用顺序存储。即按层序存储,所用数组起始单元为 1,则结点 i i i 的父结点为 ⌊ i 2 ⌋ \lfloor \frac{i}{2}\rfloor 2i,左、右孩子为 2 i 2i 2i 2 i + 1 2i+1 2i+1
const int maxn = 100;
int heap[maxn];		// 定义堆

  • 最大堆 / 大顶堆 (max heap):任一结点的值 ≥ \geq 其子结点的值,因此最大堆中根结点元素最大
    • 优先队列一般默认使用大顶堆
      在这里插入图片描述
  • 最小堆 / 小顶堆 (min heap):任一结点的值 ≤ \leq 其子结点的值,因此最小堆中根结点元素最小
    在这里插入图片描述

排序过程

思路

  • 初建堆:将无序序列建成一个堆,则堆顶是关键字最小 (或最大) 的记录
  • 调整堆:输出堆顶记录后,将剩余的记录重新调整成一个新堆,则可得到剩余记录中的最小值 (或最大值)
  • 重复执行上一步 ,直到得到一个有序序列

调整堆 / 筛选

  • (1) 将堆顶记录(根)与堆中最后一个记录交换位置 (也就是将堆顶记录输出)
  • (2) 然后将根结点值与其左、右孩子进行比较,并与其中大的进行交换
  • (3) 重复上述操作,直至叶子结点,将得到新的堆,这个从堆顶至叶子的调整过程称为“筛选
    • 可以看出,最小堆适合于升序排序,而最大堆适合于降序排序
// O(logn)
void down_adjust(int heap[], int root, int len)
{
	int max_child;
	heap[0] = heap[root];

	while (root * 2 <= len)
	{
		max_child = root * 2;
		if (max_child + 1 <= len
			&& heap[max_child + 1] > heap[max_child])
		{
			++max_child;
		}
		if (heap[max_child] > heap[0])
		{
			heap[root] = heap[max_child];
			root = max_child;
		}
		else {
			break;
		}
	}
	heap[root] = heap[0];
}

  • 如果想向堆里添加一个元素,那么可以把想要添加的元素放在数组最后(也就是完全二叉树的最后一个结点后面),然后进行向上调整操作:
    • 向上调整总是把欲调整结点与父亲结点比较,如果权值比父亲结点大,那么就交换其与父亲结点,这样反复比较,直到达堆顶或是父亲结点的权值较大为止
// 其中 root 一般设置为 1, leaf 表示欲调整结点的数组下标
void up_adjust(int heap[], int root, int leaf) {
	heap[0] = heap[leaf];
	while (leaf / 2 >= root)
	{
		if (heap[leaf / 2] < heap[leaf])
		{
			heap[leaf] = heap[leaf / 2];
			leaf /= 2;
		}
		else {
			break;
		}
	}
	heap[leaf] = heap[0];
}
void insert(int x, int &len) {
	heap[++len] = x; // 让元素个数加 1, 然后将数组末位赋值为 x
	up_adjust(heap, 1, len);
}

初建堆

  • 从无序序列的第 ⌊ n / 2 ⌋ \lfloor n/2\rfloor n/2 个元素(即此无序序列对应的完全二叉树的最后一个非叶子结点)起,至第一个元素止,进行反复筛选
  • 这种做法保证每个结点都是以其为根结点的子树中的权值最大的结点
    在这里插入图片描述
void heap_sort(int heap[], int len)
{
	// 初建堆 O(n)
	for (int i = len / 2; i >= 1; --i)
	{
		down_adjust(heap, i, len);
	}
	// O(nlogn)
	// 输出 n-1 次,调整堆 n-2 次 
	for (int i = 0; i < len - 1; ++i)
	{
		if(i > 0)
		{ 
			down_adjust(heap, 1, len - i);
		}
		swap(heap[1], heap[len - i]);
	}
}

算法评价

T ( n ) T(n) T(n)

  • (1) 初建堆 ⌊ n / 2 ⌋ \lfloor n/2\rfloor n/2 次筛选
    • 对深度为 k k k 的堆进行筛选时,最多进行 2 ( k − 1 ) 2(k-1) 2(k1) 次关键字比较。由于第 i i i 层上的结点数最多为 2 i − 1 2^{i-1} 2i1,以它们为根的二叉树深度为 h − i + 1 h-i+1 hi+1。因此,初建堆时最大的比较次数为:
      ∑ i = h − 1 1 2 i − 1 ⋅ 2 ( h − i ) = ∑ i = h − 1 1 2 i ⋅ ( h − i ) = ∑ j = 1 h − 1 2 h − j ⋅ j \begin{aligned} &\sum_{i=h-1}^1 2^{i-1} \cdot 2(h-i ) \\=& \sum_{i=h-1}^1 2^{i} \cdot (h-i ) \\=& \sum_{j=1}^{h-1} 2^{h-j} \cdot j \end{aligned} ==i=h112i12(hi)i=h112i(hi)j=1h12hjj ∵ 2 h − 1 ≤ n ≤ 2 h − 1 \because 2^{h-1}\leq n \leq 2^h-1 2h1n2h1 ∴ ∑ j = 1 h − 1 2 h − j ⋅ j ≤ ( 2 n ) ∑ j = 1 h − 1 2 − j ⋅ j ≤ 4 n \begin{aligned} \therefore &\sum_{j=1}^{h-1} 2^{h-j} \cdot j \leq (2n)\sum_{j=1}^{h-1} 2^{-j} \cdot j \leq 4n \end{aligned} j=1h12hjj(2n)j=1h12jj4n ∴ T ( n ) = O ( n ) \therefore T(n)=O(n) T(n)=O(n)
  • (2) 调整成新堆 n − 2 n-2 n2 次筛选
    • 因为 n n n 个结点的完全二叉树最大深度为 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor \log_2n\rfloor+1 log2n+1,因此比较次数最多为:
      2 ( ⌊ log ⁡ 2 ( n − 1 ) ⌋ + ⌊ log ⁡ 2 ( n − 2 ) ⌋ + . . . + log ⁡ 2 2 ) < 2 n ⌊ log ⁡ 2 n ⌋ \begin{aligned}2(\lfloor \log_2(n-1)\rfloor + \lfloor \log_2(n-2)\rfloor +...+\log_22) < 2n\lfloor \log_2n\rfloor \end{aligned} 2(⌊log2(n1)⌋+log2(n2)⌋+...+log22)<2nlog2n ∴ T ( n ) = O ( n log ⁡ 2 n ) \therefore T(n)=O(n\log_2n) T(n)=O(nlog2n)
  • 综上所述,堆排序即使在最坏的情况下,时间复杂度依然为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

S ( n ) S(n) S(n)

S ( n ) = O ( 1 ) S(n)=O(1) S(n)=O(1)

是否稳定

  • 不稳定

总结

  • 记录数较少的序列,堆排序的优越性并不明显,但对大量的记录来说堆排序是很有效的
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值