Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рацеев С.М. Программирование на языке Си

.pdf
Скачиваний:
555
Добавлен:
23.03.2016
Размер:
1.77 Mб
Скачать

Пример 8. Дана некоторая строка s. Определить сколько различных символов встречается в строке s, не рассматривая символ

‘\0’.

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

int Count(char *s)

{

int i, n, flag[256] = {0}; for(i = 0; s[i]; i++)

flag[s[i]] = 1;

for(i = n = 0; i < 256; i++) if (flag[i])

n++; return n;

}

Пример 9. Дана некоторая строка s. Проверить, все ли символы данной строки попарно различны.

int Check(char *s)

{

int i, flag = 1, count[256] = {0}; for(i = 0; s[i] && flag; i++)

{

count[s[i]]++;

if (count[s[i]] > 1) flag = 0;

}

return flag;

}

Заметим, что возможности языка Си позволяют записать функцию Count() в более компактной и оптимальной форме:

int Check(char *s)

{

131

int i, count[256] = {0};

for(i = 0; s[i] && ++count[s[i]] < 2; i++)

;

return s[i] == '\0';

}

Пример 10. Дана некоторая строка s. Выделить в данной строке все целые неотрицательные числа и найти их сумму.

Для решения данной задачи для разнообразия используем динамическую строку digit, куда будем записывать очередное выделенное число из строки s.

#include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1024

/* функция возвращает сумму целых чисел из строки s */ int Sum(char *s)

{

int i, j, sum;

char *digit = NULL; sum = 0;

/* пропускаем все символы, не являющиеся цифрами: */ for(i = 0; s[i] && !isdigit(s[i]); i++)

i++; while (s[i])

{

j = i; /* запоминаем позицию начала очередного числа */ /* пробегаем по всем подряд идущим цифрам, входящим в

состав очередного числа:

*/

while (s[i] && isdigit(s[i])) i++;

/* выделяем память для динамической строки digit: */ digit = (char *)malloc((i - j + 1) * sizeof(char));

strncpy(digit, &s[j], i - j); /* копируем цифры в строку digit */ digit[i - j] = '\0';

132

sum += atoi(digit); /* прибавляем очередное число к sum*/ free(digit); /* освобождаем память */

digit = NULL;

/* пропускаем все символы, не являющиеся цифрами: */ while (s[i] && !isdigit(s[i]))

i++;

}

return sum;

}

int main( )

{

char s[N]; fgets(s, N, stdin);

printf("sum = %d\n", Sum(s)); return 0;

}

Пример 11. Пусть SET – некоторое множество символов из таблицы символов ASCII, не содержащее символ ‘\0’. Требуется в строке s удалить все символы, принадлежащие множеству SET.

Пусть, например, SET = {‘A’, ‘B’, ‘a’, ‘b’, ‘!’}. Приведем очень эффективный алгоритм решения данной задачи. Пусть int flag[256] – логический массив, каждый индекс которого соответствует определенному символу из таблицы символов ASCII. Все элементы массива flag, индексы которых соответствуют элементам множества SET, определим единицами, а все остальные элементы – 0. Для написания алгоритма учтем пример 5 пункта 5.2 и замечание к примеру 4 данного пункта.

#define SET "ABab!"

void Del(char *s)

{

int i, j, flag[256] = {0};

/* Все элементы массива flag, индексы которых соответствуют элементам множества SET, определяем единицами:

133

*/

for(i = 0; SET[i]; i++) flag[SET[i]] = 1;

/* Пробегаем все символы строки, не принадлежащие множеству SET:

*/

for(j = 0; s[j] && !flag[s[j]]; j++)

;

/* Переставляем на новые позиции все символы в строке s, не принадлежащие множеству SET:

*/

for(i = j; s[i]; i++) if (!flag[s[i]])

s[j++] = s[i]; s[j] = '\0';

}

Пример 12. Пусть s и t – некоторые строки. Требуется написать функцию, которая определяет количество символов, одновременно входящих в обе данные строки без учета символа ‘\0’.

Сложность приведенного ниже алгоритма равна length(s)+length(t).

int Count(char *s, char *t)

{

int i, n, flag[256] = {0}; for(i = n = 0; s[i]; i++)

flag[s[i]] = 1; for(i = 0; t[i]; i++) if (flag[t[i]])

n++; return n;

}

Пример 13. Пусть имеются строки SET_A, SET_B и s. Требуется проверить, чередуются ли в строке s элементы, принадлежащие строкам SET_A и SET_B. Например, если SET_A – строка, со-

134

стоящая из латинских букв, а SET_B – строка, состоящая из цифр, то требуется проверить, чередуются ли в строке s буквы и цифры. Или если SET_A – строка, состоящая из гласных латинских букв, SET_B – строка, состоящая из согласных латинских букв, то требуется проверить, чередуются ли в строке s гласные и согласные латинские буквы.

Рассмотрим очень быстрый и оптимальный алгоритм решения данной задачи, не использующий функцию strchr(), которая, как было отмечено выше, будет тормозить работу алгоритма. Заведем два целочисленных массива flag_a и flag_b, каждый из которых состоит из 256 элементов (количество элементов из таблицы символов ASCII), и пусть i-ый элемент массива flag_a (flag_b) соответствует i-ому элементу из таблицы символов. Напротив каждого элемента в массиве flag_a, которые входят в строку SET_A, выставим флаг, равный 1, а все остальные элементы в массиве flag_a определим нулевыми значениями:

int flag_a[256] = {0}; for(i = 0; SET_A[i]; i++)

flag_a[SET_A[i]] = 1;

Точно так же поступим и с массивом flag_b. После этого очень легко и, самое главное, очень быстро можно проверить, принадлежит ли один из символов s[i-1] и s[i] строки s строке SET_A, а другой – строке SET_B:

if (flag_a[s[i-1]] && flag_b[s[i]] || flag_b[s[i-1]] && flag_a[s[i]])

Алгоритм решения данной задачи теперь будет иметь такой вид:

#define SET_A "ABCD" #define SET_B "xyz"

int Check(char *s)

{

int i, flag_a[256] = {0}, flag_b[256] ={0}; if (s[0] == 0)

return 1;

135

for(i = 0; SET_A[i]; i++) flag_a[SET_A[i]] = 1; for(i = 0; SET_B[i]; i++) flag_b[SET_B[i]] = 1;

i = 1;

while(s[i] && (flag_a[s[i-1]] && flag_b[s[i]] || flag_b[s[i-1]] && flag_a[s[i]])) i++;

return s[i] == '\0';

}

Рассмотрим также частный случай данной задачи. Пусть требуется проверить, чередуются ли в строке s гласные и согласные латинские буквы. В этом случае, конечно, можно в строку SET_A записать все гласные латинские буквы, а в строку SET_B – согласные и применить функцию Check(), но все же в этом случае алгоритм можно упростить и немного ускорить.

Пусть VOWEL – строка, содержащая все гласные латинские буквы, а flag[256] – массив, в котором каждой гласной букве соответствует единица, а всем остальным символам – 0. Поэтому если символы s[i-1] и s[i] являются латинскими буквами и одна из них гласная, а другая согласная, то flag[s[i-1]]+flag[s[i]]==1. Используем это свойство в следующем алгоритме:

#define VOWEL "AEIOUYaeiouy"

int Check(char *s)

{

int i, flag[256] = {0}; if (s[0] == 0)

return 1;

if (!isalpha(s[0])) return 0;

for(i = 0; VOWEL[i]; i++) flag[VOWEL[i]] = 1;

i = 1;

while(s[i] && isalpha(s[i]) && (flag[s[i-1]] + flag[s[i]] == 1)) i++;

return s[i] == '\0';

136

}

Пример 14. Пусть s – некоторая строка, c – некоторый символ. Требуется в строке s каждую серию подряд идущих символов c заменить одним символом c.

void Zamena(char *s, char c)

{

int i, j, n; i = j = 0; while(s[i])

{

n = 0; /* количество подряд идущих символов c */ while (s[i] && s[i] == c)

{

i++;

n++;

}

if (n > 0) s[j++] = c;

while (s[i] && s[i] != c) s[j++] = s[i++];

}

s[j] = '\0';

}

int main( )

{

char s[1024]; fgets(s, 1024, stdin); Zamena(s, 'a'); puts(s);

return 0;

}

Пример 15. В строке s заменить каждую серию подряд идущих одинаковых символов на один символ данной серии. Например, строку s=”aabbbccd” необходимо преобразовать в s=”abcd”.

137

void Zamena(char *s)

{

int i, j;

if (s[0] == '\0') return;

for (i = j = 1; s[i]; i++) if (s[i] != s[i-1]) s[j++] = s[i];

s[j] = '\0';

}

Пример 16. Из строки s удалить все слова, имеющие в своем составе хотя бы одну цифру.

#include <stdio.h> #include <string.h> #include <ctype.h> #define DEL " .,:;\n\t" #define N 1024

/* проверка, присутствует ли в массиве s хотя бы одна цифра: */ int Check(char *s, int len)

{

int i;

for(i = 0; i < len && !isdigit(s[i]); i++)

;

return i >= len;

}

int main( )

{

char s[N]; int i, j, k, len;

fgets(s, N, stdin); i = k = 0;

while (s[i])

{

138

while (s[i] && strchr(DEL, s[i])) s[k++] = s[i++];

j = i;

while (s[i] && !strchr(DEL, s[i])) i++;

len = i - j;

if (Check(s + j, len))

{

strncpy(s + k, s + j, len); k += len;

}

}

s[k] = '\0'; puts(s); return 0;

}

8.7. Разбиение строки на лексемы

Рассмотрим задачу выделения всех слов в строке-предложе- нии. Обозначим через sentence исходную строку-предложение, в которой требуется выделить все слова, а через word – очередное выделенное слово в строке sentence.

Возможен случай, когда переменная sentence является массивом символов, например:

char sentence[1024];

Также возможен случай, когда переменная sentence является указателем на строку:

char *sentence;

То же самое можно сказать и о переменной word. Данные случаи исходят из той или иной задачи работы со строками.

Случай 1. Рассмотрим такой случай: пусть переменная sentence является массивом символов и некоторые ее элементы, например, символы-разделители разрешается заменить на символ '\0', а переменная word является указателем на строку. Составим программу, которая будет заменять первый символ из каждой се-

139

рии подряд идущих символов-разделителей на '\0', а указатель word будет каждый раз содержать адрес первого символа очередной лексемы в предложении sentence.

Для эффективной реализации данного алгоритма введем логический массив int flag[256], в котором порядковые номера (индексы) элементов соответствуют кодам символов из таблицы символов ASCII. Элементы данного массива инициализируем следующим образом: если символ с кодом i является символомразделителем, то полагаем flag[i] = 1, иначе flag[i] = 0. И так для всех символов из таблицы символов ASCII (i = 0, 1, …, 255):

int flag[256] = {0};

for (i = 0; DELIMITERS[i]; i++) flag[DELIMITERS[i]] = 1;

После данного цикла массив flag будет иметь такой вид:

Теперь проверка, является ли i-й символ строки sentence символомразделителем, будет выглядеть следующим образом:

if (flag[sentence[i]])

Ниже приводится очень эффективный алгоритм выделения слов из строки sentence. Заметим, что сложность данного алгоритма равна m+n, где m – длина строки DELIMITERS, n – длина строки sentence, при этом данный метод разбиения строки на слова работает в разы быстрее чем функция strtok().

#include <stdio.h>

#define DELIMITERS " .,:;?!\n\t" /* символы-разделители */ #define N 1024

int main( )

{

char sentence[N]; /* исходная строка */

char *word; /* адрес начала очередного слова в предложении */

140