`xÅt{tÅ| STRUKTUR DATA Dengan Bahasa C EKO SEDIYONO FAKULTAS TEKNOLOGI INFORMASI UNIVERSITAS KRISTEN SATYA WACANA SALATIGA 2007 DAFTAR ISI I. PENDAHULUAN II. TIPE DATA TERSTRUKTUR III. POINTER IV. LINKED LISTS V. TUMPUKAN (STACK) VI. REKURSI VII. ANTRIAN (QUEUE) VIII. POHON (TREE) DAFTAR PUSTAKA 1 I. PENDAHULUAN Diktat ini tidak membahas lagi bahasa pemrograman C, karena pembaca dianggap sudah menguasai bahasa pemrograman C yang dipelajari pada matakuliah Dasar-dasar pemrograman. Oleh karena itu sebelum tertinggal jauh dengan pembahasan materi ini, maka diharapkan pembaca segera mempelajari dan menguasai bahasa pemrograman C baik dengan menggunakan Turbo C, Borland C, maupun Microsoft Visual C. Menurut Wikipedia, struktur data adalah cara untuk menyimpan data di komputer sehingga data tersebut dapat digunakan secara efisien. Dengan memilih struktur data yang tepat akan berpengaruh pada penggunaan algoritma yang efisien pula. Struktur data yang dirancang dengan baik juga akan memberikan peluang untuk melakukan komputasi-komputasi yang rumit dengan menggunakan sumberdaya yang sesedikit mungkin, sehingga menghemat waktu eksekusi dan memori. Contoh dari masalah ini akan dibahas pada pembahasan tiap topik. Struktur data diimplementasikan dengan menggunakan tipe data, referensi dan operasi-operasi yang tersedia dalam bahasa pemrograman, yang dalam hal ini adalah bahasa C. Tipe data dalam bahasa C terdiri dari tipe data dasar (int, char, float, double, long int, dan lain-lain), dan tipe data terstruktur (struct, union, enum, static) yang akan dibahas pada bab 2. Setiap variabel yang diberi tipe data tertentu memberikan pengertian acuan (referensi) pada suatu tempat (alamat) di dalam memori. Dengan model referensi tersebut maka user atau programmer tidak perlu repot menentukan alamat memori untuk menempatkan data. Dengan menggunakan tipe data tertentu pada tiap variabel maka pada saat kompilasi akan ditentukan alamat memori relatif untuk tiap variabel tersebut (detil dari pemetaan alamat memori dipelajari pada organisasi sistem komputer). Setiap operasi (fungsi) yang ada dalam bahasa pemrograman juga memiliki tipe tertentu, seperti misalnya fungsi printf() memiliki tipe void, div() memiliki tipe float, dan lain-lain. Setiap struktur data hanya cocok digunakan untuk aplikasi tertentu, bahkan beberap struktur data khusus dibuat untuk aplikasi tertentu. Contohnya struktur data B-tree hanya cocok digunakan untuk basisdata, struktur data graph digunakan untuk merepresentasikan jaringan, dan lainlain. Dalam merancang suatu aplikasi yang besar, struktur data menjadi bagian utama yang harus diperhatikan. Kesalahan rancangan struktur data menyebabkan kesulitan dalam merealisasikan rancangan dan menyebabkan kualitas dan unjuk kerja aplikasi menjadi jelek. Tahap berikutnya, setelah rancangan struktur data adalah algoritmanya. Dari penjelasan di atas dapat disimpulkan bahwa menguasai struktur data adalah hal pokok supaya dapat menciptakan aplikasi yang baik dan efisien. Dalam bahasa-bahasa pemrograman modern seperti C++, Java, dan .NET, struktur data telah dikemas dalam bentuk pustaka standar (standard libraries). Dalam bahasa C++ tersedia Standard Template Library, dalam Java tersedia Java API, dan dalam Microsoft .NET tersedia .NET Framework. 1 II. TIPE DATA TERSTRUKTUR Pada bab ini akan dibahas tipe-tipe data lanjut yang belum dibahas pada matakuliah dasar-dasar pemrograman. Tipe-tipe berikut ini dapat digunakan pada compiler C atau C++. 2.1. Structure (struct) Tipe data struct adalah tipe data untuk menggabung tipe data – tipe data yang kita inginkan. Misalnya kita ingin menyimpan data tentang senapan (magazines). Informasi2 yang akan disimpan untuk tiap senapan adalah nama (name), ukuran (magazinesize), dan kaliber (calibre). Maka cara membuatnya adalah sebagai berikut : struct gun { char name[50]; int magazinesize; float calibre; }; struct gun koleksiku; Kata-kata yang tercetak tebal adalah identifier yang kita buat sendiri. Dengan menggunakan struct maka kita dapat menggabungkan beberapa variabel yang berbeda tipe menjadi satu kesatuan (instant). Sekarang gun menjadi shortcut untuk menyatakan beberapa variabel secara bersama-sama. Pada contoh ini variabel-variabel yang digabung adalah nama, ukuran dan kaliber. Selanjutnya untuk mendefinisikan variabel dengan gabungan tiga tipe tersebut maka dibuat perintah struct gun koleksiku; Supaya tidak mengulang definisi variabel koleksiku, maka dapat dibuat seperti contoh di bawah ini. struct gun { char name[50]; int magazinesize; float calibre; } koleksiku ; Variabel dalam struct juga dapat langsung diinisialisasi, misalnya : struct gun koleksiku={"Pistol",30,7}; menyatakan koleksiku sudah ada satu yaitu Pistol kaliber 7 dengan ukuran 30 mm. Untuk mengakses anggota (atau field) dari struct, C menggunakan operator “.” Misalnya ukuran diameter pelurunya yang semula 30 akan diganti dengan 100 maka dapat dilakukan assignment hanya untuk magazinesize sebagai berikut : koleksiku.magazineSize=100; 2 Mendefinisikan Tipe Data Baru Untuk mendefinisikan tipe data baru dengan struct dapat digunakan typedef . Dengan cara berikut ini maka tipesenapan dapat menjadi tipe data baru typedef struct gun { char name[50]; int magazinesize; float calibre; } tipesenapan; tipesenapan senapanku ={"Pistol",30,7}; Dalam hal ini gun hanya berfungsi sebagai shortcut (bukan tipe) dan bersifat opsional. Setelah mendefinisikan tipe data baru tipesenapan, maka identifier gun boleh tidak digunakan. Variabel senapanku adalah variabel yang memiliki tipe data tipesenapan dan terdiri dari 3 field nama, ukuran dan kaliber. Selanjutnya apabila koleksinya banyak, maka type struct dapat dikombinasi dengan array. Misalnya kita akan menyimpan koleksi sampai dengan 1000 senapan, maka struktur datanya dapat dibuat seperti berikut ini. typedef struct gun { char name[50]; int magazinesize; float calibre; } tipesenapan; tipesenapan senapanku[1000]; Untuk mengakses koleksi ke 50 dan menetapkan kalibernya dengan nilai 100, maka caranya adalah : senapanku [50].calibre =100; Untuk mencatat (mengcopy) kaliber dari senapan pertama ke dalam suatu variabel baru maka perlu diperhatikan tipe dari kaliber. Karena calibre bertipe float, maka variabel baru misalnya kaliber1 harus bertipe float, sebagai berikut. float kaliber1; kaliber1 = senapanku[0].calibre; Contoh berikut ini adalah perbandingan penggunaan struktur data yang tepat dan tidak tepat. Dari contoh tersebut dapat dilihat kesulitan-kesulitan yang dihadapi pada saat harus memanipulasi data. 3 Soal : Ingin dibuat laporan penjualan secara berkelompok menurut kode barang. Informasi yang diketahui untuk setiap item barang adalah kode barang (KodeBar), Nama barang (NamaBar) dan jumlah barang yang terjual (Terjual). Karena yang menjadi kelompok adalah kode barang yang bertipe string maka harus dilakukan pengurutan (sorting) string. Kita tidak terlalu memperhatikan algoritma sorting, oleh karena itu dipilih saja metode sorting yang sederhana yaitu selection sort. Jawaban pertama menggunakan struktur data array of string, yaitu untuk kode barang dan nama barang disediakan array 50 elemen dan masing-masing elemen bertipe string 5 karakter dan string 10 karakter, dan variabel Terjual disediakan array 50 elemen masing-masing bertipe integer. Jawaban ke dua menggunakan struktur data struct. Untuk data dengan beberapa field yang berbeda seperti ini lebih mudah digunakan struktur data struct. Jawaban model 1 (struktur data array of string) Nama Program : jklp_arr.c #include <stdio.h> #include <conio.h> #include <string.h> char char int int KodeBar[50][5]; NamaBar[50][10]; Terjual[50]; n; // Jumlah barang char TukarKode[5]; char TukarNama[10]; int TukarJual; //variabel pembantu untuk menukar KodeBarang //variabel pembantu untuk menukar NamaBarang //variabel pembantu untuk menukar Terjual char Batas; int SubTotal,Total; //variabel pembatas sub total void main() { int i,j; clrscr; printf("\nJumlah Barang : "); scanf("%d", &n); // Input n barang for(i=0;i<n; i++) { printf("\nKode Barang scanf("%s",&KodeBar[i]); printf("\nNama Barang scanf("%s",&NamaBar[i]); printf("\nTerjual scanf("%d",&Terjual[i]); } : "); : "); : "); 4 // Sorting for (i=0;i< n-1;i++) for(j=i+1;j<n; j++) if (strcmp(KodeBar[i],KodeBar[j]) > 0) { strcpy(TukarKode,KodeBar[i]); strcpy(KodeBar[i],KodeBar[j]); strcpy(KodeBar[j],TukarKode); strcpy(TukarNama,NamaBar[i]); strcpy(NamaBar[i],NamaBar[j]); strcpy(NamaBar[j],TukarNama); TukarJual = Terjual[i]; Terjual[i]=Terjual[j]; Terjual[j]=TukarJual; } ; printf("\nData yang urut"); for(i=0;i<n;i++) printf("\n %4s %10s %3d ",&KodeBar[i],&NamaBar[i],Terjual[i]); printf("\nTekan Sembarang tombol"); getch(); // Laporan dengan subtotal kelompok Batas=KodeBar[0][0]; // ambil huruf A atau B saja SubTotal=Total=0; printf("\n LAPORAN PER KELOMPOK"); printf("\nKodeBar NamaBar Terjual"); for (i=0;i<n;i++) if (Batas != KodeBar[i][0]) { printf("\n Sub Total ---> %3d",SubTotal); SubTotal = 0; Batas=KodeBar[i][0]; SubTotal += Terjual[i]; Total += Terjual[i]; printf("\n %4s %10s %3d", &KodeBar[i],&NamaBar[i],Terjual[i]); }else { SubTotal += Terjual[i]; Total += Terjual[i]; printf("\n %4s %10s %3d", &KodeBar[i],&NamaBar[i],Terjual[i]); } printf("\n Sub Total ---> %3d",SubTotal); printf("\n Total -------> %3d",Total); } Jika inputnya ada 5 barang berikut ini B001 semen 30 A002 benih 20 A001 pupuk 50 B002 cat 40 A003 insekt 30 5 Maka hasilnya adalah: Data yang urut A001 pupuk 50 A002 benih 20 A003 insekt 30 B001 semen 30 B002 cat 40 Tekan sembarang tombol LAPORAN PER KELOMPOK KodeBar NamaBar Terjual A001 pupuk 50 A002 benih 20 A003 insekt 30 Sub Total ---> 100 B001 semen 30 B002 cat 40 Sub Total ---> 70 Total -------> 170 Jawaban model 2 (struktur data struct) Nama Program : jklp_str.c #include <stdio.h> #include <conio.h> #include <string.h> typedef struct item { char KodeBar[5]; char NamaBar[10]; int Terjual; } itembarang; //ini struktur datanya itembarang Barang[50]; int n; //ini variabel barang //Jumlah barang itembarang TukarBarang; //variabel pembantu untuk menukar Barang char Batas; int SubTotal,Total; //Variabel pembatas sub total void main() { int i,j; clrscr; printf("\nJumlah Barang : "); scanf("%d", &n); // Input n barang for(i=0;i<n; i++) { printf("\nKode Barang : "); scanf("%s",&Barang[i].KodeBar); printf("\nNama Barang : "); 6 scanf("%s",&Barang[i].NamaBar); printf("\nTerjual : "); scanf("%d",&Barang[i].Terjual); } // Sorting for (i=0;i< n-1;i++) for(j=i+1;j<n; j++) if (strcmp(Barang[i].KodeBar,Barang[j].KodeBar) > 0) { TukarBarang = Barang[i]; Barang[i] = Barang[j]; Barang[j] = TukarBarang; }; printf("\nData yang urut"); for(i=0;i<n;i++) printf("\n %4s %10s %3d ", &Barang[i].KodeBar,&Barang[i].NamaBar, Barang[i].Terjual); printf("\nTekan Sembarang tombol"); getch(); // Laporan dengan subtotal kelompok Batas=Barang[0].KodeBar[0]; //ambil karakter pertama dari kodebar SubTotal=Total=0; printf("\n LAPORAN PER KELOMPOK"); printf("\nKodeBar NamaBar Terjual"); for (i=0;i<n;i++) if (Batas != Barang[i].KodeBar[0]) { printf("\n Sub Total ---> %3d", SubTotal); SubTotal = 0; Batas=Barang[i].KodeBar[0]; SubTotal += Barang[i].Terjual; Total += Barang[i].Terjual; printf("\n %4s %10s %3d", &Barang[i].KodeBar,&Barang[i].NamaBar, Barang[i].Terjual); } else { SubTotal += Barang[i].Terjual; Total += Barang[i].Terjual; printf("\n %4s %10s %3d", &Barang[i].KodeBar,&Barang[i].NamaBar, Barang[i].Terjual); } printf("\n Sub Total ---> %3d",SubTotal); printf("\n Total -------> %3d",Total); } Dengan menggunakan struktur data struct maka pada bagian sorting lebih sederhana (lihat bagian yang tercetak tebal), tidak perlu menukarkan nilai untuk tiap field. 7 2.2. Unions Union adalah tipe data untuk variabel yang dapat menyimpan obyek yang berbeda ukuran dan tipe. Bahasa C menggunakan union untuk menggabung beberapa tipe yang berbeda. union number { short shortnumber; long longnumber; double floatnumber; } anumber ; Kata-kata yang tercetak tebal adalah identifier / variabel yang kita ciptakan sendiri. number adalah shortcut untuk menggabungkan (nunion) shortnumber, longnumber, dan floatnumber. anumber adalah obyek yang berisi tiga bilangan tadi. Setiap anggota dari obyek anumber dapat diakses dengan operator “.” Misalnya nilai salah satu anggota obyek ingin dicetak, maka perintahnya adalah sebagai berikut : printf("%ld \n",anumber.longnumber); Union juga dapat digunakan untuk menggabungkan tipe struct buatan kita sendiri. Contoh berikut ini adalah struktur data untuk menyatakan pesawat terbang yang terdiri dari beberapa macam pesawat, dan untuk tiap pesawat memiliki satuan ukuran yang berbeda. Untuk jenis pesawat jet ukuran satuannya adalah jumlah penumpang. Untuk helikopter ukuran satuannya adalah kapasitas angkutnya, dan untuk pesawat kargo ukuran satuannya adalah maksimum biaya angkutnya. Untuk menyatakan hal tersebut dapat dibuat struktur data berikut ini. typedef struct { int maxpassengers; } jet; typedef struct { int liftcapacity; } helicopter; typedef struct { int maxpayload; } cargoplane; typedef union { jet jetu; helicopter helicopteru; cargoplane cargoplaneu; } aircraft; Perbedaan dan Persamaan Union dan Structure Dari forum diskusi yang ada di internet diperoleh beberapa hal tentang perbedaan dan persamaan antara Union dan Structure. Beberapa perbedaan tersebut adalah : 8 1. union mengalokasikan memori sebesar memori maksimum yang diminta oleh field anggotanya. Contohnya untuk struktur number di atas, memori yang dibutuhkan adalah 8 byte karena field dengan alokasi memori terbesar adalah floadnumber dengan tipe double. Structure mengalokasikan memori sebanyak jumlah byte dari seluruh field anggotanya. 2. Dalam union, satu blok memori digunakan bersama oleh seluruh anggota dalam union, sedangkan dalam structure masing-masing field anggota memiliki ruang memori sendiri. 3. union menyimpan satu nilai aktual saja. Jika ada elemen baru disimpan sebelum elemen pertama diambil, maka elemen pertama tersebut akan hilang tertimpa oleh elemen baru. Oleh karena itu programmer perlu hati-hati dalam mengelola struktur union. Dalam structure, karena masing-masing elemen anggota dialokasikan di ruang memori yang berbeda, maka perubahan pada satu elemen tidak mempengaruhi elemen lainnya. Beberapa persamaan dari union dan structure adalah : 1. Keduanya menggunakan operator dot (.) untuk mengakses field anggotanya. 2. Keduanya menggunakan tanda { } untuk menyatakan obyek datanya. Keduanya juga menggunakan nama shortcut dan inisialisasi eksplisit. 3. Keduanya dapat diketahui alokasi memori maksimumnya dalam byte dengan menggunakan operator sizeof(). Berikut ini adalah contoh perbedaan dan persamaannya di dalam program. Nama Program : unionstr.c (Jalan di Visual C++ 6.0) #include <stdio.h> #include <stdlib.h> /* Deklarasi union */ union one{ char satu; int dua; float tiga; }; /* Deklarasi structure */ struct two{ char satu; int dua; float tiga; }; int main(void) { /* Menggunakan identifier shortcut untuk membuat struktur S dan union U. */ struct two S; union one U; /* Output ukuran obyek ke layar */ 9 printf("%d adalah jumlah byte dari S, sebagai structure.\n", sizeof(S)); printf("%d adalah jumlah byte dari U, sebagai union.\n\n", sizeof(U)); /* Mengambil nilai untuk S dan U */ S.satu = 'A'; S.dua = 3645; S.tiga = 678.32; U.satu = 'A'; U.dua = 3645; U.tiga = 678.32; /* Masukannya ditampilkan di layar supaya mudah membandingkan*/ printf("Nilai 'A', 3645, dan 678.32 telah dimasukkan " "ke anggota struktur S dan\n anggota union U \n\n"); /* Nilai yang diterima untuk setiap anggota S */ printf("%c sama dengan nilai S.satu\n", S.satu); printf("%d sama dengan nilai S.dua\n", S.dua); printf("%3.2f sama dengan nilai S.tiga\n\n", S.tiga); /* Nilai yang diterima untuk setiap anggota U (Terkorupsi !!). */ printf("%c sama dengan nilai U.satu\n", U.satu); puts("Oops! Tidak sama dengan nilai 'A' yang dimasukkan!\n"); printf("%d sama dengan nilai U.dua\n", U.dua); puts("Oops! Tidak sama dengan nilai 3645 yang dimasukkan!\n"); printf("%3.2f sama dengan nilai U.tiga\n\n", U.tiga); system("PAUSE"); return 0; } Outputnya adalah : 12 adalah jumlah byte dari S, sebagai structure. 4 adalah jumlah byte dari U, sebagai union. Nilai ‘A’, 3645, dan 678.32 telah dimasukkan ke anggota S Dan anggota union U A sama dengan nilai S.satu 3645 sama dengan nilai S.dua 678.32 sama dengan nilai S.tiga { sama dengan nilai U.satu Oops! Tidak sama dengan nilai ’A’ yang dimasukkan! 1143575675 sama dengan nilai U.dua Oops! Tidak sama dengan nilai 3645 yang dimasukkan! 678.32 sama dengan nilai U.tiga 10 Pembahasan : Struktur data struct mengalokasikan memori untuk setiap field anggotanya. Nilai 12 diperoleh dari 1 untuk char, 4 untuk int, 4 untuk float, dan 3 sisanya untuk menyimpan struktur yang beranggotakan 3 field. Dalam struktur data union hanya dinyatakan 4, karena field terbesar adalah int atau float dengan 4 byte . Struktur data struct dapat menerima setiap masukan nilai anggotanya secara sekuensial, tetapi union tidak. Setiap masukan nilai akan mengubah semua nilai anggotanya, karena union hanya mengalokasikan satu ruang memori saja. 2.3. Coercion atau Type-Casting Bahasa C adalah sedikit dari bahasa pemrograman yang menyediakan fasilitas coersion, yaitu fasilitas untuk memaksa suatu variabel dengan tipe tertentu berubah menjadi tipe lain pada saat program berjalan. Operator yang digunakan adalah ( ) di depan variabel yang akan diubah tipenya. Contoh : int integernumber; float floatnumber=9.87; integernumber=(int)floatnumber; Memaksa 9.87 menjadi bilangan bulat, yaitu dengan membuang .87 sehingga hasilnya integernumber menyimpan nilai 9. int integernumber=10; float floatnumber; floatnumber=(float)integernumber; Memaksa bilangan 10 menjadi bilangan pecahan (float) sehingga floatnumber bernilai 10.0. Coersion dapat juga digunakan untuk semua tipe data dasar termasuk char. Sehingga apabila ada char di integerkan akan menjadi seperti berikut ini. int integernumber; char letter='A'; integernumber=(int)letter; Huruf A memiliki kode ASCII 65, sehingga integernumber bernilai 65. Beberapa type casting dilakukan secara otomatis, terutama untuk integer. Contohnya dalam pembagian (/), secara otomatis bilangan-bilangan pembagi dan yang dibagi diubah menjadi float. Tetapi ada aturan yang menganjurkan : “Bila ragu, maka lakukan cast”. Contoh : floatnumber = (float) internumber/(float) anotherint; 11 Memastikan pembagian secara floating point. Hal ini dilakukan untuk membuat hasil pembagian menjadi lebih teliti. 2.4. Tipe Enumerasi Tipe enumerasi adalah daftar bilangan-bilangan konstan yang dapat diberi alamat sebagai bilangan bulat (integer). Cara mendeklarasikannya adalah sebagai berikut : enum hari {senin, selasa, rabu, kamis, jum’at, sabtu, minggu} seminggu; enum hari minggu1, minggu2; Kata-kata yang tercetak tebal adalah identifier atau nilai yang kita buat sendiri. Kata hari adalah tag (shortcut) untuk menyatakan hari-hari dalam satu minggu. Kata-kata seminggu, minggu1 dan minggu2 adalah variabel yang bernilai seperti array yang dimulai dari 0. Jadi senin adalah 0, selasa 1, dan seterusnya. Contoh lainnya, enum dapat digunakan untuk menyatakan karakter-karakter control, seperti berikut ini. enum escapes {bell = `\a',backspace =`\b',tab =`\t', newline = `\n',vtab = `\v',return = `\r'}; Nilai default yang dimulai dengan 0 dapat diganti dengan cara seperti contoh berikut ini. enum months {jan = 1, feb, mar, ......, dec}; Hal ini berarti januari bernilai 1, februari bernilai 2 dan seterusnya. 2.5. Variabel Statik (Static Variables ) Variabel statik adalah variabel yang bersifat lokal untuk suatu fungsi, nilainya akan diinisialisasi satu kali saja pada saat dipanggil pertama kali. Selain itu, pada saat meninggalkan fungsi variabel tersebut mendapatkan nilai yang terakhir. Pada saat pemanggilan fungsi berikutnya, maka nilai variabel statik tersebut menggunakan nilai yang terbaru, bukan dimulai dari nilai awal. Untuk mendefinisikan variabel statik tinggal menambahkan kata-kata static di depan variabel yang akan kita buat sebagai variabel statik. Contohnya adalah sebagai berikut : Nama Program : static.c #include <stdio.h> void stat(); /* prototype fungsi */ void main() { int i; for (i=0;i<5;++i) 12 stat(); } void stat() { int auto_var = 0; static int static_var = 0; printf("auto = %d, static = %d \n", auto_var, static_var); ++auto_var; ++static_var; } Output dari program di atas adalah : auto_var auto_var auto_var auto_var auto_var = = = = = 0, 0, 0, 0, 0, static_var= 0 static_var = 1 static_var = 2 static_var = 3 static_var = 4 Jadi variabel auto_var dibuat setiap kali fungsi dipanggil, sedangkan variabel static_var dibuat hanya sekali saja. 2.6. Soal Latihan Latihan 1. Tulislah program untuk menebak nama hari pada tahun 2007, apabila dalam program diinputkan tanggal, bulan dan tahun. Diketahui Tanggal 1 Januari 2007 adalah hari senin. Latihan 2 Buatlah program untuk menyimpan database sederhana yang berisi nama, umur, tanggal lahir, alamat, dan nomor telpon. 13 III. POINTER Pointer digunakan di hampir semua aplikasi dengan menggunakan bahasa C. Jika pembaca sudah mengenal pointer, maka tidak akan menghadapi masalah dalam membangun aplikasi dengan bahasa C, tetapi kalau belum menguasai pointer maka harus lebih banyak membaca buku tentang pointer, atau bahkan bertanya kepada dosen atau orang lain untuk lebih memahaminya. Pada dasarnya pointer dalam bahasa C hampir sama dengan pointer dalam bahasa Pascal, bahakan penggunaan pointer dalam bahasa C lebih bebas dari pada dalam Pascal. Bahasa C menggunakan pointer untuk tiga hal, yaitu : pertama untuk membangun struktur data dinamik, yaitu struktur data yang dibangun pada saat program berjalan dengan mengalokasikan satu blok memori. Pointer dalam Pascal hanya digunakan untuk keperluan ini. Ke dua, pointer dalam bahasa C digunakan untuk menangani parameter variabel yang dilewatkan pada fungsi. Ke tiga, pointer dalam bahasa C digunakan untuk mengakses informasi yang disimpan dalam bentuk array. Hal ke tiga ini terutama berguna pada saat menangani string. Dalam bahasa C terdapat hubungan yang sangat erat antara pointer dan array. Dalam banyak hal, penggunaan pointer membuat program menjadi lebih efisien, tetapi disisi lain, penggunaan pointer dalam program juga menyebabkan program tersebut sulit dipelajari. Dalam hal ini, kita tidak boleh tanggung-tanggung menguasai pointer dari ketiga hal tersebut di atas, karena bahasa C digunakan secara luas untuk keperluan praktis. 3.1. Dasar-dasar Penggunaan Pointer Variabel biasa dalam bahasa C mengandung arti lokasi memori yang digunakan untuk menyimpan suatu nilai. Misalnya dideklarasikan variabel i bertipe integer, maka di memori disediakan ruang sebanyak 4 byte. Untuk menuju lokasi tersebut dalam program harus menggunakan variabel i. Pada level bahasa mesin, lokasi memori tersebut dinyatakan dengan alamat dari lokasi memori sebesar 4 byte untuk integer. Pointer adalah variabel yang menunjuk ke variabel lain. Hal ini berarti variabel pointer memegang alamat memori dari variabel lain. Dengan kata lain, variabel pointer tidak memegang nilai, tetapi memegang alamat dari variabel lain. Dapat diumpamakan anda memegang uang kontan Rp. 100.000,- adalah variabel biasa. Apabila anda membutuhkan nilai (uangnya) langsung ada. Sebaliknya pointer dapat diumpamakan dengan anda memiliki uang Rp. 100.000,- tetapi disimpan oleh Bank dalam bentuk tabungan. Anda mempunyai hak atas uang tersebut, tetapi yang dipegang adalah nomor rekeningnya. Kalau ingin mengambil uang anda harus pergi ke bank. Dalam hal ini anda adalah variabel pointer, dan Bank adalah variabel yang alamatnya (nomor rekeningnya) anda pegang. Karena pointer tidak memegang nilai tetapi memegang alamat, maka dalam pointer terdapat dua bagian, yaitu pointer itu sendiri yang memegang alamat dan alamat tersebut menunjuk ke suatu nilai. Jadi perlu dicatat mulai sekarang, supaya tidak bingung, dalam pointer ada alamat dan nilai yang ditunjuk. Berikut ini adalah contoh penggunaan pointer yang sederhana. 14 Nama Program : pointer1.c #include <stdio.h> void main() { int i,j; int *p; /* pointer ke integer */ p = &i; *p=5; j=i; printf("%d %d %d\n",i,j,*p); } Baris int *p adalah mendeklarasikan sebuat variabel pointer yang bernama p. Tanda (*) digunakan untuk menyatakan variabel pointer. Variabel pointer dapat dibuat menunjuk ke tipe apa saja, baik tipe data dasar maupun tipe data buatan seperti yang dibahas pada Bab 2. Baris p = &i; adalah pemberian nilai (assignment) alamat variabel i kepada variabel p. Dalam bahasa C , & disebut operator alamat. Pernyataan &i berarti adalah “alamat memori dari variabel i”. Dengan demikian baris p = &i berarti p menunjuk ke variabel i. Sebelum baris tersebut dieksekusi maka p bernilai acak yang tidak diketahui alamatnya. Oleh karena itu kalau pointer (p) tidak diberi nilai akan menyebabkan kesalahan segmentasi (segmentation fault). Pernyataan p = &i menyebabkan satu lokasi memiliki dua nama yaitu i itu sendiri dan *p. Dengan demikian jelas bahwa pointer p terdiri dari dua bagian yaitu p adalah lokasi yang menyimpan alamat dan *p adalah lokasi yang menyimpan nilai yang ditunjuk oleh p. Pernyataan *p = 5 berarti bahwa lokasi yang ditunjuk oleh p diberi nilai 5. Karena lokasi tersebut juga bernama i, maka pernyataan tersebut juga menyebabkan nilai menjadi 5. Dan selanjutnya j=i berarti j diberi nilai 5 juga. dan akhirnya perintah printf menghasilkan 5 5 5. Nama Program : pointer2.c #include <stdio.h> void main() { int i,j; int *p; /* pointer ke integer */ printf("%d %d\n",p,&i); p = &i; printf("%d %d\n",p,&i); } Program tersebut memberitahukan kepada compiler untuk mencetak alamat yang dipegang oleh p dan alamat dari i. Variabel p akan memberikan sembarang nilai atau 0 dan &i (alamat i) bernilai besar sekali. Dengan demikian outputnya tidak tentu, tetapi sebagai salah satu contohnya adalah sebagai berikut : 0 2147478276 2147478276 2147478276 15 Sekarang perhatikan program berikut ini. Apa yang akan dihasilkannya ? Nama Program : pointer3.c #include <stdio.h> void main() { int *p; /* a pointer to an integer */ printf("%d\n",*p); } Program tersebut memberitahukan kompiler untuk mencetak nilai yang ditunjuk oleh p. Tetapi karena p belum diberi nilai awal, maka nilainya adalah alamat 0 dan menghasilkan segmentation fault, karena menunjuk ke alamat yang tidak ada. 3.2. Penggunaan Pointer Sebagai Parameter Kebanyakan programmer C menggunakan pointer untuk parameter fungsi. Misalnya diketahui prosedur dalam Pascal untuk menukarkan dua nilai integer berikut ini. program samp; var a,b:integer; procedure swap(var i,j:integer); var t:integer; begin t:=i; i:=j; j:=t; end; begin a:=5; b:=10; writeln(a,b); swap(a,b); writeln(a,b); end. Program Pascal menggunakan parameter variabel, sehingga pertukaran nilai a dan b dapat dilakukan dengan tepat di dalam prosedur swap. Dalam bahasa C tidak ada mekanisme yang formal untuk melewatkan nilai ke dalam fungsi. Semua dilewatkan ke dalam fungsi melalui nilainya (passing by value). Hal ini dapat diuji coba melalui program di bawah ini. Nama program : param.c #include <stdio.h> 16 void swap(int i, int j) { int t; t=i; i=j; j=t; } void main() { int a,b; a=5; b=10; printf("%d %d\n",a,b); swap(a,b); printf("%d %d\n",a,b); } Kalau program tersebut dijalankan maka tidak ada pertukaran nilai antara a dan b. Nilai a dan b dibawa masuk ke dalam fungsi swap, tetapi tidak ada nilai yang dikembalikan (return), maka di luar fungsi tidak terjadi perubahan apapun. Supaya fungsi tersebut berjalan dengan benar maka harus digunakan pointer. Bandingkan program di atas dengan program di bawah ini. Nama program : paramptr.c #include <stdio.h> void swap(int *i, int *j) { int t; t = *i; *i = *j; *j = t; } void main() { int a,b; a=5; b=10; printf("%d %d\n",a,b); swap(&a,&b); printf("%d %d\n",a,b); } Kalau program tersebut dijalankan maka, perintah printf yang pertama akan menghasilkan 5 10, karena a diberi nilai 5 dan b diberi nilai 10. Selanjutnya perintah printf yang ke dua dieksekusi setelah fungsi swap. Fungsi swap menggunakan parameter pointer *i dan *j yang merupakan nama lain dari a dan b. Di dalam fungsi swap nilai dari *i ditukar dengan nilai dari *j, maka diluar dari fungsi swap menyebabkan nilai a dan b tertukar, sehingga printf yang ke dua menghasilkan output 10 5. 17 Dari contoh ini kita menjadi tahu mengapa perintah scanf menjadi error bila pada variabel parameternya tidak diawali dengan &. Dalam hal ini scanf menggunakan pointer untuk variabel yang menyimpan hasil input dari keyboard. Tanpa menggunakan tanda &, maka scanf akan mengembalikan alamat asal-asalan sehingga program crash. 3.3. Penggunaan Pointer untuk Struktur Data Dinamik Struktur data dinamik adalah struktur data yang dapat bertambah atau berkurang sesuai dengan kebutuhan pada saat program berjalan. Struktur data dinamik sangat penting dalam pemrograman C. Struktur data dinamik dapat mengalokasikan blok-blok memori sesuai dengan yang diinginkan dalam program. Masing-masing blok dirangkai (link) dengan menggunakan struktur pointer. Bila suatu blok memori tidak digunakan lagi dalam program maka blok memori tersebut dilepas supaya dapat digunakan untuk keperluan lain. Dua contoh program berikut ini menunjukkan perbedaan pengelolaan memori (heap) dari Pascal dan C. Contoh pertama adalah mengalokasikan satu blok memori untuk integer, mengisi nilai, menulis output di layar dan menghilangkan blok memori tersebut yang dibuat dalam Pascal dan C. Program Pascalnya adalah sebagai berikut : program samp; var p:^integer; begin new(p); p^:=10; writeln(p^); dispose(p); end. Kode yang sama dalam bahasa C adalah sebagai berikut : Nama program : dinamik.c #include <stdio.h> #include <stdlib.h> void main() { int *p; p=(int *) malloc (sizeof(int)); *p=10; printf("%d\n",*p); free(p); } Kedua program tersebut di atas mendemonstrasikan alokasi, dealokasi dan menggunakan blok memori. Perintah malloc dalam C sama dengan perintah new dalam Pascal, yaitu mengalokasikan blok memori sebesar yang diinginkan yaitu sebesar sizeof(int) byte. Karena int bernilai 4 byte maka perintah tersebut mengalokasikan 4 byte. 18 Fungsi malloc mengembalikan pointer ke blok yang dialokasikan. Karena yang digunakan adalah pointer generik, maka menggunakan pointer tanpa type casting akan memunculkan peringatan kesalahan tipe (type warning) dari compiler. Type cast (int *) mengubah pointer generik yang dihasilkan oleh malloc menjadi pointer ke integer, seperti yang diharapkan dalam p. Perintah dispose dalam Pascal sama dengan perintah free dalam C, yaitu melepaskan blok memori yang tidak digunakan. Contoh ke dua menggambarkan fungsi yang sama dengan contoh pertama, tetapi dalam contoh ke dua menggunakan record. Dalam Pascal direalisasikan sebagai berikut : program samp; type rec=record i:integer; f:real; c:char; end; var p:^rec; begin new(p); p^.i:=10; p^.f:=3.14; p^.c='a'; writeln(p^.i,p^.f,p^.c); dispose(p); end. Dalam bahasa C programnya adalah sebagai berikut : Nama program : dinamik1.c #include <stdio.h> #include <stdlib.h> struct rec { int i; float f; char c; }; void main() { struct rec *p; p=(struct rec *) malloc (sizeof(struct rec)); (*p).i=10; (*p).f=3.14; (*p).c='a'; printf("%d %f %c\n",(*p).i,(*p).f,(*p).c); free(p); } Perhatikan baris (*p).i=10; mengapa tidak menggunakan *p.i=10; saja ? 19 Seperti dalam operator (tanda operasi) pada umumnya, masing-masing memiliki derajat (precedence). Dalam bahasa C, tanda “.” memiliki derajat lebih tinggi dari pada tanda “*”. Oleh karena itu maka *p harus berada di dalam tanda kurung. Banyak orang yang malas menulis (*p).i, maka dalam C notasi tersebut dapat diganti dengan p-> = 10. (*p).i=10; p->i=10; Keduanya adalah sama, tetapi baris ke dua lebih mudah mengetiknya. Penggunaan struktur data dinamis yang lebih kompleks akan dibahas di bab 5 mengenai stack (tumpukan). 3.4. Penggunaan Pointer Bersama dengan Array Array dan pointer memiliki hubungan yang sangat erat dalam bahasa C. Supaya dapat mengunakan array secara efektif maka anda perlu mengerti penggunaan pointer bersama array. Penjelasan dimulai dengan mengenal array dalam Pascal atau bahasa lainnya. Dalam bahasa C konsep array sangat berbeda, oleh karena itu contoh di bawah ini sangat membantu pemahaman. Berikut ini adalah contoh penggunaan array dalam Pascal. program samp; const max=9; var a,b:array[0..max] of integer; i:integer; begin for i:=0 to max do a[i]:=i; b:=a; end. Elemen-elemen dalam array a diberi nilai dari 0 sampai dengan max=9, kemudian semua isi elemen a dikopi ke b, sehingga a dan b sama. Sekarang bandingkan dengan konsep array dalam C melalui contoh berikut ini. #define MAX 10 void main() { int a[MAX]; int b[MAX]; int i; for(i=0; i<MAX; i++) a[i]=i; b=a; } 20 Jika program C di atas dikompilasi maka akan terjadi kesalahan, karena dalam C tidak dikenal array assignment b = a; seperti dalam program di atas. Jika ingin mengkopi isi array a ke b maka harus dilakukan dengan cara berikut ini. for (i=0; i<MAX; i++) a[i]=b[i]; atau cara yang lebih ringkas adalah seperti berikut ini for (i=0; i<MAX; a[i]=b[i], i++); Array dalam bahasa C bukanlah array seperti dalam Pascal atau bahasa lainnya, tetapi pointer permanen ke array. Dengan kata lain array dalam C adalah pointer ke blok memori yang menyimpan elemen-elemen array. Variabel array menyimpan alamat dari array yang sesungguhnya, tetapi karena pointernya permanen maka kita tidak bisa mengubah alamatnya. Dengan demikian pernyataan a = b; tidak benar. Beberapa keuntungan lain dalam penggunaan array dapat dilihat pada contoh berikut ini : Nama program : pointarr.c #include <stdio.h> #define MAX 10 void main() { int a[MAX]; int b[MAX]; int i; int *p,*q; for(i=0; i<MAX; i++); a[i]=i; p=a; printf("%d\n",*p); } Perintah p = a; dalam program di atas dapat berjalan karena a adalah pointer. Secara teknis a menunjuk ke elemen ke 0 dari array yang sesungguhnya. Elemen ke 0adalah integer, maka a adalah pointer ke integer tunggal. Oleh karena itu dengan mendeklarasikan p sebagai pointer ke integer dan mengisinya dengan alamat a ( p = a) program berjalan dengan baik. Cara yang sama untuk menyatakan p = a; adalah dengan p = &a[0]; karena a berisi alamat a[0] yang dapat dinyatakan dengan &a[0]. Sekarang p menunjuk ke elemen ke 0 dari array a, demikian juga dengan a. Perbedaannya adalah a adalah pointer permanen, sedangkan p bukan. Dengan demikian kita dapat melakukan manipulasi terhadap pointer p. Contohnya: dapat dibuat perintah p++; Compiler C tahu bahwa p menunjuk ke integer, maka operasi menambah p dengan 1 (increment) artinya adalah alamat yang dipegang oleh p ditambah dengan sejumlah byte yang sesuai untuk menunjuk ke elemen berikutnya. Dalam hal ini karena p menunjuk integer (4 byte) maka dengan p++; maka p akan menunjuk ke alamat 4 byte berikutnya atau ke elemen berikutnya. (Ingat ! yang diincrement bukan nilainya, tetapi alamat yang dipegang oleh pointer p). 21 Kita juga dapat mengkopi array a ke array b dengan menggunakan pointer. Program berikut ini dapat menggantikan perintah (for i=0; i<MAX; a[i]=b[i], i++); : p=a; q=b; for (i=0; i<MAX; i++) { *q = *p; q++; p++; } atau dengan meringkas kode menjadi seperti berikut ini. p=a; q=b; for (i=0; i<MAX; i++) *q++ = *p++; atau dapat diringkas lagi menjadi for (p=a,q=b,i=0; i<MAX; *q++ = *p++, i++); Apa yang terjadi kalau kita melewati batas atas/bawah dari array a atau b dengan menggunakan pointer p atau q ? Bahasa C tidak memperdulikannya, kalau p dan q ditambah terus maka apa saja yang ada di alamat tersebut akan dikopi. Kita dapat melewatkan array ke dalam fungsi dengan dua cara. Contoh berikut ini adalah fungsi dump yang menerima array integer sebagai parameter dan menampilkan isinya dengan perintah printf. Cara pertama adalah sebagai berikut : void dump(int a[],int nia) { int i; for (i=0; i<nia; i++) printf("%d\n",a[i]); } atau dengan cara ke dua : void dump(int *p,int nia) { int i; for (i=0; i<nia; i++) printf("%d\n",*p++); } Variabel nia digunakan untuk menyatakan jumlah elemen array. Jadi yang dilewatkan sebagai parameter adalah pointer ke array, bukan isinya. Demikian juga fungsi C dapat menerima ukuran elemen array lewat parameter. Hal ini tidak mungkin dilakukan dalam Pascal. 22 IV. LINKED LIST Linked list hampir sama dengan array, yaitu sama-sama menyimpan sekumpulan data. Perbedaannya adalah dalam mengalokasikan memori. Array mengalokasikan memori untuk semua elemen dalam satu blok memori, sedangkan linked list mengalokasikan memori elemen per elemen pada saat program berjalan, sehingga tempatnya di memori juga acak. Dalam linked list, tiap elemen disebut “node”. Walaupun tempatnya terpencar tetapi tiap node dirangkai dengan menggunakan pointer. Dalam tiap node terdiri dari dua bagian yaitu : field-field “data” untuk menyimpan informasi apa saja dengan tipe data yang diketahui, dan bagian ke dua adalah satu field “next” yang berisi pointer ke node berikutnya. Setiap node dialokasikan dalam memori (heap) dengan memanggil fungsi malloc(), dan didealokasi (dibuang dari memori) dengan memanggil fungsi free(). Kedua fungsi tersebut terdapat pada header <stdlib.h>. Kepala dari list tersebut adalah pointer ke node pertama. Untuk lebih jelasnya lihat Gambar 4.1 yang menggambarkan linked list dengan 3 node masing-masing berisi integer 1,2, dan 3. Heap Stack BuildStack() head 3 2 1 Gambar 4.1. Gambaran Linked List dari Sisi Pengguna (Stack) dan di Sisi Memori (Heap) Gambar 4.1. bagian kiri menggambarkan variabel dari sisi aplikasi. Variabel head adalah variabel adalah variabel lokal di dalam fungsi BuildStack() yang bertipe linked list yang menunjuk ke node pertama. Gambar 4.1. bagian kanan menggambarkan susunannya di dalam memori (heap). Node pertama terdiri dari field data yang berisi nilai 1 dan field next yang berisi pointer ke node berikutnya. Node terakhir berisi field data yang berisi nilai 3 dan field next diisi NULL, yang perarti tidak menunjuk kemanapun. Programnya dapat dilihat di bawah ini. Nama program : llist1.c #include <stdio.h> #include <stdlib.h> struct node { int data; struct node* next; }; struct node* BuildStack() { struct node* head = NULL; struct node* second = NULL; struct node* third = NULL; 23 head= (struct node*) malloc(sizeof(struct node)); second= (struct node*) malloc(sizeof(struct node)); third= (struct node*) malloc(sizeof(struct node)); head->data = 1; head->next=second; second->data = 2; second->next=third; third->data = 3; third->next = NULL; return head; } void main() { struct node* gelang; gelang=BuildStack(); printf("\nData node 1 = %d",gelang->data); printf("\nData node 2 = %d",gelang->next->data); printf("\nData node 3 = %d",gelang->next->next->data); free(gelang); } Pada program di atas struktur datanya bernama node dengan 2 field yaitu data bertipe integer. Informasi-informasi lain dapat dimasukkan di sini dengan field data dengan tipe-tipe yang diketahui. Field berikutnya adalah next bertipe node itu sendiri. Jadi dalam hal ini next menunjuk node berikutnya yang bertipe node juga. BuildStack() adalah fungsi untuk membangun stack seperti yang digambarkan pada Gambar 4.1., yang terdiri dari node 1 (head), node 2 (second), dan node 3 (third). Masing-masing node dialokasikan memori sebesar byte yang dibutuhkan oleh struct node, dengan menggunakan fungsi malloc(). Di depan fungsi malloc dibuat type casting (struct node*) supaya nilai kembaliannya bertipe sama dengan penerimanya. Kalau tidakdi casting maka nilai kembalian dari malloc adalah void * (pointer ke void). Selanjutnya setiap node diisi nilai. Untuk node pertama field data diisi 1 dan field next diisi node berikutnya (second), node ke dua field datanya diisi 2 dan field next nya diisi third, dan node ke tiga field data diisi 3 dan field next nya diisi NULL, karena node terakhir tidak menunjuk kemanapun. Nilai kembalian dari fungsi BuildStack() adalah head yang bertipe (struct node*). Di dalam program utama berisi pemanggilan fungsi BuildStack(), yang nilai kembaliannya diterima oleh variabel gelang. Selanjutnya untuk mencetak nilai data tiap node, secara sederhana dilakukan dengan menuliskan gelang->next->... sebagai parameter fungsi printf(). Program tersebut di atas dapat dikembangkan, misalnya dengan membuat fungsi untuk menghitung jumlah node yang ada. Fungsinya adalah sebagai berikut. int length(struct node* head) { struct node* current = head; 24 int count = 0; while (current != NULL) { count++; current =current->next; } return count; } Dalam main program dapat ditambahkan perintah untuk mencetak hasil pemanggilan fungsi length() tersebut di atas sebagai berikut. printf("\nJumlah nodenya = %d",length(gelang)); length(gelang) adalah memanggil fungsi length dengan melewatkan variabel gelang sebagai parameter. Variabel gelang, tidak lain adalah variabel yang telah berisi linked list 3 node tadi. Nilai kembalian dari pemanggilan fungsi length() diterima sebagai parameter fungsi printf(), maka hasilnya akan tercetak Jumlah nodenya = 3 Dari fungsi length() kita dapat belajar dua hal yaitu : 1. Melewatkan list dengan menggunakan pointer head. Linked list dilewatkan ke dalam fungsi length() dengan menggunakan satu pointer head. Pointernya dikopi dari pemanggil (gelang) ke head. Pengkopian ini tidak menyebabkan seluruh node terkopi tetapi pointer gelang dan pointer head menunjuk ke alamat yang sama. 2. Pengulangan (iterasi) list dengan pointer lokal. Dengan perintah current = current->next yang diulang-ulang dalam while-loop maka variabel current akan menunjuk ke setiap node dari awal sampai akhir. Kembali ke fungsi BuildStack() di atas. Cara pengisian node yang ada di dalam fungsi tersebut tidak baik. Cara yang baik adalah dengan menggunakan fungsi, misalnya fungsi push() yang di dalamnya berisi perintah-perintah menciptakan node baru, memasukkan data yang ada di parameter, dan menyambung node ke dalam node yang sudah ada. Program di atas dimodifikasi dengan menambahkan fungsi push(). Nama program : llist2.c #include <stdio.h> #include <stdlib.h> struct node { int data; struct node* next; }; void push(struct node** headRef, int data) { struct node*NewNode=(struct node*)malloc(sizeof( struct node)); NewNode->data = data; 25 NewNode->next = *headRef;//'*' adalah ref ulang ke head *headRef = NewNode; } struct node* BuildStack() { struct node* head = NULL; struct node* second = NULL; struct node* third = NULL; int i; head= (struct node*) malloc(sizeof(struct node)); head->data = 0; head->next=second; for (i=1; i<10; i++) push(&head, i); return head; } int length(struct node* head) { struct node* current = head; int count = 0; while (current != NULL) { count++; current =current->next; } return count; } void main() { struct node* gelang; int i=1; gelang=BuildStack(); printf("\nJumlah nodenya = %d",length(gelang)); while(gelang != NULL) { printf("\nData node %d = %d",i,gelang->data); gelang=gelang->next; i++; } free(gelang); } Output program ini adalah : Jumlah nodenya = 10 Data node 1 = 9 Data node 2 = 8 Data node 3 = 7 Data node 4 = 6 Data node 5 = 5 26 Data Data Data Data Data node node node node node 6 = 4 7 = 3 8 = 2 9 = 1 10 = 0 Soal latihan : Program BuildStack() di atas adalah program untuk membangun linked list dengan menambahkan node di depan. Sekarang buatlah program untuk membangun list {1,2,3,4,5} dengan menambah node di belakang. Skema linked list nya adalah sebagai berikut . Heap Stack head 1 2 tail NewNode 3 27 V. TUMPUKAN (STACK) 5.1. Dasar-Dasar Stack Stack adalah struktur data yang mengikuti aturan Last In First Out (LIFO), yaitu data yang masuknya terakhir harus keluar terlebih dahulu. Mirip seperti bola tenis yang berada di dalam tempatnya. Apabila ingin mengambil bola yang paling dalam harus mengeluarkan beberapa bola di atasnya. Prinsip ini digunakan di hampir semua sistem komputer modern. Contohnya adalah dalam pemanggilan beberapa fungsi dalam program. Program memanggil fungsi satu, di dalam fungsi satu memanggil fungsi dua, di dalam fungsi dua memanggil fungsi tiga. Di dalam memori, fungsi satu akan disimpan dulu dalam stack, fungsi dua juga disimpan di dalam stack. Setelah fungsi tiga selesai dikerjakan, maka proses akan kembali ke fungsi dua dulu, setelah fungsi dua selesai baru kembali ke fungsi satu, dan akhirnya kembali ke program utama. Mekanisme tersebut digambarkan pada Gambar 5.1. main() { ….. call func1() ….. } func1() { …… call func2() ….. } stack top func2() func2() { …… call func3() ….. } func3 () { …… …… ….. } push pop func1() bottom main() Gambar 5.1. Mekanisme Pemanggilan Fungsi dan Stack Pada saat fungsi func1() dijalankan maka main() disimpan di stack yang paling bawah. Demikian juga pada saat fungsi func1() memanggil func2(), maka func1() disimpan di stack, dan pada saat func2() memanggil func3(), func2() disimpan di stack. Tahap penyimpanan di dalam stack disebut PUSH. Setelah func3() selesai mengerjakan proses, maka kembalinya sesuai dengan posisinya dalam stack, yaitu func2() diselesaikan, kembali ke func1(). Fungsi func1() selesai, kembali ke main(). Proses pengambilan isi stack disebut POP. 28 Struktur data stack dapat diimplementasikan menggunakan array maupun linked list. Dalam array, apabila proses PUSH menggunakan indek dari kecil ke besar, maka proses POP nya dari indek besar ke kecil, atau sebaliknya. Dalam linked list, bila PUSH nya dengan menambah node di depan (dekat dengan head), maka proses POP nya membuang node dari depan ke belakang. Berikut ini adalah contoh program membuat stack dengan menggunakan linked list yang memasukkan karakter ‘I’, ‘D’, ‘E’ dan tercetak ‘E’, ‘D’, ‘I’, sesuai dengan prinsip LIFO. Nama program : stack.c /*Tumpukan (Stack).Uji tumpukan kosong, Cipta tumpukan, push, pop dan cetak tumpukan */ #include <stdio.h> #include <malloc.h> #include <stdlib.h> typedef enum { FALSE = 0, TRUE = 1} BOOL; // Deklarasi Node struct nod { char data; struct nod *next; }; typedef struct nod NOD; // Uji Tumpukan Kosong BOOL TumpukanKosong(NOD *T) { return ((BOOL)(T == NULL)); } //Cipta Node Baru NOD *NodBaru (char item) { NOD *n; n = (NOD*) malloc(sizeof(NOD)); if(n != NULL) { n->data = item; n->next = NULL; } return n; } void CiptaTumpukan (NOD **T) { *T = NULL; // Buat Tumpukan Kosong } 29 // Push void Push(NOD **T, char item) { NOD *n; n = NodBaru(item); n->next = *T; *T = n; } // Pop char Pop(NOD **T) { NOD *P; char item; if ( ! TumpukanKosong(*T)) { P = *T; *T = (*T)->next; item = P->data; free(P); } return item; } void CetakTumpukan (NOD *T) { NOD *p; printf("T --> "); for (p = T; p != NULL; p = p->next) { printf("[%c] --> ", p->data); } printf("NULL\n"); } int main() { NOD *T; CiptaTumpukan(&T); Push(&T, 'I'); Push(&T, 'D'); Push(&T, 'E'); CetakTumpukan(T); return 0; } Output program tersebut di atas adalah : T --> [E] --> [D] --> [I] --> NULL 30 5.2. Aplikasi Stack Evaluasi dan Konversi Infix – Prefix – Postfix Dalam aritmatika sehari-hari operasi tambah (+), kurang (-), kali (x) dan bagi (:) ditulis dengan operator terletak di tengah diantara operan-operan. Contohnya : A + B, A – B, A x B, atau A : B Dalam bahasa pemrograman operator tersebut dinotasikan secara berturut-turut : +, - , *, dan /. Masing-masing operator memiliki tingkat kepentingan (precedence) sendiri. Operator * dan / memiliki tingkat yang sama, tetapi lebih tinggi dari pada + dan /. Hal ini berarti, dalam operasi aritmatika yang melibatkan banyak operator, maka operatoroperator * atau / akan didahulukan evaluasinya dari pada + atau -. Contohnya : 3+4*2 hasilnya bukan 24, tetapi 11, karena operator * dievaluasi pertama, baru kemudian operator + dievaluasi. Operasi aritmatika yang operatornya terletak di tengah disebut infix. Bentuk operasi lain yang banyak digunakan dalam sistem komputer adalah prefix, yaitu operatornya terletak di depan, dan postfix yang operatornya terletak di belakang. Beberapa contoh konversi infix, prefix dan postfix dapat dilihat pada tabel di bawah ini. Tabel 5.1. Konversi Infix, Prefix, Postfix infix A+B A+B-C (A + B) * (C – D) B * C – D + E/F/(G+H) A-B/(C*D) prefix +AB - + ABC *+AB-CD -*BC+D//EF+GH -A/B*CD postfix AB+ AB+C AB+CD-* BC*D-EFGH+//+ ABCD*/- Agar tahap-tahap konversinya tidak salah, maka dapat digunakan bantuan tanda ( ). Contohnya : ingin mengubah operasi (A+B) * (C-D) menjadi operasi prefix. Operasi yang akan dievaluasi pertama kali adalah (A+B), dan bentuk prefixnya adalah (+AB). Langkah berikutnya adalah mengevaluasi (C-D), dan bentuk prefixnya adalah (-CD). Terakhir adalah mengevaluasi (+AB) * (-CD), dan bentuk prefixnya adalah *(+AB)(-CD). Kalau tanda kurung dibuang akan menjadi *+AB-CD. Demikian juga cara mengubah nya menjadi operasi postfix. Pertama kali yang akan dievaluasi adalah (A+B), dan bentuk postfixnya adalah (AB+). Kemudian operasi (C-D), bentuk postfixnya adalah (CD-). Langkah selanjutnya adalah mengevaluasi (AB+) * (CD-), yang bentuk postfixnya adalah (AB+)(CD-)*. Kalau dibuang tanda kurungnya akan menjadi AB+CD-*. Proses mengevaluasi (menghitung nilai) dan mengkonversi prefix dan postfix menggunakan stack. Pada bagian ini akan dibahas tentang mengevaluasi dan mengkonversi operasi postfix, sedangkan untuk operasi prefix dapat digunakan sebagai latihan. Proses evaluasi dilakukan dengan cara membaca satu-persatu karakter input. jika karakter tersebut bilangan maka dianggap sebagai operand dan dimasukkan ke dalam stack (push), tetapi kalau karakter input yang dibaca adalah tanda atau operator, maka ambil dua operand teratas (pop dua kali) dan lakukan evaluasi. 31 Hasilnya dikembalikan ke dalam stack (push) untuk dievaluasi dengan operator berikutnya. Algoritmanya dapat dilihat di bawah ini. opndstk = stack kosong; /* baca input string karakter demi karakter dan disimpan di variabel symb */ while (not end of input) { symb = karakter input berikutnya; if (symb adalah operand) push(opndstk,symb); else { /* symb adalah operator */ opnd2 = pop(opndstk); opnd1 = pop(opndstk); value = hasil operasi opnd1, opnd2 dengan symb; push(opndstk, value); } /* end else */ } /* end while */ return(pop(opndstk)); Perhatikan contoh evaluasi postfix dari string berikut ini 623+-382/+* Langkah demi langkah prosesnya dapat dilihat pada tabel di bawah ini Tabel 5.2. Proses Evaluasi Postfix symb 6 2 3 + 3 8 2 / + * opnd1 opnd2 value 2 6 3 5 5 1 8 3 1 2 4 7 4 7 7 opndstk 6 6,2 6,2,3 6,5 1 1,3 1,3,8 1,3,8,2 1,3,4 1,7 7 Setiap operand (bilangan) yang terbaca dimasukkan ke stack (opndstk). Dalam hal ini stack nya tumbuh ke kanan, maka top stack adalah karakter yang paling kanan pada kolom opndstk. Isi stack yang terakhir adalah 7. Inilah hasil evaluasi akhir. Program untuk mengevaluasi operasi postfix adalah sebagai berikut. Nama program : postfix.c /*Evaluasi operasi postfix menggunakan Stack */ #include <stdio.h> #include <malloc.h> #include <stdlib.h> #include <ctype.h> 32 #define MAXCOLS 80 typedef enum { FALSE = 0, TRUE = 1} BOOL; // Deklarasi Node struct nod { float data; struct nod *next; }; typedef struct nod NOD; // Uji Tumpukan Kosong BOOL TumpukanKosong(NOD *T) { return ((BOOL)(T == NULL)); } //Cipta Node Baru NOD *NodBaru (float item) { NOD *n; n = (NOD*) malloc(sizeof(NOD)); if(n != NULL) { n->data = item; n->next = NULL; } return n; } void CiptaTumpukan (NOD **T) { *T = NULL; // Buat Tumpukan Kosong } // Push void Push(NOD **T, float item) { NOD *n; n = NodBaru(item); n->next = *T; *T = n; } // Pop float Pop(NOD **T) { NOD *P; float item; if ( ! TumpukanKosong(*T)) { P = *T; *T = (*T)->next; 33 item = P->data; free(P); } return item; } void CetakTumpukan (NOD *T) { NOD *p; printf("T --> "); for (p = T; p != NULL; p = p->next) { printf("[%c] --> ", p->data); } printf("NULL\n"); } /* fungsi evaluasi operasi */ float oper(symb,op1,op2) int symb; float op1,op2; { switch (symb) { case '+' : return (float)(op1 case '-' : return (float)(op1 case '*' : return (float)(op1 case '/' : return (float)(op1 default : printf("\n Operasi } /* end switch */ } /* end oper */ + op2); - op2); * op2); / op2); salah"); exit(1); /* Fungsi evaluasi Postfix */ float eval(expr) char expr[]; { int c, position; float opnd1,opnd2, value; NOD *opndstk; for(position=0; (c = expr[position]) != '\0'; position++) if (isdigit(c)) /* operand -- koonversi ke float dan push */ Push(&opndstk, (float) (c-'0')); else { /* operator */ opnd2 = (float) Pop (&opndstk); opnd1 = (float) Pop (&opndstk); value = oper(c,opnd1,opnd2); Push (&opndstk, value); } /* end else */ return (float) (Pop(&opndstk)); } /* end eval */ int main() { 34 char expr[MAXCOLS]; /* string input */ int position = 0; /*baca string input */ while((expr[position++]=getchar())!='\n'); expr[--position] = '\0'; /* beri pembatas string */ printf("\nString yang akan dievaluasi adalah: %s", expr); printf("\nHasilnya : %f\n",eval(expr)); return 0; } Dengan memasukkan string 623+-382/+* pada program di atas, maka output yang dihasilkan adalah String yang akan dievaluasi adalah: 623+-382/+* Hasilnya : 7.00000 Proses konversi operasi infix ke postfix menggunakan algoritma berikut ini. opstk = stack kosong; while (not end of input) { symb = input karakter berikutnya; if (symb adalah operand) tambahkan symb ke string postfix else { while (!empty(opstk) && prcd(stacktop(opstk),symb)){ topsymb=pop(opstk); tambahkan topsymb ke string postfix; } /* end while */ } /* end else */ } /* end while */ /* tampilkan oprator sisanya */ while (!empty(opstk)) { topsymb = pop(opstk); tambahkan topsymb ke string postfix; } /* end while */ Contoh 1 Diketahui string operasi infix A+B*C, maka hasil konversi langkah-demi langkahnya pada variabel symb, opstk dan string postfixnya adalah sebagai berikut. Tabel 5.3. Langkah-langkah Konversi Infix – Postfix No. 1 2 3 4 5 6 7 symb A + B * C String postfix A A AB AB ABC ABC* ABC*+ opstk + + +* +* + Contoh 2 Sekarang jika yang diketahui adalah string infix (A+B)*C, maka hasil konversinya akan berbeda. Langkah demi langkah prosesnya dapat dilihat pada tabel di bawah ini. 35 Tabel 5.3. Langkah-langkah Konversi Infix – Postfix No. 1 2 3 4 5 6 7 8 symb ( A + B ) * C String postfix A A AB AB+ AB+ AB+C AB+C* opstk ( ( (+ (+ * * Untuk menyelesaikan masalah tersebut di atas dapat dibuat program seperti di bawah ini. Nama program : in2pos.c /*Konversi Infix ke postfix menggunakan Stack */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #define MAXCOLS 80 typedef enum { FALSE = 0, TRUE = 1} BOOL; // Deklarasi Node struct nod { char data; struct nod *next; }; typedef struct nod NOD; // Uji Tumpukan Kosong BOOL TumpukanKosong(NOD *T) { return ((BOOL)(T == NULL)); } //Cipta Node Baru NOD *NodBaru (char item) { NOD *n; n = (NOD*) malloc(sizeof(NOD)); if(n != NULL) { n->data = item; n->next = NULL; } return n; 36 } void CiptaTumpukan (NOD **T) { *T = NULL; // Buat Tumpukan Kosong } // Push void Push(NOD **T, char item) { NOD *n; n = NodBaru(item); n->next = *T; *T = n; } // Pop char Pop(NOD **T) { NOD *P; char item; if ( ! TumpukanKosong(*T)) { P = *T; *T = (*T)->next; item = P->data; free(P); } return item; } /* Fungsi menentukan Tingkat Kepentingan Operator Precedence */ BOOL prcd(char topsymb,char symb) { if ((topsymb=='+') || (topsymb=='-')) switch (symb) { case '+' : return TRUE; case '-' : return TRUE; case '*' : return FALSE; case '/' : return FALSE; case '(' : return FALSE; case ')' : return TRUE; } if ((topsymb=='*')||(topsymb=='/')) switch (symb) { case '+' : return TRUE; case '-' : return TRUE; case '*' : return TRUE; case '/' : return TRUE; case '(' : return FALSE; case ')' : return TRUE; } 37 if (topsymb=='(') return FALSE; return FALSE; } /* Fungsi Konversi Infix-Postfix */ void postfix(infix,postr) char infix[]; char postr[]; { int position; int outpos = 0; char topsymb = '+'; char symb; NOD *opstk=NULL; for(position=0;(symb=infix[position])!='\0';position++){ if((symb=='+')||(symb=='-')||(symb=='*')|| (symb=='/')||(symb=='(')||(symb==')')){ while((!TumpukanKosong(opstk))&& prcd(opstk->data,symb)) { topsymb=Pop(&opstk); postr[outpos++]=topsymb; } /* end while */ Push(&opstk,symb); } /* end if */ else postr[outpos++]=symb; }; /* end for */ while(!TumpukanKosong(opstk)){ topsymb=Pop(&opstk); if (!( topsymb=='(' || topsymb ==')')) postr[outpos++]= topsymb; } postr[outpos]='\0'; free(opstk); } /* end of postfix */ int main() { char infix[MAXCOLS]; char postr[MAXCOLS]; int pos = 0; /*baca string input */ while((infix[pos++]=getchar())!='\n'); infix[--pos] = '\0'; /* beri pembatas string */ printf("\nString infix yang asli: %s", infix); postfix(infix,postr); printf("\nHasilnya : %s\n",postr); return 0; } 38 Latihan : Dengan menggunakan contoh program yang telah dipelajari di atas, maka 1. Buat program untuk mengevaluasi nilai prefix 2. Buat program untuk mengkonversi infix ke prefix 3. Buat program untuk mengkonversi postfix ke infix 39 VI. REKURSI Rekursif merupakan konsep dasar dalam matematika maupun ilmu komputer. Banyak masalah-masalah komputasi praktis yang lebih mudah diselesaikan dengan menggunakan pola rekursif. Definisi secara umum, fungsi (program atau algoritma) rekursif adalah fungsi yang memanggil dirinya sendiri di dalam tubuh fungsi. Secara matematis suatu fungsi rekursif dinyatakan sebagai : jika n = 0 atau n = 1 ⎧ 0 f ( n) = ⎨ ⎩ f (n − 1) + 1 untuk n lainnya (6.1) dalam hal ini fungsi dipanggil secara berulang-ulang dengan menggunakan argumen yang nilainya makin kecil. Pada fungsi 6.1 berkurangnya nilai argumen dinyatakan dengan n-1. Sebelum fungsi selesai dikerjakan, dia sudah memanggil dirinya dengan argumen yang semakin kecil. Dengan pemanggilan berulang-ulang tersebut, maka nilai n akan mencapai 0 atau 1. Kalau n sudah bernilai 0 atau 1, maka fungsi tersebut bernilai 0. Kalau fungsi yang dipanggil terakhir bernilai 0, maka fungsi pemanggil sebelumnya dapat dievaluasi yaitu 0+1 atau 1, fungsi pemanggil sebelumnya lagi dapat diselesaikan dengan 1+1 atau 2, fungsi sebelumnya lagi dapat diselesaikan dengan 2+1 atau 3 dan seterusnya sampai dapat diselesaikan fungsi yang pertama memanggil. Dari uraian di atas dapat diketahui bahwa ciri-ciri suatu fungsi rekursif adalah: 1. Terdapat nilai fungsi setelah argumennya mencapai batas bawah yang ditentukan. (disebut juga base case). Dalam hal ini fungsinya sudah cukup sederhana sehingga nilainya dapat langsung diketahui. 2. Dalam tubuh fungsi ada pernyataan yang memanggil dirinya sendiri dengan argumen fungsi yang semakin kecil. (disebut juga recursive case). Dalam hal ini terdapat tiga langkah yaitu: masalahnya dibagi menjadi masalah-masalah yang lebih kecil, kemudian panggil fungsi secara rekursif untuk tiap masalah, dan terakhir menggabung hasilnya menjadi hasil akhir Contoh penggunaan fungsi rekursif adalah untuk menghitung faktorial, yang memakai simbol (!). Misalnya : 7 ! = 7 * 6 * 5 * 4 * 3 * 2 * 1. Secara rekursif 7! dapat dinyatakan dengan 7 * 6!, sedangkan 6! dapat dinyatakan sebagai 6 * 5!, dan seterusnya. Untuk 0! didefinisikan bernilai 1, demikian juga 1! didefinisikan bernilai 1. Jadi fungsi faktorial secara matematis dapat dinyatakan sebagai : ⎧ 1 jika n = 0 atau n = 1 fakt (n) = ⎨ ⎩n * f (n − 1) untuk n lainnya (6.2) Dalam bahasa C fungsi faktorial didefinisikan sebagai berikut. long int fakt(long int n) { if(n<2) return (1); else return (n* fakt(n-1)); } 40 Dalam tubuh fungsi fakt, terdapat perintah memanggil fungsi fakt lagi dengan argumen (n-1). Batas bawah fungsinya adalah n < 2, yaitu untuk n=1 atau n=0. Misalnya, akan dihitung fakt(7), maka skema penyelesaian fungsi faktorial tersebut dapat digambarkan seperti gambar 5.1 berikut ini. fakt(7) 7*fakt(6) = 5040 6*fakt(5) = 720 5*fakt(4) = 120 = 24 4*fakt(3) 3*fakt(2) =6 2*fakt(1) =2 1 Gambar 5.1. Skema Penyelesaian Rekursif Contoh program di bawah ini terdapat tiga fungsi yaitu : faktorial, fibonacci, dan penggandaan (perkalian) rekursif. Fibonacci adalah bilangan yang merupakan hasil penjumlahan dari dua bilangan fibonaci sebelumnya, dimana fibonacci(0) = 0 dan fibonacci (1) = 1. Sedangkan penggandaan n dengan a, ditulis dengan ganda(n,a) adalah n + ganda(a-1), dimana ganda(n,1) = n. Pelajarilah program di bawah ini dengan baik. Nama program : rekursif.c (jalan di Visual C++ 6.0) /* Fungsi rekursif untuk menghitung faktorial, Fibonacci, dan Penggandaan*/ #include <stdio.h> /* Fungsi Faktorial */ long int fakt(long int n) { if(n<2) return (1); else return (n* fakt(n-1)); } 41 /* Bilangan Fibonacci = Jumlah dari dua bilangan fibonacci sebelumnya */ long int fibo(long int n) { if ((n==0) || (n==1)) return(n); else return(fibo(n-2) + fibo(n-1)); } /* Penggandaan (perkalian) bilangan a dan b */ long int ganda(long int a, long int b) { if (b==1) return(a); else return(ganda(a, b-1) + a); } int main() { long int a, b, c, d, iFaktorial, iFibonacci, iGanda; printf("Masukkan bilangan yang akan diFaktorialkan: "); scanf("%d", &a); iFaktorial = fakt(a); printf("Faktorial dari bilangan %d ialah %d\n\n", a, iFaktorial); printf("Masukkan bilangan yang akan diFibonaccikan: "); scanf("%d", &b); iFibonacci = fibo(b); printf("Fibonacci dari bilangan %d ialah %d\n\n", b, iFibonacci); printf("Masukkan bilangan: "); scanf("%d", &c); printf("Masukkan bilangan pengali: "); scanf("%d", &d); iGanda = ganda(c, d); printf("Hasil penggandaan bilangan %d dengan %d ialah %d\n\n", c,d, iGanda); return 0; } Latihan : 1. Buatlah fungsi untuk menghitung pangkat secara rekursif. Dengan pengertian: n pangkat a, yang dinyatakan dengan pangkat(n,a) = n * pangkat(a-1), dimana pangkat(n,0)=1. 42 2. Buatlah fungsi untuk menghitung logaritma n dengan bilangan dasar a. Dengan pengertian : log(n,a) = 1+log((n div a),a), dan log(1,a) = 0. Dengan syarat n = (a pangkat k), dimana k = 1,2,3, ... 3. Buatlah fungsi rekursif untuk membalik string. Contoh 43 VII. ANTRIAN (QUEUE) Antrian (Queue , baca : kiu) adalah struktur data dengan aturan "First In First Out" (FIFO). Hal ini dapat digambarkan dengan deretan orang yang sedang mengantri karcis. Orang yang pertama kali datang akan dilayani dulu. Orang yang baru datang harus mengantri di belakang. Struktur data antrian biasanya digunakan untuk pencarian model BFS (Breadth First Search). Operasi-operasi dalam Antrian Operasi-operasi dalam antrian adalah : 1. enqueue (enq) – Memasukkan item baru di belakang (rear) antrian Q 2. dequeue (deq)- Mengambil (dan menghapus) item paling depan (front) dari antrian Q 3. init (clear) – Mengintialisasi antrian Q, dan mereset semua variabel. 4. Empty – Mengetahui apakah antrian Q kosong atau tidak. Antrian kosong ditandai dengan item belakang = item depan atau front = rear. Operasi ini digunakan untuk syarat bagi operasi dequeue. 5. Full – Mengetahui apakah antrian Q penuh. Operasi ini hanya digunakan bila menggunakan array. Ilustrasi dari struktur data antrian dapat dilihat pada Gambar 7.1 berikut ini. Gambar 7.1. Ilustrasi Antrian (QUEUE) [2] Program untuk mengimplementasikan struktur data antrian adalah sebagai berikut : Nama program : antrian.c # include<stdio.h> # define MAX 100 typedef int itemtype; typedef struct queue{ itemtype item[MAX]; int f; int r; }Queue; void clear(Queue *q) { q->r=0; 44 q->f=0; } void enq(Queue *q,itemtype x) { q->r=(q->r+1)%MAX; q->item[q->r]=x; } void deq(Queue *q,itemtype *x) { q->f=(q->f+1)%MAX; *x=q->item[q->f]; } int empty(Queue *q) { return (q->r==q->f); } int full(Queue *q) { return ((q->r+1)%MAX==q->f); } main() { int i; Queue q1; clear(&q1); for(i=0;i<10;i++) if (!full(&q1)) enq(&q1,i); while(!empty(&q1)) { deq(&q1,&i); printf("%d\n",i); } } 45 VIII. POHON (TREES) Sturktur data pohon adalah salah satu struktur data buatan yang banyak digunakan dalam aplikasi. Salah satu kegunaannya adalah digunakan untuk mempercepat proses pencarian. Disebut struktur pohon karena konsepnya menyerupai pohon, ada yang disebut akar (root) yaitu bagian puncak dari struktur pohon, dan cabang (branch). Setiap titik disebut node, berisi informasi (data), dan cabangcabangnya. Suatu cabang juga dapat berupa struktur pohon, (memiliki pengertian rekursif). Node paling bawah, yang tidak memiliki cabang, disebut leaf. Suatu pohon yang setiap cabangnya terdiri dari maksimum dua cabang disebut struktur pohon biner (binary tree). Dalam struktur data, yang banyak digunakan adalah struktur pohon biner. Gambar 8.1. menunjukkan struktur pohon biner, dengan masing-masing node berisi informasi angka (bilangan bulat). Pohon tersebut terdiri dari 6 node, dengan root berisi nilai 5, cabang kiri 3 dan cabang kanan 9. Node 3 dan node 9, masingmasing dapat dianggap sebagai tree bagi cabang-cabang di bawahnya. Node 1,4 dan 6 adalah leaf bagi seluruh pohon. Gambar 8.1. Struktur Pohon Biner Node paling atas (root) disebut sebagai level 0, node-node cabangnya disebut level 1, node-node dibawahnya lagi disebut level 2, dan seterusnya. Kalau struktur pohonnya biner maka banyaknya level dalam pohon biner, disebut juga ketinggian pohon (height), adalah ⎣log2 ( n) ⎦, dimana n adalah banyaknya node, dan tanda ⎣⎦ adalah tanda pembulatan ke bawah. Seperti contoh pada Gambar 9.1 terdapat 6 node, maka banyaknya level adalah : ⎣log2 ( 6) ⎦ = ⎣2.5850⎦ = 2, yaitu : level 0 terdiri dari node 5, level 1 terdiri dari node-node 3, 9, dan level 2 terdiri dari node-node 1,4, 6. 8.1. Aplikasi pada Pohon Biner Struktur data pohon biner sangat diperlukan pada saat membuat keputusan dalam suatu proses. Misalnya ingin dicari bilangan yang sama pada sekelompok bilangan-bilangan. Kalau menggunakan cara biasa, yaitu dengan membandingkan satu 46 sama lain bilangan-bilangan yang diketahui, maka akan terjadi jumlah perbandingan yang sangat banyak. Jumlah perbandingan dalam mencari bilangan-bilangan yang sama dapat dikurangi dengan menggunakan pohon biner. Caranya sebagai berikut : bilangan pertama kita pasang sebagai root. Bilangan ke dua dibandingkan dengan root, jika sama maka ada diplikasi, tetapi jika tidak sama maka jika bilangan ini lebih kecil dari root, maka bilangan ini diletakkan sebagai cabang kiri, jika lebih besar, maka diletakkan di cabang kanan. Selanjutnya bilangan ke tiga dibandingkan dengan root. Jika bilangannya sama dengan root maka ada duplikasi, tetapi jika tidak sama maka jika lebih kecil dari root, perbandingan dilanjutkan ke cabang kiri, tetapi jika lebih besar dari root dilanjukan perbandingan ke cabang kanan. Dan seterusnya sampai semua bilangan dibandingkan. Agar lebih jelas, maka diberikan contoh sekelompok bilangan : 14, 15, 4, 9, 7, 18, 3, 5, 16, 4, 20. Skemanya dapat dilihat pada gambar 8.2 di bawah ini. 14 14 14 15 14 4 15 4 15 9 14 14 4 15 4 9 7 14 15 9 4 18 3 15 9 18 7 Gambar 8.2. Skema Pembangunan Pohon Biner 47 14 14 4 3 15 4 3 18 9 7 15 9 16 18 7 5 5 Selanjutnya bilangan 4 duplikasi, maka informasinya dicatat di tempat lain dan 4 tidak dimasukkan dalam struktur pohon. Kemudian dilanjutkan dengan bilangan berikutnya lagi. 14 4 3 15 9 16 7 18 20 5 Gambar 8.2. (Lanjutan) Bentuk akhir dari pohon biner di atas tampak tidak seimbang, dan tidak memenuhi syarat log2 (n). Dalam hal ini masalahnya bukan membuat pohon biner yang seimbang tetapi mencari data duplikasi. Dengan struktur data pohon biner, jika terdapat n data maka jumlah perbandingannya hanya log2 (n). Bandingkan dengan pencarian duplikasi dengan cara biasa (linear) yang membutuhkan pembandingan sebanyak n2. Program aplikasinya dalam bahasa C, dibuat sebagai bahan latihan. Struktur data dasar untuk struktur pohon biner adalah sebagai berikut : 48 struct nod { struct nod *left; char data; struct nod *right; }; Setiap node terdiri dari node kiri yang bertipe node juga (struct nod *left;), informasi untuk tiap node (char data; ), dan node kanan yang bertipe node juga (struct nod *right; ). Dalam hal ini informasinya berupa satu data karakter. Dalam aplikasi yang sesungguhnya, informasi bisa bermacam-macam dan lebih dari satu informasi. 8.2. Penelusuran Pohon Biner Ada tiga macam penelusuran pohon biner, yaitu : preorder (atau disebut depth-first order), inorder (atau disebut symetric order), dan postorder. Penelusuran preorder menggunakan algoritma berikut ini. 1. Kunjungi root dan catat 2. Telusuri sub pohon kiri secara preorder 3. Telusuri sub pohon kanan secara preorder Penelusuran secara inorder menggunakan algoritma berikut ini. 1. Telusuri sub pohon kiri secara inorder 2. Kunjungi root dan catat 3. Telusuri sub pohon kanan secara inorder Dan penelusuran secara postorder menggunakan algoritma berikut ini. 1. Telusuri sub pohon kiri secara post order 2. Telusuri sub pohon kanan secara post order 3. Kunjungi root dan catat Sebagai contoh untuk memperjelas prosedur penelusuran tersebut, berikut ini diketahui sebuah pohon biner berikut ini. A B D E C F Gambar 8.3. Contoh Pohon Biner 49 Penelusuran secara pre order adalah : catat root A, catat root sub pohon kiri B, catat root sub sub pohon kiri (kosong). Sub pohon yang paling kiri dan paling dalam sudah habis, maka dilanjutkan dengan sub pohon bagian kanan yang terdalam. Catat root C. Sub pohon kiri sudah habis, maka dilanjutkan dengan sub pohon kanan. Catat root D, catat root dari sub pohon kiri (kosong), dan lanjutkan ke sub sub pohon kanan. Catat root E, dan catat root sub sub pohon kiri F. Semua node sudah dikunjungi dan dicatat, maka hasil akhirnya adalah : A – B – C – D – E – F. Hasil penelusuran secara inorder adalah : B – C – A – D – F – E. Sedangkan hasil penelusuran secara postorder adalah : C – B – F – E – D – A. Program untuk ketiga proses penelusuran tersebut dapat dilihat di bawah ini. Nama program : pohon.c /* Aplikasi Pohon (tree) */ #include <stdio.h> #include <malloc.h> struct nod { struct nod *left; char data; struct nod *right; }; typedef struct nod NOD; typedef NOD POHON; NOD *NodBaru(char item) { NOD *n; n = (NOD*) malloc(sizeof(NOD)); if(n != NULL) { n->data = item; n->left = NULL; n->right = NULL; } return n; } void CiptaPohon(POHON **T) { *T = NULL; } typedef enum { FALSE = 0, TRUE = 1} BOOL; BOOL PohonKosong(POHON *T) { return((BOOL)(T == NULL)); } 50 void TambahNod(NOD **p, char item) { NOD *n; n = NodBaru(item); *p = n; } void preOrder(POHON *T) { if(!PohonKosong(T)) { printf("%c ", T->data); preOrder(T->left); preOrder(T->right); } } void inOrder(POHON *T) { if(!PohonKosong(T)) { inOrder(T->left); printf("%c ", T->data); inOrder(T->right); } } void postOrder(POHON *T) { if(!PohonKosong(T)) { postOrder(T->left); postOrder(T->right); printf("%c ", T->data); } } int main() { POHON *kelapa; char buah; CiptaPohon(&kelapa); TambahNod(&kelapa, buah = 'A'); TambahNod(&kelapa->left, buah = 'B'); TambahNod(&kelapa->left->right, buah = 'C'); TambahNod(&kelapa->right, buah = 'D'); TambahNod(&kelapa->right->right, buah = 'E'); TambahNod(&kelapa->right->right->left, buah = 'F'); printf("Urutan secara PreOrder: "); preOrder(kelapa); printf("\nUrutan secara InOrder: "); inOrder(kelapa); 51 printf("\nUrutan secara PostOrder: "); postOrder(kelapa); printf("\n\n"); return 0; } Latihan : Realisasikan program untuk menyusun pohon biner seperti pada gambar 8.2. Cetak data yang ada duplikasinya. 52 DAFTAR PUSTAKA 1. Anonim, “Queue”, http://www.harvestsoft.net/ 2. 3. Embedded Artists, “ Reference Documentation Queue Data Structure v 1.0.1”, ESICTM – Embedded Systems Infrastructure Component, Sweden, 2003. Jenkins Thomas, 1998, Recursion, http://engr.nmsu.edu/~etti/winter98/computers jenkins/ jenkins.html 4. Marshall Dave , “Programing In C, New Edition” , LEMU , 1999. 5. Parlante Nick, “ Linked List Basic”, http://cslibrary.stanford.edu/ 6. Tenenbaum Aaron M, Yedidyah Langsam, Moshe J. Augenstein, “ Data Structures Using C”, Prentice Hall, New Jersey, 1996. 53