Лицензия Creative Commons
Содержимое блога доступно по лицензии Creative Commons Атрибуция — С сохранением условий
(Attribution-ShareAlike) 3.0 Unported
, если не указано иное.
Показаны сообщения с ярлыком алгоритмы. Показать все сообщения
Показаны сообщения с ярлыком алгоритмы. Показать все сообщения

суббота, 12 февраля 2011 г.

Рисуем новогоднюю ёлку с помощью Си: алгоритм номер 1.

В предыдущем посте я рассказал о задаче, которая меня вдохновила на выращивание хвойных в консоли. Так же был рассмотрен способ отрисовки равнобедренного треугольника.

Теперь пришло время нарисовать первое подобие ёлки.

Постановка задачи


Должно получиться следующее:


   _
  |      X <-------- звезда
  |      *
  |     ***
  |     ***
 10    *****
  |    ***** <------ ёлка
  |   ******* 
  |   *******
  |  *********
  |_ *********
    |----9----|

       рис.1

Всё очень просто: нужно взять за основу алгоритм отрисовки треугольника и удвоить каждую строку: 1, 1, 3, 3, 5, 5...

Высота дерева задаётся пользователем.

Дабы повысить соответствие оригиналу, я хочу поместить на верхушку ёлки "звезду". Ну, вроде тех массивных стеклянных/пластмассовых штуковин, которые часто надевают на верхушку ёлки для усиления праздничного эффекта. За звезду сойдёт английская буква "X" (см. рис.1)

Реализация


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


for(i = 0; i < height; i++) {
  // ...
}

С ним не будет особых проблем. А вот над вложенным циклом нужно подумать. Каким образом реализовать удвоение каждой строки? Посмотрим, как будет расти количество звёздочек в строке, в зависимости от номера строки. Как видно из куска кода выше, переменная i изменяется в цикле от 0 до height - 1 (о чём говорит знак "меньше").


  i | кол. звёздочек в строке
  --|------------------------
  0 | 1        X
  1 | 1        *
  2 | 3       ***
  3 | 3       ***
  4 | 5      *****
  5 | 5      *****
  6 | 7     *******
  7 | 7     *******
  8 | 9    *********
  9 | 9    *********
  ...
        рис.2

Из рисунка выше видно, что количество звёздочек увеличивается на 2 на каждой чётной строке. Это даёт подсказку, как можно реализовать алгоритм.

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


for(i = 1; i <= h; i++)
  for(j = 1; j <= (h + i); j++) {
    if(j <= (h - i + 1))
      putchar(' ');
    else
      putchar('*');
    putchar('\n');
  }

Начальное значение счётчиков i, j равно 1, чтобы обеспечить отступ в один дополнительный символ перед каждой строкой.

Нарисуем 3 строку треугольника (i = 3). Пусть высота h равна 5.


h + i = 8;
h - i + 1 = 3;

 |<------------- 8 ------------->|
  _______________________________
 |   |   |   | * | * | * | * | * |
  -------------------------------
             |<----- j > 3 ----->|

             рис.3

Я думаю, из рисунка 3 можно понять работу этого алгоритма.

В алгоритме отрисовки ёлки половину её ширины нужно вычислять отдельно - ведь теперь эта величина не равна высоте, как в случае с отрисовкой треугольника. Однако, не всё так уж и плохо - есть простая формула, о которой я уже говорил в предыдущем посте.

hw = h / 2 - 1          (1)


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

Теперь вернёмся к проблеме удвоения каждой строки. Поскольку, как уже было сказано выше, количество звёздочек увеличивается на 2 на чётной строке, попробуем сделать вот что.

Я просто разделю i на 2 во всех проверках условий во вложенном цикле. Вот так:

// ...
int hw;
// ...
hw = height / 2 - 1;

for(i = 0; i < height; i++)
  /* Печатаю номер строки в обратном порядке (снизу вверх) */
  printf("%02d\t", height-i);

  for(j = 0; j <= (hw + i / 2); j++) { // <-- здесь
    if(j < (hw - i / 2)) // <-- и здесь
      putchar(' ');
    else
      if(i == 0) // или можно написать: if(!i)
        /* Рисую большую звезду на верхушке ёлки */
        putchar('X');
      else
        putchar('*');

    putchar('\n');
  }
// ...

Если строка нечётная, то hw будет увеличиваться в проверке условия завершения цикла for() и уменьшаться в условии if() - на остаток от деления, т.е. на 1. В результате, количество звёздочек в нечётной строке увеличиться на 2. Если же номер строки - чётный, то никаких изменений не происходит, просто копируется предыдущая строка.

Мне кажется, это потрясающе просто и понятно.

суббота, 29 января 2011 г.

Рисуем новогоднюю ёлку с помощью Си: алгоритм номер 0.

Некоторые даже в предпраздничные дни забивают себе голову алгоритмами, программированием и прочими непраздничными вещами.

Вот и я перед новым годом (который, кстати, уже наступил - если что) придумал себе проблему, и упорно решал её на протяжении длительного времени.

С чего всё началось


А началось всё со следующей задачи.

По условию задачи пользователь вводит число строк, а программа должна нарисовать равнобедренный треугольник с помощью звёздочек - "*". Треугольник должен выглядеть так:


    *
   ***
  *****

Для примера, положим, что я ввёл высоту, равную 5 строкам.


 _
|      *
|     ***
5    *****
|   *******
|_ *********
  |----9----|

Тогда, как видно на примере выше, ширина основания треугольника равна 9 звёздочкам. Если я возьму другую высоту - например, 10 строк - то получу треугольник шириной 19 звёздочек. Отсюда выводим формулу:

w = h / 2 - 1          (1)


где w - ширина, h - высота.

Рисую равнобедренный треугольник


Чтобы нарисовать ровный треугольник, мне нужно помещать определённое количество пробелов перед каждой строкой, в зависимости от номера строки.


 -Что есть-  -Что должно получиться-
1 *              1     *
2 ***            2    ***
3 *****      =>  3   *****
4 *******        4  *******
5 *********      5 *********
 \             /
  номер строки 


Чтобы это осуществить, надо знать номер центрального "столбца".


строки
  1     *
  2    ***
  3   *****
  4  *******
  5 *********
    123456789  <-- столбцы
        ^
   центральный
     столбец

Очевидно, в треугольнике высотой 5 строк, центральным будет 5-й столбец. И тут оказывается, что для нахождения центрального столбца нам нужно знать только высоту h треугольника, введённую пользователем - ведь, если верить формуле 1, то номер центрального столбца всегда будет совпадать с высотой. Это позволяет упростить программу.

Вот, кстати, код на Си:


#include <stdio.h>

int main() {
  int height;
  int i, j;

  printf("Please enter height:\n> ");
  scanf("%d", &height);

  for(i = 1; i <= height; i++) {
    for(j = 1; j <= (height + i); j++) {
      if(j <= (height - i + 1))
        putchar(' '); // Печатаем пробел
      else
        putchar('*'); // Печатаем звёздочку
    }
    putchar('\n'); // Переходим на новую строку
  }

  return 0;    
}

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

Первый цикл работает, пока i меньше или равно половины ширины треугольника. А поскольку, как уже было сказано выше, высота треугольника совпадает с номером центрального столбца, т.е. его центра - то я могу использовать значение введённой пользователем высоты height. Что я и делаю.

Второй цикл работает, пока j меньше или равно height плюс значение i (т.е. номер текущей строки).

Далее я проверяю, если j меньше или равно height минус i плюс 1. Единица здесь задаёт смещение всего треугольника от левой границы экрана. Если условие выполняется, то я печатаю пробел, иначе - звёздочку.

Эта программа решает поставленную задачу - рисует треугольник из звёздочек высотой N строк. Но...

Что не так с этим алгоритмом?..


То, что рисует эта программа, совсем не похоже на новогоднюю ёлку. А значит, эти красивые деревья будут продолжать вырубать для того, чтобы они простояли несколько дней в какой-нибудь городской квартире, пока не засохнут. Так скоро в лесу одни пеньки останутся. А пеньки, - как говорил известный персонаж известной повести, - они только для старушек хороши: на них сидеть можно.

Как бы нарисовать что-то, похожее на ёлку - да ещё так, чтобы эта ёлка высотой N, будучи вписанной в квадрат размером NxN, в основании была так же равной N?

Я подошёл к решению этой проблемы со всей серьёзностью, и нарисовал следующее:


fig. 1 отображает алгоритм отрисовки звёздочек, который я уже описывал выше. Получается равнобедренный треугольник.

Алгоритм отрисовки треугольника я не случайно назвал "алгоритмом номер 0" - следующие два алгоритма отрисовки ёлки реализуются на основе идей, реализованных в программе, решающей задачу с треугольником.

fig. 2 показывает, как можно нарисовать нечто похожее на ёлку - путём удвоения каждой строки. Но ширина основания по-прежнему меньше высоты.

fig. 3 показывает алгоритм отрисовки ёлки. Это как раз то, что нужно. Как видно из рисунка, ширина ёлки при таком способе заполнения строк равна высоте. Причём, здесь можно заметить интересную особенность данного алгоритма - она показана на рисунке справа от fig. 3.

Пока это всё. Предлагаю читателям подумать над реализацией второго и третьего алгоритма отрисовки ёлки.

Реализацию второго алгоритма я рассмотрю в следующем посте.