Struktur data dan algoritma di Java, Bahagian 1: Gambaran keseluruhan

Pengaturcara Java menggunakan struktur data untuk menyimpan dan mengatur data, dan kami menggunakan algoritma untuk memanipulasi data dalam struktur tersebut. Semakin anda memahami tentang struktur data dan algoritma, dan bagaimana ia berfungsi bersama, semakin berkesan program Java anda.

Tutorial ini melancarkan siri pendek yang memperkenalkan struktur data dan algoritma. Dalam Bahagian 1, anda akan mengetahui apa itu struktur data dan bagaimana struktur data dikelaskan. Anda juga akan belajar apa itu algoritma, bagaimana algoritma diwakili, dan bagaimana menggunakan fungsi kerumitan masa dan ruang untuk membandingkan algoritma yang serupa. Setelah anda memperoleh asas-asas ini, anda akan bersedia untuk belajar mencari dan menyusun dengan tatasusunan satu dimensi, di Bahagian 2.

Apakah struktur data?

Struktur data berdasarkan jenis data abstrak (ADT), yang ditakrifkan oleh Wikipedia sebagai berikut:

[A] model matematik untuk jenis data di mana jenis data ditentukan oleh perilakunya (semantik) dari sudut pandang pengguna data, khususnya dari segi nilai yang mungkin, kemungkinan operasi pada data jenis ini, dan tingkah laku operasi ini.

ADT tidak mementingkan perwakilan memori nilainya atau bagaimana operasinya dilaksanakan. Ini seperti antara muka Java, yang merupakan jenis data yang terputus dari pelaksanaan apa pun. Sebaliknya, struktur data adalah pelaksanaan konkrit dari satu atau lebih ADT, serupa dengan bagaimana kelas Java melaksanakan antaramuka.

Contoh ADT termasuk Pekerja, Kenderaan, Array, dan Daftar. Pertimbangkan List ADT (juga dikenali sebagai Sequence ADT), yang menerangkan koleksi elemen yang teratur yang mempunyai jenis yang sama. Setiap elemen dalam koleksi ini mempunyai kedudukannya sendiri dan unsur pendua dibenarkan. Operasi asas yang disokong oleh List ADT merangkumi:

  • Membuat senarai baru dan kosong
  • Menambah nilai ke hujung senarai
  • Memasukkan nilai dalam senarai
  • Memadamkan nilai dari senarai
  • Mengulangi senarai
  • Menghancurkan senarai

Struktur data yang dapat melaksanakan List ADT merangkumi array satu dimensi bersaiz tetap dan bersaiz dinamik dan senarai berangkai tunggal. (Anda akan diperkenalkan dengan tatasusunan di Bahagian 2, dan senarai yang dipautkan di Bahagian 3.)

Mengelaskan struktur data

Terdapat banyak jenis struktur data, mulai dari pemboleh ubah tunggal hingga tatasusunan atau senarai objek yang dipautkan yang mengandungi banyak bidang. Semua struktur data dapat diklasifikasikan sebagai primitif atau agregat, dan beberapa diklasifikasikan sebagai wadah.

Primitif dan agregat

Jenis struktur data yang paling mudah menyimpan item data tunggal; sebagai contoh, pemboleh ubah yang menyimpan nilai Boolean atau pemboleh ubah yang menyimpan bilangan bulat. Saya merujuk struktur data seperti primitif .

Banyak struktur data yang mampu menyimpan pelbagai item data. Sebagai contoh, array boleh menyimpan banyak item data dalam pelbagai slotnya, dan objek dapat menyimpan banyak item data melalui bidangnya. Saya merujuk struktur data ini sebagai agregat .

Semua struktur data yang akan kita lihat dalam siri ini adalah agregat.

Bekas

Apa-apa dari mana item data disimpan dan diambil boleh dianggap sebagai struktur data. Contohnya merangkumi struktur data yang berasal dari ADT Pekerja, Kenderaan, Array, dan Senarai yang disebutkan sebelumnya.

Banyak struktur data yang dirancang untuk menggambarkan pelbagai entiti. Contoh Employeekelas adalah struktur data yang ada untuk menggambarkan pelbagai pekerja, misalnya. Sebaliknya, beberapa struktur data wujud sebagai kapal penyimpanan generik untuk struktur data lain. Sebagai contoh, array boleh menyimpan nilai primitif atau rujukan objek. Saya merujuk kepada struktur data kategori terakhir ini sebagai wadah .

Selain agregat, semua struktur data yang akan kita lihat dalam siri ini adalah bekas.

Struktur dan algoritma data dalam Koleksi Java

Java Collections Framework menyokong banyak jenis struktur data berorientasikan kontena dan algoritma yang berkaitan. Siri ini akan membantu anda memahami kerangka ini dengan lebih baik.

Reka corak dan struktur data

Sudah menjadi kebiasaan untuk menggunakan corak reka bentuk untuk memperkenalkan pelajar universiti dengan struktur data. Makalah Brown University meninjau beberapa corak reka bentuk yang berguna untuk merancang struktur data berkualiti tinggi. Antara lain, kertas menunjukkan bahawa corak Adapter berguna untuk merancang susun dan barisan. Kod demonstrasi ditunjukkan dalam Penyenaraian 1.

Penyenaraian 1. Menggunakan corak Adapter untuk tumpukan dan barisan (DequeStack.java)

public class DequeStack implements Stack { Deque D; // holds the elements of the stack public DequeStack() { D = new MyDeque(); } @Override public int size() { return D.size(); } @Override public boolean isEmpty() { return D.isEmpty(); } @Override public void push(Object obj) { D.insertLast(obj); } @Override public Object top() throws StackEmptyException { try { return D.lastElement(); } catch(DequeEmptyException err) { throw new StackEmptyException(); } } @Override public Object pop() throws StackEmptyException { try { return D.removeLast(); } catch(DequeEmptyException err) { throw new StackEmptyException(); } } }

Listing 1 excerpts the Brown University paper's DequeStack class, which demonstrates the Adapter pattern. Note that Stack and Deque are interfaces that describe Stack and Deque ADTs. MyDeque is a class that implements Deque.

Overriding interface methods

The original code that Listing 1 is based on didn't present the source code to Stack, Deque, and MyDeque. For clarity, I've introduced @Override annotations to show that all of DequeStack's non-constructor methods override Stack methods.

DequeStack adapts MyDeque so that it can implement Stack. All of DequeStack's method are one-line calls to the Deque interface's methods. However, there is a small wrinkle in which Deque exceptions are converted into Stack exceptions.

What is an algorithm?

Historically used as a tool for mathematical computation, algorithms are deeply connected with computer science, and with data structures in particular. An algorithm is a sequence of instructions that accomplishes a task in a finite period of time. Qualities of an algorithm are as follows:

  • Receives zero or more inputs
  • Produces at least one output
  • Consists of clear and unambiguous instructions
  • Terminates after a finite number of steps
  • Is basic enough that a person can carry it out using a pencil and paper

Note that while programs may be algorithmic in nature, many programs do not terminate without external intervention.

Many code sequences qualify as algorithms. One example is a code sequence that prints a report. More famously, Euclid's algorithm is used to calculate the mathematical greatest common divisor. A case could even be made that a data structure's basic operations (such as store value in array slot) are algorithms. In this series, for the most part, I'll focus on higher-level algorithms used to process data structures, such as the Binary Search and Matrix Multiplication algorithms.

Flowcharts and pseudocode

How do you represent an algorithm? Writing code before fully understanding its underlying algorithm can lead to bugs, so what's a better alternative? Two options are flowcharts and pseudocode.

Using flowcharts to represent algorithms

A flowchart is a visual representation of an algorithm's control flow. This representation illustrates statements that need to be executed, decisions that need to be made, logic flow (for iteration and other purposes), and terminals that indicate start and end points. Figure 1 reveals the various symbols that flowcharts use to visualize algorithms.

Consider an algorithm that initializes a counter to 0, reads characters until a newline (\n) character is seen, increments the counter for each digit character that's been read, and prints the counter's value after the newline character has been read. The flowchart in Figure 2 illustrates this algorithm's control flow.

A flowchart's simplicity and its ability to present an algorithm's control flow visually (so that it's is easy to follow) are its major advantages. Flowcharts also have several disadvantages, however:

  • It's easy to introduce errors or inaccuracies into highly-detailed flowcharts because of the tedium associated with drawing them.
  • It takes time to position, label, and connect a flowchart's symbols, even using tools to speed up this process. This delay might slow your understanding of an algorithm.
  • Flowcharts belong to the structured programming era and aren't as useful in an object-oriented context. In contrast, the Unified Modeling Language (UML) is more appropriate for creating object-oriented visual representations.

Using pseudocode to represent algorithms

An alternative to flowcharts is pseudocode, which is a textual representation of an algorithm that approximates the final source code. Pseudocode is useful for quickly writing down an algorithm's representation. Because syntax is not a concern, there are no hard-and-fast rules for writing pseudocode.

You should strive for consistency when writing pseudocode. Being consistent will make it much easier to translate the pseudocode into actual source code. For example, consider the following pseudocode representation of the previous counter-oriented flowchart:

 DECLARE CHARACTER ch = '' DECLARE INTEGER count = 0 DO READ ch IF ch GE '0' AND ch LE '9' THEN count = count + 1 END IF UNTIL ch EQ '\n' PRINT count END

The pseudocode first presents a couple of DECLARE statements that introduce variables ch and count, initialized to default values. It then presents a DO loop that executes UNTILch contains \n (the newline character), at which point the loop ends and a PRINT statement outputs count's value.

For each loop iteration, READ causes a character to be read from the keyboard (or perhaps a file--in this case it doesn't matter what constitutes the underlying input source) and assigned to ch. If this character is a digit (one of 0 through 9), count is incremented by 1.

Choosing the right algorithm

The data structures and algorithms you use critically affect two factors in your applications:

  1. Memory usage (for data structures).
  2. CPU time (for algorithms that interact with those data structures).

It follows that you should be especially mindful of the algorithms and data structures you use for applications that will process lots of data. These include applications used for big data and the Internet of Things.

Balancing memory and CPU

When choosing a data structure or algorithm, you will sometimes discover an inverse relationship between memory usage and CPU time: the less memory a data structure uses, the more CPU time associated algorithms need to process the data structure's data items. Also, the more memory a data structure uses, the less CPU time associated algorithms will need to process the data items–leading to faster algorithm results.

As much as possible, you should strive to balance memory use with CPU time. You can simplify this task by analyzing algorithms to determine their efficiency. How well does one algorithm perform against another of similar nature? Answering this question will help you make good choices given a choice between multiple algorithms.

Measuring algorithm efficiency

Some algorithms perform better than others. For example, the Binary Search algorithm is almost always more efficient than the Linear Search algorithm–something you'll see for yourself in Part 2. You want to choose the most efficient algorithm for your application's needs, but that choice might not be as obvious as you would think.

For instance, what does it mean if the Selection Sort algorithm (introduced in Part 2) takes 0.4 seconds to sort 10,000 integers on a given machine? That benchmark is only valid for that particular machine, that particular implementation of the algorithm, and for the size of the input data.

As computer scientist, we use time complexity and space complexity to measure an algorithm's efficiency, distilling these into complexity functions to abstract implementation and runtime environment details. Complexity functions reveal the variance in an algorithm's time and space requirements based on the amount of input data:

  • A time-complexity function measures an algorithm's time complexity--meaning how long an algorithm takes to complete.
  • A fungsi ruang-kerumitan mengukur algoritma ini kerumitan ruang --meaning jumlah memori overhead diperlukan oleh algoritma untuk melaksanakan tugasnya.

Kedua-dua fungsi kerumitan berdasarkan ukuran input ( n ), yang entah bagaimana mencerminkan jumlah data input. Pertimbangkan pseudocode berikut untuk percetakan array:

 DECLARE INTEGER i, x[] = [ 10, 15, -1, 32 ] FOR i = 0 TO LENGTH(x) - 1 PRINT x[i] NEXT i END

Kerumitan masa dan fungsi kerumitan masa

Anda dapat menyatakan kerumitan waktu algoritma ini dengan menentukan fungsi kerumitan masa , di mana (pengganda tetap) mewakili jumlah masa untuk menyelesaikan lelaran satu gelung, dan mewakili masa penyediaan algoritma. Dalam contoh ini, kerumitan masa adalah linear.t(n) = an+bab