لیست پیوندی (LinkedList) در برنامه نویسی

لیست پیوندی (LinkedList) در برنامه نویسی

بررسی لیست پیوندی

بر خلاف list که عناصر آرایه وار مستقیم روی حافظه ذخیره میشن، در لیست پیوندی (LinkedList) عناصر در گره (node) ها ذخیره میشن.

در لیست پیوندی (LinkedList) هر عنصر داخل یک node ذخیره میشه و هر node به node بعدی اشاره میکنه و آخرین node در حالت استاندارد به null اشاره میکنه؛ به عبارتی هر node به node بعدی زنجیر (link) میشه.

زمانی که LinkedList خالی باشه تنها یک node داخلش وجود داره، مقدار عنصری که داخل node نگهداری میشه null و node بعدی که بهش اشاره میکنه null است.

زمانی که بخوایم عناصر رو از ابتدا یا انتها حذف یا اضافه کنیم، استفاده از LinkedList بهینه تر از استفاده از list است؛ اما اگه بخوایم به عناصر با index به صورت تصادفی دسترسی داشته باشیم استفاده از list بهینه تر از LinkedList است.

ساختار لیست پیوندی

هر عنصر داخل یک node نگهداری میشه و هر node به node بعد از خودش node (در صورتی که اخرین node نباشه) و به node قبل از خودش (در صورتی که اولین node نباشه) اشاره میکنه (Doubly LinkedList).

در زیر به صورت تصویری شرح داده شده که چطور عناصر در LinkedList نگهداری میشن.

ساختار لیست پیوندی و نحوه ی نگهداری عناصر در لیست پیوندی
ساختار لیست پیوندی و نحوه ی نگهداری عناصر در لیست پیوندی

انواع لیست پیوندی

انواع مختلفی از پیاده سازی لیست پیوندی (LinkedList) داریم که در ادامه سه تا از پیاده سازی های معروف رو بیان میکنیم.

توجه

در این مطلب فقط به جزییات Doubly LinkedList می پردازیم، نحوه ی پیاده سازی سایر LinkedList ها تقریبا مشابه است.

لیست پیوندی یک طرفه (Singly LinkedList)

در این نوع لیست پیوندی (LinkedList) هر node به node بعد خودش اشاره (point) میکنه تا آخرین node که node بعدش null است.

جستجو در این نوع LinkedList یک طرفه است یعنی برای پیدا کردن یک عنصر، جستجو از اولین node شروع میشه تا به node مورد نظر برسیم.

ساختار لیست پیوندی یک طرفه (Singly LinkedList)
ساختار لیست پیوندی یک طرفه (Singly LinkedList)

لیست پیوندی دو طرفه (Doubly LinkedList)

در این نوع لیست پیوندی هر node به node بعد از خودش و node قبل از خودش اشاره (point) میکنه و اولین node به null به عنوان node قبل از خودش اشاره میکنه و اخرین node به null به عنوان node بعد از خودش اشاره میکنه.

جستجو در این نوع LinkedList دو طرفه است، یعنی جستجو برای پیدا کردن یک عنصر، هم از اولین node به آخرین node امکان پذیر است هم از اخرین node به اولین node

ساختار لیست پیوندی دو طرفه (Doubly LinkedList)
ساختار لیست پیوندی دو طرفه (Doubly LinkedList)

لیست پیوندی مُدور (Circular LinkedList)

در این نوع LinkedList اخرین node بجای اشاره به null به عنوان node بعد خودش به اولین node در LinkedList اشاره می کنه.

ساختار یک Circular LinkedList
ساختار لیست پیوندی مدور (Circular LinkedList)

بررسی گره (node) در لیست پیوندی

با افزودن هر عنصر یک گره (node) برای عنصر ایجاد میشه و عنصر داخل node نگهداری میشه؛ node علاوه بر نگهداری عنصر، به node قبل یا بعد خودش اشاره میکنه و اینجوری یک زنجیره بین عناصر توسط node ها ایجاد میشه.

به زبو‍‍ن ساده هر عنصر به عنصر بعد یا قبل از خودش به وسیله ی node گره زده میشه و این گره ها لیست پیوندی (LinkedList) رو تشکیل میدن.

فرم کلی node:

record node{
  Node next
  Node prev
  E element
}
          

افزودن عنصر به لیست پیوندی

با افزودن هر عنصر به LinkedList یک node برای نگهداری عنصر ایجاد میشه، در LinkedList میتونیم node ها رو به ابتدا، انتها یا وسط سایر node ها اضافه کنیم.

افزودن node به ابتدای LinkedList

اگه بخوایم node رو به ابتدای LinkedList اضافه کنیم، node ایجاد شده به اولین node موجود در LinkedList اشاره میکنه. پیچیدگی زمان (time complexity) در این حالت O(1) است.

function addFirst(e: E){

  newNode := Node(e)

  if size = 0 
    lastNode := newNode
    firstNode := newNode
  else
    newNode.next := firstNode
    firstNode.prev := newNode
    firstNode := newNode

  size++

}
            

افزودن node به انتهای لیست پیوندی

اگه بخوایم node رو به انتهای LinkedList اضافه کنیم، اخرین node در LinkedList به node جدید اشاره میکنه؛ پیچیدگی زمان در این حالت O(1) است.

function addLast(e: E){
  
  newNode := Node(e)

  if size = 0
    lastNode := newNode
    firstNode := newNode
  else
    lastNode.next := newNode
    newNode.prev := lastNode
    lastNode := newNode

  size++

}
            

افزودن node به وسط لیست پیوندی

اگه بخوایم node رو وسط node ها اضافه کنیم، node قبلی به node جدید اشاره میکنه و node جدید به node بعد؛ پیچیدگی زمان در این حالت O(n) است.

function add(index: int, e: E){

  if index < 0 or index > size - 1
     throw IndexOutOfBoundException

  newNode := Node(e)

  if firstNode = null
     firstNode := newNode
     lastNode := newNode
     size++
  else
      count := 0
      currentNode := firstNode

      while count != index
        currentNode := currentNode.next
        count++

      newNode.next = currentNode.next
      currentNode.next = newNode
      size++
  
}
            

صدا زدن عنصر با index در لیست پیوندی

برای صدا زدن یک عنصر با index در LinkedList باید node مربوط به index مورد نظر رو پیدا کنیم؛ صدا زدن یک عنصر در LinkedList دارای پیچیدگی زمان O(n) است.

function get(index: int): E {
  
  if index < 0 or index > size - 1
     throw IndexOutOfBoundException

     count := 0
     node := firstNode

     while count != index
         node := node.next

    return node.element
}
        

حذف عنصر از لیست پیوندی

برای حذف یک عنصر از LinkedList باید node که عنصر مورد نظر رو نگهداری میکنه از زنجیره خارج کنیم؛ میتونیم یک عنصر رو از ابتدا، انتها یا وسط یک LinkedList حذف کنیم.

حذف عنصر از ابتدای LinkedList

برای حذف اولین عنصر از LinkedList، باید اولین node در LinkedList رو از زنجیره خارج کنیم.

function removeFirst(): E {
  
  Node removedNode := null

  if size = 0
     throw NoSuchElementException

  if size = 1
    removedNode := firstNode
    firstNode := null
    lastNode := null

  else 
     removedNode := firstNode
     firstNode.next := firstNode.next.next
     firstNode := firstNode.next

  return removedNode.element
}

          

حذف عنصر از انتهای لیست پیوندی

برای حذف کردن آخرین عنصر از LinkedList، آخرین node رو از زنجیره خارج کنیم.

توجه

حذف اخرین node در Singly LinkedList دارای پیچیدگی زمان O(n) و در Doubly LinkedList دارای پیچیدگی زمان O(1) است؛ اما همانطور که گفتیم در این مطلب به Doubly LinkedList می پردازیم.

function removeLast(): E{
  Node removedNode := null

  if size = 0
    throw NoSuchElementException
  
  if size = 1
    removedNode := firstNode
    firstNode := null
    lastNode := null
  else  
    prevNode = lastNode.prev
    prevNode.next = null
    lastNode := prevNode

  return removedNode.element
}
          

حذف کردن عنصر با استفاده از index در لیست پیوندی

با استفاده از index در LinkedList میتونیم یک عنصر رو در ابتدا، وسط یا انتهای LinkedList حذف کنیم، برای این کار کافیه node مربوط به index رو پیدا کرده و از زنجیره حذفش کنیم.

حذف یک node با استفاده از index در doubly linkedlist
حذف یک node با استفاده از index در doubly linkedlist
function remove(index: int): E {

  Node removedNode := null

  if size = 0 
    throw NoSuchElementException

  if index < 0 or index > size - 1 
    throw IndexOutOfBoundException


  if size = 1
    removedNode := firstNode
    firstNode := null
    lastNode := null
  else 
    count := 0
    Node node := firstNode
  
    while count != index
      count++
      node := node.next

    prevNode := node.prev
    prevNode.next := node.next


    return node.element
}
          

پیاده سازی لیست پیوندی در زبان های برنامه نویسی

میخوایم لیست پیوندی (LinkedList) رو به صورت عملی با استفاده از زبان های برنامه نویسی (جاوا و کاتلین) پیاده سازی کنیم.

در این پیاده سازی توابع remove(index: int), add(index: int, element: E), removeLast(): E به عنوان تمرین رها شده تا خودتون انجامش بدید.

public class MyLinkedList<E> {

    private Node<E> firstNode;
    private Node<E> lastNode;

    private int size;


    public boolean isEmpty(){
        return size == 0;
    }

    public int getSize() {
        return size;
    }

    public void addFirst(E element){

        Node<E> newNode = new Node<>(element, null, null);

        if (isEmpty()){
            firstNode = newNode;
            lastNode = newNode;
        }else{
            firstNode.prev = newNode;
            newNode.next = firstNode;
            firstNode = newNode;
        }

        size++;
    }

    public void addLast(E element){
        Node<E> newNode = new Node<>(element, null, null);

        if (isEmpty()){
            firstNode = newNode;
            lastNode = newNode;
        }else {
            lastNode.next = newNode;
            newNode.prev = lastNode;
            lastNode = newNode;
        }

        size++;
    }

    public E getFirst(){
        if(isEmpty()) throw NoSuchElementException("The LinkedList is empty");

        return firstNode.element;
    }

    public E getLast(){
        if(isEmpty()) throw NoSuchElementException("The LinkedList is empty");

        return lastNode.element;
    }

    public E get(int index){
        if (index < 0 || index > size - 1)
            throw new IndexOutOfBoundsException("The index should be between 0 and " + (size -1));

        Node<E> node;

        if (size == 1){
            node = firstNode;
        }else {
            int count = 0;
            Node<E> currentNode = firstNode;
            while (count != index){
                currentNode = currentNode.next;
                count++;
            }

            node = currentNode;
        }

        return node.element;
    }

    public E removeFirst(){

        if (isEmpty())
            throw new NoSuchElementException("The list is empty");

        Node<E> removedNode;

        if (size == 1){
            removedNode = firstNode;
            firstNode = null;
            lastNode = null;
        }else {
            removedNode = firstNode;
            firstNode = firstNode.next;
            firstNode.prev = null;
        }

        size--;
        return removedNode.element;
    }


    public E add(int index, E element){
        //TODO Left as an exercise

        return null;
    }

    public E remove(int index){
        //TODO Left as an exercise

        return null;
    }

    public E removeLast(){
        //TODO Left as an exercise

        return null;
    }

    private static final class Node<E> {
        private final E element;
        private Node<E> next;
        private Node<E> prev;

        Node(E element, Node<E> next, Node<E> prev) {
            this.element = element;
            this.next = next;
            this.prev = prev;
        }

    }
}
        
class MyLinkedList<E> {

    private var firstNode: Node<E>? = null
    private var lastNode: Node<E>? = null
    var size = 0

    val  empty get() = size == 0

    fun addFirst(element: E){

        val newNode: Node<E> = Node(element)

        if (size == 0) {
            firstNode = newNode
            lastNode = newNode
        }else{
            newNode.next = firstNode
            firstNode!!.prev = newNode
            firstNode = newNode
        }

        size++
    }


    fun addLast(element: E){
        if (size == 0){
            firstNode = Node(element)
            lastNode = Node(element)
        }else{
            val node = Node(element)
            node.prev = lastNode
            lastNode!!.next = node
            lastNode = node
        }

        size++
    }

    fun getFirst(): E?{
        return firstNode?.element
    }


    fun getLast(): E?{
        return lastNode?.element
    }


    fun get(index: Int): E? {

        if (index !in 0..<size)
            throw IndexOutOfBoundsException("index should be between 0 to ${size - 1}")

        if (empty)
            throw NoSuchElementException("The List is empty")

        var node: Node<E>?

        var count = 0

        var currentNode = firstNode

        while (count != index ){
            currentNode = currentNode?.next
            count++
        }

        node = currentNode

        return node?.element
    }

    fun removeFirst(): E {

        if (empty)
            throw NoSuchElementException("There's no element in list")

        val node: Node<E>

        if (size == 1) {
            node = firstNode!!
            firstNode = null
            lastNode = null
        } else{
            node = firstNode!!
            firstNode = firstNode!!.next
            firstNode!!.prev = null
        }

        size--

        return node.element
    }

    fun add(index: Int, element: E){
        //TODO left as an exercise
    }

    fun removeLast(): E? {
        //TODO left as an exercise
        return null
    }

    fun remove(index: Int): E?{
        //TODO left as an exercise
        return null
    }
    
   private data class Node<E>(val element: E, var next: Node<E>? = null, var prev: Node<E>? = null)
}
        

استفاده از لیست پیوندی در زبان های برنامه نویسی

در بسیاری از زبان های برنامه نویسی لیست پیوندی (LinkedList) به عنوان یک کتابخونه ی استاندارد پیاده سازی شده و در اینجا میخوایم استفاده از LinkedList رو بررسی کنیم.

LinkedList<String> colors = new LinkedList<>();
colors.add("Blue");
colors.add("Pink");
colors.add("Purple");
colors.addFirst("Cyan");
colors.add(1, "Red");
colors.removeLast();
colors.addLast("Yellow");

System.out.println("Display colors ");
for(int i =0; i<colors.size(); i++){
  String c = colors.get(i);
  System.out.print(c + " ");
}

System.out.println("Remove and Display colors backward: ");

while(!colors.isEmpty()){
  String c = colors.removeLast();
  System.out.println(c);
  
}
        
val colors: LinkedList<String> = LinkedList<>();
colors.add("Blue")
colors.add("Pink")
colors.add("Purple")
colors.addFirst("Cyan")
colors.add(1, "Red")
colors.removeLast()
colors.addLast("Yellow")

System.out.println("Display colors ");
for(i in 0 until colors.size ){
  val c = colors.get(i)
  print("$c ")
}

println("Remove and Display colors backward: ")

while(!colors.isEmpty()){
  val c = colors.removeLast()
  println(c)
}
        

خلاصه

  • در LinkedList عناصر داخل node ها ذخیره میشن.
  • هر node در LinkedList به node بعدی اشاره میکنه.
  • انواع پیاده سازی LinkedList وجود داره از جمله: Singly LinkedList, Doubly LinkedList و Circular LinkedList
  • در Singly LinkedList، یک node به node بعدی اشاره میکنه
  • در Doubly LinkedList یک node به node بعد و node قبل از خودش اشاره میکنه (Doubly LinkedList).
  • پیچیدگی زمان هنگام افزودن یک عنصر به ابتدا یا انتهای یک LinkedList O(1) و هنگام افزودن یک عنصر با index برابر با O(n) است.
  • پیچیدگی زمان برای صدا زدن یک عنصر در LinkedList با index برابر با O(n) است.
  • پیچیدگی زمان (time complexity) برای حذف کردن یک عنصر از ابتدا یا انتهای یک LinkedList برابر با O(1) و برای حذف کردن یک عنصر وسط LinkedList برابر با O(n) است.

برای اطلاع از جدیدترین مطالب یا پرسش و پاسخ عضو کانال و گروه تلگرامی ما شوید.

arrow_drop_up
کپی شد!