Sebelum memulai menulis kode (atau ngoding), kita membutuhkan IDE untuk menyederhanakan proses pengkodean. Apa itu IDE? Sederhananya, IDE atau Intregated Development Environment adalah aplikasi editor kode yang menyertakan compiler. Berikut ini adalah daftar aplikasi IDE bahasa C yang dapat digunakan.
Dalam suatu program, sebuah statement atau pernyataan adalah intruksi/perintah yang mempunyai makna tertentu sehingga menyebabkan program tersebut dapat melakukan suatu tindakan.
Analoginya, untuk berbicara kepada seseorang, kita menggunakan bahasa tertentu yang dapat dimengerti, sehingga kita dapat menyampaikan apa yang ingin disampaikan. Nah, seperti itulah statement. Apa yang kita sampaikan kepada orang lain layaknya statement pada program yang kita sampaikan kepada komputer.
Mari kita mulai dengan membuat program sederhana, yakni program untuk menampilkan kalimat “Hello, world!” pada layar (konsol). Berikut adalah kode program tersebut.
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
Salinlah kode di atas pada IDE, kemudian compile dan jalankan. Proses compilation akan menghasilkan output sebagai berikut.
Hello, world!
Untuk menjelaskan cara kerja program di atas, mari kita pisahkan bagian-bagian kode satu per satu.
Baris 1 pada kode di atas disebut dengan preprocessor directive. Preprocessor yang digunakan dalam hal ini adalah #include
.
Preprocessor #include
menjelaskan bahwa program tersebut menyertakan file header <stdio.h>
. File header tersebut merupakan library bawaan C yang berisi fungsi-fungsi penting yang dibutuhkan oleh program, misalnya pada kasus ini, program membutuhkan fungsi printf()
untuk mencetak kalimat “Hello, world!”. Tanpa adanya library, program tersebut tidak akan bisa di-compile.
main()
Fungsi main()
pada kode tersebut ditunjukan pada baris ke 3 hingga baris ke 7.
int main()
{
printf("Hello, world!\n");
return 0;
}
Dalam bahasa C, fungsi main()
adalah fungsi utama yang harus ada. Hal ini dikarenakan eksekusi kode akan dimulai dari awal fungsi main()
.
main()
. Kemudian terdapat tanda {
yang menandakan awal pembuka dari fungsi main()
.main()
.}
yang menandakan akhir dari fungsi main()
. Pada dasarnya, tubuh dari sebuah fungsi berada di antara tanda { }
.Dapat diperhatikan pada kode bahwa baris 2 dan 4 kosong (tidak terdapat karakter apapun). Ini disebut dengan whitespace. Whitespace adalah area kosong pada kode, dan biasanya dtujukan agar kode mudah dibaca.
Terdapat tiga jenis whitespace, yakni space, tab, dan new line. Baris 2 dan 4 adalah contoh dari new line.
Di dalam fungsi main()
, terdapat dua statement yang ditunjukkan pada baris 5 dan 6. Sebagian besar statement diakhiri oleh tanda titik-koma (;
).
printf("Hello, world!\n");
return 0;
Statement pada baris 5 menginstruksikan program untuk memanggil fungsi printf()
. Fungsi printf()
adalah fungsi yang tersedia dalam library header <stdio.h>
dan digunakan untuk mencetak output pada konsol (layar). Dalam hal ini, fungsi printf()
menerima argumen string bertuliskan “Hello, world!\n”. Tanda ‘\n’
dalam string tersebut merupakan karakter spesial yang berfungsi untuk mencetak new line.
Sedangkan statement pada baris 6 disebut dengan return statement. Perintah return 0
pada fungsi main()
digunakan untuk mengakhiri program dan menandakan program tersebut sukses dieksekusi.
Komentar (comment) adalah bagian dari program yang tidak akan dieksekusi. Komentar sangat berguna untuk mendeskripsikan program yang dibuat, misalnya saja untuk menjelaskan bagian dari kode agar mudah dipahami oleh programmer lainnya. Terdapat dua jenis komentar dalam bahasa C.
Seperti namanya, komentar single-line hanya bekerja pada satu baris. Komentar single-line diawali dengan simbol //
. Semua karakter (pada satu baris) dibelakang simbol //
akan diabaikan.
// Ini adalah komentar single-line
// Fungsi untuk mencetak ke layar
printf("HALO\n");
Sedangkan komentar multi-line dapat bekerja pada lebih dari satu baris. Pasangan simbol /*
dan */
digunakan untuk membuat komentar multi-line. Semua karakter yang berada di antara dua simbol tersebut akan diabaikan.
/*
Ini adalah komentar multi-line
Semua yang berada di sini akan
diabaikan
*/
Keyword merupakan kata khusus yang telah dipesan (reserved) pada bahasa pemograman. Kata-kata khusus tersebut mempunyai makna tersendiri bagi compiler, dan kata-kata tersebut merupakan bagian dari sintaks dan tidak dapat digunakan sebagai identifier. Berikut adalah daftar keyword pada bahasa C.
auto |
double |
int |
struct |
break |
else |
long |
switch |
case |
enum |
register |
typedef |
char |
extern |
return |
union |
continue |
for |
signed |
void |
do |
if |
static |
while |
default |
goto |
sizeof |
volatile |
const |
float |
short |
unsigned |
Identifier merujuk kepada penamaan sebuah entitas, seperti pada variabel, fungsi, structures dan lain-lain. Karena identifier menamai sebuah entitas, maka nama dari identifier harus unik (dua entitas tidak boleh mempunyai nama identifier yang sama).
Aturan penamaan identifier:
variable
berbeda dengan vAriaBle
.Variabel adalah suatu tempat yang digunakan untuk menampung data atau nilai pada memori yang mempunyai nilai yang dapat berubah–ubah selama proses program. Dalam bahasa C, variabel menyimpan data/nilai dengan tipe data tertentu. Seperti halnya variabel yang menyimpan suatu angka yang termasuk bilangan bulat (integer).
Variabel dapat dianalogikan sebagai sebuah gelas. Gelas dalam hal ini disebut sebagai variabel. Pada umumnya, gelas digunakan untuk menampung cairan. Dalam hal ini cairan adalah tipe datanya. Kemudian, contoh dari cairan adalah air, yakni dalam hal ini data yang akan disimpan oleh gelas.
Pada dasarnya program bekerja dengan mengolah data-data. Data-data inilah yang kemudian disimpan pada variabel.
Pada bahasa C, variabel harus dideklarasikan terlebih dahulu sebelum bisa digunakan. Seperti halnya gelas tadi, gelas tersebut harus ada terlebih dahulu sebelum bisa digunakan.
Untuk mendeklarasikan sebuah variabel, sintaksnya adalah sebagai berikut.
<tipe_data> <identifier>
Misalkan, potongan kode berikut mendeklarasikan variabel x
yang bertipe int
.
int x
Jika ingin mendeklarasikan lebih dari satu variabel dengan tipe yang sama, bisa menggunakan operator koma (,
).
<tipe_data> <variabel1>, <variabel2>, ... dst;
int x, y;
Setelah dideklarasikan, variabel dapat diisi oleh sebuah nilai. Untuk melakukannya, gunakan operator assignment (simbol =
).
identifier_variabel = <nilai yang bersesuaian>
Contoh:
int x, y;
x = 10;
y = -2;
Dalam hal ini, variabel x
dan y
akan mempunyai nilai masing-masing 10
dan -2
.
Deklarasi dan pengisian nilai pada variabel dapat dilakukan dalam satu instruksi sekaligus. Hal ini disebut dengan inisialisasi. Dengan melakukan inisialisasi variabel, berarti kita memberikan nilai awal pada variabel tersebut.
tipe_data identifier_variabel = <nilai yang bersesuaian>;
Contoh:
int x = 10;
Istilah konstanta merujuk pada suatu nilai tetap, tidak dapat berubah/diubah bahkan oleh program itu sendiri. Literal adalah konstanta yang nilainya kita tulis secara eksplisit pada kode. Contohnya:
5 // Literal bilangan bulat
1.23 // Literal bilangan real
'S' // Literal karakter
"Hai" // Literal string
Berikut adalah jenis-jenis literal dalam bahasa C.
Jenis Literal | Contoh | Tipe Default |
---|---|---|
Bilangan bulat | 10, 0, -1 dll. |
int |
Bilangan real (floating) | 202.15, 33.24, -12.45 dll. |
double |
Karakter | ‘A’, ‘1’, ‘#’ |
char |
String | “Hai” |
const char[4] |
Terdapat tiga cara untuk menuliskan literal bilangan bulat. Yakni menggunakan basis 10 (desimal), basis 8 (oktal) dan basis 16 (heksadesimal).
100, -22
.077, 033
.0x7f, 0x521
.Bilangan real (floating) dituliskan dengan menggunakan pemisah tanda titik (.) antara bilangan bulat dan bilangan pecahannya. Contohnya 2.321, -11.22, 0.00012
.
Literal karakter dituliskan dengan mengapitnya menggunakan tanda petik. Misalnya karakter A ditulis ‘A’. Selain karakter normal, terdapat karakter khusus dalam bahasa C yang mempunyai fungsi-fungsi khusus atau karakter yang tidak bisa begitu saja dituliskan dengan bentuk aslinya. Misalnya, “new line” direpresentasikan dengan ‘\n’, simbol backslash direpresentasikan dengan ‘\’. Karakter-karakter tersebut disebut dengan escape sequence. Berikut adalah escape sequence yang terdapat dalam bahasa C.
Escape Sequence | Karakter |
---|---|
\b |
Backspace |
\f |
Form feed |
\n |
Newline |
\r |
Return |
\t |
Tab horisontal |
\v |
Tab vertikal |
\\ |
Backslash |
\’ |
Tanda petik |
\” |
Tanda petik dua |
\? |
Tanda tanya |
\0 |
Karakter null |
\b |
Backspace |
String adalah kumpulan dari satu karakter atau lebih. Literal string diapit oleh tanda kutip. Misalnya, “Hello, world.”
. Representasi string dalam bahasa C menggunakan array bertipe char
. Kita akan mempelajarinya di bagian selanjutnya.
Untuk mendefinisikan konstanta, dapat dilakukan dengan cara-cara seperti berikut.
Variabel juga dapat dibuat konstan nilainya agar tidak dapat berubah/diubah selama program berjalan. Variabel konstan dibuat dengan menambahkan keyword const
saat pendefinisian variabel.
const tipe_data nama_var = <nilai yang bersesuaian>
Perlu diperhatikan bahwa definisi variabel konstan harus disertai inisialisasinya. Contoh:
const int konstInt = 23;
const double konstDouble = 23.12;
Segala bentuk perubahan yang dilakukan terhadap variabel konstan akan menghasilkan error.
const int konstInt = 23;
konsInt = 11; // Error
Cara lainnya adalah menggunakan prepocessor directive #define
. Sintaksnya adalah sebagai berikut.
#define nama <nilai yang bersesuaian>
Contoh:
#define konstInt 23
#define konstDouble 23.11
int main()
{
int a = konstInt;
double b = konstDouble;
}
Secara sederhana tipe data adalah jenis data dan ukuran data yang akan ditampung dan oleh variabel (atau objek secara umum). Tipe data menentukan tipe dan jenis data seperti apa yang akan dimiliki oleh suatu variabel.
Dalam bahasa C terdapat beberapa jenis tipe data. Di antaranya adalah tipe data dasar, tipe data turunan, dan void. Untuk kali ini kita akan berfokus pada tipe data dasar.
Bilangan Bulat adalah bilangan yang tidak mempunyai nilai pecahan (real). Tipe data bilangan bulat pada bahasa C diantaranya sebagai berikut.
Tipe Data | Memori (Byte) | Jangkauan Nilai | Format Specifier | ||
---|---|---|---|---|---|
Min | Max | ||||
short | 2 | -215 | s.d | 215 - 1 | %hi |
unsigned short | 2 - 4 | 0 | s.d | 216 - 1 | %hu |
int | 4 | -231 | s.d | 231 - 1 | %d |
unsigned int | 4 | 0 | s.d | 232 - 1 | %u |
long | 4 | -231 | s.d | 231 - 1 | %ld |
unsigned long | 4 | 0 | s.d | 232 - 1 | %lu |
long long | 8 | -263 | s.d | 263 - 1 | %lld |
unisgned long long | 8 | 0 | s.d | 264 - 1 | %llu |
Seperti namanya, tipe-tipe data di atas adalah tipe data yang digunakan untuk merepresentasikan bilangan bulat (positif dan negatif) dan bilangan 0. Misalnya, 0, -5, 12, -1, 200 dsb. Perlu ditekankan bahwa tipe-tipe data di atas tidak dapat digunakan untuk merepresentasikan bilangan floating-point (bilangan real).
Jika diperhatikan, terdapat dua jenis tipe data antara lain signed dan unsigned. Lalu apa perbedaan dari kedua jenis tersebut? Perbedaannya adalah terletak pada kemampuan untuk menampung bilangan negatif. signed dapat menampung bilangan negatif, sedangkan unsigned tidak.
Bilangan Real atau floating-point adalah bilangan yang mempunyai nilai pecahan (real). Tipe data bilangan real pada bahasa C di antaranya adalah sebagai berikut.
Tipe Data | Memori (Byte) | Jangkauan Nilai | Format Specifier |
---|---|---|---|
float | 4 | ±3.4 x 10±38 (estimasi) | %f |
double | 8 | ±1.7 x 10±308 (estimasi) | %lf |
Tipe data di atas digunakan untuk menyimpan data berupa bilangan real (floating-point)/bilangan berkoma. Misalnya, 2.35, -12.246, 0.005
dsb.
Karakter dalam bahasa C sebenarnya adalah bilangan bulat. Setiap karakter mempunyai kode tersendiri yang disebut dengan ASCII dan kode tersebut dapat direpresentasikan sebagai sebuah bilangan bulat.
Tipe Data | Memori (Byte) | Jangkauan Nilai | Format Specifier |
---|---|---|---|
char | 1 | -27 s.d 27 - 1 | %c |
unsigned char | 1 | 0 s.d 28 - 1 | %c |
Penggunaan paling umum dari tipe data di atas adalah untuk merepresentasikan satu karakter. Misalnya, ‘A’
, ‘-‘
, dan sebagainya.
Program yang kita buat dapat dijadikan program yang interaktif. Kita dapat menginstruksikannya untuk menerima input (dari keyboard) lalu menampilkan hasil output (pada konsol layar). Fungsi-fungsi yang berkaitan dengan input/output ada di dalam library <stdio.h>
(standard input output).
printf()
Untuk mencetak output pada konsol, fungsi yang digunakan adalah fungsi printf()
. Seperti yang sudah kita ketahui sebelumnya, fungsi printf()
dapat menerima string sebagai argumen.
#include <stdio.h>
int main()
{
printf("Ini adalah sebuah string\n");
return 0;
}
Output
Ini adalah sebuah string
Kita juga dapat menambahkan escape sequence pada string. Misalkan, kita ubah statement printf()
di atas menjadi:
printf("Ini adalah sebuah string\nAku adalah new-line\n\tAku adalah karakter \\tab");
Ini adalah sebuah string
Aku adalah new-line
Aku adalah karakter \tab
Memisahkan dua statement printf()
pada baris berbeda bukan berarti mencetak pada baris berbeda juga.
#include <stdio.h>
int main()
{
printf("Pikirmu aku akan");
printf("Berpindah baris?");
return 0;
}
Output
Pikirmu aku akanBerpindah baris?
Potongan-potongan kode di atas adalah contoh untuk mencetak string tetap. Lalu bagaimana jika kita ingin mencetak string bersama dengan nilai dari suatu variabel?
Untuk mencetak nilai dari suatu variabel, kita perlu menambahkan argumen pada fungsi printf()
. Argumen pertama pada fungsi printf()
selalu berupa string. Kita dapat memasukkan variabel/nilai pada argumen ke-2, 3, 4 dan seterusnya sesuai kebutuhan.
Ingat, pada chapter Tipe Data Dasar, setiap tipe data mempunyai format specifier masing-masing. Nah, format specifier inilah yang akan kita gunakan untuk mencetak nilai dari suatu variabel.
printf(“<format string>”, var1, var2, var3, ... dst);
Misalnya saja, kita mempunyai dua variabel bertipe int dan char yakni a = 2
dan b = ‘X’
. Kita hendak mencetak nilai dari a dan b dipisahkan oleh spasi, maka programnya seperti:
#include <stdio.h>
int main()
{
int a = 2;
char b = 'X';
printf("%d %c", a, b);
return 0;
}
Output
2 X
Perhatikan ilustrasi di bawah.
Dengan menyertakan format specifier dari tipe data yang bersesuaian, kita dapat mencetak nilai dari variabel tersebut.
printf()
di atas mencetak string dengan nilai dua variabel (dua format specifier yang dipisahkan spasi).printf("Nilai dari a = %d, dan b = '%c'", a, b);
Nilai dari a = 2, dan b = ‘X’
scanf()
Umumnya kita melakukan input untuk menerima data/nilai dari user. Kemudian, data/nilai tersebut akan dimasukkan pada variabel yang akan diproses kemudian.
Untuk melakukan input dari keyboard fungsi yang digunakan adalah fungsi scanf()
. Parameter dari fungsi scanf()
sama persis dengan fungsi printf()
. Kita menggunakan format specifier untuk menentukan jenis tipe data yang kita input, kemudian nilai input tersebut akan di-assign pada variabel.
Contoh:
Program di bawah menerima input berupa bilangan bulat yang disimpan pada variabel n, kemudian mencetak nilai variabel n dengan format “n mempunyai nilai = n”.
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
printf("n mempunyai nilai = %d", n);
return 0;
}
Input
3
Output
n mempunyai nilai = 3
Operator dapat dipahami sebagai sesuatu yang dapat melakukan operasi pada operan (variabel/nilai). Contohnya, operator + digunakan untuk operasi penjumlahan.
Dilihat dari jumlah operannya, operator dibagi menjadi tiga jenis, yaitu:
Dilihat dari kegunaannya, berikut adalah jenis-jenis operator pada bahasa C.
Operator Assignment digunakan untuk mengisikan (assign) sebuah nilai ke variabel. Simbol yang biasa digunakan adalah tanda sama dengan =
. Contohnya:
int x, y;
x = 4;
y = 3;
x = x + y; // x = 7
y = x + x; // y = 14
Seperti namanya, operator aritmatika melakukan operasi layaknya pada matematika seperti penjumlahan, pengurangan, pembagian dsb. Beberapa operator menggunakan simbol yang sama pada matematika (penjumlahan dengan simbol ‘+’
, pengurangan dengan ‘-‘
, dst.). Operator-operator aritmatika pada bahasa C adalah sebagai berikut.
Simbol | Operasi | Contoh |
---|---|---|
+ | Penjumlahan pada dua operan | a + b |
- | Pengurangan pada dua operan | a - b |
* | Perkalian pada dua operan | a * b |
/ | Pembagian pada dua operan | a / b |
% | Menghitung sisa pembagian dua operan (operasi modulo) | a % b |
Operator ++
disebut dengan operator increment, sedangkan operator --
merupakan operator decrement. Kedua operator ini digunakan untuk menambah (increment)/mengurangi (decrement) nilai dari suatu variabel sebanyak satu.
Terdapat dua cara untuk menggunakan operator ini.
Prefix - yakni dengan meletakkan operator increment/decrement didepan nama variabel.
int a, b;
a = 5;
++a; // Nilai a sekarang adalah 6
--a; // Nilai a sekarang adalah 5
Cara kerja dari operator increment/decrement prefix adalah dengan menambahkan/mengurangi nilai variabel sebanyak satu terlebih dahulu, sebelum operan tersebut digunakan pada operasi lainnya pada sekuens intstruksi yang sama. Untuk lebih jelasnya, perhatikan potongan kode berikut
int a, b;
a = 5;
b = ++a; // Nilai b sekarang adalah 6
a = --b; // Nilai a sekarang adalah 5
Di sini, saat instruksi b = ++a;
dieksekusi, yang terjadi pertama kali adalah nilai dari a
ditambahkan satu terlebih dahulu, kemudian baru di-assign nilainya ke variabel b
.
Postfix - yakni dengan meletakkan operator increment/decrement di belakang nama variabel. Cara kerja dari operator increment/decrement postfix berbeda dari prefix. Pada postfix, nilai variabel akan ditambah satu setelah operan digunakan pada operasi lainnya pada sekuens instruksi yang sama. Perhatikan potongan kode berikut.
int a, b;
a = 5;
b = a++; // Nilai b sekarang adalah 5
a = b--; // Nilai a sekarang adalah 5
Di sini, saat instruksi b = a++;
dieksekusi, yang terjadi pertama kali adalah nilai dari a
akan di-assign terlebih dahulu ke variabel b
, kemudian baru ditambahkan satu. Karena itulah variabel b
mendapat nilai dari a
sebelum terjadi penambahan.
Operator Relasional digunakan untuk memeriksa relasi dan membandingkan nilai dari dua operan. Jika benar akan menghasilkan nilai TRUE (direpresentasikan angka 1), jika salah maka akan menghasilkan nilai FALSE (direpresentasikan angka 0).
Berikut adalah operator relasional dalam bahasa C.
Operator | Simbol | Keterangan | Contoh |
---|---|---|---|
Sama dengan | == | Digunakan untuk memeriksa apakah kedua operan memiliki nilai yang sama. | 5 == 2 (FALSE) 5 == 5 (TRUE) |
Tidak Sama dengan | != | Digunakan untuk memeriksa apakah kedua operan memiliki nilai yang tidak sama. | 5 != 2 (TRUE) 5 != 5 (FALSE) |
Lebih besar | > | Digunakan untuk membandingkan apakah operan pertama lebih besar nilainya dari operan kedua. | 5 > 2 (TRUE) 5 > 5 (FALSE) 2 > 4 (FALSE) |
Lebih kecil | < | Digunakan untuk membandingkan apakah operan pertama lebih kecil nilainya dari operan kedua. | 5 < 2 (FALSE) 5 < 5 (FALSE) 2 < 4 (TRUE) |
Lebih besar sama dengan | >= | Digunakan untuk membandingkan apakah operan pertama lebih besar atau sama nilainya dari operan kedua. | 5 >= 2 (TRUE) 5 >= 5 (TRUE) 2 >= 4 (FALSE) |
Lebih kecil sama dengan | <= | Digunakan untuk membandingkan apakah operan pertama lebih kecil atau sama nilainya dari operan kedua. | 5 <= 2 (FALSE) 5 <= 5 (TRUE) 2 <= 4 (TRUE) |
Operator Logika digunakan untuk melakukan tes pada kondisi/ekspresi, apakah kondisi tersebut benar atau salah. Operator logika hanya akan menghasilkan nilai TRUE (jika benar) atau FALSE (jika salah). TRUE direpresentasikan oleh angka 1, sedangkan FALSE oleh angka 0.
Operator-operator logika dalam bahasa C adalah sebagai berikut.
Operator | Simbol | Keterangan | Nilai Kebenaran |
---|---|---|---|
Logical NOT | ! | Operator NOT digunakan untuk membalikkan kondisi, TRUE menjadi FALSE dan FALSE menjadi TRUE. | !1 = 0 !0 = 1 |
Logical AND | && | Operator AND akan menghasilkan nilai TRUE jika kedua operan mempunyai nilai TRUE. | 1 && 1 = 1 0 && 1 = 0 1 && 0 = 0 0 && 0 = 0 |
Logical OR | || | Operator OR akan menghasilkan nilai TRUE jika salah satu operan mempunyai nilai TRUE. | 1 \|\| 1 = 1 0 \|\| 1 = 1 1 \|\| 0 = 1 0 \|\| 0 = 0 |
Operator Logika NOT merupakan operator unary yang artinya hanya pada bekerja pada satu operan
Operator logika pada umumnya digunakan bersamaan dengan operator relasional untuk melakukan tes pada ekspresi yang berhubungan dengan kebenaran suatu kondisi. Penggunaan paling umum adalah untuk melakukan percabangan (akan dipelajari di bagian selanjutnya).
Contoh:
int a, b, c, d;
a = 11;
b = 24;
c = 11;
d = ((a == c) && (b > a)); // 1 (TRUE)
d = ((a >= b) || (a < c)); // 0 (FALSE)
d = ((b != b) || (b > c)) && (c == a); // 1 (TRUE)
Operator Gabungan adalah operator yang terdiri dari gabungan dua operator. Tujuan dari operator gabungan adalah untuk mempersingkat penulisan kode. Berikut adalah operator gabungan dalam bahasa C.
Operator | Contoh | Ekuivalen Dengan |
---|---|---|
+= | a += b |
a = a + b |
-= | a -= b |
a = a - b |
*= | a *= b |
a = a * b |
/= | a /= b |
a = a / b |
%= | a %= b |
a = a % b |
&= | a &= b |
a = a & b |
|= | a \|= b |
a = a \| b |
^= | a ^= b |
a = a ^ b |
»= | a >>= b |
a = a >> b |
«= | a <<= b |
a = a << b |
Control Flow adalah cara kita mengatur jalan penyataan, instruksi, dan pemanggilan fungsi suatu program. Tanpa control flow, program kita hanya bergerak dari atas ke bawah saja (sequential). Control flow bahasa C ada 2, yaitu percabangan (selection) dan perulangan (repetition). Di Modul ini, kita akan memfokuskan pembahasan di percabangan terlebih dahulu.
Percabangan memungkinkan kita untuk menentukan kode manakah yang akan kita eksekusi berdasarkan suatu kondisi. Percabangan di bahasa C ada 4, yaitu if
, if-else
, if-else if
, dan switch
.
Sintaks yang digunakan dalam percabangan menggunakan if
adalah sebagai berikut.
if (<Ekspresi/Kondisi>) {
// Kode yang akan dieksekusi jika kondisi tersebut benar
}
Cara kerja percabangan if
yaitu memeriksa dan mengevaluasi suatu kondisi untuk menentukan apakah instruksi selanjutnya dalam bracket akan dijalankan atau tidak oleh program.
Contoh
Sebagai contoh, di dashboard mobil terdapat indikator bahan bakar yang akan menyala jika bahan bakar yang tersisa kurang dari level tertentu (misal kurang dari 10 liter) dengan kondisi “Apakah bahan bakar kurang dari 10 liter?”.
Pada kasus ini terdapat kondisi
#include <stdio.h>
int main()
{
int gasoline = 0;
gasoline = 3;
if (gasoline < 10) //jika bahan bakar kurang dari 10 liter
{
// Apa yang perlu dilakukan ketika kondisi terpenuhi?
printf("Lampu Indikator menyala!\n");
}
}
Output
Lampu Indikator menyala!
Sintaks yang digunakan dalam percabangan menggunakan if-else
adalah sebagai berikut.
if (<Ekspresi/Kondisi>) {
// Kode yang akan dieksekusi jika kondisi tersebut benar
} else {
// Kode yang akan dieksekusi jika kondisi tersebut salah
}
Cara kerja percabangan if-else
yaitu memeriksa kondisi dalam if
.
if
.else
lah yang akan dijalankan.Sebagai contoh, kita ingin mencari tahu apakah seseorang absen dari kelas atau tidak. Apabila ia tidak hadir, maka absensinya akan dicoret (bernilai X), dan apabila hadir, maka absensinya akan di centang (bernilai V).
Sehingga dari kasus tersebut, didapat dua alternatif kondisi.
#include <stdio.h>
#define DATANG 1
int main()
{
int hadir = DATANG;
if (hadir) // Jika orang tersebut hadir
{
printf("V\n");
} else {
printf("X\n");
}
}
Output
V
Sintaks yang digunakan dalam percabangan menggunakan if-else if
adalah sebagai berikut.
if (<Ekspresi/Kondisi>) {
// Kode yang akan dieksekusi jika kondisi tersebut benar
}else if (<Ekspresi/Kondisi>) {
// Kode yang akan dieksekusi jika kondisi tersebut salah
}
// Boleh menambahkan else{} apabila perlu
Cara kerja percabangan if-else
yaitu memeriksa kondisi dalam if
.
if
.else-if
, apabila bernilai TRUE (1), maka ia akan menjalankan perintah dalam bracket tersebut, apabila tidak maka ia akan menjalankan sequence selanjutnya.if
dan else-if
tidak memenuhi atau FALSE (0), maka secara otomatis ia akan menjalankan perintah di dalam else
tersebut.Selain penggunaan statement if
untuk memilih diantara banyak alternatif, terdapat pula statement switch
yang memiliki fungsi yang sama, untuk memilih diantara banyak alternatif berdasarkan sebuah kondisi. Kondisi pada statemen switch
berisi ekspresi yang dapat menggunakan sebuah variable tunggal bertipe int atau char yang akan diperiksa nilainya di setiap blok case.
Sintaks untuk Switch-Case:
switch(ekspresi) {
case ekspresi-konstan:
statement;
break;
case ekspresi-konstan:
statement;
break;
/* Anda bisa memiliki jumlah case sebanyak mungkin */
/* Anda harus mengakhiri blok kode Switch-Case dengan "default",
yaitu bagian kode yang akan dieksekusi jika tidak ada case yang memenuhi */
default:
statement;
}
Setiap blok, case harus ditambahkan statement break
, karena apabila tidak maka ia akan tetap menjalankan blok case di bawahnya hingga bertemu break
lain atau pada akhir blok switch.
Contoh
#include <stdio.h>
int main()
{
char platNomor;
printf("Masukkan huruf awal plat nomor anda: ");
scanf("%c", &platNomor);
switch(platNomor)
{
case 'L':
printf("Surabaya");
break;
case 'B':
printf("Jakarta");
break;
case 'D':
printf("Bandung");
break;
case 'N':
printf("Malang/Lumajang");
break;
default:
printf("Karakter plat nomor tidak diketahui");
}
return 0;
}
Dalam contoh di atas, ekspresi yang digunakan adalah PlatNomor, di mana case-case nya adalah, huruf plat nomor tersebut, L, B, D, N, dan sebagainya.
? :
)Operator kondisional mempunyai bentuk sintaks sebagai berikut:
(ekspresi/kondisi) ? (ekspresi jika TRUE) : (ekspresi jika FALSE);
Operator kondisional adalah satu-satunya operator ternary dalam bahasa C. Operator kondisional bertingkah seperti layaknya percabangan if - else
. Ekspresi/kondisi yang akan dievaluasi diletakkan sebelum tanda tanya (?
). Apabila menghasilkan TRUE, maka program akan mengeksekusi bagian di kiri tanda titik dua. Jika FALSE, akan mengeksekusi bagian di kanan tanda titik dua.
Contoh:
#include <stdio.h>
int main()
{
int mark;
scanf("%d", &mark);
printf(mark >= 75 ? "Lulus\n" : "Tidak Lulus\n");
return 0;
}
Program di atas ekuivalen dengan:
#include <stdio.h>
int main()
{
int mark;
scanf("%d", &mark);
if (mark >= 75) {
printf("Lulus\n");
} else {
printf("Tidak Lulus\n");
}
return 0;
}
Perulangan atau looping memungkinkan kita untuk mengeksekusi potongan kode berulang-ulang hingga mencapai suatu kondisi. Ada 3 jenis perulangan dalam bahasa C, yaitu while
, do - while
, dan for
.
Perulangan while
adalah bentuk perulangan yang paling sederhana. Sintaksnya adalah sebagai berikut.
//initial value misal, i = 0
while (<Ekspresi/Kondisi>) {
// Potongan kode yang ingin dieksekusi
.
.
.
// increment/decrement misalnya, i++
}
Cara kerja perulangan while
mirip dengan if
. Jika pada if potongan kode akan dieksekusi sekali saja apabila ekspresi/kondisi bernilai TRUE, pada while potongan kode akan terus dieksekusi hingga ekspresi/kondisi menghasilkan FALSE.
Contoh
#include <stdio.h>
int main()
{
int i = 0;
while (i < 10)
{
printf("Hello World! ke-%d \n",i);
i++;
}
return 0;
}
Sehingga pada contoh di atas:
i
bernilai 0.while
, dan i bernilai kurang dari 10 (TRUE), maka kode didalam while
akan dijalankan, yakni print Hello world ke-i.i
akan di increment, dan kembali ke statement while
untuk memeriksa apakah i
masih kurang dari 10 setelah di-incrementi
di-increment nilainya masih 1 dan kurang dari 10, maka while
akan dijalankan lagi hingga i
bernilai 10 yang berarti tidak memenuhi kondisi while
.Sintaks dari perulangan do – while
adalah sebagai berikut.
do {
// Potongan kode yang dieksekusi.
.
.
// increment/decrement
} while (<Ekspresi/Kondisi>)
Cara kerja dari perulangan do – while
mirip dengan perulangan while
. Hanya saja, pada perulangan do – while
, potongan kode dijamin akan dieksekusi tepat satu kali sebelum mengevaluasi ekspresi/kondisi.
Contoh
#include <stdio.h>
int main()
{
int num = 0;
do
{
printf("Num sekarang adalah %d\n",num);
printf("Masukkan bilangan integer positif (-1 untuk keluar ): ");
scanf("%d", &num);
} while (num != -1);
return 0;
}
Perulangan for
merupakan perulangan paling rumit diantara perulangan lainnya. Sintaksnya adalah sebagai berikut.
for (init_statement; kondisi/ekspresi; end_statement) {
// Potongan kode yang dieksekusi
.
.
}
Cara kerjanya adalah sebagai berikut:
init_statement
digunakan untuk inisialisasi variabel yang akan digunakan dalam perulangan. Bagian ini hanya dijalankan sekali saka pada saat awal perulangan.kondisi/ekspresi
akan dievaluasi. Jika menghasilkan TRUE, maka akan mengeksekusi potongan kode. Jika menghasilkan FALSE, maka perulangan berhenti.end_statement
. Biasanya bagian ini digunakan sebagai increment/decrement.Contoh
#include <stdio.h>
int main()
{
int i;
// init;condition; increment
for (i = 0; i < 10 ; i++) {
printf("Hello World!\n");
}
}
i
bernilai 0i
apakah kurang dari 10i
akan di-increment, dan diperiksa lagi.i
kurang dari 10, maka command dalam block dieksekusi, apabila tidak maka for loop akan berhentiPercabangan dan perulangan juga dapat dibuat secara bersarang (membuat percabangan/perulangan di dalam percabangan/perulangan), tentu saja menyesuaikan kebutuhan. Contoh berikut adalah perulangan for
bersarang.
int i, j;
for (i = 1; i <= 10; ++i) {
// Statement yang ingin dijalankan pada level terluar
for (j = 1; j <= 10; j++) {
// Statement yang ingin dijalankan pada level terdalam
// Percabangan di dalam perulangan
if (i == 10) {
// Lakukan sesuatu
}
}
// Statement yang ingin dijalankan pada level terluar
}
Keyword break
dan continue
digunakan untuk mengendalikan (kontrol) alur pada perulangan. Berikut penjelasannya.
Perintah break
digunakan untuk menghentikan perulangan (secara paksa). Apabila perintah break
pada suatu perulangan dijalankan, maka perulangan tersebut akan dihentikan (secara paksa) dari titik dimana perintah break muncul.
Contoh
#include <stdio.h>
int main()
{
int i;
for (i = 1; i <= 6; i++) {
printf("%d\n", i);
// Jika i adalah 4, maka keluar dari perulangan
if (i == 4) {
break;
}
}
return 0;
}
Kebalikan dari perintah break, perintah continue
digunakan untuk melanjutkan perulangan. Pada perulangan, apabila menemui perintah continue
, maka perintah-perintah dibawah continue
akan diabaikan dan kembali akan mengevaluasi ekspresi/kondisi. Sedangkan pada perulangan for
akan langsung mengekekusi bagian end_statement kemudian mengevaluasi ekspresi/kondisi.
Contoh
#include <stdio.h>
int main()
{
int i;
for (i = 1; i <= 6; i++) {
// Jika i adalah 4, maka abaikan perintah printf()
if (i == 4) {
continue;
}
printf("%d\n",i);
}
return 0;
}
Infinite loop adalah kasus di mana perulangan tidak akan pernah berhenti. Hal ini terjadi karena perulangan tidak akan pernah menemui kondisi yang membuatnya berhenti. Contoh di bawah akan menghasilkan infinite loop.
#include <stdio.h>
int main()
{
int i;
for (i = 1; i <= 100; i--) {
// Perulangan tak akan pernah berhenti
}
}
Secara umum, string merupakan kumpulan dari satu atau lebih karakter. Spesifik pada bahasa C, string didefinisikan sebagai kumpulan karakter yang diakhiri oleh karakter null ('\0'
).
Misalkan string "Dasar"
, pada bahasa C direpresentasikan sebagai kumpulan karakter 'D'
, 'a'
, 's'
, 'a'
, 'r'
, dan '\0'
.
Pada bahasa C, string direpresentasikan oleh array bertipe char
.
Contoh pendeklarasian string:
#include <stdio.h>
int main ()
{
char str[] = "Halo";
return 0;
}
Contoh di atas akan mendeklarasikan string bernama str dengan kapasitas 5 karakter, di mana str[0] = 'H'
, str[1] = 'a'
, str[2] = 'l'
, str[3] = 'o'
, dan str[4] = '\0'
.
Perhatikan bahwa str[4] berisi karakter ‘\0’ (null character), walaupun dalam literal string di atas tidak ada karakter tersebut.
Dalam bahasa C, karakter null digunakan untuk menandakan akhir dari sebuah string.
Contoh pendeklarasian string (2):
#include <stdio.h>
int main () {
char array[10];
return 0;
}
Contoh di atas akan mendeklarasikan string bernama array yang dapat menampung maksimal 10 karakter, termasuk null character.
Untuk menerima input string dari user, kita dapat menggunakan scanf
atau gets
. Perintah scanf
akan membaca inputan string dari user dan berhenti ketika ada whitespace ataupun interupsi dari pengguna. Sedangkan gets
akan membaca satu baris kumpulan karakter hingga enter atau interupsi dari pengguna.
Contoh source code penggunaan scanf
untuk membaca string:
#include <stdio.h>
int main () {
char arr[10];
while(1)
{
scanf("%s", arr);
printf("-- %s\n", arr);
}
return 0;
}
Contoh penggunaan gets
untuk membaca string:
#include <stdio.h>
int main () {
char arr[100];
while(true)
{
gets(arr);
printf("-- %s\n", arr);
}
return 0;
}
String yang dibaca dengan mengunakan scanf
atau gets
akan secara otomatis memiliki null character di akhir.
Dalam bahasa pemrograman C, terdapat library yang dibuat dengan tujuan memudahkan pengguna dalam mengolah string. Library tersebut tersimpan dalam <string.h>
, oleh karena itu, untuk mengakses library ini, diperlukan tambahan preprocessor, yaitu:
#include <string.h>
Berikut adalah fungsi-fungsi yang dibagi berdasarkan kegunaannya dalam mengolah sebuah string (diambil dari www.cplusplus.com):
memcpy
(Copy block of memory)memmove
(Move block of memory)strcpy
(Copy string)strncpy
(Copy characters from string)strcat
(Concatenate strings)strncat
(Append character from string)memcmp
(Compare two blocks of memory)strcmp
(Compare two strings)strcoll
(Compare two strings using locale)strncmp
(Compare characters of two strings)strxfrm
(Transform string using locale)memchr
(Locate character in block of memory)strchr
(Locate first occurrence of character in string)strcspn
(Get span until character in string)strpbrk
(Locate characters in string)strrchr
(Locate last occurrence of character in string)strspn
(Get span of character set in string)strstr
(Locate substring)strtok
(Split string into tokens)memset
(Fill block of memory)strerror
(Get pointer to error message string)strlen
(Get string length)Berikut adalah beberapa fungsi dan penjelasannya.
strcpy
char * strcpy ( char * destination, const char * source );
Fungsi strcpy digunakan untuk melakukan copy dari sebuah string ke string lainnya. Contoh penggunaan dalam kode program:
#include <stdio.h>
#include <string.h>
int main () {
char a[] = "Halo";
char b[10];
// Copy string a ke string b
strcpy(b, a);
printf("%s\n", b);
return 0;
}
strcat
char * strcat ( char * destination, const char * source );
Fungsi strcat digunakan untuk melakukan penempelan sebuah string pada akhir string yang lain. Contoh penggunaan dalam kode program:
#include <stdio.h>
#include <string.h>
int main () {
char a[] = "Halo";
char b[] = " Kawan";
char c[20];
// Copy string a ke string b
strcpy(c, a);
// Tempelkan string b ke akhir string c
strcat(c, b);
printf("%s\n", c);
return 0;
}
strcmp
int strcmp ( const char * str1, const char * str2 );
Fungsi strcmp
digunakan untuk melakukan pembandingan sebuah string dengan string yang lain. Return value dari fungsi ini dapat berupa bilangan negatif, nol ataupun positif. Jika fungsi ini mengembalikan nilai negatif, maka str1 memiliki tingkat leksikografi lebih kecil dari str2. Sedangkan jika fungsi ini mengembalikan nilai postifi, maka str1 memiliki tingkat leksikografi lebih besar dari str2. Terakhir, jika return value-nya nol, maka str1 sama dengan str2.
Berikut adalah contoh penggunaan fungsi ini dalam kode progam:
#include <stdio.h>
#include <string.h>
int main () {
char a[] = "Halo";
char b[] = "Hai";
char c[] = "Halo";
if(strcmp(a, b) == 0) printf("String a sama dengan b\n");
else printf("String a tidak sama dengan b\n");
if(strcmp(a, c) == 0) printf("String a sama dengan c\n");
else printf("String a tidak sama dengan c\n");
return 0;
}
strlen
size_t strlen ( const char * str );
Fungsi strlen
digunakan untuk mengetahui panjang dari sebuah string.
#include <stdio.h>
#include <string.h>
int main () {
char a[] = "Halo";
printf("Panjang string a adalah %d\n", strlen(a));
return 0;
}
Kompleksitas | Sebutan | Contoh |
---|---|---|
O( $1$ ) | Constant Time | Waktu yang dibutuhkan selalu konstan tidak peduli seberapa besar jumlah data. Contohnya mengakses elemen array. |
O( $N$ ) | Linear Time | Waktu yang dibutuhkan sebanding dengan jumlah data. Contohnya Linear Search. |
O( $logN$ ) | Logarithmic Time | Biasanya pada algoritma yang membagi masalah menjadi masalah yang lebih kecil (sub-problem). Contohnya Binary Search. |
O( $NlogN$ ) | Linearithmic Time | Contohnya pada Merge-Sort. |
O( $N^{2}$ ) | Quadratic Time | Contohnya pada Bubble-Sort. |
O( $2^{N}$ ) | Exponential Time | Mencari seluruh subset dari sebuah set memerlukan waktu $2^{N}$. |
O( $N!$ ) | Factorial Time | Mencari seluruh permutasi dari sebuah string dengan panjang N. |
Tree merupakan salah satu bentuk struktur data non-linear yang menggambarkan hubungan yang bersifat hirarkis (hubungan one to many) antara elemen-elemen.
Gambar oleh Paddy3118 - Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=83223854
Tree bisa didefinisikan sebagai kumpulan node dengan elemen khusus yang disebut Root dan referensi ke node lain yang disebut dengan Child. Suatu node dari tree bersama dengan seluruh node di bawahnya membentuk sebuah subtree. Subtree dari sebuah tree juga merupakan sebuah tree.
Binary Search Tree tidak memiliki implementasi STL dalam bahasa C++
Sumber Gambar: https://adrianmejia.com/images/tree-parts.jpg
Binary Tree merupakan tree yang tiap-tiap nodenya mempunyai paling banyak dua child (left child dan right child).
Sumber Gambar: https://media.geeksforgeeks.org/wp-content/uploads/binary_tree-1.png
Binary Search Tree (BST) adalah struktur data Binary Tree berbasis node yang memiliki properti berikut:
Sumber Gambar: https://cdn.programiz.com/sites/tutorial2program/files/bst-vs-not-bst.jpg
Link Implementasi Lengkap Binary Search Tree
dapat dilihat di sini
Sebuah node dalam Binary Search Tree paling dasar mempunyai properti:
typedef struct bstnode_t {
int key;
struct bstnode_t \
*left, *right;
} BSTNode;
typedef struct bst_t {
BSTNode *_root;
unsigned int _size;
} BST;
Untuk melakukan pencarian sebuah key pada Binary Search Tree, dapat kita lakukan secara rekursif maupun iteratif. Implementasi kali ini akan menggunakan cara iteratif.
Utility Function
BSTNode* __search(BSTNode *root, int value) {
while (root != NULL) {
if (value < root->key)
root = root->left;
else if (value > root->key)
root = root->right;
else
return root;
}
return root;
}
Primary Function
bool bst_find(BST *bst, int value) {
BSTNode *temp = __search(bst->_root, value);
if (temp == NULL)
return false;
if (temp->key == value)
return true;
else
return false;
}
Contoh
Sumber Gambar: https://cdn.programiz.com/sites/tutorial2program/files/bst-search-downward-recursion-step.jpg
Key baru selalu dimasukkan pada leaf. Kita mulai mencari key dari root hingga kita menyentuh node leaf. Setelah node leaf ditemukan, node baru ditambahkan sebagai anak dari node leaf.
Utility Function
BSTNode* __insert(BSTNode *root, int value) {
if (root == NULL)
return __createNode(value);
if (value < root->key)
root->left = __insert(root->left, value);
else if (value > root->key)
root->right = __insert(root->right, value);
return root;
}
Primary Function
void bst_insert(BST *bst, int value) {
if (!bst_find(bst, value)) {
bst->_root = __bst__insert(bst->_root, value);
bst->_size++;
}
}
Contoh
Sumber Gambar: https://cdn.programiz.com/sites/tutorial2program/files/bst-downward-recursion-step.jpg
Utility Function
FindMinNode :
BSTNode* __findMinNode(BSTNode *node) {
BSTNode *currNode = node;
while (currNode && currNode->left != NULL)
currNode = currNode->left;
return currNode;
}
Remove :
BSTNode* __remove(BSTNode *root, int value) {
if (root == NULL) return NULL;
if (value > root->key)
root->right = __remove(root->right, value);
else if (value < root->key)
root->left = __remove(root->left, value);
else {
if (root->left == NULL) {
BSTNode *rightChild = root->right;
free(root);
return rightChild;
}
else if (root->right == NULL) {
BSTNode *leftChild = root->left;
free(root);
return leftChild;
}
BSTNode *temp = __findMinNode(root->right);
root->key = temp->key;
root->right = __remove(root->right, temp->key);
}
return root;
}
Primary Function
void bst_remove(BST *bst, int value) {
if (bst_find(bst, value)) {
bst->_root = __bst__remove(bst->_root, value);
bst->_size--;
}
}
Apabila urutan insertion tree kalian 1 2 3 4 5 6 7, maka bentuk tree akan seperti gambar di bawah, dan ini dinamakan Skewed Tree.
Binary Search Tree akan menjadi skewed apabila urutan insertionnya berupa sekuens yang telah urut (baik ascending maupun descending). Skewed BST merupakan bentuk BST yang paling tidak seimbang.
Urutan insertion pada BST, akan mempengaruhi bentuk tree. Misalkan, jika urutan insertionnya 5 2 1 4 3 6 7, maka bentuk tree akan seperti berikut.
void __inorder(BSTNode *root) {
if (root) {
__inorder(root->left);
printf("%d ", root->key);
__inorder(root->right);
}
}
void __postorder(BSTNode *root) {
if (root) {
__postorder(root->left);
__postorder(root->right);
printf("%d ", root->key);
}
}
void __preorder(BSTNode *root) {
if (root) {
printf("%d ", root->key);
__preorder(root->left);
__preorder(root->right);
}
}
Misal pada Binary Search Tree berikut :
Hasil printout maka seperti berikut :
Self-Balancing BST merupakan sebuah binary tree yang dapat menyeimbangkan dirinya sendiri, dalam hal ini meminimalisir perbedaan tinggi (height) saat terjadi insertion atau deletion pada node-node pohon tersebut. Self-Balancing BST ini berguna untuk mempercepat pencarian dalam sebuah binary tree. Dengan menggunakan Self Balancing BST memungkinkan kita mendapatkan kompleksitas waktu O(log n).
Terdapat beberapa tree yang menerapkan konsep Self Blanced BST. Beberapa diantaranya adalah
Red-Black Tree, AVL Tree, B-Tree dan lainya. Setiap Self-Balanced BST mempunyai kondisi tertentu dalam melakukan balancing. Pada Red-Black Tree menggunakan pewarnaan node sebaagi metode balancing.
Pada Bahasa C++, terdapat * STL container* std::set
dan std::map
yang menggunakan Red-Black Tree dalam implementasinya.
Gambar Red-Black Tree. Sumber: https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Red-black_tree_example.svg/1200px-Red-black_tree_example.svg.png
Sementara pada AVL Tree untuk melakukan balancing digunakan perbedaan height antara node kiri dan kanan. Untuk menjaga keseimbangan tingginya maka AVL melakukan rotasi node ketika melakukan insertion dan deletion. Pada Modul ini akan dibahas lebih lanjut mengenai AVL Tree.
AVL tree merupakan sebuah self balanced BST dimana setiap nodenya mempertahankan perbedaan tinggi antara node kiri dan node kananya yang disebut balance factor. Nilai balance factor pada AVL tidak boleh melebihi 1.
AVL memiliki terminology yang sama seperti pada modul sebelumnya, hanya terdapat beberapa tambahan terminology yang belum dibahas sebelumnya.
Link Implementasi Lengkap AVL Tree
dapat dilihat di sini >
Representasi node pada AVL Tree sama dengan BST hanya saja ada tambahan data berupa tinggi pada tiap nodenya.
typedef struct AVLNode_t
{
int data;
struct AVLNode_t *left,*right;
int height;
}AVLNode;
typedef struct AVL_t
{
AVLNode *_root;
unsigned int _size;
}AVL;
Untuk menginisiasi sebuah AVl kita bisa menggunakan fungsi avl_init()
.
void avl_init(AVL *avl) {
avl->_root = NULL;
avl->_size = 0u;
}
Variable height digunakan untuk mendapatkan balance factor dari suatu node.
int _getHeight(AVLNode* node){
if(node==NULL)
return 0;
return node->height;
}
AVL sendiri akan menyeimbangkan dirinya dengan merotasi tree tersebut hingga tree tersebut menjadi balanced.
Terdapat 2 macam rotasi utama yang dipakai dalam avl yaitu Rotasi Right dan Rotasi Left. Untuk menentukan rotasi apa yang harus dilakukan maka pertama kita harus tahu balance factor dari tree tersebut dengan membandingkan selisih antara height subtree kiri dan height subtree kanan.
int _getBalanceFactor(AVLNode* node){
if(node==NULL)
return 0;
return _getHeight(node->left)-_getHeight(node->right);
}
Dari balance factor tersebut kita memiliki 4 kemunkinan kejadian:
Terdapat dua macam rotasi yang digunakan, yaitu rotasi kiri dan kanan.
AVLNode* _rightRotate(AVLNode* pivotNode){
AVLNode* newParrent=pivotNode->left;
pivotNode->left=newParrent->right;
newParrent->right=pivotNode;
pivotNode->height=_max(_getHeight(pivotNode->left),
_getHeight(pivotNode->right))+1;
newParrent->height=_max(_getHeight(newParrent->left),
_getHeight(newParrent->right))+1;
return newParrent;
}
pivotNode
merupakan current Node kita yang akan kita jadikan patokan rotasi.
Untuk rotasi kanan caranya adalah child sebelah kiri dari pivotNode menjadi parent baru. Kemudian anak sebelah kanan dari parent baru akan menjadi left child dari pivotNode. Kemudian pivotNode akan menjadi right child dari parent baru. Kemudian lakukan update height untuk pivotNode dan newParrent node.
Right rotation ini bisa menyelesaikan permasalahan untuk Case Left Skewed.
AVLNode* _leftCaseRotate(AVLNode* node){
return _rightRotate(node);
}
AVLNode* _leftRotate(AVLNode* pivotNode){
AVLNode* newParrent=pivotNode->right;
pivotNode->right=newParrent->left;
newParrent->left=pivotNode;
pivotNode->height=_max(_getHeight(pivotNode->left),
_getHeight(pivotNode->right))+1;
newParrent->height=_max(_getHeight(newParrent->left),
_getHeight(newParrent->right))+1;
return newParrent;
}
Pada rotasi kiri caranya adalah right child dari pivotNode akan menjadi menjadi parent baru. Kemudian left child dari newParent akan menjadi right child pivotNode. Kemudian pivotNode akan menjadi right child dari parent baru. Kemudian lakukan update height untuk pivotNode dan newParrent node.
Left Rotation ini bisa menyelesaikan permasalahan untuk Case Right Skewed.
AVLNode* _rightCaseRotate(AVLNode* node){
return _leftRotate(node);
}
Case left right zigzag bisa diselesaikan menggunakan left rotation diikuti right rotation.
AVLNode* _leftRightCaseRotate(AVLNode* node){
node->left=_leftRotate(node->left);
return _rightRotate(node);
}
AVLNode* _rightLeftCaseRotate(AVLNode* node){
node->right=_rightRotate(node->right);
return _leftRotate(node);
}
Dalam melakukan insert pada sebuah AVL kita perlu terlebih dahulu melakukan insert newnode standar seperti BST. Kemudian update nilai height setiap ancestor newnode. Untuk nilai height pada leaf adalah 1( nilai ini dipilih agar lebih mudah dalam menghandle empty node). Untuk setiap node selain leaf heightnya adalah nilai tertinggi diantara kedua childnya ditambah 1. Kemudian lakukan pengecekan terhadap balance factor setiap ancestor nodenya. Balance factor bisa didapat dari tinggi node kiri dikurangi tinggi node kanan. Jika ditemukan ada node yang tidak balance maka akan dilakukan rotasi.
Untuk membuat sebuah node baru.
AVLNode* _avl_createNode(int value) {
AVLNode *newNode = (AVLNode*) malloc(sizeof(AVLNode));
newNode->data = value;
newNode->height=1;
newNode->left = newNode->right = NULL;
return newNode;
}
Untuk melakukan pencarian node dengan nilai tertentu.
AVLNode* _search(AVLNode *root, int value) {
while (root != NULL) {
if (value < root->data)
root = root->left;
else if (value > root->data)
root = root->right;
else
return root;
}
return root;
}
Fungis utilitas untuk melakukan insert data dalam sebuah AVL.
AVLNode* _insert_AVL(AVL *avl,AVLNode* node,int value){
if(node==NULL)
return _avl_createNode(value);
if(value < node->data)
node->left = _insert_AVL(avl,node->left,value);
else if(value > node->data)
node->right = _insert_AVL(avl,node->right,value);
node->height= 1 + _max(_getHeight(node->left),_getHeight(node->right));
int balanceFactor=_getBalanceFactor(node);
if(balanceFactor > 1 && value < node->left->data)
return _leftCaseRotate(node);
if(balanceFactor > 1 && value > node->left->data)
return _leftRightCaseRotate(node);
if(balanceFactor < -1 && value > node->right->data)
return _rightCaseRotate(node);
if(balanceFactor < -1 && value < node->right->data)
return _rightLeftCaseRotate(node);
return node;
}
Fungsi utama untuk mencari sebuah nilai.
bool avl_find(AVL *avl, int value) {
AVLNode *temp = _search(avl->_root, value);
if (temp == NULL)
return false;
if (temp->data == value)
return true;
else
return false;
}
Fungsi utama untuk memasukan sebuah data.
void avl_insert(AVL *avl,int value){
if(!avl_find(avl,value)){
avl->_root = _insert_AVL(avl,avl->_root,value);
avl->_size++;
}
}
Tidak jauh berbeda dengan melakukan insert pada AVL dalam melakukan remove pada sebuah AVL kita perlu terlebih dahulu melakukan remove standar seperti BST. Kemudian update nilai height setiap ancestor node. Kemudian lakukan pengecekan terhadap balance factor setiap ancestor nodenya. Jika ditemukan ada node yang tidak balance maka akan dilakukan rotasi.
AVLNode* _findMinNode(AVLNode *node) {
AVLNode *currNode = node;
while (currNode && currNode->left != NULL)
currNode = currNode->left;
return currNode;
}
AVLNode* _remove_AVL(AVLNode* node,int value){
if(node==NULL)
return node;
if(value > node->data)
node->right=_remove_AVL(node->right,value);
else if(value < node->data)
node->left=_remove_AVL(node->left,value);
else{
AVLNode *temp;
if((node->left==NULL)||(node->right==NULL)){
temp=NULL;
if(node->left==NULL) temp=node->right;
else if(node->right==NULL) temp=node->left;
if(temp==NULL){
temp=node;
node=NULL;
}
else
*node=*temp;
free(temp);
}
else{
temp = _findMinNode(node->right);
node->data=temp->data;
node->right=_remove_AVL(node->right,temp->data);
}
}
if(node==NULL) return node;
node->height=_max(_getHeight(node->left),_getHeight(node->right))+1;
int balanceFactor= _getBalanceFactor(node);
if(balanceFactor>1 && _getBalanceFactor(node->left)>=0)
return _leftCaseRotate(node);
if(balanceFactor>1 && _getBalanceFactor(node->left)<0)
return _leftRightCaseRotate(node);
if(balanceFactor<-1 && _getBalanceFactor(node->right)<=0)
return _rightCaseRotate(node);
if(balanceFactor<-1 && _getBalanceFactor(node->right)>0)
return _rightLeftCaseRotate(node);
return node;
}
void avl_remove(AVL *avl,int value) {
if(avl_find(avl,value)) {
avl->_root=_remove_AVL(avl->_root,value);
avl->_size--;
}
}
std::set
dan std::map
merupakan STL container yang menerapkan ADT Self-Balancing BST dalam implementasinya.
Jika std::set
hanya menyimpan satu nilai (key), maka std::map
menyimpan sepasang nilai (key and value).
Modul ini akan menjelaskan cara menggunakan kedua STL container tersebut.
std::set
#include <bits/stdc++.h>
using namespace std;
int main(){
set<int> s;
if(s.empty()){
cout << "set ini kosong" << endl;
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main(){
set<int> s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
for(auto i = s.begin(); i != s.end(); i++){
cout << *i << endl;
}
return 0;
}
std::set akan secara otomatis mengurutkan data secara ascending. Ingin menampilkan data secara descending? Gunakanlah fungsi rbegin() dan rend() atau lihat pada contoh dibawah.
#include <bits/stdc++.h>
using namespace std;
int main(){
set<int, greater<int>> s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
for(auto i = s.begin(); i != s.end(); i++){
cout << *i << endl;
}
return 0;
}
catatan: std::set tidak dapat menyimpan dua nilai yang sama
#include <bits/stdc++.h>
using namespace std;
int main(){
set<int> s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
auto pointer = s.find(5);
if(pointer == s.end()){
cout << "data tidak ditemukan" << endl;
}
else{
cout << "data ditemukan" << endl;
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main(){
set<int> s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
s.erase(2);
// tidak menggunakan iterator
for(auto i = s.begin(); i != s.end(); i++){
cout << *i << endl;
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main(){
set<int> s;
s.insert(4);
s.insert(3);
s.insert(2);
s.insert(1);
auto pointer = s.find(2);
s.erase(pointer);
// menggunakan iterator
for(auto i = s.begin(); i != s.end(); i++){
cout << *i << endl;
}
return 0;
}
Selengkapnya mengenai std::set
dapat dibaca di sini atau pada dokumentasi bahasa C++ di sini.
std::map
#include <bits/stdc++.h>
using namespace std;
int main(){
map<int, int> m;
if(m.empty()){
cout << "map ini kosong" << endl;
}
return 0;
}
Ada banyak cara untuk memasukkan nilai pada std::map
.
#include <bits/stdc++.h>
using namespace std;
int main(){
map<int, int> m;
m.insert({1, 2});
// secara langsung
m.insert(make_pair(2, 3));
// menggunakan fungsi make_pair
m.insert(pair<int, int>(3, 4));
// membuat object pair, sama seperti contoh sebelumnya
m[4] = 5;
// menggunakan indexing
m[5]++;
// increment
for(auto i = m.begin(); i != m.end(); i++){
cout << i->first << " " << i->second << endl;
}
return 0;
}
Bagaimana jika kita ingin agar data key dalam std::map
terurut secara descending?
#include <bits/stdc++.h>
using namespace std;
int main(){
map<int, int, greater<int>> m;
m.insert({1, 2});
m.insert(make_pair(2, 3));
m.insert(pair<int, int>(3, 4));
m[4] = 5;
m[5]++;
for(auto i = m.begin(); i != m.end(); i++){
cout << i->first << " " << i->second << endl;
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main(){
map<int, int> m;
m.insert({1, 2});
m.insert(make_pair(2, 3));
m.insert(pair<int, int>(3, 4));
m[4] = 5;
m[5]++;
auto pointer = m.find(1);
/*
menggunakan fungsi find
mengembalikan iterator yang menunjuk pada elemen map
dengan nilai key = 1
*/
if(pointer != m.end()){
cout << pointer->first << " " << pointer->second << endl;
}
cout << m[5] << endl;
/*
indexing, hanya menampilkan value pada elemen map
dengan nilai key = 5
*/
return 0;
}
#include <bits/stdc++.h>
using namespace std;
int main(){
map<int, int> m;
m.insert({1, 2});
m.insert(make_pair(2, 3));
m.insert(pair<int, int>(3, 4));
m[4] = 5;
m[5]++;
m[6] = 6;
m.erase(1);
// menghapus elemen map dengan key = 1
auto pointer1 = m.find(2);
m.erase(pointer1);
// menggunakan iterator dari fungsi find
auto pointer2 = m.find(5);
m.erase(m.begin(), pointer2);
// menghapus semua elemen dengan key < 5
m[5] = 0;
/*
tidak benar-benar menghapus, hanya merubah
value menjadi 0 pada elemen dengan key = 5
*/
for(auto i = m.begin(); i != m.end(); i++){
cout << i->first << " " << i->second << endl;
}
return 0;
}