Невозможно получить сортировку вставки из введения в алгоритмы 3-е изд. правильно. Где моя ошибка?

Я работаю над книгой "Введение в алгоритмы", 3-е издание. Одной из первых вещей объясняется сортировка вставки. На странице 18 есть некоторый псевдо-код:

A = {5, 2, 4, 6, 1, 3};

INSERTION-SORT(A)
1 for j = 2 to A.length
2   key = A[j]
4   i = j - 1

5   while (i > 0 and A[i] > key)
6     A[i + 1] = A[i]
7     i = i - 1

8   A[i + 1] = key

В нем говорится, что псевдокод используется так, что он легко переводится на любой язык (C, С++, Java, они не упоминаются, но я думаю, С# тоже). Поскольку я программирую на С#, я перевел его в LinqPad.

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

for (var j = 1; j < a.Length; j++)
{
    var key = a[j];

    var i = j - 1;

    while(i > 0 && a[i] > key)
    {
        a[i + 1] = a[i];
        i--;
    }

    a[i + 1] = key;
}

a.Dump();

Вероятно, вы спросите, почему я начинаю с 1, когда он ясно говорит 2? В книге массив имеет индекс, начинающийся с 1. И да, теперь я, вероятно, должен был обновить все [i - 1] и [i + i].

В любом случае, после того, как я закончил, я запустил код и заметил, что он действительно не сортируется правильно. Выходной сигнал { 5, 1, 2, 3, 4, 6 }. Было уже поздно и должно было остановиться, но я изо всех сил старался сделать код правильным. Я сделал все, даже взяв псевдокод, как из книги (начиная с 2). Все еще не правильный выход.

Я связался с одним из профессоров книги, и он пришлет мне код для сортировки вставки в C:

void insertion_sort(int *A, int n) {
  for (int j = 2; j <= n; j++) {
    int key = A[j];
    int i = j-1;

    while (i > 0 && A[i] > key) {
      A[i+1] = A[i];
      i--;
    }

    A[i+1] = key;
  }
}

Переведено на С#:

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

for (var j = 2; j <= a.Length; j++)
{
    var key = a[j];

    var i = j - 1;

    while(i > 0 && a[i] > key)
    {
        a[i + 1] = a[i];
        i--;
    }

    a[i + 1] = key;
}

Я получаю массив за пределы. Тогда может быть:

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

for (var j = 2; j <= a.Length - 1; j++)
{
    var key = a[j];

    var i = j - 1;

    while(i > 0 && a[i] > key)
    {
        a[i + 1] = a[i];
        i--;
    }

    a[i + 1] = key;
}

Выход: {5, 1, 2, 3, 4, 6}

Я думаю, это не может быть правильно. Псевдокод говорит 2 для array.Length. Это 2 < array.Length или 2 <= array.Length? Что здесь происходит?

Я лично считаю, что это из-за предиката 0 > 0 в цикле while. Он на самом деле падает каждый раз каждый раз.

Мое объяснение (от моего письма, отправленного профессору, ленив, чтобы набрать его):

Причина, по которой цикл все еще заканчивается { 5, 1, 2, 3, 4, 6 }, обусловлен предикатом i > 0. Каждый раз в цикле while вычитаете 1 из я (i--). Это в конечном итоге приведет к 0 > 0, который заканчивается ложным (только 0 == 0 вернет true), но это происходит, когда цикл еще нужно запустить еще раз. Он непрерывно падает на один короткий. Для правильного сортировки нужно сделать цикл while еще 1 раз.

Другое объяснение:

Когда j начинается с 2, ключ == 4, я == 1 и [i] == 2. В этом случае цикл while не будет работать, потому что 2 > 0, но 2 не больше 4.

j == 3, key == 6, i == 2, a[i] == 4

Пока цикл не будет запущен, потому что 4 не больше 6

j == 4, key == 1, i == 3, a[i] == 6

Пока цикл работает на ходу:

a[i + 1] = a[i] -> a[4] = a[3] -> { 5, 2, 4, 6, 6, 3 } i-- -> i == 2

Опять цикл while, потому что 2 > 0 и 4 > 1

a[i + 1] = a[i] -> a[3] = a[2] -> { 5, 2, 4, 4, 6, 3 } i-- -> i == 1

Опять цикл while, потому что 1 > 0 и 2 > 1

a[i + 1] = a[i] -> a[2] = a[1] -> { 5, 2, 2, 4, 6, 3 } i-- -> i == 0

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

Профессор уверяет меня, что он прав, но я не могу получить правильный результат. Где мое мышление идет не так?


Массив в коде C, который был отправлен мне профессором, фактически начинался с индекса 1. Я не знал этого и проверял массивы C, я видел, что все они начинаются с 0. Да, тогда C код не дает правильного вывода. Профессор объяснил это мне, и теперь фигуры теперь попадают на его место.

Ответ 1

Я думаю, что prof использует однозначную запись массива, поэтому с while (i > 0 && a[i] > key) вам не хватает элемента [0] в цикле. Измените исходный код на это, затем он работает:

for (var j = 1; j < a.Length; j++)
{
    var key = a[j];

    var i = j - 1;

    while(i >= 0 && a[i] > key)  <----------- Try this, or you'd miss the first number
    {
        a[i + 1] = a[i];
        i--;
    }

    a[i + 1] = key;
}

Кроме того, если вы хотите использовать код профессора, просто игнорируйте 0-й элемент там.

С какой стороны вы связались? Ривест? Корман? В следующий раз, когда я запутаюсь, я думаю, что попробую связаться с ним, так как кажется, что профессор отвечает на письма:)

Ответ 2

Вы не должны думать о переводе псевдокода, но о перевод вашего понимания алгоритма.

Сначала массив полностью несортирован. Алгоритм работает взяв последовательные несортированные элементы и вставив их в уже отсортированная часть. Начальная "отсортированная часть" - это первый элемент, который тривиально "сортируется". Таким образом, первым элементом для вставки является второй. Каков индекс второго элемента? Ваш j должен начните оттуда.

Затем i должно пройти через каждый из индексов отсортированных элементов, назад, пока он не найдет место для вставки текущего значения или заканчивается из элементов. Итак, где это должно начаться, и где она должна закончиться? Позаботьтесь, чтобы он действительно смотрел на каждый элемент имеет значение.

Ошибки по очереди, как правило, трудно обнаружить (и смешение понятия 1-based и 0-based массивов наверняка не помогают), но не просто играйте вокруг, пока это не сработает. Попытайтесь понять, что код действительно делает.

Ответ 3

Я считаю, что ваш аргумент о i>0 правильный, независимо от того, что проф. говорит. В псевдокоде цикл равен while i > 0, а индексирование массива начинается с 1. В С# индексирование массива начинается с 0, поэтому вы должны иметь while i >= 0.

Ответ 4

Я также столкнулся с вашей проблемой, и я нашел решение этого. Я закодировал алгоритм в java, как показано ниже.

int a[] = {5,2,4,3,1};
    int key;
    int i;
    for(int j = 0; j < 5; j++)
    {
        key = a[j];
        i = j - 1;

        while(i>=0 && a[i]>key)
        {
            a[i+1]= a[i];
            i--;
        }
        a[i+1] = key;

        for(int k=0; k<a.length;k++)
        {
            System.out.print(a[k]+" ");
        }
    }

Ответ 5

У меня возникла такая же проблема. Ниже приведен код в C, который правильно реализует вышеуказанный псевдокод. Я не использую указатели, как другие решения.

Действительно, сложная часть этого заключалась в том, что псевдокод использует нотации на основе 1-го уровня, в отличие от большинства языков программирования!

#include <stdio.h>

int main(void)
{
  int A[] = { 50, 20, 10, 40, 60, 30 };
  int j, key, len, i;
  len = (sizeof(A)) / (sizeof(A[0]));

    for (j = 1; j < 6; j++) {  <-- Change here
      key = A[j];
      // Insert key into the sorted sequence A[1 .. j - 1].
      i = j - 1;
      while (i >= 0 && A[i] > key) {  <-- Change here
          A[i + 1] = A[i];
          i--;
      }
      A[i + 1] = key;
    }

    for (int z = 0; z < len; z++) {
       printf("%d ", A[z]);
    }
   printf("\n");
 }

Ответ 6

Помните: A. длина идет от 0 до n, поэтому длина должна быть A.Length -1. Я сделал этот алгоритм для своих студентов на С++ на испанском языке, используя эту книгу. Простой перевод на С#.

некоторый перевод, чтобы вы могли лучше понять

largo = length
actual = current
cadena = chain

void InsertionSort::Sort(char cadena[])
{
    int largo = strlen(cadena) - 1;
    char actual = '0';
    int i = 0;

    for (int j = 1; j <= largo; j++)
    {
        actual = cadena[j];
        i = j - 1;
        while(i >= 0 && cadena[i] > actual)
        {
            cadena[i + 1] = cadena[i];
            i--;
        }
        cadena[i + 1] = actual;
    }
}