sinau-c/content/linked_lists.md

13 KiB

---LESSON_INFO--- Learning Objectives:

  • Memahami konsep daftar tertaut (linked list) dalam bahasa C
  • Belajar membuat dan mengelola struktur daftar tertaut
  • Mengenal operasi dasar pada daftar tertaut
  • Memahami perbedaan antara array dan daftar tertaut

Prerequisites:

  • Pemahaman tentang pointer dan struktur
  • Pemahaman dasar tentang alokasi memori dinamis

---END_LESSON_INFO---

Daftar Tertaut dalam C

Pengantar

Daftar tertaut adalah contoh terbaik dan paling sederhana dari struktur data dinamis yang menggunakan pointer untuk implementasinya. Namun, memahami pointer sangat penting untuk memahami bagaimana daftar tertaut bekerja, jadi jika Anda melewatkan tutorial pointer, Anda harus kembali dan mengulanginya. Anda juga harus familiar dengan alokasi memori dinamis dan struktur.

Pada dasarnya, daftar tertaut berfungsi sebagai array yang bisa berkembang dan menyusut sesuai kebutuhan, dari titik mana pun dalam array.

Daftar tertaut memiliki beberapa keunggulan dibandingkan array:

  1. Item bisa ditambahkan atau dihapus dari tengah daftar
  2. Tidak perlu mendefinisikan ukuran awal

Namun, daftar tertaut juga memiliki beberapa kelemahan:

  1. Tidak ada akses "acak" - tidak mungkin mencapai item ke-n dalam array tanpa terlebih dahulu mengiterasi semua item hingga item yang diinginkan. Ini berarti kita harus mulai dari awal daftar dan menghitung berapa kali kita maju dalam daftar sampai kita sampai ke item yang diinginkan.
  2. Membutuhkan alokasi memori dinamis dan pointer, yang memperumit kode dan meningkatkan risiko kebocoran memori dan kesalahan segmen.
  3. Daftar tertaut memiliki overhead yang jauh lebih besar dibandingkan array, karena item daftar tertaut dialokasikan secara dinamis (yang kurang efisien dalam penggunaan memori) dan setiap item dalam daftar juga harus menyimpan pointer tambahan.

Apa itu daftar tertaut?

Daftar tertaut adalah kumpulan node yang dialokasikan secara dinamis, disusun sedemikian rupa sehingga setiap node berisi satu nilai dan satu pointer. Pointer selalu menunjuk ke anggota berikutnya dari daftar. Jika pointer adalah NULL, maka itu adalah node terakhir dalam daftar.

Daftar tertaut dipegang menggunakan variabel pointer lokal yang menunjuk ke item pertama dari daftar. Jika pointer itu juga NULL, maka daftar dianggap kosong.

------------------------------
|          |            |
|   DATA   |   NEXT     |------>
|          |            |
------------------------------

Mari kita definisikan node daftar tertaut:

typedef struct node {
    int val;
    struct node * next;
} node_t;

Perhatikan bahwa kita mendefinisikan struct secara rekursif, yang mungkin dalam C. Mari kita beri nama tipe node kita node_t.

Sekarang kita bisa menggunakan node. Mari kita buat variabel lokal yang menunjuk ke item pertama dari daftar (disebut head).

node_t * head = NULL;
head = (node_t *) malloc(sizeof(node_t));
if (head == NULL) {
    return 1;
}
head->val = 1;
head->next = NULL;

Kita baru saja membuat variabel pertama dalam daftar. Kita harus mengatur nilai, dan item berikutnya menjadi kosong, jika kita ingin selesai mengisi daftar. Perhatikan bahwa kita harus selalu memeriksa apakah malloc mengembalikan nilai NULL atau tidak.

Untuk menambahkan variabel ke akhir daftar, kita bisa terus maju ke pointer berikutnya:

node_t * head = NULL;
head = (node_t *) malloc(sizeof(node_t));
head->val = 1;
head->next = (node_t *) malloc(sizeof(node_t));
head->next->val = 2;
head->next->next = NULL;

Ini bisa terus berjalan, tetapi yang seharusnya kita lakukan adalah maju ke item terakhir dari daftar, sampai variabel next akan menjadi NULL.

Iterasi melalui daftar

Mari kita buat fungsi yang mencetak semua item dari daftar. Untuk melakukan ini, kita perlu menggunakan pointer current yang akan terus melacak node yang sedang kita cetak. Setelah mencetak nilai node, kita mengatur pointer current ke node berikutnya, dan mencetak lagi, sampai kita mencapai akhir dari daftar (node berikutnya adalah NULL).

void print_list(node_t * head) {
    node_t * current = head;
    while (current != NULL) {
        printf("%d\\n", current->val);
        current = current->next;
    }
}

Menambahkan item ke akhir daftar

Untuk mengiterasi semua anggota dari daftar tertaut, kita gunakan pointer yang disebut current. Kita atur untuk mulai dari head dan kemudian di setiap langkah, kita majukan pointer ke item berikutnya dalam daftar, sampai kita mencapai item terakhir.

void push(node_t * head, int val) {
    node_t * current = head;
    while (current->next != NULL) {
        current = current->next;
    }

    /* sekarang kita bisa menambahkan variabel baru */
    current->next = (node_t *) malloc(sizeof(node_t));
    current->next->val = val;
    current->next->next = NULL;
}

Kasus penggunaan terbaik untuk daftar tertaut adalah tumpukan dan antrian, yang akan kita implementasikan sekarang:

Menambahkan item ke awal daftar (pushing ke daftar)

Untuk menambahkan ke awal daftar, kita perlu melakukan hal berikut:

  1. Buat item baru dan atur nilainya
  2. Hubungkan item baru ke pointer ke head dari daftar
  3. Atur head dari daftar menjadi item baru kita

Ini akan secara efektif membuat head baru untuk daftar dengan nilai baru, dan menjaga sisa daftar terhubung ke sana.

Karena kita menggunakan fungsi untuk melakukan operasi ini, kita ingin bisa memodifikasi variabel head. Untuk melakukan ini, kita harus melewati pointer ke variabel pointer (pointer ganda) sehingga kita bisa memodifikasi pointer itu sendiri.

void push(node_t ** head, int val) {
    node_t * new_node;
    new_node = (node_t *) malloc(sizeof(node_t));
    new_node->val = val;
    new_node->next = *head;
    *head = new_node;
}

Menghapus item pertama (popping dari daftar)

Untuk pop variabel, kita perlu membalikkan tindakan ini:

  1. Ambil item berikutnya yang ditunjuk oleh head dan simpan
  2. Bebaskan item head
  3. Atur head menjadi item berikutnya yang telah kita simpan di sisi

Berikut adalah kode:

int pop(node_t ** head) {
    int retval = -1;
    node_t * next_node = NULL;

    if (*head == NULL) {
        return -1;
    }

    next_node = (*head)->next;
    retval = (*head)->val;
    free(*head);
    *head = next_node;

    return retval;
}

Menghapus item terakhir dari daftar

Menghapus item terakhir dari daftar sangat mirip dengan menambahkannya ke akhir daftar, tetapi dengan satu perbedaan besar - karena kita harus mengubah satu item sebelum item terakhir, kita sebenarnya harus melihat dua item ke depan dan melihat apakah item berikutnya adalah item terakhir dalam daftar:

int remove_last(node_t * head) {
    int retval = 0;
    /* jika hanya ada satu item dalam daftar, hapus */
    if (head->next == NULL) {
        retval = head->val;
        free(head);
        return retval;
    }

    /* dapatkan ke item kedua dari akhir dalam daftar */
    node_t * current = head;
    while (current->next->next != NULL) {
        current = current->next;
    }

    /* sekarang current menunjuk ke item kedua dari akhir item dari daftar, jadi mari kita hapus current->next */
    retval = current->next->val;
    free(current->next);
    current->next = NULL;
    return retval;
}

Menghapus item tertentu

Untuk menghapus item tertentu dari daftar, baik berdasarkan indeksnya dari awal daftar atau berdasarkan nilainya, kita perlu melewati semua item, terus melihat ke depan untuk mengetahui apakah kita telah mencapai node sebelum item yang ingin kita hapus. Ini karena kita perlu mengubah lokasi ke mana pointer node sebelumnya menunjuk juga.

Berikut adalah algoritma:

  1. Iterasi ke node sebelum node yang ingin kita hapus
  2. Simpan node yang ingin kita hapus dalam pointer sementara
  3. Atur pointer next dari node sebelumnya untuk menunjuk ke node setelah node yang ingin kita hapus
  4. Hapus node menggunakan pointer sementara

Ada beberapa kasus khusus yang perlu kita tangani, jadi pastikan Anda memahami kode.

int remove_by_index(node_t ** head, int n) {
    int i = 0;
    int retval = -1;
    node_t * current = *head;
    node_t * temp_node = NULL;

    if (n == 0) {
        return pop(head);
    }

    for (i = 0; i < n-1; i++) {
        if (current->next == NULL) {
            return -1;
        }
        current = current->next;
    }

    if (current->next == NULL) {
        return -1;
    }

    temp_node = current->next;
    retval = temp_node->val;
    current->next = temp_node->next;
    free(temp_node);

    return retval;
}

Tabel Operasi pada Daftar Tertaut

Operasi Deskripsi Contoh
Push Menambahkan item ke awal daftar push(&head, value)
Pop Menghapus item dari awal daftar pop(&head)
Print Mencetak semua elemen daftar print_list(head)
Remove Menghapus item berdasarkan nilai remove_by_value(&head, value)

---EXERCISE---

Latihan: Menghapus Elemen Berdasarkan Nilai

Anda harus mengimplementasikan fungsi remove_by_value yang menerima pointer ganda ke head dan menghapus item pertama dalam daftar yang memiliki nilai val.

Requirements:

  • Gunakan pointer ganda untuk mengakses head
  • Hapus node pertama yang memiliki nilai tertentu
  • Bebaskan memori dari node yang dihapus
  • Tangani kasus ketika head perlu dihapus

Expected Output:

1
2
4

Try writing your solution in the code editor below!

---KEY_TEXT--- previous->next = current->next free(current) current = current->next pop(head) ---END_KEY_TEXT---

---EXPECTED_OUTPUT--- 1 2 4 ---END_EXPECTED_OUTPUT---

---INITIAL_CODE--- #include <stdio.h> #include <stdlib.h>

typedef struct node {
    int val;
    struct node * next;
} node_t;

void print_list(node_t * head) {
    node_t * current = head;
    while (current != NULL) {
        printf("%d\\n", current->val);
        current = current->next;
    }
}

int pop(node_t ** head) {
    int retval = -1;
    node_t * next_node = NULL;

    if (*head == NULL) {
        return -1;
    }

    next_node = (*head)->next;
    retval = (*head)->val;
    free(*head);
    *head = next_node;

    return retval;
}

int remove_by_value(node_t ** head, int val) {
    /* TODO: isi kode Anda di sini */
}

int main() {
    node_t * test_list = (node_t *) malloc(sizeof(node_t));
    test_list->val = 1;
    test_list->next = (node_t *) malloc(sizeof(node_t));
    test_list->next->val = 2;
    test_list->next->next = (node_t *) malloc(sizeof(node_t));
    test_list->next->next->val = 3;
    test_list->next->next->next = (node_t *) malloc(sizeof(node_t));
    test_list->next->next->next->val = 4;
    test_list->next->next->next->next = NULL;

    remove_by_value(&test_list, 3);

    print_list(test_list);
}

---END_INITIAL_CODE---

---SOLUTION_CODE--- #include <stdio.h> #include <stdlib.h>

typedef struct node {
    int val;
    struct node * next;
} node_t;

void print_list(node_t * head) {
    node_t * current = head;
    while (current != NULL) {
        printf("%d\\n", current->val);
        current = current->next;
    }
}

int pop(node_t ** head) {
    int retval = -1;
    node_t * next_node = NULL;

    if (*head == NULL) {
        return -1;
    }

    next_node = (*head)->next;
    retval = (*head)->val;
    free(*head);
    *head = next_node;

    return retval;
}

int remove_by_value(node_t ** head, int val) {
    node_t *previous, *current;

    if (*head == NULL) {
        return -1;
    }

    if ((*head)->val == val) {
        return pop(head);
    }

    previous = *head;
    current = (*head)->next;
    while (current) {
        if (current->val == val) {
            previous->next = current->next;
            free(current);
            return val;
        }
        previous = current;
        current = current->next;
    }
    return -1;
}

void delete_list(node_t *head) {
    node_t *current = head, *next = head;
    while (current) {
        next = current->next;
        free(current);
        current = next;
    }
}

int main(void) {
    node_t * test_list = (node_t *) malloc(sizeof(node_t));
    test_list->val = 1;
    test_list->next = (node_t *) malloc(sizeof(node_t));
    test_list->next->val = 2;
    test_list->next->next = (node_t *) malloc(sizeof(node_t));
    test_list->next->next->val = 3;
    test_list->next->next->next = (node_t *) malloc(sizeof(node_t));
    test_list->next->next->next->val = 4;
    test_list->next->next->next->next = NULL;

    remove_by_value(&test_list, 3);

    print_list(test_list);
    delete_list(test_list);
    return EXIT_SUCCESS;
}

---END_SOLUTION_CODE---