Сортировка матриц сортировкой C - PullRequest
1 голос
/ 08 апреля 2020

У меня проблема с сортировкой матрицы, например:

0 0 4
1 0 3
0 1 4
1 1 5
0 2 3
1 2 4

по:

1 0 3
0 2 3
0 0 4
0 1 4
1 2 4
1 1 5

Таким образом, строки остаются одинаковыми, от меньшего к большему для столбцов, как показано в примере. Матрица всегда будет иметь 3 столбца и x строк.

Я уже пробовал Вставить Сортировку, и, несмотря на работу, она неэффективна и требует много времени для выполнения, учитывая количество строк в реальном приложении.

Я приведу небольшой код того, что я притворяюсь:

#include <stdio.h>

int main() {
    int abc[6][3] = {
        { 0, 0, 4 },
        { 1, 0, 3 },
        { 0, 1, 4 },
        { 1, 1, 5 },
        { 0, 2, 3 },
        { 1, 2, 4 },
    };

    //
    //sort 
    //

    for (int i = 0; i < 6; ++i) {
        printf("%d %d %d\n", abc[i][0], abc[i][1], abc[i][2]);
    }
    return 0;
}

Я хотел бы попробовать и mergesort, и quicksort, но любая помощь приветствуется.

Заранее спасибо.

Ответы [ 2 ]

1 голос
/ 08 апреля 2020

Вы можете написать свою функцию сравнения одним из четырех способов (два формально правильных, но все эквивалентные 1 ). Страшный qsort прототип int compare (const void *a, const void *b) - это просто способ, которым C передает указатель на элементы сортируемого массива. Ваша задача - просто привести обратно к правильному типу перед сравнением в функции.

Реальный вопрос: "Какой указатель типа передается?

Когда вы отвечаете, что это легко. Вы сортируете двумерный массив по строки. 2D-массив на самом деле является массивом одномерных массивов. Каждый указатель, переданный для сравнения, будет указателем на одномерный массив (строки)

Так что каждый a и b будет указатель на массив из int [3]. В вашей функции сравнения вам нужно будет привести к указателю на массив int [3], а затем разыменовать один раз, чтобы оставить тип как int [3], а затем сравнить на основе третьего элемент. (но когда вы получаете доступ к вашему текущему типу int [3] преобразование указателя массива применяется также и к этому массиву, поэтому вам просто нужно добавить 2 к значению вашего указателя и разыменование для сравнения третьим элементом).

Вы можете использовать результаты двух условных тестов, чтобы избежать потенциального переполнения, которое может возникнуть в результате простого возврата b - a, который, если b - большое отрицательное число и a - большое (или b - большое положительное число и a (большое отрицательное число) произойдет, если результат превысит объем памяти для int. Так что вместо этого для сортировки по возрастанию вы можете использовать:

    return (a > b) - (a < b);

(для сортировки по убыванию просто переверните сравнения, например, (a < b) - (a > b). Попробуйте. Выберите любые два значения для a и b и вычислите результат. В возрастающем случае a меньше b, вы return 0 - 1; (или -1), например, означает a сортирует до b)

Так как же выглядит функция сравнения? Поскольку формальный тип для указателя на массив из int [3] равен int (*)[3], вы можете сделать:

int comp (const void *a, const void *b)
{
    int *pa2 = *(int (*)[3])a + 2,
        *pb2 = *(int (*)[3])b + 2;

    return (*pa2 > *pb2) - (*pa2 < *pb2);
}

или если вы хотите просто сделать int pa2 и не беспокоиться о При необходимости разыменования указателя при сравнении, для второго формального приведения вы можете заключить полное приведение a в круглые скобки, а затем просто использовать (stuff a)[2] для непосредственного доступа к третьему целочисленному значению - это просто выглядит более запутанным приведение, например,

int comp (const void *a, const void *b)
{
    int pa2 = (*(int (*)[3])a)[2],
        pb2 = (*(int (*)[3])b)[2];

    return (pa2 > pb2) - (pa2 < pb2);
}

(что имеет для вас наибольшее значение)

Тогда сортировка по третьему элементу каждой строки в вашем двумерном массиве действительно тривиальна, например,

#include <stdio.h>
#include <stdlib.h>

int comp (const void *a, const void *b)
{
    int *pa2 = *(int (*)[3])a + 2,
        *pb2 = *(int (*)[3])b + 2;

    return (*pa2 > *pb2) - (*pa2 < *pb2);
}

int main() {

    int abc[6][3] ={{0,0,4},
                    {1,0,3},
                    {0,1,4},
                    {1,1,5},
                    {0,2,3},
                    {1,2,4}};

    qsort (abc, 6, sizeof *abc, comp);

    for (int i = 0; i < 6; ++i)
        printf(" %2d %2d %2d\n", abc[i][0], abc[i][1], abc[i][2]);

}

Пример использования / вывода

$ ./bin/qsort2d+2
  1  0  3
  0  2  3
  0  0  4
  0  1  4
  1  2  4
  1  1  5

Теперь два последних эквивалентных способа написания функций сравнения основаны на последовательном применении преобразования массива / указателя и понимании того, что указатель на массив и сам массив будут указывать на один и тот же адрес (но формально это разные типы) Формально у вас есть int (*)[3], но, поскольку вы знаете, что просто хотите второй int После этого адреса вы можете привести указатель сравнения a и b к int * и добавить два. Это эквивалентно, но не отражает формальный состав. (вы также можете заключить весь состав в скобки и также использовать [2]).

В этом случае приведение выглядит намного проще, но может быть несколько менее очевидно, что происходит на самом деле. Я оставлю это вам, чтобы попробовать заменить *(int (*)[3]) в полной программе на (int *). (это позволит избежать затянувшегося обсуждения, которое будет происходить в комментариях)

Просмотрите все и продумайте оба «Какой указатель типа передается?» и зачем использовать результат двух Сравнение, а не только вычитание в качестве результата в вашей функции сравнения, чтобы избежать переполнения. Сообщите мне, если у вас возникнут дополнительные вопросы.

Сноски:

1.) C11 Стандарт - 6.3.2. 1 Другие операнды - L-значения, массивы и обозначения функций (p3) За исключением случаев, когда это операнд оператора sizeof, оператор _Alignof или унарный оператор '&' или строковый литерал , используемый для инициализации массива, выражение с типом «массив типа» преобразуется в выражение с типом «указатель на тип» , которое указывает на начальный элемент массива объекта и не является lvalue.

0 голосов
/ 08 апреля 2020

Опираясь на Давида C. Ответ Ранкина, есть еще одна проблема, которую вы должны принять во внимание: какой точный порядок сортировки вы получите sh?

Из приведенного вами примера можно сделать вывод, что третий столбец двумерного массива это первичный ключ. Однако этого недостаточно для полного указания порядка сортировки. Вот три варианта:

  1. порядок строк с одинаковым элементом в третьем столбце не имеет значения. В этом случае может быть много разных выходов, все правильные.
  2. порядок линий с одинаковыми элементами должен быть таким же, как в исходном двумерном массиве. Для этого вам нужен стабильный алгоритм сортировки, такой как mergesort, но qsort обычно реализуется с помощью сочетания быстрой сортировки и других методов для патологических случаев, что делает его нестабильным и, следовательно, неприемлемым для ваших целей.
  3. порядок строк с одинаковыми элементами в столбце 3 должен определяться из значений во втором столбце, а если эти значения также идентичны, из значений в первом столбце. Приведенный пример согласуется с этим методом. (из вашего ответа на комментарий Джонатана Леффлера это должно быть вашим предпочтением).

qsort() подходит для методов 1 и 3, но не для метода 2.

Вот Более простая функция сравнения для метода 1:

int comp2(const void *a, const void *b) {
    const int *pa = a;
    const int *pb = b;

    return (pa[2] > pb[2]) - (pa[2] < pb[2]);
}

comp2 может использоваться для метода 2, но вы должны вызвать mergesort() вместо qsort():

mergesort(abc, sizeof abc / sizeof *abc, sizeof *abc, comp2);

Вот функция сравнения для метода 3:

int comp_full(const void *a, const void *b) {
    const int *pa = a;
    const int *pb = b;

    if (pa[2] != pb[2])
        return (pa[2] > pb[2]) - (pa[2] < pb[2]);
    if (pa[1] != pb[1])
        return (pa[1] > pb[1]) - (pa[1] < pb[1]);
    else
        return (pa[0] > pb[0]) - (pa[0] < pb[0]);
}

Вот тестовая программа для систем BSD:

#include <stdio.h>
#include <stdlib.h>

int comp_full(const void *a, const void *b) {
    const int *pa = a;
    const int *pb = b;

    if (pa[2] != pb[2])
        return (pa[2] > pb[2]) - (pa[2] < pb[2]);
    if (pa[1] != pb[1])
        return (pa[1] > pb[1]) - (pa[1] < pb[1]);
    else
        return (pa[0] > pb[0]) - (pa[0] < pb[0]);
}

int comp2(const void *a, const void *b) {
    const int *pa = a;
    const int *pb = b;

    return (pa[2] > pb[2]) - (pa[2] < pb[2]);
}

int use_stable_sort = 0;

int main() {
    int abc[][3] = {
        { 0, 0, 4 },
        { 1, 0, 3 },
        { 0, 1, 4 },
        { 1, 1, 5 },
        { 0, 2, 3 },
        { 1, 2, 4 },
    };

    if (use_stable_sort)
        mergesort(abc, sizeof abc / sizeof *abc, sizeof *abc, comp2);
    else
        qsort(abc, sizeof abc / sizeof *abc, sizeof *abc, comp_full);

    for (int i = 0, n = sizeof abc / sizeof *abc; i < n; ++i)
        printf("%d %d %d\n", abc[i][0], abc[i][1], abc[i][2]);

    return 0;
}
...