03-堆排序(包含选择排序)

女生的网名这么多〃 提交于 2019-12-20 07:03:22

选择排序:顾名思义,就是每次循环从数组[i,n)选择一个最小的元素放入位置i,一共n个元素,需要n-1轮循环。伪代码如下:

void selectionSort(ElementType A[], int N){
    for(i=0; i<N; i++){
        //从A[]到A[]中找最小元,并将其位置赋给MinPosition
        MinPosition=scanForMin(A,i,N-1);
        //将未排序部分的最小元换到有序部分的最后位置
        swap(A[i],A[MinPositon]);
    }
}
#include<bits/stdc++.h>
using namespace std;

int scanForMin(vector<int>& arr,int start,int end) {
	int minPos = start;
	for (int i = start; i <= end;i++) {
		minPos = arr[i] < arr[minPos] ? i : minPos;
	}
	return minPos;
}

void selectionSort(vector<int>& arr) {
	int n = arr.size();
	int minPos = 0;
	for (int i = 0; i < n; i++) {
		minPos = scanForMin(arr, i, n - 1);
		swap(arr[i], arr[minPos]);
	}
}

int main() {
	vector<int> arr = { 8,6,10,7,9,4,3,2,5,1 };
	selectionSort(arr);
	for (auto x : arr) {
		cout << x << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
1 2 3 4 5 6 7 8 9 10
请按任意键继续. . .

性能分析:无论如何,T=Θ(N^2)。如何快速找到最小元呢,针对N个位置的遍历无法加速,我们唯一能加速的就是通过scanForMin找到MinPosition的过程,每次找最小的元素,很容易联想到堆数据结构。利用小顶堆每个找到最小元素,获取最小元素O(1)时间复杂度,调整堆O(logn)时间复杂度,所以时间复杂度变为O(Nlog N)。不过建队需要额外O(N)空间,并且复制元素需要时间。

  • 时间复杂度:O(n^2)
  • 稳定性:不稳定

堆排序:利用堆数据结构加快scanForMin过程从而降低时间复杂度。

算法1:建立小顶堆,每次从堆顶取出最小元素。

#include<bits/stdc++.h>
using namespace std;

void percDown(vector<int>& arr,int p,int n) { //小顶堆的percDown操作,n:元素个数
	int parent, child;
	int x = arr[p];
	for (parent = p; parent*2+1<n; parent=child) {
		child = 2 * parent + 1;
		if (child != n - 1 && arr[child] > arr[child + 1]) child++;
		if (x < arr[child]) break;
		else arr[parent] = arr[child];
	}
	arr[parent] = x;
}

void buildHeap(vector<int>& arr) { //建小顶堆
	int n = arr.size();
	for (int i = n / 2 - 1; i >= 0; i--) {
		percDown(arr,i,n);
	}
}

int deleteMin(vector<int>& arr,int n) {
	int res = arr[0];
	arr[0] = arr[n-1];
	percDown(arr,0,n-1);
	return res;
}

void heapSort1(vector<int>& arr) {      //O(nlog n)
	int n = arr.size();
	buildHeap(arr);                     //O(n)
	vector<int> tmp;
	for (int i = 0; i < n; i++) {       //O(n)
		tmp.push_back(deleteMin(arr,n-i));  //O(log n)
	}
	for (int i = 0; i < n; i++) {       //O(n)
		arr[i] = tmp[i];
	}
}

int main() {
	vector<int> arr = { 8,6,10,7,9,4,3,2,5,1 };
	heapSort1(arr);
	for (auto x : arr) {
		cout << x << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
1 2 3 4 5 6 7 8 9 10
请按任意键继续. . .

算法2:建立一个大顶堆,每次将对顶元素与当前需要调节的最后一个元素互换,这样最大元素调整到最终位置,现在只要对前面的元素调用percDown操作即可,以此类推...直到数组整体有序。

#include<bits/stdc++.h>
using namespace std;

void percDown(vector<int>& arr,int p,int n) { //大顶堆的percDown操作,n:元素个数
	int parent, child;
	int x = arr[p];
	for (parent = p; parent*2+1<n; parent=child) {
		child = 2 * parent + 1;
		if (child != n - 1 && arr[child] < arr[child + 1]) child++;
		if (x > arr[child]) break;
		else arr[parent] = arr[child];
	}
	arr[parent] = x;
}

void buildHeap(vector<int>& arr) { //建大顶堆
	int n = arr.size();
	for (int i = n / 2 - 1; i >= 0; i--) {
		percDown(arr,i,n);
	}
}

void heapSort(vector<int>& arr) {   //O(nlog n)
	int n = arr.size();
	buildHeap(arr);                 //O(n)
	for (int i = n-1; i > 0; i--) {
		swap(arr[0],arr[i]);        //deleteMax操作
		percDown(arr,0,i);
	}
}

int main() {
	vector<int> arr = { 8,6,10,7,9,4,3,2,5,1 };
	heapSort(arr);
	for (auto x : arr) {
		cout << x << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}
#include<bits/stdc++.h>
using namespace std;
//将n个元素的数组以arr[p]为根的子堆调整为最大堆
void percDown(vector<int>& arr,int p,int n) { //大顶堆的percDown操作,n:元素个数
	int parent, child;
	int x = arr[p];
	for (parent = p; parent*2+1<n; parent=child) {
		child = 2 * parent + 1;
		if (child != n - 1 && arr[child] < arr[child + 1]) child++;  //child指向左右子节点的较大者
		if (x > arr[child]) break; //已经在合适位置,直接break
		else arr[parent] = arr[child]; //下滤
	}
	arr[parent] = x;
}

void heapSort(vector<int>& arr) {   //O(nlog n)
	int n = arr.size();
	for (int i = n / 2 - 1; i >= 0; i--) {  //建大顶堆
		percDown(arr, i, n);
	}
	for (int i = n-1; i > 0; i--) {
		swap(arr[0],arr[i]);        //deleteMax操作
		percDown(arr,0,i);
	}
}

int main() {
	vector<int> arr = { 8,6,10,7,9,4,3,2,5,1 };
	heapSort(arr);
	for (auto x : arr) {
		cout << x << " ";
	}
	cout << endl;
	system("pause");
	return 0;
}

性能分析:建堆是从第一个非叶子结点开始的,不断调用percDown操作,大堆和小堆的percDown操作结构一致,只是内部小于大于号判断有所变化。算法2原地操作,O(1)额外空间开销,时间复杂度也能达到O(nlog n)。(perc:percolate)

  • 定理:堆排序处理N个不同元素的随机排列的平均比较次数是2NlogN-O(Nlog logN)。
  • 虽然堆排序给出最佳平均时间复杂度,但实际效果不如用Sedgewick增量序列的希尔排序。
  • 稳定性:不稳定
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!