《算法导论》第八章----线性时间排序(决策树+计数排序+基数排序)
比较排序:各个元素的次序基于输入元素间的比较。下界为Ω(nlgn)。
线性排序:用非比较的操作来确定元素的顺序。
决策树
比较排序可以抽象地视为决策树,表示某排序算法对输入元素的所有比较。下图为插入排序对于三个元素的输入序列上的决策树:
对于n个元素,排序结果有n!种排列,对应与决策树上的每一个叶子。一个正确的排序算法必须可以产生每一种排列,对于决策树来说就是从根结点出发可以到达每一个
叶结点。
在决策树中,从根到任一个叶结点之间最长路径的长度表示对应的排序算法中最坏情况下的比较次数。
因此,一个比较排序的最坏情况比较次数与其决策树的高度相等。
所以可得任意一个比较排序在最坏情况下,都需要做Ω(nlgn)次比较。
假设一个决策树高度为h、有l个叶结点,对应n个输入元素。
n个元素有n!种排列,因此n! < l。
高度为h的树,叶结点数目最多为2h。
可得:n! ≤ l ≤ 2h
取对数得 h ≥ lg(n!) = Ω(nlgn)
计数排序
计数排序假设n个输入元素都是位于[0, k]之间的整数。
基本思想为对于每一个输入元素x,确定出小于x的元素个数,然后直接将x放置在最终数组的位置上。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void counting_sort(int A[], int B[], int length, int max); 5 6 int main(){ 7 int num, i; 8 printf("Input the number:\n"); 9 scanf("%d", &num); 10 int *array = malloc((num + 1) * sizeof(int)); 11 int *arrayB = malloc((num + 1) * sizeof(int)); 12 int max = -1; 13 printf("Input the element:"); 14 for(i = 1; i <= num; i++){ 15 scanf("%d", &array[i]); 16 if(max < array[i]) 17 max = array[i]; 18 } 19 20 counting_sort(array, arrayB, num, max); 21 for(i = 1; i <= num; i++) 22 printf("%d ", arrayB[i]); 23 printf("\n"); 24 25 return 0; 26 } 27 28 /* 29 * 基本思想是对每一个输入元素x,统计小于x元素的个数,然后把x直接放到最终输出的数组中的正确位置上。 30 * 当出现多个元素值一样的时候,需要一些修改,因为不能把相同的元素放到同一个位置上。 31 * 数组A为输入数组,数组B为存放最终结果。 32 */ 33 void counting_sort(int A[], int B[], int length, int max){ 34 int i; 35 int *C = malloc((max+1) * sizeof(int)); //数组C提供临时存储区 36 for(i = 0; i <= max; i++) 37 C[i] = 0; 38 39 int j; 40 for(j = 1; j <= length; j++) 41 C[A[j]]++; 42 /*C[j]记录等于j的元素个数*/ 43 44 for(i = 1; i <= max; i++) 45 C[i] += C[i-1]; 46 /*C[j]此时存储的值为小于等于j的元素个数*/ 47 48 for(j = length; j >= 1; j--){ 49 B[C[A[j]]] = A[j]; 50 C[A[j]]--; 51 } 52 53 /* 54 for(j = 1; j <= length; j++){ 55 B[C[A[j]]] = A[j]; 56 C[A[j]]--; 57 } 58 */ 59 }
计数排序的总运行时间为θ(k + n),当k = O(n)时,运行时间为Θ(n)
优于比较排序的下界Ω(nlgn),因为在计数排序的过程中没有出现输入元素之间的比较,而是用了输入元素的实际值来确定它们在数组的位置。
额外插入排序算法的稳定。
排序算法的稳定的定义为:具有相同值的元素在输出数组中的相对次序与它们在输入数组中的次序相同。可参考维基
对于简单数据的排序,稳定性好像没什么作用,但是对于复杂数据(不只一项属性)的排序,稳定性很重要。
计数排序是稳定的排序,因此计数排序被用作基数排序算法的子过程。
练习8.2-2
在counting_sort过程中,假设将for(j = length; j >= 1; j--) 改为for(j = 1; j <= length; j++)。证明该算法仍能正常地工作,修改后的算法是稳定的吗?
计数排序算法不依赖数组A的顺序,所以仍能正常运行,而且结果正确。
但是修改后的算法不稳定。因为没修改前,在数组A里靠后的元素,在数组B也靠后(针对与元素值相同),如果修改后,结果会相反。违反了具有相同值的元素在输出数组中的相对次序与它们在输入数组中的次序相同,即稳定性。
练习8.2-4
给出一个算法,使之对于给定介于0和k之间的n个整数进行预处理,并能在O(1)时间内,回答出输入的整数中有多少个落在区间[a..b]内。你给出的算法的预处理时间应为Θ(n + k)。
用一个数组C,记录小于或等于其每个下标的值的元素个数。C[b] - C[a-1]为落在区间内的元素个数。
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int count(int A[], int length, int k, int a, int b); 5 6 int main(){ 7 int num, i, k, a, b, cnt; 8 printf("Input the k:\n"); 9 scanf("%d", &k); 10 printf("Input the a and b:\n"); 11 scanf("%d %d", &a, &b); 12 printf("Input the number of the elements:\n"); 13 scanf("%d", &num); 14 int *array = malloc((num + 1) * sizeof(int)); 15 printf("Input the element:"); 16 for(i = 1; i <= num; i++){ 17 scanf("%d", &array[i]); 18 } 19 20 cnt = count(array, num, k, a, b); 21 printf("The number of the elements which are in the [a..b] is %d\n", cnt); 22 return 0; 23 } 24 25 int count(int A[], int length, int k, int a, int b){ 26 int i; 27 int *C = malloc((k + 1) * sizeof(int)); 28 for(i = 0; i <= k; i++) 29 C[i] = 0; 30 31 int j; 32 for(j = 1; j <= length; j++) 33 C[A[j]]++; 34 35 for(i = 1; i <= k; i++) 36 C[i] += C[i-1]; 37 38 return C[b] - C[a-1] ; 39 }
基数排序
基数排序是一种用在老式穿卡机上的算法。先对最低位有效数字进行排序,然后再对高一位的有效数字进行排序,依次继续,知道最高位有效数字都排序完。
对于这个算法最重要的是按位排序时要稳定。如果不稳定会导致,本来低一位的顺序已经排好,但是因为按位排序不稳定,使得对于同一位理,相同的值原来的按低一位的顺序
改了,导致不正确。
证明:给定n个d位数,每一位数有k种可能性。如果一次稳定排序需要Θ(n+k)时间,那么总的时间就为Θ(d(n+k))。
基数排序的时间代价分析取决于稳定的中间排序算法。当每一位数字都介于0到k-1之间,而且k不大的时候,可以选择计数排序。n个d位数每一次中间排序的时间为Θ(n+k),总共有d次,总运行时间为Θ(d(n+k))。
当d为常数,k = O(n)时,基数排序有线性运行时间。
下为基数排序的代码实现
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void radix_sort(int A[], int d, int length); 6 7 int get_d(int x); 8 9 int main(){ 10 int num, i; 11 printf("Input the number:\n"); 12 scanf("%d", &num); 13 int *array = malloc((num + 1) * sizeof(int)); 14 int d = 0; 15 printf("Input the element:"); 16 for(i = 1; i <= num; i++){ 17 scanf("%d", &array[i]); 18 if(d < get_d(array[i])) 19 d = get_d(array[i]); 20 } 21 22 radix_sort(array, d, num); 23 for(i = 1; i <= num; i++) 24 printf("%d ", array[i]); 25 printf("\n"); 26 return 0; 27 } 28 29 int get_d(int x){ 30 int count = 0; 31 while(x / 10 != 0 || x % 10 != 0){ 32 x /= 10; 33 count++; 34 } 35 return count; 36 } 37 38 void radix_sort(int A[], int d, int length){ 39 int *B = malloc((length + 1) * sizeof(int)); 40 int *C = malloc(10 * sizeof(int)); 41 42 int i, j, k, temp; 43 int radix = 1; 44 for(i = 1; i <= d; i++){ 45 for(j = 0; j < 10; j++) 46 C[j] = 0; 47 for(k = 1; k <= length; k++){ 48 temp = (A[k] / radix) % 10; 49 C[temp]++; 50 } 51 52 for(j = 1; j < 10; j++) 53 C[j] += C[j-1]; 54 55 for(k = length; k >= 1; k--){ 56 temp = (A[k] / radix) % 10; 57 B[C[temp]] = A[k]; 58 C[temp]--; 59 } 60 radix *= 10; 61 memcpy(A, B, (length + 1) * sizeof(int)); 62 63 } 64 }
对于基数排序与基于比较的排序算法(快速排序)之间哪个算法更好,在算法导论上有详细的介绍。大一的时候很随便看书的时候真的天真的以为基数排序比快速排序好,
那时候只看算法实现,没有看算法的分析,后来现在才发现,原来当时错的很离谱。好好继续努力!