Отсортировать N чисел в порядке цифр

Учитывая диапазон номеров N [от 1 до 100], отсортируйте числа в порядке цифр (т.е.) для чисел от 1 до 100 отсортированный вывод будет 1 10 100 11 12 13 . . . 19 2 20 21..... 99

Это похоже на сортировку по основанию, но только в том, что цифры сортируются в порядке, обратном тому, что было бы сделано в обычной сортировке по основанию.

Я попытался сохранить все цифры в каждом числе в виде связанного списка для более быстрой работы, но это приводит к большой пространственной сложности.

Мне нужен рабочий алгоритм для вопроса.

Из всех ответов «Преобразование в строки» является вариантом, но нет ли другого способа сделать это? Также можно указать алгоритм сортировки строк, как указано выше.


person Mor Eru    schedule 01.08.2010    source источник
comment
Всегда ли N чисел начинаются с 1 и заканчиваются на N?   -  person kennytm    schedule 01.08.2010
comment
Нет... они не должны начинаться с 1... Можно указать любой диапазон чисел   -  person Mor Eru    schedule 01.08.2010
comment
@KennyTM, да .. только последовательные числа   -  person Mor Eru    schedule 01.08.2010


Ответы (7)


Используйте любой алгоритм сортировки, который вам нравится, но сравнивайте числа как строки, а не как числа. Это в основном лексиографическая сортировка обычных чисел. Вот пример сортировки gnome в C:

#include <stdlib.h>
#include <string.h>

void sort(int* array, int length) {
    int* iter = array;
    char buf1[12], buf2[12];
    while(iter++ < array+length) {
        if(iter == array || (strcmp(itoa(*iter, &buf1, 10), itoa(*(iter-1), &buf2, 10) >= 0) {
            iter++;
        } else {
            *iter ^= *(iter+1);
            *(iter+1) ^= *iter;
            *iter ^= *(iter+1);
            iter--;
        }
    }
}

Конечно, для этого необходимо, чтобы нестандартная функция itoa присутствовала в stdlib.h. Более стандартной альтернативой было бы использование sprintf, но это делает код немного более загроможденным. Возможно, вам лучше сначала преобразовать весь массив в строки, затем отсортировать, а затем преобразовать его обратно.

Редактировать. Для справки, соответствующий бит здесь — strcmp(itoa(*iter, &buf1, 10), itoa(*(iter-1), &buf2, 10) >= 0, который заменяет *iter >= *(iter-1).

person You    schedule 01.08.2010
comment
Кто-нибудь может дать алгоритм??? А также, нет ли другого способа сделать это, кроме преобразования в строки??? - person Mor Eru; 01.08.2010
comment
Вы также можете сравнивать числа по цифрам, но это довольно утомительно. - person You; 01.08.2010
comment
Это то, что я пытался сделать все это время!! :-) - person Mor Eru; 01.08.2010
comment
itoa действует не так, как вы его здесь используете. - person Steve Jessop; 01.08.2010
comment
Правильно, забыл аргумент base. Фиксированный. - person You; 01.08.2010
comment
Нет, он не возвращает новую строку. Вы должны передать буфер, и он записывает в этот буфер и возвращает его. Итак, объявите пару массивов символов как локальные переменные, затем strcmp(itoa(iter[0],bufone,10), itoa(iter[-1],buftwo,10)). - person Steve Jessop; 01.08.2010
comment
Правда, просматривал cplusplus.com/reference/clibrary/cstdlib/itoa немного побыстрее. Фиксированный. - person You; 01.08.2010

У меня есть решение, но не совсем алгоритм. Все, что вам нужно сделать, это преобразовать все числа в строки и отсортировать их как строки.

person Shady M. Najib    schedule 01.08.2010
comment
Я думаю, что приведенное выше решение — лучшее, что вы можете получить. как целочисленные массивы.. например, 100 будут сохранены в int[3] как {1,0,0}. Все ответы кажутся разумными (у меня не было времени прочитать их полностью.. но переопределение оператора сравнения, если ваш PL поддерживает это, будет более читабельным (здесь я говорю о вашем коде, а не о вашем алгоритме) - person Shady M. Najib; 01.08.2010
comment
Таким образом, вы все равно будете сортировать их как строки, но вы сами реализуете сортировку строк (например, для Radix). - person Shady M. Najib; 01.08.2010

Вот как это можно сделать с помощью рекурсивной функции (код на Java):

void doOperation(List<Integer> list, int prefix, int minimum, int maximum) {
    for (int i = 0; i <= 9; i++) {
        int newNumber = prefix * 10 + i;
        if (newNumber >= minimum && newNumber <= maximum) {
            list.add(newNumber);
        }
        if (newNumber > 0 && newNumber <= maximum) {
            doOperation(list, newNumber, minimum, maximum);
        }
    }
}

Вы называете это так:

List<Integer> numberList = new ArrayList<Integer>();
int min=1, max =100;
doOperation(numberList, 0, min, max);
System.out.println(numberList.toString());

РЕДАКТИРОВАТЬ:

Я перевел свой код на C++ здесь:

#include <stdio.h> 

void doOperation(int list[], int &index, int prefix, int minimum, int maximum) {
    for (int i = 0; i <= 9; i++) {
        int newNumber = prefix * 10 + i;
        if (newNumber >= minimum && newNumber <= maximum) {
            list[index++] = newNumber;
        }
        if (newNumber > 0 && newNumber <= maximum) {
            doOperation(list, index, newNumber, minimum, maximum);
        }
    }
}

int main(void) { 
        int min=1, max =100;
        int* numberList = new int[max-min+1];
        int index = 0;
        doOperation(numberList, index, 0, min, max);
        printf("["); 
        for(int i=0; i<max-min+1; i++) {
                printf("%d ", numberList[i]); 
        }
        printf("]"); 
        return 0; 
}

По сути, идея такова: для каждой цифры (0-9) я добавляю ее в массив, если она находится между minimum и maximum. Затем я вызываю ту же функцию с этой цифрой в качестве префикса. Он делает то же самое: для каждой цифры добавляет ее к префиксу (prefix * 10 + i) и, если она находится между пределами, добавляет ее в массив. Он останавливается, когда newNumber больше максимума.

person True Soft    schedule 01.08.2010
comment
+1 Хорошая мысль. Я пропустил, что это непрерывный диапазон. Ваш способ потенциально использует намного меньше памяти в тех случаях, когда вы можете заменить list.add на System.out.println или любую другую операцию, что означает, что вам не нужен весь список сразу. - person Steve Jessop; 01.08.2010
comment
Да, исходного списка значений нет. Чтобы написать алгоритм на C, OP может заменить список массивом целых чисел и добавить текущий индекс в качестве параметра функции. - person True Soft; 01.08.2010
comment
Лучшим начальным значением для prefix может быть min/10, а не 0. - person jfs; 01.08.2010
comment
True Soft... Извините, но я не могу понять концепцию, стоящую за этим. Я был бы очень благодарен, если бы вы могли четко объяснить логику этого. Извините и заранее спасибо... - person Mor Eru; 01.08.2010
comment
@Дж.Ф. Себастьян: Это не сработает, если я установлю prefix в min/10, потому что для [40, 100] первым числом в массиве будет 100, что делается на втором шаге цикла (при i=1); если начальное значение prefix будет равно 4, он будет просматривать 4, 40, 400, затем 5, 50, 500... и никогда не дойдет до 100. - person True Soft; 01.08.2010

я думаю, что если вы преобразуете числа в строку, вы можете использовать сравнение строк для их сортировки. вы можете использовать для этого алгоритм сортировки anny.

"1" < "10" < "100" < "11" ...

person mhshams    schedule 01.08.2010

Оптимизируйте способ хранения чисел: используйте двоично-десятичный код (BCD), который дает простой доступ к определенной цифре. Затем вы можете использовать свой текущий алгоритм, который Стив Джессоп правильно определил как сортировка по основанию с наиболее значимой цифрой.

Я попытался сохранить все цифры в каждом числе в виде связанного списка для более быстрой работы, но это приводит к большой пространственной сложности.

Хранение каждой цифры в связанном списке занимает место по двум причинам:

  1. Для хранения цифры (0-9) требуется всего 4 бита памяти, но вы, вероятно, используете от 8 до 64 бит. Тип char или short занимает 8 бит, а int может занимать до 64 бит. Это использует от 2 до 16 раз больше памяти, чем оптимальное решение!
  2. Связанные списки добавляют дополнительные ненужные накладные расходы памяти. Для каждой цифры вам потребуется дополнительно от 32 до 64 бит для хранения адреса памяти следующей ссылки. Опять же, это увеличивает объем памяти, необходимый для каждой цифры, в 8-16 раз.

Более эффективное использование памяти позволяет хранить в памяти цифры BCD непрерывно:

  1. BCD использует только 4 бита на цифру.
  2. Храните цифры в непрерывном блоке памяти, подобно массиву. Это избавляет от необходимости хранить адреса памяти. Вам не нужна способность связанных списков легко вставлять/удалять из середины. Если вам нужна возможность увеличивать числа до неизвестной длины, есть другие абстрактные типы данных, которые позволяют это сделать с гораздо меньшими затратами. Например, вектор.

Один из вариантов, если другие операции, такие как сложение/умножение, не важны, состоит в том, чтобы выделить достаточно памяти для хранения каждой двоично-десятичной цифры плюс один двоично-десятичный терминатор. Ограничитель BCD может быть любой комбинацией из 4 битов, которая не используется для представления цифры BCD (например, двоичная 1111). Однако такое хранение сделает другие операции, такие как сложение и умножение, более сложными.

Обратите внимание, что это очень похоже на идею преобразования в строки и лексикографической сортировки этих строк. Целые числа хранятся внутри компьютера как двоичные (с основанием 2). Хранение в BCD больше похоже на базу 10 (фактически на 16, но 6 комбинаций игнорируются), а строки похожи на базу 256. Строки будут использовать примерно в два раза больше памяти, но уже есть эффективные функции, написанные для сортировки строк. Для BCD, вероятно, потребуется разработать собственный тип BCD для ваших нужд.

person Leftium    schedule 01.08.2010
comment
Wonsungi .. Большое спасибо за выявление недостатков в моей идее. Я, вероятно, буду использовать строки для решения проблемы... - person Mor Eru; 01.08.2010

Изменить: я пропустил, что это непрерывный диапазон. В этом случае все ответы, в которых говорится о сортировке массива, неверны (включая вашу идею, изложенную в вопросе, что это похоже на сортировку по основанию), и ответ True Soft правильный.

то же, что и Radix Sort, только цифры сортируются в обратном порядке

Хорошо подмечено :-) Если вы действительно делаете это таким образом, как ни странно, это называется сортировкой по основанию MSD.

http://en.wikipedia.org/wiki/Radix_sort#Most_significant_digit_radix_sorts

Вы можете реализовать его очень просто или с большим количеством высоких технологий и фанфар. В большинстве языков программирования ваш конкретный пример сталкивается с небольшими трудностями. Извлечение десятичных цифр из естественного формата хранения целого числа не является особенно быстрой операцией. Вы можете игнорировать это и посмотреть, сколько времени это займет (рекомендуется), или вы можете добавить еще больше фанфар, преобразовав все числа в десятичные строки перед сортировкой.

Конечно, вам не нужно реализовывать его как сортировку по основанию: вы можете использовать алгоритм сортировки сравнением с соответствующим компаратором. Например, в C следующее подходит для использования с qsort (если я не напутал):

int lex_compare(void *a, void *b) {
    char a_str[12];  // assuming 32bit int
    char b_str[12];
    sprintf(a_str, "%d", *(int*)a);
    sprintf(b_str, "%d", *(int*)b);
    return strcmp(a_str,b_str);
}

Не очень эффективно, так как выполняет много повторяющейся работы, но просто.

person Community    schedule 01.08.2010
comment
Извлечение цифр и надлежащая организация их для поиска является проблемой. Вот где я попытался использовать связанные списки для хранения каждой цифры в числе, а затем использовать их для поиска, потому что вместо вызова функции для получения цифры для сравнения каждый раз я подумал, что это будет проще... Можете ли вы предложить эффективный способ сделать это. - person Mor Eru; 01.08.2010
comment
Я бы не стал использовать связанные списки цифр - слишком много проблем с использованием памяти, дополнительными косвенными обращениями и нелокальностью ссылок. Какой язык программирования вы используете? Просто хранить их как строки должно быть довольно хорошо. Но на самом деле извлечение конкретной цифры — это просто модуль и деление, поэтому, если вы уже знакомы с сортировкой по основанию, во что бы то ни стало, просто посмотрите эту статью в Википедии и немного измените то, что вы сделали раньше. Производительность будет неплохой, потому что для каждого числа вам нужно выбрать каждую цифру из него только один раз в сортировке по основанию. - person Steve Jessop; 01.08.2010
comment
Я использую C... Но мысль о преобразовании в строки даже не приходила мне в голову!! - person Mor Eru; 01.08.2010
comment
да, но каждый раз нам нужно выполнять модуль и деление для извлечения символа. Я подумал, что это добавит сложности, поскольку каждое из чисел может иметь разную длину (количество цифр), и поэтому решил использовать связанные списки. - person Mor Eru; 01.08.2010
comment
Ну, предположим, что мы говорим здесь о int, а не о целых числах произвольной точности, чтобы получить первую цифру числа, вы сначала получаете порядок величины (возможно, с набором сравнений), чтобы выяснить, является ли первая цифра цифра - это цифра тысяч, цифра 100k или что-то еще. Затем вы делаете мод и деление. Это не огромный объем работы, и это, конечно, не добавляет сложности, поскольку все это постоянное время. Связанные списки обычно не работают быстро. Вы можете легко обнаружить, что одно выделение памяти занимает больше времени, чем извлечение всех 12 цифр по одной. - person Steve Jessop; 01.08.2010
comment
Также имейте в виду, что до тех пор, пока базовый алгоритм является верным, небольшие арифметические вычисления для каждого элемента редко заставят вашу программу работать настолько медленно, что вы это заметите. Сортировка по основанию равна O(n), поэтому алгоритм правильный. Напишите самую простую вещь, которая работает, протестируйте ее и, если она слишком медленная, измените ее. Лично я бы сначала написал код для использования qsort, как указано выше, и утруждал себя изменением только в том случае, если меня не устраивала результирующая скорость. Что может быть вполне вероятным в данном случае, но нет никакого смысла тратить больше времени на беспокойство об этом заранее, чем я потратил бы на его переписывание, если первая попытка не удалась. - person Steve Jessop; 01.08.2010
comment
Я думаю, что вы правы. Я должен перестать думать о доступе к отдельным цифрам и вместо этого использовать связанные списки. Спасибо, Стив Джессоп... - person Mor Eru; 01.08.2010
comment
@Steve: сначала вы получаете порядок величины (возможно, с набором сравнений) — log10 будет лучшим выбором. - person You; 01.08.2010
comment
@You: может быть, но это касается двойников. Это вводит ряд неопределенностей, с которыми я не хочу иметь дело в целочисленной задаче — например, для log10(1000) допустимо возвращать 2.99999999. Зачем заниматься такой ерундой? @Shyam: ну, возможно, неправильно преувеличивать. Но вы сказали, что беспокоитесь об использовании памяти, и True Soft предлагает решение, которое выводит правильные результаты в виде потока. Если вам не нужен результат в виде массива, он предлагает возможность использования памяти O (1). - person Steve Jessop; 02.08.2010

Если вы не хотите преобразовывать их в строки, но у вас достаточно места для хранения дополнительной копии списка, я бы сохранил наибольшую мощность в десять раз меньше, чем элемент в копии. Вероятно, проще всего это сделать с помощью цикла. Теперь назовите исходный массив x и степени десяти y.

int findPower(int x) {
   int y = 1;
   while (y * 10 < x) {
      y = y * 10;
   }
   return y;
}

Вы также можете вычислить их напрямую

y = exp10(floor(log10(x)));

но я подозреваю, что итерация может быть быстрее, чем преобразования в и из с плавающей запятой.

Чтобы сравнить элементы ith и jth

bool compare(int i, int j) {
  if (y[i] < y[j]) {
    int ti = x[i] * (y[j] / y[i]);
    if (ti == x[j]) {
      return (y[i] < y[j]);  // the compiler will optimize this
    } else {
      return (ti < x[j]);
    }
  } else if (y[i] > y[j]) {
    int tj = x[j] * (y[i] / y[j]);
    if (x[i] == tj) {
      return (y[i] < y[j]);  // the compiler will optimize this
    } else {
      return (x[i] < tj);
    }
  } else {
     return (x[i] < x[j];
  }
}

Здесь мы умножаем меньшее число на соответствующую степень десяти, чтобы два числа имели одинаковое количество цифр, а затем сравниваем их. если два модифицированных числа равны, то сравните длины цифр.

Если у вас нет места для хранения массивов y, вы можете вычислять их при каждом сравнении.

В общем, вам, вероятно, лучше использовать предварительно оптимизированные процедуры преобразования цифр.

person deinst    schedule 01.08.2010