Нижегородский государственный университет им. Н.И.Лобачевского
Опубликован: 25.11.2008 | Доступ: свободный | Студентов: 9592 / 1296 | Оценка: 4.06 / 3.66 | Длительность: 21:16:00
Лекция 9:

Работа с массивами

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >

Пример 8.4. Количество "счастливых" билетов. Билеты для общественного транспорта хранятся в рулонах и идентифицируются номерами от 000000 до 999999. В молодежной среде "счастливым" считался билет, у которого сумма трех первых цифр совпадала с суммой трех последних цифр. Попытаемся оценить количество "счастливых билетов в полном рулоне с целью определения вероятности получить такой билет.

Самый простой способ заключается в организации 6 вложенных друг в друга циклов, где каждый счетчик перебирает цифры от 0 до 9, а во внутреннем цикле сравниваются суммы первых трех и последних трех счетчиков:

#include <iostream.h>
#include <conio.h>
void main()
{ int n=0;
  for(int j1=0; j1<10; j1++)
   for(int j2=0; j2<10; j2++)
    for(int j3=0; j3<10; j3++)
     for(int j4=0; j4<10; j4++)
      for(int j5=0; j5<10; j5++)
       for(int j6=0; j6<10; j6++)
        if(j1+j2+j3==j4+j5+j6) n++;
  cout << n;
  getch();
}

Считает эта программа правильно и довольно быстро (на ПК с частотой 2 ГГц время работы не превышает 1 сек). Но работу этой программы можно ускорить почти в 1000 раз за счет использования массивов. Сумма трех цифр принадлежит диапазону [0,27]. Допустим, что нам удалось подсчитать, сколько раз встретилась каждая сумма – s0 (количество сочетаний, давших сумму 0), s1 (количество сочетаний, давших сумму 1), ... . Тогда количество "счастливых" билетов будет равно (s0)2+(s1)2+ .... Поэтому гораздо более быстрой программой будет следующая:

#include <iostream.h>
#include <conio.h>
void main()
{ int n=0,k,s[28];
  for(k=0; k<28; k++) s[k]=0;
  for(int j1=0; j1<10; j1++)
    for(int j2=0; j2<10; j2++)
      for(int j3=0; j3<10; j3++)
        s[j1+j2+j3]++;
  for(k=0; k<28; k++)
    n += s[k]*s[k];
  cout << n;
  getch();
}

Количество циклов, которое "крутится" в этой программе равно 28+1000+28, тогда как в предыдущей программе тело самого внутреннего цикла повторялось 1000000 раз.

Пример 8.5. Ход конем. Одна из задач, связанных с шахматами заключается в определении минимального количества ходов коня, за которое он может перебраться из стартовой клетки шахматного поля в заданную клетку. Напомним, что конь ходит "буквой Г", совершая за один ход перемещение на 2 клетки по одной координате (вертикали или горизонтали) и на одну клетку по другой координате. Шахматисты пользуются алфавитно-цифровой кодировкой полей шахматной доски, обозначая их по горизонтали буквами (A,B,C,D,E,F,G,H), а по вертикали – цифрами (1,2,3,4,5,6,7,8). Однако для программы удобнее иметь дело только с цифровыми индексами, изменяющимися (с учетом требований языка C) от 0 до 7. Идея определения минимального количества ходов заключается в заполнении клеток шахматного поля числами досягаемости. Сначала мы находимся в стартовой позиции и пытаемся сделать из нее один из 8 возможных ходов (не выходя за пределы шахматной доски). Каждую из достижимых таким образом клеток отметим числом 1. Затем из каждой из них попытаемся сделать тоже один из возможных ходов, не возвращаясь в те клетки, где мы уже побывали. Вновь достижимые клетки пометим числом 2. Таким способом можно заполнить все клетки шахматного поля, включая и ту, в которую мы должны были придти из стартовой позиции. Для того чтобы выделить свободные клетки, еще не помеченные уровнем досягаемости, распишем в начале все клетки числом -1.

Пусть массив a размерности 8x8 моделирует шахматную доску. Распишем элементы этого массива числами -1 (признак того, что все клетки свободны). Затем запросим у пользователя индексы стартовой позиции и занесем в этот элемент массива 0 (из стартовой позиции в нее же можно добраться за 0 ходов). Пусть функция, которая пытается сделать один из 8 возможных ходов, называется newlevel и использует в качестве исходной информации данные, вынесенные в глобальные переменные – матрицу a, уровень досягаемости k и управляющую переменную xod. Ее задачей является поиск всех незанятых клеток, находящихся на расстоянии одного хода от клеток с уровнем досягаемости k и записью в них кода k+1. Значение управляющей переменной xod=1, если хотя бы один такой ход удалось сделать. Если все клетки шахматной доски помечены неотрицательными уровнями досягаемости, т.е. ни одного хода больше сделать нельзя, то значение переменной xod равно 0. В функции newlevel удобно выделить процедуру try_ (подчерк добавлен к имени потому, что слово try является служебным), которая пробует, достижима и свободна ли позиция с индексами ( p,q ), в которой надо разместить очередной уровень досягаемости ( k+1 ).

Описанные выше функции newlevel и try_ могут быть организованы следующим образом:

void try_(int p, int q)
{ if(p>=0 && p<8 && q>=0 && q<8 && a[p][q]<0)
    { a[p][q]=k+1; xod=1; }
}
//----------------------------------
void newlevel()
{ char di[8]={-2,-2,-1,-1, 1,1, 2,2};
  char dj[8]={-1, 1,-2, 2,-2,2,-1,1};
  xod=0;
  for(int i=0; i<8; i++)
    for(int j=0; j<8; j++)
      if(a[i][j]==k)
        for(int r=0; r<8; r++)
          try_(i+di[r],j+dj[r]);
  k++;
}
//----------------------------------

Обратите внимание на массивы di и dj. Их одноименные элементы образованы из всевозможных сочетаний \pm 1 и \pm 2, которые определяют 8 всевозможных направлений для хода конем. Цикл по r перебирает все эти комбинации относительно позиции ( i,j ).

Головная программа, обращающаяся к функции newlevel, может иметь следующий вид:

#include <stdio.h>
#include <conio.h>
char a[8][8],i,j,k=0,xod;
void main()
{ for(i=0;i<8;i++)
    for(j=0; j<8;j++) a[i][j]=-1;
  printf("begin position=");
  scanf("%d %d",&i,&j);
  a[i][j]=0;
  do newlevel();
  while (xod==1);
  for(i=0;i<8;i++)
    { for(j=0; j<8; j++) printf("%3d",a[i][j]);
      printf("\n");
    }
  getch();
}

Результат работы для начальной позиции (1,1) приведен на рис 8.2.

Ход конем

Рис. 8.2. Ход конем

Пример 8.6. Определение количества разных элементов в целочисленном массиве. Первый вариант программы основан на предварительной сортировке исходного массива. После того как массив отсортирован, следует проанализировать соседние элементы и, как только встречается пара разных чисел, к счетчику надо добавлять 1.

#include <stdio.h>
#include <conio.h>
void sort(int *a,int n)
{ int tmp;
  for(int i=0;i<n-1;i++)
    for(int j=i+1;j<n; j++)
      if(a[j]<a[i])
        {tmp=a[i]; a[i]=a[j]; a[j]=tmp; }
}
int difference(int *a,int n)
{ int i,m=1;
  sort(a,n);	//сортировка исходного массива
  for(i=0; i<n-1; i++)
    if(a[i]!=a[i+1]) m++;
  return m;
}
void main()
{ int a0[5]={0,0,0,0,0};
  int a1[5]={1,1,1,1,1};
  int a2[5]={0,1,1,1,1};
  int a3[5]={0,0,1,1,2};
  int a4[5]={0,1,2,3,4};
  int a5[5]={1,2,3,4,5};
  printf("\na0:%d",difference(a0,5));
  printf("\na1:%d",difference(a1,5));
  printf("\na2:%d",difference(a2,5));
  printf("\na3:%d",difference(a3,5));
  printf("\na4:%d",difference(a4,5));
  printf("\na5:%d",difference(a5,5));
  getch();
}
//=== Результат работы ===
a0:1
a1:1
a2:2
a3:3
a4:5
a5:5

Вариант 2. При первом просмотре определяем, содержится ли в исходном массиве хотя бы один нулевой элемент. Если содержится, то в переменную k0 заносим 1, в противном случае в k0 заносим 0. При втором проходе нулевые элементы исключаем из рассмотрения и сравниваем a[i] c a[j]. В случае равенства в элемент a[i] заносим 0. При третьем проходе подсчитываем ненулевые элементы и добавляем к сумме k0. Мы ограничимся только видоизмененной функцией difference:

int difference(int *a,int n)
{ int i,j,k0=0,m=0;
  for(i=0; i<n; i++)		//первый проход
    if(a[i]==0) { k0=1; break; }	//поиск нулевого элемента
  for(i=0; i<n-1; i++)	//второй проход
    { if(a[i]==0) continue;	//обход нулей
      for(j=i+1; j<n; j++)		//поиск дубликатов
        if(a[i]==a[j])
          { a[i]=0; break; }	//забой дубликата
    }
  for(i=0;i<n;i++)	//подсчет ненулевых элементов
    if(a[i]!=0) m++;
  return m+k0;
}

Вариант 3. Использование логической шкалы для подсчета количества разных элементов в целочисленном массиве с положительными данными. Если массив содержит отрицательные элементы, то при первом проходе можно определить максимальный из них и на эту величину сместить все значения. Идея использования логической шкалы состоит в том, что с каждым двоичным разрядом шкалы можно связать последовательно возрастающие целые числа. Сначала все биты логической шкалы сбрасываются в 0. Затем организуется цикл просмотра всех элементов массива, при котором мы определяем номер разряда в шкале, соответствующий проверяемому значению. Если в соответствующей позиции шкалы находится 0, то такое число встретилось впервые и к счетчику разных чисел надо добавить 1. В противном случае такое число уже попадалось и его надо пропустить.

Обратите внимание на следующие приемы работы со шкалой. Во-первых, под нее надо запросить память (желательно, чистую). Это можно сделать следующим образом. Если длина нашего массива не превышает 32767 элементов, то под логическую шкалу потребуется не более 4096 байт. Запрос памяти под шкалу лучше всего организовать через библиотечную функцию calloc. У нее задается два аргумента – количество элементов данных и число байт, необходимых для хранения каждого элемента:

b=calloc(4096,1);	//запрос чистой памяти

Во вторых, для перевода целого числа N в соответствующий разряд шкалы b удобнее воспользоваться номером байта шкала (переменная byte ) и номером бита (переменная bit ) в этом байте. Кроме этого нам потребуется массив из 8 однобайтовых констант, каждая из которых представляет один бит шкалы:

char mask[8]={128,64,32,16,8,4,2,1};

Для определения номеров байта и бита, соответствующих числу N, достаточно проделать два деления:

byte=N / 8;
  bit =N % 8;

В итоге окончательный вид функции difference таков:

int difference(int *a,int n)
{ int bit,byte,i,m=0;
  char mask[8]={128,64,32,16,8,4,2,1};
  char *b=(char *)calloc(4096,1);	//запрос и очистка памяти
  for(i=0; i<n; i++)
    { byte = a[i] / 8;
      bit = a[i] % 8;
      if((b[byte]& mask[bit])==0)	//проверка бита шкалы
        { m++; b[byte] |= mask[bit]; }	//вписывание бита в шкалу
    }
  free(b);      //освобождение памяти
  return m;
}

При окончательной сборке программы надо не забыть подключить заголовочный файл alloc.h, в котором находится прототип функции calloc.

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >
Alexey Ku
Alexey Ku

Попробуйте часть кода до слова main заменить на 

#include "stdafx.h" //1

#include <iostream> //2
#include <conio.h>

using namespace std; //3

Александр Талеев
Александр Талеев

#include <iostream.h>
#include <conio.h>
int main(void)
{
int a,b,max;
cout << "a=5";
cin >> a;
cout <<"b=3";
cin >> b;
if(a>b) max=a;
else max=b;
cout <<" max="<<max;
getch();
return 0;
}

при запуске в visual express выдает ошибки 

Ошибка    1    error C1083: Не удается открыть файл включение: iostream.h: No such file or directory    c:\users\саня\documents\visual studio 2012\projects\проект3\проект3\исходный код.cpp    1    1    Проект3

    2    IntelliSense: не удается открыть источник файл "iostream.h"    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    1    1    Проект3

    3    IntelliSense: идентификатор "cout" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    6    1    Проект3

    4    IntelliSense: идентификатор "cin" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    7    1    Проект3

при создании файла я выбрал пустой проект. Может нужно было выбрать консольное приложение?