《王道》第15章 排序--PART2

                        《王道》第15章 排序--PART2

目录

3 选择排序

3.1 简单选择排序

基本思想

算法实现

算法分析

3.2 堆排序

基本思想

算法实现

算法分析

算法应用——TOP K问题


3 选择排序

    选择排序基本思想:每一趟在n - i + 1(i = 1,2,…n - 1)个记录中选取关键字最小的记录作为有序序列中第i个记录。

    选择排序引申出简单选择排序和堆排序。

3.1 简单选择排序

基本思想

    第1趟,在待排序记录r[1]~r[n]中选出最小的记录,将它与r[1]交换;第2趟,在待排序记录r[2]~r[n]中选出最小的记录,将它与r[2]交换;以此类推,第i趟在待排序记录r[i]~r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕。

    以下为简单选择排序的存储状态,其中大括号内为无序区,大括号外为有序序列:

    初始序列:{ 49 27 65 97 76 12 38 }

    第1趟:12与49交换:12{ 27 65 97 76 49 38 }

    第2趟:27不动 :12 27{65 97 76 49 38}

    第3趟:65与38交换:12 27 38{97 76 49 65}

    第4趟:97与49交换:12 27 38 49{76 97 65}

    第5趟:76与65交换:12 27 38 49 65{97 76}

    第6趟:97与76交换:12 27 38 49 65 76 97 完成

算法实现

#include
#include 
using namespace std;
void SelectSort(int *a, int n)
{
	int i,j,min;
	for (i = 0; i < n - 1; i++)
	{
		min = i;                            //每次将min置成无序组起始位置元素下标   
		for (j = i +1; j < n; j++)          //遍历无序组,找到最小元素
                {              
		    if (a[min]>a[j])			
		        min = j;
		}
		if (min != i)     //如果最小元素不是无序组起始位置元素,则与起始元素交换位置   
		    swap(a[i], a[min]);	
	}
}

int main()
{
	int a[6] = { 5, 4, 3, 2, 9, 1 };
	int i = 0;
	SelectSort(a, 6);                  //这里需要将数列元素个数传入。可用sizeof在函数内求得元素个数。   
	for (i = 0; i < 6; i++)
	{
		printf("%d ", a[i]);
	}
	system("pause");
	return 0;
}

算法分析

    由于要选出最小值,故无序区中的每个元素都要参与比较,所以无论初始数据序列的状态如何,总的比较次数为:

    C = n - 1 + n - 2 + n - 3 + … + 2 + 1 = n(n - 1) / 2
 

    故任何时候直接选择排序的时间复杂度为O(N ^ 2),空间复杂度为O(1)

   直接选择排序是一个不稳定的算法。例如,排序序列为{ 5,3,2,5,4,1 },第一趟排序后得到{ 1,3,4,5,4,5 },两个5的相对位置发生了变化。

3.2 堆排序

基本思想

    参考博客
    堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想是,将待排序的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复执行,便能得到一个有序序列了。

算法实现

#include 
using namespace std;



// 函数AdjustDown将元素k向下进行调整,堆主要的两个函数之一
/*
    A为序列,start为初始下标,end为终止下标
*/
void HeapAdjust(int *A, int start, int end)
{
	int i = 0;
	int temp = A[start];        //A[start]暂存
	for (i = 2 * start+1; i < end; i =i*2+1){       
		if (i < end && A[i] < A[i + 1]){
			i++;                             
		}
		if (temp > A[i])
		{
			break;                          
		}
		else{
			A[start] = A[i];                     
			start = i;                          
		}
	}
	A[start] = temp;                             //将筛选结点的值放入最终位置
}


//堆排序算法
void HeapSort(int *A, int len){
	int i = 0;
    for(i = len / 2 - 1; i >= 0; i--)
    {
        HeapAdjust(A, i, len -1);     
    }
	          
    for (i = len-1; i > 0; i--)
    {          
	swap(A[i], A[0]);               //输出堆顶元素(和堆底元素交换)
	HeapAdjust(A, 0, i-1);         //整理,把剩余的i个元素整理成堆
    }
}

算法分析

时间复杂度  O(nlog2n),在最好、最坏和平均情况下,堆排序的时间复杂度为O(nlog2n)

空间复杂度  O(1),仅使用了常数个辅助单元

稳定性:不稳定

另外,由于初始构建堆所需的比较次数比较多,因此,它并不适合待排序序列个数较少的情况。 

算法应用——TOP K问题

    如果要选出前K大,则将数据中的前K个元素建立成一个小根堆,从第K + 1个元素开始往后依次比较,如果元素大于小根堆的堆顶,那么就和堆顶交换,交换后重新调整为小根堆。这样变量一遍所有数据,最后得到的大小为K的小根堆就是前K大的树。

    如果要选出前K小,则将数据中的前K个元素建立成一个大根堆,从第K + 1个元素开始往后依次比较,如果元素小于小根堆的堆顶,那么就和堆顶交换,交换后重新调整为大根堆。这样变量一遍所有数据,最后得到的大小为K的大根堆就是前K小的树。

    以选出前k小的数为例,代码:

#include 
using namespace std;
int main()
{
	int data[] = { 1, 3, 5, 6, 2, 4, 8, 7 };   //存储输入的n个数
	int len = sizeof(data) / sizeof(int);
	int k;
	cin >> k;

	if (k>len || k <= 0)
		return;

	int *b = new int[k];
	for (int i = 0; ib[0])
			continue;
		else
		{
			b[0] = data[i];
			AdjustDown(b, 0, k);
		}
	}

	for (int i = 0; i

4 排序总结

参考博客1

参考博客2

关于排序你应该知道的

《王道》第15章 排序--PART2_第1张图片

PS:更正下表错误:简单选择排序不稳定

《王道》第15章 排序--PART2_第2张图片

1)稳定性

    所有简单排序(时间复杂度为O(n^2))都是稳定排序,简单选择排序除外;

    所有改进排序都是不稳定排序,归并排序除外。

2)比较次数

    比较次数与初始排列无关的是选择排序。

    在初始序列基本有序的情况下,最优的是直接插入排序,此时时间复杂度O(n),其次是冒泡排序,时间复杂度也为O(n),快速排序在此时性能最差,时间复杂度O(n^2)。

    同时,快速排序在初始序列逆序的时候,性能也最差,此时时间复杂度也为O(n^2)。

    堆排序对初始数据集的排列顺序不敏感,在最好、最坏和平均情况下,堆排序的时间复杂度均为O(nlog2n)。

3)空间复杂度

    基于比较的排序算法中,归并排序的空间复杂度最高,为O(n),其次为快速排序,为O(logn),其余为O(1)。

4)时间复杂度

    基于比较的排序算法时间复杂度下界为O(nlog2n)。

 

 

你可能感兴趣的:(【专项】《王道》)