算法导论 第8章 线性时间排序

合并排序和堆排序的时间复杂度为O(nlgn),插入排序和冒泡排序的时间复杂度为O(n^2),快速排序的时间复杂度在平均情况下是O(nlgn),这些排序算法都是通过对元素进行相互比较从而确定顺序的,因此都叫比较排序。

比较排序可以看做是决策树(一个满二叉树),因为每一次比较都是一个分支。n个元素的序列,其排序的结果有 n! 种可能(n个元素的全排),所以这个决策树有 n! 个叶子结点,假设树的高度为h,则有:n! <= 2^h,所以h >= lg(n!) = Ω(nlgn)。一次比较排序就是从决策树的根节点走到叶节点,所以比较排序的时间复杂度为Ω(nlgn)。

而计数排序、基数排序和桶排序都是非比较排序,其时间复杂度为O(n),但是这三种排序算法都不是原地排序,占用内存空间较多,而比较排序算法大多都是原地排序。

/*
 *	算法导论 第八章 线性时间排序
 *	计数排序、基数排序和桶排序
 */

#include <iostream>
#include <cmath>
#include <vector>
#include <ctime>
using namespace std;

void printArray(int arr[], int len, char *str)
{
	cout << str << endl;
	for (int i=0; i<len; i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}

int* countingSort(int *arr, int len, int k);
int* radixSort(int *arr, int len, int d);
int getDigit(int num, int d);
int* bucketSort(int *arr, int len, int maxNum);

int main()
{
	int len = 30;
	int k = 10;
	srand(time(NULL));
	int *arr = new int[len];
	for (int i=0; i<len; i++)
	{
		arr[i] = rand() % k;
	}

	//计数排序
	printArray(arr, len, "计数排序前数组");
	int *result = countingSort(arr, len, k);
	printArray(result, len, "计数排序后数组");
	delete[] result;

	//基数排序
	for (int i=0; i<len; i++)
	{
		arr[i] = 100 + rand() % 500;
	}
	printArray(arr, len, "基数排序前数组");
	result = radixSort(arr, len, 3);
	printArray(result, len, "基数排序后数组");
	delete[] result;

	//桶排序
	for (int i=0; i<len; i++)
	{
		arr[i] = rand() % 100;
	}
	printArray(arr, len, "桶排序前数组");
	result = bucketSort(arr, len, 100);
	printArray(result, len, "桶排序后数组");
	delete[] result;

	return 0;
}

/*
 *	计数排序
 *	时间复杂度为O(n+k)
 *	使用计数排序需要在所有元素都在一个小的范围内,即k远小于n
 *	在k=O(n)时,时间复杂度为O(n)
 */
int* countingSort(int *arr, int len, int k)
{
	int *numCount = new int[k]();
	int *result = new int[len];

	//numCount中存储等于i的元素个数
	for (int i=0; i<len; i++)
	{
		numCount[arr[i]]++;
	}

	//numCount中存储小于等于i的元素个数
	for (int i=1; i<k; i++)
	{
		numCount[i] += numCount[i-1];
	}

	//从后至前依次对元素进行排序,保证稳定性,也可以从前往后,但是排序就不稳定了
	for (int i=len-1; i>=0; i--)
	{
		result[numCount[arr[i]]-1] = arr[i];
		numCount[arr[i]]--;
	}

	delete[] numCount;
	return result;
}

/*
 *	基数排序
 *	是建立在计数排序的基础之上的,计数排序的稳定性很重要
 *	否则基数排序就会出错,例如数组[27, 15, 43, 42],如果子排序过程不稳定
 *	则结果就为[15, 27, 43, 42]
 *	时间复杂度为O(d*(n+k)),在d为常数,k=O(n)时,时间复杂度为O(n)
 */
int* radixSort(int *arr, int len, int d)
{
	int *A = new int[len];
	for (int i=0; i<len; i++)
		A[i] = arr[i];
	for (int j=0; j<d; j++)
	{
		int k = 10;
		int *numCount = new int[k]();
		int *result = new int[len];

		//numCount中存储等于i的元素个数
		for (int i=0; i<len; i++)
		{
			numCount[getDigit(A[i], j)]++;
		}

		//numCount中存储小于等于i的元素个数
		for (int i=1; i<k; i++)
		{
			numCount[i] += numCount[i-1];
		}

		//从后至前依次对元素进行排序,保证稳定性,也可以从前往后,但是排序就不稳定了
		for (int i=len-1; i>=0; i--)
		{
			result[numCount[getDigit(A[i], j)]-1] = A[i];
			numCount[getDigit(A[i], j)]--;
		}
		delete[] A;
		delete[] numCount;
		A = result;
	}
	return A;
}

int getDigit(int num, int d)
{
	return (num % (int)pow(10.0, d+1)) / pow(10.0, d);
}

/*
 *	桶排序
 *	在输入符合均匀分布时,桶排序的效果较好
 *	将各个元素分布在n个桶中,每个桶内再使用插入排序
 *	只要各个桶的尺寸的平方和与总的元素数呈线性关系
 *	则其时间复杂度就为O(n)
 */
int* bucketSort(int *arr, int len, int maxNum)
{
	//建立n个桶
	vector<int> *result = new vector<int>[len];
	//将各个元素分布到各个桶内
	for (int i=0; i<len; i++)
	{
		result[(int)((arr[i]/(double)maxNum)*len)].push_back(arr[i]);
	}

	for (int i=0; i<len; i++)
	{
		int n = result[i].size();
		//插入排序
		for (int j=1; j<n; j++)
		{
			int k = j - 1;
			int key = result[i][j];
			while (k>=0 && result[i][k]>key)
			{
				result[i][k+1] = result[i][k];
				k--;
			}
			result[i][k+1] = key;
		}
	}
	//合并各个桶中的元素
	for (int i=0, j=0; j<len; j++)
	{
		int length = result[j].size();
		for (int k=0; k<length; k++)
		{
			arr[i++] = result[j][k];
		}
	}

	delete[] result;
	return arr;
}

算法导论 第8章 线性时间排序

时间: 2024-10-03 21:54:12

算法导论 第8章 线性时间排序的相关文章

算法导论笔记——第八章 线性时间排序

8.1 排序算法的下界 定理8.1 在最坏情况下,任何比较排序算法都需要做Ω(nlgn)次比较. 推论8.2 堆排序和归并排序都是渐进最优的比较排序算法. 8.2 计数排序 计数排序假设n个输入元素中的每一个都是在0到k区间内的一个整数,其中k为某个整数.当k=O(n)时,排序的运行时间为θ(n). 基本思想:对每一个输入元素x,确定小于x的元素个数.C[A[j]] = C[A[j]] + 1 8.3 基数排序 Radix Sort 从LSB到MSB,一位数排序算法必须是稳定的. 若b<(lgn

【算法导论】学习笔记——第8章 线性时间排序

本章节主要证明对包含n个元素的输入序列来说,任何比较排序在最坏情况下都要经过omega(nlgn)次比较.从而证明归并排序和堆排序是渐近最优的.同时,介绍了三种线性时间复杂度的排序算法:计数排序.基数排序和桶排序. 1. 排序算法的下界在确定排序算法的下界时,借助决策树模型.决策树模型是一棵完全二叉树,它可以表示在给定输入规模情况下,某一特定排序算法对所有元素的比较操作.对于比较操作,假定元素互异,因此仅使用小于等于和大于比较操作符.典型的决策树模型如下图所示:显然,n个元素,n!种不同排列,均

算法导论笔记 第8章 线性时间排序

任何比较排序在最好情况下都要经过Ω(nlgn),即比较排序的下界为Ω(nlgn). 合并排序和堆排序都是渐进最优的. 要突破Ω(nlgn),就要进行非比较排序.计数排序.基数排序和桶排序都有非比较的一些操作来确定排序顺序,它们可以达到线性运行时间. 这三种排序都是以空间换时间.应用的不广,先不细看了. 原文地址:https://www.cnblogs.com/jackson-zhou/p/8419798.html

算法导论学习笔记——第8章 线性时间排序

任意一种比较排序算法,在最坏情况下的运行时间下限是Ω(nlgn) 计数排序 假设n个输入元素中的每一个都是介于0到k之间的整数,k为某个整数,当k=O(n)时,计数排序的运行时间为Θ(n) 1 //输入数组A[1..n],存放排序结果数组B[1..n],临时存储区C[0..k] 2 COUNTING-SORT(A,B,k) 3 for i←0 to k 4 do C[i]←0 5 for j←1 to length[A] 6 do C[A[j]]←C[A[j]]+1 7 for i←1 to k

『算法设计_伪代码』线性时间排序及排序算法对比

一.计数排序 二.基数排序 三.桶排序 四.对比不同排序方法 原文地址:https://www.cnblogs.com/hellcat/p/9255591.html

算法导论 第八章 线性时间排序(python)

比较排序:各元素的次序依赖于它们之间的比较{插入排序O(n**2) 归并排序O(nlgn) 堆排序O(nlgn)快速排序O(n**2)平均O(nlgn)} 本章主要介绍几个线性时间排序:(运算排序非比较排序)计数排序O(k+n)基数排序O() 第一节:用决策树分析比较排序的下界 决策树:倒数第二层满,第一层可能满的二叉树,它用来表示所有元素的比较操作{于此来分析下界},忽略控制,移动操作 1:2 #A[1]和A[2]比 <= 走左边 >走右边 <3,1,2> 最后的结果 下标对应排

算法导论-- 线性时间排序(计数排序、基数排序、桶排序)

线性时间排序 前面介绍的几种排序,都是能够在复杂度nlg(n)时间内排序n个数的算法,这些算法都是通过比较来决定它们的顺序,这类算法叫做比较排序 .下面介绍的几种算法用运算去排序,且它们的复杂度是线性时间. -------------------------------------- 1.计数排序 计数排序采用的方法是:对每个元素x,去确定小于x的元素的个数,从而就可以知道元素x在输出数组中的哪个位置了. 计数排序的一个重要性质是它是稳定的,即对于相同的两个数,排序后,还会保持它们在输入数组中的

算法导论第八章线性时间排序

一.线性时间排序算法历史概览 计数排序首先是由 Harold H. Seward 于1954年提出,而且他还提出将计数排序和基数排序进行结合的思想:基数排序是L.J.Comrie于1929年首次在一篇描述卡片穿孔机文档中提出的一种方法,它是从最低有效位开始,对一个有多位数组成的数进行排序的方法:而桶排序的基本思想则由E.J.Isaac和R.C.Singleton于1956年提出的,之后很多研究人员在这三种算法的基础上针对不同的应用场景又进一步改进,到了今天一个很成熟.很通用的地步. 二.O(nl

算法导论之六:线性时间排序之 决策树&amp;计数排序

本系列前五篇都是讲述的比较排序算法,从本文开始,将进入线性时间排序.什么是比较排序,简单的说,就是排序的过程依赖于数组中数据大小的比较,从而来确定数据在排好序输出时的位置. 比较排序法比较直观,但是也有它的不足,我们容易证明任何比较排序法,在最坏的情况下的时间复杂度的下限都是 nlgn.要证明这个问题,我们首先要搞清楚一个模型:决策树模型. 一.决策树模型 什么是决策树?决策树从形态上来讲,是一颗完全二叉树,它除叶子节点之外,其他层的节点都是满的.它的每一个叶子节点表示对输入数据组合的一种排序可