Помощь - Поиск - Пользователи - Календарь
Полная версия: Односвязные линейные списки
Форум «Всё о Паскале» > Современный Паскаль и другие языки > Ада и другие языки
18192123
Дан текстовый файл. Строки этого файла расположить в порядке убывания их длины и удалить пять самых коротких из них.
Для размещения в памяти содержимого файлов использовать односвязные линейные списки.

Предполагаю заносить каждую строку в одно поле списка, но не знаю, как это сделать именно со строкой и сколько выделять памяти.
volvo
После прочтения строки из файла, проверяй ее длину (strlen) и выделяй под информационное поле в списке на 1 байт больше (для хранения завершающего '\0')
18192123
хочу сделать функцию для расположения строк в порядке убывания их длины.
пусть до этого у меня есть ф-я, которая формирует список (ф-я возвращает указатель на голову списка). тогда в ф-ю упорядочения передаём указатель на голову списка. а дальше я не разберусь , как с помощью списка собственно упорядочить строки....
помогите пожалуйста!
volvo
Ты какой путь выбираешь, сразу заносить очередной элемент на соответствующее место, или сначала занести ВСЕ элементы, а потом - сортировать?

Скорее всего, второй? Тогда я уже выкладывал где-то переведенную на Паскаль рекурсивную функцию сортировки списка вставками (из книги Sedgewick-а "Fundamental C++"), если надо - выложу оригинал на С ...
18192123
Цитата(volvo @ 1.05.2007 17:30) *

Ты какой путь выбираешь, сразу заносить очередной элемент на соответствующее место, или сначала занести ВСЕ элементы, а потом - сортировать?

Скорее всего, второй? Тогда я уже выкладывал где-то переведенную на Паскаль рекурсивную функцию сортировки списка вставками (из книги Sedgewick-а "Fundamental C++"), если надо - выложу оригинал на С ...


да, второй. только это не должна быть рекурсивная ф-я....
volvo
Ну, тогда тебе и карты в руки... Опять изобретение велосипеда? Что же за преподаватели-то такие? Они же просто угробят все, что только можно!!! С одной стороны они ограничили тебя односвязным списком, то есть, у тебя только последовательный проход по списку (про сотню проходов по списку из 100 элементов я не говорю - это вообще полный бред), а с другой стороны - те методы, которые эффективны при последовательном доступе (в частности - mergesort) - они рекурсивные... А рекурсия запрещена. Замкнутый круг...
18192123
Цитата(volvo @ 1.05.2007 18:46) *

Ну, тогда тебе и карты в руки... Опять изобретение велосипеда? Что же за преподаватели-то такие? Они же просто угробят все, что только можно!!! С одной стороны они ограничили тебя односвязным списком, то есть, у тебя только последовательный проход по списку (про сотню проходов по списку из 100 элементов я не говорю - это вообще полный бред), а с другой стороны - те методы, которые эффективны при последовательном доступе (в частности - mergesort) - они рекурсивные... А рекурсия запрещена. Замкнутый круг...

ээээ...и как быть?
18192123
попыталась написать ф-ю, которая формирует список (ф-я возвращает указатель на голову списка).
но насчёт занесения строки в информационное поле элемента списка - ???

#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <alloc.h>
typedef struct list {
char number[50];
struct list *next;
} LIST;

LIST *read_list(LIST *lst)
{
LIST *p;
FILE *f;
f=fopen("rg.txt","r");
if (!feof(f))
{
lst=(LIST *)malloc(sizeof(LIST));
p=lst;
while (1)
{
fscanf(f,"%s",p->number[50]);
if (!feof(f))
{
p->next=(LIST *)malloc(sizeof(LIST));
p=p->next;
} else break;
}
p->next=NULL;
}
else printf("Файл пустой\n");
fclose(f);
return lst;
}



volvo
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <alloc.h>

typedef struct list {

char number[50];
struct list *next;

} LIST;

LIST *read_list() {
char s[255];

LIST *p, *lst, *final;
FILE *f;
f=fopen("rg.txt","r");

lst = NULL;
while(!feof(f)) {

fscanf(f, "%s", s);
p = (LIST*)malloc(sizeof(list));
strcpy(p -> number, s);

if(!lst) lst = p;
else final -> next = p;

final = p;

}
fclose(f);

if(!lst) {
printf("empty file\n");
return NULL;
}
final -> next = NULL;

return lst;
}

void sort(LIST **first) {

LIST *L, *p;
char s[255];

for(L = *first; L -> next; L = L -> next)

for(p = L -> next; p; p = p -> next) {

if(strcmp(L -> number, p -> number) > 0) {
strcpy(s, L -> number);
strcpy(L -> number, p -> number);
strcpy(p -> number, s);
}
}

}


int main() {
struct list *p;
p = read_list();
sort(&p);
for(; p; p = p -> next)
printf("%s\n", p -> number);

return 0;
}

Так работает?
18192123
Цитата(volvo @ 1.05.2007 21:48) *


strcpy(p -> number, s);




да, работает!
А что значит эта строка?

if(!lst) lst = p;
else final -> next = p;



и что это?

*final - указатель на конец списка, да?
volvo
Цитата
А что значит эта строка?
Эта строка значит следующее: если lst == NULL (или, как я написал, отрицание lst не равно нулю, что одно и то же для С), значит в список пока ничего не было добавлено, в таком случае в lst надо записать значение p, поскольку в данный момент добавляется первый элемент...

А по ветке else - если список уже НЕ пуст, то в указатель next последнего поля надо записывать p, связывая выделенную память с собственно списком...

Цитата
*final - указатель на конец списка, да?
Именно... Это указатель на тот элемент списка, к которому будет присоединяться новый при добавлении его в конец списка...
18192123
А как будет выглядеть удаление 5 нужных элементов? ( тогда это ещё одна ф-я, куда, наверно, нужно передать указатель на голову списка...а что-то кроме этого нужно передавать?)

А когда всё необходимое выполнено, нам ведь нужно освободить занятые участки памяти и указателям, ссылающимся на них, присвоить NULL? (т.е. я так понимаю - отсортировали, удалили лишнее, выводим на экран содержимое оставшихся информационных полей и попутно освобождаем занятые участки.... а как это выполняется?)

volvo
Цитата
А как будет выглядеть удаление 5 нужных элементов?
А по какому признаку будешь удалять?

Цитата
А когда всё необходимое выполнено, нам ведь нужно освободить занятые участки памяти
Нужно... Пробежаться по всему списку, и сделать free для каждого узла списка. Алгоритм - точно такой же, как и в FAQ по Паскалю -> "Все о динамических структурах данных".
18192123
Цитата(volvo @ 2.05.2007 12:35) *

А по какому признаку будешь удалять?


удалять 5 самых коротких, т.е. вроде как после сортировки стоящих в конце
volvo
Будет гораздо проще сделать наоборот: отсортировать слова по возрастанию длины, а потом удалить 5 первых, поскольку у тебя односвязный список, и работать от "головы" к "хвосту" легче, чем от "хвоста" к "голове"... Смотри:

#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <alloc.h>

typedef struct list {

char number[50];
struct list *next;

} LIST;

LIST *read_list() {
char s[255];

LIST *p, *lst, *final;
FILE *f;
f=fopen("rg.txt","r");

lst = NULL;
while(!feof(f)) {

fscanf(f, "%s", s);
p = (LIST*)malloc(sizeof(list));
strcpy(p -> number, s);

if(!lst) lst = p;
else final -> next = p;

final = p;

}
fclose(f);

if(!lst) {
printf("empty file\n");
return NULL;
}
final -> next = NULL;

return lst;
}

void sort(LIST **first) {

LIST *L, *p;
char s[255];

for(L = *first; L -> next; L = L -> next)

for(p = L -> next; p; p = p -> next) {

if(strlen(L -> number) > strlen(p -> number)) { /* сортируем по длине */
strcpy(s, L -> number);
strcpy(L -> number, p -> number);
strcpy(p -> number, s);
}
}

}


int main() {
struct list *root, *p, *T;
int n;

/* читаем список из файла */
root = read_list();

/* сортируем его по возрастанию длин строк */
sort(&root);
/* и печатаем ... */
for(p = root; p; p = p -> next)
printf("%s\n", p -> number);

/* теперь удаляем первые 5 элементов */
n = 0;
for(p = root; p && n < 5; n++) {

p = (T = p) -> next; /* запоминаем текущее P в переменной T, для того чтобы потом удалить */
free(T);
}
/* и устанавливаем новое начало списка */
root = p;

/* еще раз проходим, и печатаем весь список, сразу удаляя все узлы, которые уже распечатаны */
for(p = root; p; ) {
printf("%s\n", p -> number);
p = (T = p) -> next;
free(T);
}
root = NULL;
return 0;
}

Вот и все...
18192123
вот такие тестовые данные ввожу в исходный файл:
qwerty
asdf
qwertyu
qwertyqwerty
qwertyuioop
zxcvbnmjhy
qw
q
zxcvfgh

результат в прикреплённом файле.
не пойму, почему строчка zxcvfgh выводится 2 раза?
volvo
Потому что из списка удаляется всего 5 элементов, а не все элементы, с 5-ю наименьшими длинами...

Добавлено через 3 мин.
А, так он у тебя именно дублируется? Интересно... У меня - нет...
18192123
Цитата(volvo @ 8.05.2007 22:47) *

Потому что из списка удаляется всего 5 элементов, а не все элементы, с 5-ю наименьшими длинами...

Добавлено через 3 мин.
А, так он у тебя именно дублируется? Интересно... У меня - нет...

именно дублируется....и при сортировке выводится 2 раза и после удаления 5-ти самых коротких строк тоже 2 раза...
volvo
Так... Кажется мне удалось повторить проблему, которая есть у тебя...

Убедись, что после последней строки файла данных НЕТ перевода строки, т.е., последняя строка - НЕ пустая... Если она будет пустой - то получишь то, что ты сказала...
18192123
Цитата(volvo @ 8.05.2007 23:24) *

Так... Кажется мне удалось повторить проблему, которая есть у тебя...

Убедись, что после последней строки файла данных НЕТ перевода строки, т.е., последняя строка - НЕ пустая... Если она будет пустой - то получишь то, что ты сказала...

да, действительно. спасибо.
18192123
всё-таки хочу разобраться и таким вариантом : как, после сотировки строк по убыванию их длины, удалить самые короткие, идя к ним от головы списка? (т.е. считаем, что самые короткие в конце списка). Как предусмотреть, если самых коротких меньше 5? (например вывести сообщение о том, что нужного кол-ва строк нет)
volvo
С односвязным списком - только через извращение, которое убивает все преимущества использования списков: первым проходом пробегать по всему списку и считать, сколько элементов в нем есть, и вторым проходом - пропускать первые N - 5 элементов, а остальные удалять...

Но это делай сама, я ЭТО делать не буду.
18192123
Я хочу добиться того, чтобы после удаления 5 элементов (остальные элементы расположены в порядке возрастания их длины), оставшиеся элементы были расположены в обратном порядке (т.е. в порядке убывания длины).
Как это будет выглядеть?
И куда в этом случае привязать проверку на наличие пяти самых коротких строк?

И ещё такой вопрос:
void sort(LIST **first) - почему мы в эту ф-ю передаём указатель на указатель на голову списка?
18192123
Цитата(18192123 @ 9.05.2007 16:55) *

Я хочу добиться того, чтобы после удаления 5 элементов (остальные элементы расположены в порядке возрастания их длины), оставшиеся элементы были расположены в обратном порядке (т.е. в порядке убывания длины).
Как это будет выглядеть?


Это я поняла.
А вот с остальными вопросами не разобралась....(про проверку на наличие 5 коротких и про void sort(LIST **first))
volvo
Цитата
почему мы в эту ф-ю передаём указатель на указатель на голову списка?
Потому, что если мы будем передавать просто указатель на голову списка, то изменить его (если это понадобится) мы не сможем... Это получается некоторый аналог Var-параметра в Паскале...

В С++ это очень просто решается с помощью ссылок, но в чистом С ссылки не введены, поэтому приходится передавать указатель на <что-то что может меняться>
18192123
объясните пожалуйста некоторые моменты:
1) о сортировке

for(L = *first; L -> next; L = L -> next)

for(p = L -> next; p; p = p -> next)


здесь L = *first - голова, p = L -> next - элемент, следующий за головой, а что подразумевается под L -> next и p?
Как я понимаю, операциями L = L -> next и p = p -> next мы переходим к следующим элементам,которые нужно сравнивать, да?

2)почему мы ставим здесь взятие адреса?

sort(&root);



3) здесь мы печатаем отсортированный по возрастанию длины список:

for(p = root; p; p = p -> next)
printf("%s\n", p -> number);




печатаем начиная с головы p = root и заканчивая р, в процессе передвигаясь от головы к концу списка, так?
тогда р - у нас последний элемент списка? или тут другое?

4)
момент с удалением 5-ти первых:

for(p = root; p && n < 5; n++)


почему у нас тут такое условие завершения цикла?

5) и последнее - почему при печати оставшегося списка мы использует таокй цикл

for(p = root; p; )


?

Объясните пожалуйста!


Добавлено через 19 мин.
2) вопрос отпал. а вот с остальным - объясните пожалуйста.
volvo
Давай по порядку:

1) организацию циклов в С знаешь? Я имею в виду, синтаксис оператора цикла?
for(начальное_присвоение;условие_окончания;и
зменение_на_каждой_итерации) {
собственно_операторы_тела_цикла;
}

где
начальное_присвоение - делается один раз перед началом цикла;
условие_окончания - проверяется после завершения каждой итерации
изменение_на_каждой_итерации - делается после завершения итерации ПЕРЕД проверкой условия_окончания

причем любой (!!!) из этих параметров может отсутствовать (моогут, кстати, отсутствовать и 2 и все 3 - тогда цикл будет "вечным", и выходе из него возможен будет только через break)...

А теперь с учетом этого - смотри, что происходит:


/*
ВНЕШНИЙ цикл

Поскольку first передается как указатель на указатель
(причину я объяснял выше), то L присваиваем значению
разыменованного first (т.е. изначально L = указателю на
голову списка)

Условие окончания внешнего цикла - (L->next) подразумевает НЕравенство
этого значения нулю... То есть, как только значение L->next станет NULL, цикл завершится...

После обработки одной итерации продвигаемся по списку, путем L = L -> next
*/
for(L = *first; L -> next; L = L -> next)

/*
ВНУТРЕННИЙ цикл
изначально p = L -> next
условие окончания: закончится цикл - когда p будет нулевым
продвижение - понятно как осуществляется...
*/
for(p = L -> next; p; p = p -> next)


2)
Цитата
почему мы ставим здесь взятие адреса?
Потому, что я объяснил, зачем передается указатель на указатель, а чтобы передать его - надо взять адрес указателя...

3)
Цитата
здесь мы печатаем отсортированный по возрастанию длины список:
Опять же - смотри комментарии, и постарайся разобраться в принципе работы оператора цикла, я не могу каждый раз писать такие объемные комментарии...

/*
изначально p = root
окончание - когда p = NULL
продвижение - p = p -> next
*/
for(p = root; p; p = p -> next)
printf("%s\n", p -> number);


4)
Цитата
почему у нас тут такое условие завершения цикла?

Условие завершения может быть любым правильным логическим условием, в отличие от оператора For в Паскале... Что делается здесь: предварительно я n установил в 0, правда? После каждой итерации (т.е., после удаления очередного элемента) n увеличивается на 1, а так как нам надо удалить ЛИБО первые 5 элементов, ЛИБО, если их всего меньше 5, то все - мы ставим операцию AND, и как только одно из условий НЕ выполняется (или уже удалили 5 элементов и n = 5, или достигли конца цикла, и p = NULL) - то цикл завершается...

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

  for(p = root; p; ) {
printf("%s\n", p -> number);
p = (T = p) -> next; /* <--- вот эту строку ты разобрала, как она работает? */
free(T);
}

в отмеченной строке происходит следующее: сначала выполняется действие в скобках, т.е. T = p, потом в p заносится новое значение, равное T -> next, то есть, фактически, старое p запоминается в переменной T, а потом p продвигается на следующий элемент списка...
18192123
volvo, большое спасибо! теперь всё поняла! smile.gif
18192123
Возник ещё вопрос:

for(L = *first; L -> next; L = L -> next)

for(p = L -> next; p; p = p -> next) {

if(strlen(L -> number) > strlen(p -> number)) { /* сортируем по длине */
strcpy(s, L -> number);
strcpy(L -> number, p -> number);
strcpy(p -> number, s);
}
}



Здесь при сортировке строк по длине мы фактически работаем с содержимым информационных полей каждого элемента, верно?
а можно сделать так, чтобы не строки копировать из одной области памяти в другую, а вместо этого изменять указатели?
volvo
Можно, только код будет больше по размеру... Не забывай, у тебя односвязные списки, то есть, для того, чтобы пройти к предыдущему (менять-то указатель с предыдущего на новое значение надо?) тебе придется пробегать еще раз по списку с самого начала...
18192123
Цитата(volvo @ 14.05.2007 23:52) *

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

".....(менять-то указатель с предыдущего на новое значение надо?)" - думаю, надо.
Можешь объяснить, как тогда код изменится?
18192123
Цитата(18192123 @ 14.05.2007 19:13) *

а можно сделать так, чтобы не строки копировать из одной области памяти в другую, а вместо этого изменять указатели?

каков должен быть механизм таких действий?
volvo
Ты сама просила smile.gif

void sort(LIST **first) {

LIST *L, *p, *pp, *T, *prv_L, *prv_p;
char s[255];

start_over:
;

for(L = *first; L -> next; L = L -> next)

for(p = L -> next; p; p = p -> next) {

if(strlen(L -> number) > strlen(p -> number)) {

// Вот тут раньше было 3 строки... Смотри теперь.

if(L == *first) {

for(pp = *first; pp -> next != p; pp = pp -> next);
pp -> next = *first;

T = (*first) -> next;
*first = p;

L -> next = p -> next;
p -> next = T;

}
else {
LIST *prv_L, *prv_p;
for(prv_L = *first; prv_L -> next != L; prv_L = prv_L -> next);
for(prv_p = *first; prv_p -> next != p; prv_p = prv_p -> next);

prv_L -> next = p;
prv_p -> next = L;
T = p -> next;
p -> next = L -> next;
L -> next = T;
}

goto start_over;
}
}

}

18192123
Цитата(volvo @ 15.05.2007 17:11) *

Ты сама просила smile.gif


Спасибо!
Общую идею поняла...Попробывала переделать на свой лад....Идея такая: по заданию мне нужно расположить строки по убыванию длины. Функцией sort1 я это и делаю. Вывожу результат. Одна часть задания выполнена. Теперь сортирую наоборот (sort), после удаляем 5 самых коротких(они теперь впереди), ещё раз сортируем (т.к. по заданию нужно получить строки, упорядоченные по убыванию длины). Вывожу конечный результат.
Проблема в том, что в файл выводится список отсортированный, но не тот, что нужен мне.....
Вот так, например:
Исходные данные

qwert tyuri vbncgf
aaaaaaaaaaaaaaaaaaaaa
zxc vbn fgh qwerty qwerty
zxc
sd
a
qwervbn
zasd
cvbnnnnnn qwe rty uioopl nm

Результат

qwert tyuri vbncgf

qwervbn

zasd

zxc

sd

a


---

--------
Не пойму, откуда такие результаты?!


#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <alloc.h>

typedef struct list {

char number[255];
struct list *next;

} LIST;

LIST *read_list() {
char s[255];

LIST *p, *lst, *final;
FILE *f;
f=fopen("rgz.txt","r");

lst = NULL;
while(!feof(f)) {

fgets(s,255, f);
p = (LIST*)malloc(sizeof(LIST));
strcpy(p -> number, s);

if(!lst) lst = p;
else final -> next = p;

final = p;

}
fclose(f);

if(!lst) {
printf("empty file\n");
return NULL;
}
final -> next = NULL;

return lst;
}
void *sort(LIST *first)
{
LIST *p1=first, *p2=p1->next, *prev,*temp1, *temp2;
int flag=1;
while (flag&&p2)
{
flag=0;
prev=first;
while (p2)
{

if (strlen(p1->number)>strlen(p2->number))
{
flag=1;
temp1=p1;
temp2=p2->next;
p1=p2;
p2=temp1;
p1->next=p2;
p2->next=temp2;
if (temp1==first) first=p1; else prev->next=p1;
}
prev=p1;
p1=p1->next;
p2=p2->next;
}
p1=first; p2=p1->next;
}
}

void *sort1(LIST *first)
{
LIST *p1=first, *p2=p1->next, *prev,*temp1, *temp2;
int flag=1;
while (flag&&p2)
{
flag=0;
prev=first;
while (p2)
{

if (strlen(p1->number) < strlen(p2->number))
{
flag=1;
temp1=p1;
temp2=p2->next;
p1=p2;
p2=temp1;
p1->next=p2;
p2->next=temp2;
if (temp1==first) first=p1; else prev->next=p1;
}
prev=p1;
p1=p1->next;
p2=p2->next;
}
p1=first; p2=p1->next;
}
}







void main() {
struct list *root, *p, *T;
int n;
FILE *f;
f=fopen("RGZ2.txt","w");

root = read_list();


sort1(root);

for(p = root; p; p = p -> next)
fprintf(f,"%s\n", p -> number);
fprintf(f,"\n---\n");
sort(root);

n = 0;
for(p = root; p && n < 5; n++) {

p = (T = p) -> next;
free(T);
}

root = p;
sort1(root);


for(p = root; p; ) {
fprintf(f,"%s\n", p -> number);
p = (T = p) -> next;
free(T);
}
root = NULL; fprintf(f,"\n--------\n");

}

volvo
Сорри, я больше в эту тему НЕ отвечаю! Я тебе привел работающую функцию сортировки, на отладку которой ушло больше 2-х часов, ты ее полностью перековеркала, и теперь тебя не устраивает результат? dry.gif

Извини, но функции твои, вот и разбирайся, как они работают сама (а они НЕ работают, чтобы в этом убедиться достаточно вывести на экран содержимое списка ПЕРЕД первой сортировкой, и ПОСЛЕ нее)...
18192123
Цитата(volvo @ 15.05.2007 17:11) *



LIST *prv_L, *prv_p;
for(prv_L = *first; prv_L -> next != L; prv_L = prv_L -> next);
for(prv_p = *first; prv_p -> next != p; prv_p = prv_p -> next);





И всё-таки...Объясни пожалуйста:
А для чего вводятся *prv_L, *prv_p?
И для чего эти два цикла?
Почему в циклах используется условие prv_L -> next != L и prv_p -> next != p?

И ещё: почему мы вновь должны возвращаться к началу циклов (goto start_over;
) и когда этот механизм возврата закончит свою работу?
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.