Двусвязный линейный список

Структуры данных / Двусвязный линейный список

Каждый узел двунаправленного (двусвязного) линейного списка (ДЛС) содержит два поля указателей - на следующий и на предыдущий узлы. Указатель на предыдущий узел корня списка содержит нулевое значение. Указатель на следующий узел последнего узла также содержит нулевое значение.
Двусвязный линейный список
Узел ДЛС можно представить в виде структуры:

struct list {
  int field; // поле данных
  struct list *next; // указатель на следующий элемент
  struct list *prev; // указатель на предыдущий элемент
};

Основные действия, производимые над узлами ДЛС:

  • Инициализация списка
  • Добавление узла в список
  • Удаление узла из списка
  • Удаление корня списка
  • Вывод элементов списка
  • Вывод элементов списка в обратном порядке
  • Взаимообмен двух узлов списка
Инициализация ДЛСИнициализация двусвязного линейного списка

Инициализация списка предназначена для создания корневого узла списка, у которого поля указателей на следующий и предыдущий узлы содержат нулевое значение.

struct list * init(int a) { // а- значение первого узла
  struct list *lst;
  // выделение памяти под корень списка
  lst = (struct list*)malloc(sizeof(struct list));
  lst->field = a;
  lst->next = NULL; // указатель на следующий узел
  lst->prev = NULL; // указатель на предыдущий узел
  return(lst);
}
Добавление узла в ДЛС

Функция добавления узла в список принимает два аргумента:

  • Указатель на узел, после которого происходит добавление
  • Данные для добавляемого узла.

Процедуру добавления узла можно отобразить следующей схемой:
Добавление узла двусвязного линейного списка

Добавление узла в ДЛС включает в себя следующие этапы:

  • создание узла добавляемого элемента и заполнение его поля данных;
  • переустановка указателя "следующий" узла, предшествующего добавляемому, на добавляемый узел;
  • переустановка указателя "предыдущий" узла, следующего за добавляемым, на добавляемый узел;
  • установка указателя "следующий" добавляемого узла на следующий узел (тот, на который указывал предшествующий узел);
  • установка указателя "предыдущий" добавляемого узла на узел, предшествующий добавляемому (узел, переданный в функцию).

Таким образом, функция добавления узла в ДЛС имеет вид:

struct list * addelem(list *lst, int number) {
  struct list *temp, *p;
  temp = (struct list*)malloc(sizeof(list));
  p = lst->next; // сохранение указателя на следующий узел
  lst->next = temp; // предыдущий узел указывает на создаваемый
  temp->field = number; // сохранение поля данных добавляемого узла
  temp->next = p; // созданный узел указывает на следующий узел
  temp->prev = lst; // созданный узел указывает на предыдущий узел
  if(p!=NULL)
    p->prev = temp;
  return(temp);
}

Возвращаемым значением функции является адрес добавленного узла.

Удаление узла ДЛС

В качестве аргументов функции удаления узла ДЛС передается указатель на удаляемый узел. Поскольку узел списка имеет поле указателя на предыдущий узел, нет необходимости передавать указатель на корень списка.

Функция возвращает указатель на узел, следующий за удаляемым.

Удаление узла может быть представлено следующей схемой:
Удаление узла двусвязного линейного списка

Удаление узла ДЛС включает в себя следующие этапы:

  • установка указателя "следующий" предыдущего узла на узел, следующий за удаляемым;
  • установка указателя "предыдущий" следующего узла на узел, предшествующий удаляемому;
  • освобождение памяти удаляемого узла.
struct list * deletelem(list *lst) {
  struct list *prev, *next;
  prev = lst->prev; // узел, предшествующий lst
  next = lst->next; // узел, следующий за lst
  if(prev!=NULL)
    prev->next = lst->next; // переставляем указатель
  if(next!=NULL)
    next->prev = lst->prev; // переставляем указатель
  free(lst); // освобождаем память удаляемого элемента
  return(prev);
}
Удаление корня ДЛС

Функция удаления корня ДЛС в качестве аргумента получает указатель на текущий корень списка. Возвращаемым значением будет новый корень списка - тот узел, на который указывает удаляемый корень.

struct list * deletehead(list *root) {
  struct list *temp;
  temp = root->next;
  temp->prev = NULL;
  free(root);   // освобождение памяти текущего корня
  return(temp); // новый корень списка
}
Вывод элементов ДЛС

Функция вывода элементов ДЛС аналогична функции для ОЛС.
В качестве аргумента в функцию вывода элементов передается указатель на корень списка.
Функция осуществляет последовательный обход всех узлов с выводом их значений.

void listprint(list *lst) {
  struct list *p;
  p = lst;
  do {
    printf("%d ",p->field); // вывод значения элемента p
    p = p->next; // переход к следующему узлу
  } while(p != NULL); // условие окончания обхода
}

Для ДЛС также можно вызвать функцию вывода элементов списка в обратном порядке.

Вывод элементов ДЛС в обратном порядке

Функция вывода элементов ДЛС в обратном порядке принимает в качестве аргумента указатель на корень списка. Функция перемещает указатель на последний узел списка и осуществляет последовательный обход всех узлов с выводом их значений в обратном порядке.

void listprintr(list *lst) {
  struct list *p;
  p = lst;
  while(p->next!= NULL)
    p = p->next;  // переход к концу списка
  do {
    printf("%d ",p->field); // вывод значения элемента p
    p = p->prev; // переход к предыдущему узлу
  } while(p != NULL); // условие окончания обхода
}
Взаимообмен узлов ДЛС

В качестве аргументов функция взаимообмена узлов ДЛС принимает два указателя на обмениваемые узлы, а также указатель на корень списка. Функция возвращает адрес корневого узла списка.

Взаимообмен узлов списка осуществляется путем переустановки указателей. Для этого необходимо определить предшествующий и последующий узлы для каждого заменяемого. При этом возможны две ситуации:

  • заменяемые узлы являются соседями;
  • заменяемые узлы не являются соседями, то есть между ними имеется хотя бы один узел.

При замене соседних узлов переустановка указателей выглядит следующим образом:
Замена соседних узлов двусвязного линейного списка

При замене узлов, не являющихся соседними переустановка указателей выглядит следующим образом:
Замена узлов двусвязного линейного списка

Функция взаимообмена узлов списка выглядит следующим образом:

struct list * swap(struct list *lst1, struct list *lst2, struct list *head) {
// Возвращает новый корень списка
  struct list *prev1, *prev2, *next1, *next2;
  prev1 = lst1->prev;  // узел предшествующий lst1
  prev2 = lst2->prev;  // узел предшествующий lst2
  next1 = lst1->next; // узел следующий за lst1
  next2 = lst2->next; // узел следующий за lst2
  if(lst2 == next1) { // обмениваются соседние узлы
    lst2->next = lst1;
    lst2->prev = prev1;
    lst1->next = next2;
    lst1->prev = lst2;
    next2->prev = lst1;
    if(lst1!= head)
       prev1->next = lst2;
  } else if(lst1 == next2) { // обмениваются соседние узлы
    lst1->next = lst2;
    lst1->prev = prev2;
    lst2->next = next1;
    lst2->prev = lst1;
    next1->prev = lst2;
    if(lst2!= head)
       prev2->next = lst1;
  } else { // обмениваются отстоящие узлы
    if(lst1 != head)
       prev1->next = lst2;
    lst2->next = next1;
    if(lst2 != head)
       prev2->next = lst1;
    lst1->next = next2;
    lst2->prev = prev1;
    if(next2 != NULL)
      next2->prev = lst1;
    lst1->prev = prev2;
    if(next1 != NULL)
      next1->prev = lst2;
  }
  if(lst1 == head)
     return(lst2);
  if(lst2 == head)
     return(lst1);
  return(head);
}

Код примера использования приведенных функций
Назад

Комментариев к записи: 6

  • здравствуйте Елена. как преобразовать ДЦС в два циклических списка? первый из которых содержит первую половину, а вторая вторую половину. Пожалуйста можно с примерами


    • Елена Вставская

      Нужно найти соединение, по которому список делится на два.
      Взять точку соединения начала и конца. Начало соединить с «левой» серединой — получится первый список.
      «правую» середину соединить с концом — получится второй список.


  • Владислав

    Не могли бы вы дать полностью код, одну целую программу, если быть точнее?


  • У меня не работает обмен, при попытке обменять последний элемент с первым.


Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *