기음
영리한
CleverSort는 최첨단 (즉, 과도하게 설계되고 최적화되지 않은) 2 단계 문자열 정렬 알고리즘입니다.
1 단계에서는 기수 정렬 과 각 줄의 처음 2 바이트를 사용하여 입력 줄을 미리 정렬하여 시작 합니다. 기수 정렬은 비 비교적이며 문자열에 매우 효과적입니다.
2 단계에서는 미리 정렬 된 문자열 목록에서 삽입 정렬 을 사용합니다 . 1 단계 이후에 목록이 거의 정렬되므로 삽입 작업이이 작업에 매우 효율적입니다.
암호
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Convert first two bytes of Nth line into integer
#define FIRSTSHORT(N) *((uint16_t *) input[N])
int main()
{
char **input = 0, **output, *ptemp;
int first_index[65536], i, j, lines = 0, occurrences[65536];
size_t temp;
// Read lines from STDIN
while(1)
{
if(lines % 1000 == 0)
input = realloc(input, 1000 * (lines / 1000 + 1) * sizeof(char*));
if(getline(&input[lines], &temp, stdin) != -1)
lines++;
else
break;
}
output = malloc(lines * sizeof(char*));
// Radix sort
memset(occurrences, 0, 65536 * sizeof(int));
for(i = 0; i < lines; i++) occurrences[FIRSTSHORT(i)]++;
first_index[0] = 0;
for(i = 0; i < 65536 - 1; i++)
first_index[i + 1] = first_index[i] + occurrences[i];
memset(occurrences, 0, 65536 * sizeof(int));
for(i = 0; i < lines; i++)
{
temp = FIRSTSHORT(i), output[first_index[temp] + occurrences[temp]++] = input[i];
}
// Insertion sort
for(i = 1; i < lines; i++)
{
j = i;
while(j > 0 && strcmp(output[j - 1], output[j]) > 0)
ptemp = output[j - 1], output[j - 1] = output[j], output[j] = ptemp, j--;
}
// Write sorted lines to STDOUT
for(i = 0; i < lines; i++)
printf("%s", output[i]);
}
플랫폼
우리는 빅 엔디안 머신이 리틀 엔디안 머신보다 훨씬 효율적이라는 것을 알고 있습니다. 벤치마킹을 위해 최적화를 켠 상태에서 CleverSort를 컴파일하고 무작위로 4 바이트 라인의 10 만 개 이상의 문자열을 무작위로 생성합니다.
$ gcc -o cleversort -Ofast cleversort.c
$ head -c 300000 /dev/zero | openssl enc -aes-256-cbc -k '' | base64 -w 4 > input
$ wc -l input
100011 input
빅 엔디안 벤치 마크
$ time ./cleversort < input > /dev/null
real 0m0.185s
user 0m0.181s
sys 0m0.003s
너무 초라하지 않습니다.
리틀 엔디안 베치 마크
$ time ./cleversort < input > /dev/null
real 0m27.598s
user 0m27.559s
sys 0m0.003s
부, 리틀 엔디안! 우우!
기술
삽입 정렬은 거의 정렬 된 목록의 경우 실제로는 효율적이지 않지만 무작위로 정렬 된 목록의 경우에는 비효율적입니다.
CleverSort의 언더 핸드는 FIRSTSHORT 매크로입니다.
#define FIRSTSHORT(N) *((uint16_t *) input[N])
빅 엔디안 시스템에서는 사전 식으로 두 개의 8 비트 정수 문자열을 주문하거나 16 비트 정수로 변환 한 후 나중에 정렬하면 동일한 결과가 나타납니다.
당연히, 이것은 리틀 엔디안 머신에서도 가능하지만 매크로는
#define FIRSTSHORT(N) (input[N][0] | (input[N][1] >> 8))
모든 플랫폼에서 예상대로 작동합니다.
위의 "빅 엔디안 벤치 마크"는 실제로 적절한 매크로를 사용한 결과입니다.
잘못된 매크로와 리틀 엔디안 기계를 사용하면 목록이 모든 줄 의 두 번째 문자로 미리 정렬되어 사전 사전 관점에서 임의 순서로 정렬됩니다. 이 경우 삽입 정렬이 매우 열악합니다.