Связный список на Golang | Zhbert’s Home
Zhbert's Home
Домашняя страничка Zhbert'а

Связный список на Golang

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

Теперь давайте посмотрим, как написать его на языке Go и протестируем время работы.

Базовый элемент

Элемент однонаправленного связного списка должен содержать непосредственно данные и ссылку на следующий элемент.

Для его реализации хорошо подойдет структура (struct). Объявим новый тип, содержащий в себе данные в формате int и ссылку на следующий элемент:

type element struct {
 data int
 next *element
}

Теперь реализуем функцию создания нового связного списка:

func genNewList(data int) element {
 return element{data, nil}
}

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

Обратите внимание, функция возвращает только первый элемент!

Печать всего списка

Реализуем функцию печати всего содержимого списка:

func printList(list *element) {
 for {
  fmt.Println(list.data)
  if list.next != nil {
   list = list.next
  } else {
   break
  }
 }
}

Здесь мы принимаем ссылку на первый элемент массива и запускам цикл, в котором:

  • печатаем содержимое поля с данными элемента;
  • проверяем, есть ли ссылка на следующий элемент:
    • если он есть — делаем его главным и позвращаемся в начало массива;
    • если ее нет — завершаем цикл.

Проверим, что все работает. Создадим новый элемент в теле функции main() и выведем его содержимое:

func main() {
 list := genNewList(0)
 printList(&list)
}

Результат:

0

Все сработало, мы распечаали первый элемент списка.

Функции добавления элементов

Добавить элементы в список можно с разных сторон. И, как мы помним из сравнения его с массивом, это главная особенность и положительная сторона связного списка — возможность быстро добавить элементы в любую его часть. Рассмотрим три варианта.

Добавление элемента в конец списка

Для добавления элемента в конец списка нам понадобится дойти до нужного элемента и просто присвоить его полю next ссылку на новый элемент.

func addToEnd(list *element, data int) {
 for {
  if list.next != nil {
   list = list.next
  } else {
   list.next = &element{data: data, next: nil}
   break
  }
 }
}

Здесь мы точно так же проходим по всему списку в цикле, и когда добираемся до последнего элемента — присваиваем полю next ссылку на только что созданный новый элемент.

Проверим, что все отрабатывает, распечатав содержимое списка:

func main() {
 list := genNewList(0)
 addToEnd(&list, 5)
 printList(&list)
}

Результат:

0
5

В конец списка добавлен новый элемент со значением 5.

Добавление элемента в начало списка

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

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

func addToStart(list element, data int) element {
 newList := element{data: data, next: &list}
 return newList
}

Проверим, что все работает.

func main() {
 list := genNewList(0)
 addToEnd(&list, 5)
 list = addToStart(list, 10)
 printList(&list)
}

Результат:

10
0
5

Добавление элемента в тело списка

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

func addAfter(list *element, index int, data int) {
 counter := 0
 for {
  if counter != index {
   if list.next != nil {
    counter++
    list = list.next
   } else {
    fmt.Println("The specified index goes beyond the bounds of the list!")
    break
   }
  } else {
   newElement := element{data: data, next: list.next}
   list.next = &newElement
   break
  }
 }
}

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

Проверим, что все работает:

func main() {
 list := genNewList(0)
 addToEnd(&list, 5)
 list = addToStart(list, 10)
 addAfter(&list, 1, 20)
 printList(&list)
}

Результат:

10
0
20
5

Проверим, что при превышении размера выводится предупреждение:

addAfter(&list, 5, 20)

Результат:

The specified index goes beyond the bounds of the list!
10
0
5

Сообщение выведено, список остался неизменным.

Удаление элементов

Добавление мы рассмотрели, теперь осталось рассмотреть удаление элементов.

Удаление последнего элемента

Здесь все по аналогии с добавлением, только теперь у предпоследнего элемента нужно заменить ссылку на nil.

func delLast(list *element) {
 for {
  if list.next != nil {
   list = list.next
   if list.next.next == nil {
    list.next = nil
    break
   }
  }
 }
}

Проверяем результат:

delLast(&list)
printList(&list)

Результат:

10
0

Удаление первого элемента

Здесь тоже все просто: нужно вернуть ссылку на второй элемент (если он есть).

func delFirst(list *element) element {
 if list.next != nil {
  return *list.next
 } else {
  fmt.Println("There is nothing to delete!")
 }
 return *list
}

Проверим:

list = delFirst(&list)
printList(&list)

Результат:

0

Удаление элемента в теле списка

Все аналогично удалению первого элемента, только до «первого» элемента сначала нужно дойти.

func delAfter(list *element, index int) {
 counter := 0
 for {
  if counter != index {
   if list.next != nil {
    counter++
    list = list.next
   } else {
    fmt.Println("The specified index goes beyond the bounds of the list!")
    break
   }
  } else {
   list.next = list.next.next
   break
  }
 }
}

Проверим, что все работает (на предыдущем списке их трех элементов):

delAfter(&list, 0)
printList(&list)

Результат:

10
5

Удален средний (второй) элемент.

Здесь тонкость: мы указали индекс 0, т.к. хотим удалить второй по счету элемент. Т.к. нумерация класиически начинается с 0, вторым будет элемент с индексом 1, то есть идущий после 0. В функции мы удаляем именно элемент после заданного.

Весь код целиком

Ну и по классике весь код целиком:

package main

import (
 "fmt"
)

type element struct {
 data int
 next *element
}

func genNewList(data int) element {
 return element{data, nil}
}

func printList(list *element) {
 for {
  fmt.Println(list.data)
  if list.next != nil {
   list = list.next
  } else {
   break
  }
 }
}

func addToEnd(list *element, data int) {
 for {
  if list.next != nil {
   list = list.next
  } else {
   list.next = &element{data: data, next: nil}
   break
  }
 }
}

func addToStart(list element, data int) element {
 newList := element{data: data, next: &list}
 return newList
}

func addAfter(list *element, index int, data int) {
 counter := 0
 for {
  if counter != index {
   if list.next != nil {
    counter++
    list = list.next
   } else {
    fmt.Println("The specified index goes beyond the bounds of the list!")
    break
   }
  } else {
   newElement := element{data: data, next: list.next}
   list.next = &newElement
   break
  }
 }
}

func delLast(list *element) {
 for {
  if list.next != nil {
   list = list.next
   if list.next.next == nil {
    list.next = nil
    break
   }
  }
 }
}

func delFirst(list *element) element {
 if list.next != nil {
  return *list.next
 } else {
  fmt.Println("There is nothing to delete!")
 }
 return *list
}

func delAfter(list *element, index int) {
 counter := 0
 for {
  if counter != index {
   if list.next != nil {
    counter++
    list = list.next
   } else {
    fmt.Println("The specified index goes beyond the bounds of the list!")
    break
   }
  } else {
   list.next = list.next.next
   break
  }
 }
}

func main() {
 list := genNewList(0)
 addToEnd(&list, 5)
 list = addToStart(list, 10)
 addAfter(&list, 5, 20)
 printList(&list)
 fmt.Println("---")
 delAfter(&list, 0)
 printList(&list)
}

Также исходники доступны в репозитории на GitHub.