直接选择排序、堆排序的联系与区别

52 篇文章 2 订阅
本文详细介绍了两种经典的排序算法——直接选择排序和堆排序。直接选择排序通过每次选取无序区最小元素与有序区末尾交换实现排序,平均时间复杂度为O(n^2)。堆排序利用堆的性质调整序列,达到排序目的,平均时间复杂度为O(nlogn)。文中提供了两种排序算法的C++实现,并讨论了它们的时间复杂度、空间复杂度和稳定性。
摘要由CSDN通过智能技术生成

    在数据结构中,直接选择排序、堆排序,都属于选择排序,即每次从待排序的序列中选出最小的关键字,把它放到已排序的末尾,直到整个序列变成有序为止。其时间复杂度、空间复杂度、稳定性的对比如下:

算法对比直接选择排序堆排序
平均时间复杂度O(n^2)O(nlogn)
最坏情况O(n^2)O(nlogn)
最好情况O(n^2)O(nlogn)
空间复杂度O(1)O(1)
属于稳定算法不属于不属于

1、直接选择排序

    基本思路:假设记录放在R[0,n-1]之中,R[0,i-1]是有序区,R[i,n-1]是无序区,且有序区所有关键字均小于无序区所有关键字。需要将R[i]添加到R[0,i-1]之中,使R[0,i]变成有序。每一趟排序,都会从无序区R[i,n-1]里选中一个最小的关键字R[k],使其与R[i]进行交换,这样R[0,i]就变成有序的了,直到i=n-1为止。
//直接选择排序源码:

//直接选择排序2
vector<int> SelectionSort2(vector<int> &list) {
	int len = list.size();
	for (int i = 0; i < len - 1; i++) {
		int k = i;
		for (int j = i + 1; j < len; j++) {
			if (list[j] < list[k]) //用k记录无序区中最小元素的索引
				k = j;
		}

		int tmp = list[i];
		list[i] = list[k];
		list[k] = tmp;
	}

	return list;
}

2、堆排序

    堆是一颗顺序存储的完全二叉树,它分为2种:大根堆、小根堆。

  • 若父节点比左右孩子都大,则为大根堆;
  • 若父节点比左右孩子都小,则为小根堆;

    基本思路:首先,将R[1,n]调整为堆(这个过程称为初始建堆),交换R[1]和R[n];然后,将R[1,n-1]调整为堆,交换R[1]和R[n-1];如此反复,直到交换了R[1]和R[2]为止。

    堆排序的第一步为:将一个元素序列调整为堆。在R[i+1,m]已经是堆的情况下,可按下述方法(也叫筛选法、调整法),将R[i,m]调整为以R[i]为根的堆。

  • 从R[2i]和R[2i+1]中选出关键字较大者,不妨假设R[2i]较大,再比较R[i]和R[2i]的关键字,若前者较大,则说明以R[i]为根的子树已经是堆,不必作任何调整;否则,则要交换R[i]和R[2i]。交换后,以R[2i+1]为根的子树仍是堆,而以R[2*i]为根的子树由于根节点关键字已经改变可能不再满足堆的定义,需要做调整。
  • 重复上述过程,将以R[2*i]为根的子树调整为堆,如此一层一层进行下去,直到排到树叶位置为止。

//堆排序源码

/-------------------- 堆排序 ---------------
//调整堆
void heapAdjust(vector<int> &list, int parent, int length) {
	int tmp = list[parent];      //保存父节点
	int child = 2 * parent + 1;  //左孩子索引
	while (child < length)
	{
		//如果parent有右孩子,则要判断左孩子是否小于右孩子
		if (child + 1 < length && list[child] < list[child + 1])
			child++;

		//父节点大于子节点,就不用做交换
		if (tmp >= list[child])
			break;
		//将较大子节点的值赋值给父节点
		list[parent] = list[child];
		parent = child;

		//找到下一个左孩子
		child = 2 * parent + 1;
	}
	list[parent] = tmp;
}

//堆排序
void HeapSort(vector<int> &list) {
	int len = list.size();
	for (int i = len / 2 - 1; i >= 0;i--) {
		heapAdjust(list, i, len);
	}

	for (int i = len - 1; i > 0;i--)
	{
		int tmp = list[0];
		list[0] = list[i];
		list[i] = tmp;
		heapAdjust(list, 0, i);
	}
}

说明:完全二叉树,是一种有序树,因为它区分左子树和右子树。

  • 定义:若一颗二叉树,最多只有最下两层的结点度数(分支数)可以小于2,并且最下面一层的叶子结点依次排列在该层的最左边的位置上,则该二叉树就称为完全二叉树。如图(1)所示。
图(1) 完全二叉树的图例

3、完整源码

//seletionsort.cpp

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

//直接选择排序1
vector<int> SelectionSort(vector<int> &list) {
	int len = list.size();
	for (int i = 0; i < len - 1;i++) {
		int tmpIndex = i;
		for (int j = i + 1; j < len; j++) {
			if (list[j] < list[tmpIndex])
				tmpIndex = j;
		}

		int tmpData = list[i];
		list[i] = list[tmpIndex];
		list[tmpIndex] = tmpData;
	}

	return list;
}

//直接选择排序2
vector<int> SelectionSort2(vector<int> &list) {
	int len = list.size();
	for (int i = 0; i < len - 1; i++) {
		int k = i;
		for (int j = i + 1; j < len; j++) {
			if (list[j] < list[k]) //用k记录无序区中最小元素的索引
				k = j;
		}

		int tmp = list[i];
		list[i] = list[k];
		list[k] = tmp;
	}

	return list;
}


//输出所有的元素
void printList(vector<int> list){
	for (int i = 0; i < list.size(); i++){
		printf("%d ", list[i]);
		if ((i + 1) % 5 == 0)
			printf("\n");
	}
	printf("\n");
}

//输出前K大的元素
void printListTopK(vector<int> list, int top) {
	if (top >list.size() || top <= 0){
		printf("the top is out of range!");
		return;
	}

	int cnt = 0;
	int nBase = list.size() - 1;
	for (int i = nBase; i > nBase - top; i--){
		printf("%d ", list[i]);
		cnt++;
		if ( cnt % 5 == 0)
			printf("\n");
	}
	printf("\n");
}


//-------------------- 堆排序 ---------------
//调整堆
void heapAdjust(vector<int> &list, int parent, int length) {
	int tmp = list[parent];      //保存父节点
	int child = 2 * parent + 1;  //左孩子索引
	while (child < length)
	{
		//如果parent有右孩子,则要判断左孩子是否小于右孩子
		if (child + 1 < length && list[child] < list[child + 1])
			child++;

		//父节点大于子节点,就不用做交换
		if (tmp >= list[child])
			break;
		//将较大子节点的值赋值给父节点
		list[parent] = list[child];
		parent = child;

		//找到下一个左孩子
		child = 2 * parent + 1;
	}
	list[parent] = tmp;
}

//堆排序
void HeapSort(vector<int> &list) {
	int len = list.size();
	for (int i = len / 2 - 1; i >= 0;i--) {
		heapAdjust(list, i, len);
	}

	for (int i = len - 1; i > 0;i--)
	{
		int tmp = list[0];
		list[0] = list[i];
		list[i] = tmp;
		heapAdjust(list, 0, i);
	}
}

//找出堆中前K大的元素
vector<int> HeapSortTopK(vector<int> &list, int top) {
	vector<int> topNode;
	int len = list.size();
	//进行len/2-1次调整
	for (int i = len / 2 - 1; i >= 0;i--) {
		heapAdjust(list, i, len);
	}

	//输出元素(求前K大)
	for (int i = len - 1; i >= len - top;i--)
	{
		//将堆顶与堆的第i个元素进行互换
		int tmp = list[0];
		list[0] = list[i];
		list[i] = tmp;

		//将最大值加入vector
		topNode.push_back(tmp);

		//重新构造堆
		heapAdjust(list, 0, i);
	}
	return topNode;
}

int main() {

	int arry[5] = { 20, 40, 50, 10, 60 };
	vector<int> dataVec(arry, arry + 5);

	//1)直接选择排序
	//SelectionSort(dataVec);
	//printList(dataVec);

	//2) 输出前K大的元素
	//HeapSortTopK(dataVec, 3);
	//printListTopK(dataVec, 3);

	//3)堆排序
	HeapSort(dataVec);
	printList(dataVec);

	system("pause");
	return 0;
}

4、参考文献

李春葆.数据结构习题与解析B级.2006

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sanqima

一键三连,多多益善

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值