• 算法导论 第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-3 排序不同长度的数据项

    /*
     *	算法导论 习题8-3.a
     *	首先利用计数排序对数组按位数排序
     *	然后再利用基数排序对各不同分组排序
     *	时间复杂度为O(n)
     */
    
    #include <iostream>
    #include <ctime>
    using namespace std;
    
    typedef int (*getCountKey)(int, int);
    void countSort(int *arr, int len, int k, getCountKey getCntKey, int arg=0);
    void radixSort(int *arr, int p, int r, int digitCnt);
    int getDigit(int num, int d);
    int getDigitCnt(int num, int arg=0);
    void printArray(int arr[], int len, char *str)
    {
    	cout << str << endl;
    	for (int i=0; i<len; i++)
    	{
    		cout << arr[i] << " ";
    	}
    	cout << endl;
    }
    
    
    int main()
    {
    	int arr[] = {12, 8, 99, 90, 10, 3, 6, 7, 122, 100, 1000, 1340, 1200, 1802};
    
    	int len = sizeof(arr) / sizeof(arr[0]);
    	int k = 1;
    	for (int i=0; i<len; i++)
    	{
    		int temp = getDigitCnt(arr[i]);
    		if (temp > k)
    		{
    			k = temp;
    		}
    	}
    	k++;
    	printArray(arr, len, "原数组");
    	countSort(arr, len, k, getDigitCnt);
    	printArray(arr, len, "计数排序后");
    
    	int digitCount = getDigitCnt(arr[0]);
    	int pos = 0;
    	for (int i=0; i<len; i++)
    	{
    		int cnt = getDigitCnt(arr[i]);
    		if (cnt != digitCount)
    		{
    			radixSort(arr, pos, i-1, digitCount);
    			pos = i;
    			digitCount =cnt;
    		} else if (i == len-1) {
    			radixSort(arr, pos, len-1, digitCount);
    		}
    	}
    	printArray(arr, len, "基数排序后");
    
    	return 0;
    }
    
    void countSort(int *arr, int len, int k, getCountKey getCntKey, int arg)
    {
    	int *arr1 = new int[len];
    	int *counts = new int[k]();
    
    	for (int i=0; i<len; i++)
    	{
    		counts[getCntKey(arr[i], arg)]++;
    	}
    
    	for (int i=1; i<k; i++)
    	{
    		counts[i] += counts[i-1];
    	}
    
    	for (int i=len-1; i>=0; i--)
    	{
    		arr1[counts[getCntKey(arr[i], arg)]-1] = arr[i];
    		counts[getCntKey(arr[i], arg)]--;
    	}
    
    	for (int i=0; i<len; i++)
    	{
    		arr[i] = arr1[i];
    	}
    
    	delete[] counts;
    	delete[] arr1;
    }
    
    void radixSort(int *arr, int p, int r, int digitCnt)
    {
    	int len = r-p+1;
    	int *result = new int[len];
    	for (int i=p; i<=r; i++)
    	{
    		result[i-p] = arr[i];
    	}
    
    	int k = 10;
    	
    	for (int i=0; i<digitCnt; i++)
    	{
    		countSort(result, len, k, getDigit, i);
    	}
    
    	for (int i=p; i<=r; i++)
    	{
    		arr[i] = result[i-p];
    	}
    
    	delete[] result;
    }
    
    int getDigit(int num, int d)
    {
    	return (num % (int)pow(10.0, d+1)) / pow(10.0, d);
    }
    
    int getDigitCnt(int num, int arg)
    {
    	num = num / 10;
    	int digitCnt = 1;
    	while (num)
    	{
    		num /= 10;
    		digitCnt++;
    	}
    	return digitCnt;
    }

    /*
     *	算法导论 习题8-3.b
     *	首先利用计数排序对数组按首位排序
     *	然后数组依据字符串的首位不同而分组,
     *	各不同分组再递归的依据次高位计数排序
     *	可是必须注意在排序过程中不断去掉长度较小的字符串
     *	时间复杂度为O(n)
     */
    
    #include <iostream>
    #include <ctime>
    #include <cstring>
    using namespace std;
    
    typedef int (*getCountKey)(char*, int);
    void countSort(char **str, int start, int end, int k, getCountKey getKey, int arg);
    int getLetterPos(char* str, int arg);
    void stringSort(char **str, int start, int end, int index);
    
    void printArray(char** arr, int len, char *str)
    {
    	cout << str << endl;
    	for (int i=0; i<len; i++)
    	{
    		cout << arr[i] << " ";
    	}
    	cout << endl;
    }
    
    
    int main()
    {
    	char* str[] = {"fwse", "asd", "bes", "a", "ce", "hed", "wdr", "ccw", "cs", "cfe", "acb", "bb", "bbb"};
    	int len = 13;
    	printArray(str, len, "原字符串数组");
    	stringSort(str, 0, len-1, 0);
    	printArray(str, len, "排序后字符串数组");
    	return 0;
    }
    
    void stringSort(char **str, int start, int end, int index)
    {
    	if (start == end)
    		return;
    	int k = 'a'+26-'A'+1;
    
    	//首先依据首字母计数排序
    	countSort(str, start, end, k, getLetterPos, index);
    
    	int pos = start;
    	char letter = str[start][index];
    	for (int i=start; i<=end; i++)
    	{
    		char temp = str[i][index];
    		if (letter != temp && letter != '')
    		{//出现新的分组,而且字符串还没结束的继续递归排序
    			stringSort(str, pos, i-1, index+1);
    			pos = i;
    			letter = temp;
    		}
    	}
    	if (letter != '')
    		stringSort(str, pos, end, index+1);
    }
    
    void countSort(char **str, int start, int end, int k, getCountKey getKey, int arg)
    {
    	int len = end-start+1;
    	int *counts = new int[k]();
    	char **result =new char*[len];
    
    	for (int i=start; i<=end; i++)
    	{
    		counts[getKey(str[i], arg)]++;
    	}
    
    	for (int i=1; i<k; i++)
    	{
    		counts[i] += counts[i-1];
    	}
    
    	for (int i=end; i>=start; i--)
    	{
    		result[counts[getKey(str[i], arg)]-1] = str[i];
    		counts[getKey(str[i], arg)]--;
    	}
    
    	for (int i=0; i<len; i++)
    	{
    		str[i+start] = result[i];
    	}
    
    	delete[] counts;
    	delete[] result;
    }
    
    int getLetterPos(char* str, int arg)
    {
    	return str[arg] == '' ? 0 : str[arg]-64;
    }
    


  • 相关阅读:
    第06课:GDB 常用命令详解(下)
    第05课:GDB常用命令详解(中)
    第04课:GDB常用命令详解(上)
    第03课:GDB常用的调试命令概览
    第02课:启动GDB调试
    第01课:调试信息与调试原理
    数据库(二)
    数据库笔记(一)
    acedSSGet 翻译
    ObjectARX动态添加AutoCAD传统下拉菜单入门篇(一)
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/5218145.html
Copyright © 2020-2023  润新知