Cấu trúc dữ liệu động trong python là gì?

Dynamic programming is breaking down a problem into smaller sub-problems, solving each sub-problem and storing the solutions to each of these sub-problems in an array [or similar data structure] so each sub-problem is only calculated once

Nó vừa là phương pháp tối ưu hóa toán học vừa là phương pháp lập trình máy tính

Optimisation problems seek the maximum or minimum solution. The general rule is that if you encounter a problem where the initial algorithm is solved in O[2n] time, it is better solved using Dynamic Programming

Why Is Dynamic Programming Called Dynamic Programming?

Richard Bellman đã phát minh ra DP vào những năm 1950. bởi vì vào thời điểm đó, RAND [chủ nhân của anh ấy], không thích nghiên cứu toán học và không muốn tài trợ cho nó. Anh ấy đặt tên nó là Lập trình động để che giấu sự thật rằng anh ấy thực sự đang nghiên cứu toán học.

Bellman explains the reasoning behind the term Dynamic Programming in his autobiography, Eye of the Hurricane. An Autobiography [1984, page 159]. He explains

“I spent the Fall quarter [of 1950] at RAND

My first task was to find a name for multistage decision processes. An interesting question is, Where did the name, dynamic programming, come from?

The 1950s were not good years for mathematical research. We had a very interesting gentleman in Washington named Wilson. He was Secretary of Defense, and he actually had a pathological fear and hatred of the word research. I’m not using the term lightly; I’m using it precisely

His face would suffuse, he would turn red, and he would get violent if people used the term research in his presence. You can imagine how he felt, then, about the term mathematical. The RAND Corporation was employed by the Air Force, and the Air Force had Wilson as its boss, essentially. Hence, I felt I had to do something to shield Wilson and the Air Force from the fact that I was really doing mathematics inside the RAND Corporation. What title, what name, could I choose? In the first place I was interested in planning, in decision making, in thinking. But planning, is not a good word for various reasons. I decided therefore to use the word “programming”. I wanted to get across the idea that this was dynamic, this was multistage, this was time-varying. I thought, let’s kill two birds with one stone. Let’s take a word that has an absolutely precise meaning, namely dynamic, in the classical physical sense. It also has a very interesting property as an adjective, and that is it’s impossible to use the word dynamic in a pejorative sense. Try thinking of some combination that will possibly give it a pejorative meaning. It’s impossible. Vì vậy, tôi nghĩ lập trình động là một cái tên hay. Đó là điều mà ngay cả một Nghị sĩ cũng không thể phản đối. Vì vậy, tôi đã sử dụng nó như một chiếc ô cho các hoạt động của mình. ”
Vấn đề phụ là gì?

Vấn đề phụ là phiên bản nhỏ hơn của vấn đề ban đầu. Hãy xem một ví dụ. Với phương trình dưới đây.

$$1 + 2 + 3 + 4$$

Chúng ta có thể phá vỡ điều này để

$$1+2$$

$$3+4$$

Khi chúng tôi giải quyết hai vấn đề nhỏ hơn này, chúng tôi có thể thêm các giải pháp cho các vấn đề phụ này để tìm giải pháp cho vấn đề tổng thể

Lưu ý cách các vấn đề phụ này chia nhỏ vấn đề ban đầu thành các thành phần tạo nên giải pháp. Đây là một ví dụ nhỏ nhưng nó minh họa rõ nét vẻ đẹp của Lập trình động. Nếu chúng tôi mở rộng vấn đề để thêm 100 số thì sẽ rõ ràng hơn tại sao chúng tôi cần Lập trình động. Lấy ví dụ này

$$6 + 5 + 3 + 3 + 2 + 4 + 6 + 5$$

Ta có 6 + 5 hai lần. Lần đầu tiên chúng tôi nhìn thấy nó, chúng tôi tính ra 6+56+5. Khi chúng ta nhìn thấy nó lần thứ hai, chúng ta tự nghĩ

“À, 6+5. Tôi đã thấy điều này trước đây. Bây giờ là 11. ”

Trong Lập trình động, chúng tôi lưu trữ giải pháp cho vấn đề vì vậy chúng tôi không cần tính toán lại nó. Bằng cách tìm giải pháp cho từng vấn đề nhỏ, chúng ta có thể tự giải quyết vấn đề ban đầu

* Ghi nhớ * là hành động lưu trữ một giải pháp

Ghi nhớ trong lập trình động là gì?

Hãy xem tại sao việc lưu trữ câu trả lời cho các giải pháp lại có ý nghĩa. Chúng ta sẽ xem xét một bài toán nổi tiếng, dãy Fibonacci. Vấn đề này thường được giải quyết trong Chia để trị

Có 3 phần chính để phân chia và chinh phục

  1. Chia bài toán thành các bài toán con nhỏ hơn cùng loại
  2. Chinh phục - giải quyết các vấn đề phụ một cách đệ quy
  3. Kết hợp - Kết hợp tất cả các vấn đề phụ để tạo ra giải pháp cho vấn đề ban đầu

Lập trình động có thêm một bước được thêm vào bước 2. Đây là ghi nhớ

Dãy Fibonacci là dãy số. Đó là số cuối cùng + số hiện tại. Chúng tôi bắt đầu từ 1

$$1 + 0 = 1$$

$$1 + 1 = 2$$

$$2 + 1 = 3$$

$$3 + 2 = 5$$

$$5 + 3 = 8$$

Trong Python, đây là

def F[n]:
    if n == 0 or n == 1:
    return n
    else:
    return F[n-1]+F[n-2]

Nếu bạn không quen thuộc với đệ quy, tôi có một bài đăng trên blog dành cho bạn mà bạn nên đọc trước

Hãy tính F[4]. Trong một cây thực thi, điều này trông giống như

Chúng tôi tính F[2] hai lần. Trên các đầu vào lớn hơn [chẳng hạn như F[10]], sự lặp lại tăng lên. Mục đích của quy hoạch động là không tính toán cùng một thứ hai lần

Thay vì tính F[2] hai lần, chúng ta cất nghiệm ở đâu đó và chỉ tính một lần

Chúng tôi sẽ lưu trữ giải pháp trong một mảng. F[2] = 1. Dưới đây là một số mã Python để tính toán chuỗi Fibonacci bằng Lập trình động

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]

Cách xác định các vấn đề về lập trình động

Về lý thuyết, Lập trình động có thể giải quyết mọi vấn đề. câu hỏi là sau đó

“Khi nào tôi nên giải quyết vấn đề này bằng quy hoạch động?”

Chúng ta nên sử dụng lập trình động cho các vấn đề nằm giữa các vấn đề *có thể giải quyết được *và *không thể giải quyết được *

Các bài toán có thể giải được là những bài toán có thể giải trong thời gian đa thức. Đó là một cách nói thú vị để nói rằng chúng ta có thể giải quyết nó một cách nhanh chóng. Tìm kiếm và sắp xếp nhị phân đều nhanh. Các vấn đề nan giải là những vấn đề chạy theo thời gian theo cấp số nhân. Họ chậm chạp. Các vấn đề khó giải quyết là những vấn đề chỉ có thể được giải quyết bằng cách sử dụng vũ phu thông qua mọi kết hợp đơn lẻ [NP khó]

Khi chúng ta thấy các thuật ngữ như

“ngắn nhất/dài nhất, thu nhỏ/tối đa, ít nhất/nhiều nhất, ít nhất/lớn nhất, “lớn nhất/nhỏ nhất”

Chúng tôi biết đó là một vấn đề tối ưu hóa

Bằng chứng về tính đúng đắn của các thuật toán Lập trình động thường là hiển nhiên. Các chiến lược thuật toán khác thường khó chứng minh hơn là đúng. Do đó, dễ bị lỗi hơn

Khi chúng ta thấy các loại thuật ngữ này, bài toán có thể yêu cầu một số cụ thể [“tìm số thao tác chỉnh sửa ít nhất”] hoặc có thể yêu cầu một kết quả [“tìm dãy con chung dài nhất”]. Loại vấn đề thứ hai khó nhận ra hơn là một vấn đề lập trình động. Nếu một cái gì đó nghe có vẻ như tối ưu hóa, Lập trình động có thể giải quyết nó

Hãy tưởng tượng chúng tôi đã tìm thấy một vấn đề là vấn đề tối ưu hóa, nhưng chúng tôi không chắc liệu vấn đề đó có thể được giải quyết bằng Lập trình động hay không. Đầu tiên, xác định những gì chúng tôi đang tối ưu hóa cho. Khi chúng tôi nhận ra những gì chúng tôi đang tối ưu hóa, chúng tôi phải quyết định mức độ dễ dàng để thực hiện tối ưu hóa đó. Đôi khi, cách tiếp cận tham lam là đủ cho một giải pháp tối ưu

Lập trình động có cách tiếp cận vũ phu. Nó xác định công việc lặp đi lặp lại và loại bỏ sự lặp lại

Trước khi chúng ta bắt đầu lập kế hoạch vấn đề như một vấn đề lập trình động, hãy nghĩ về giải pháp vũ phu có thể trông như thế nào. Các bước phụ có được lặp lại trong giải pháp brute-force không?

Nắm vững lập trình động là tất cả về việc hiểu vấn đề. Liệt kê tất cả các yếu tố đầu vào có thể ảnh hưởng đến câu trả lời. Khi chúng tôi đã xác định tất cả các đầu vào và đầu ra, hãy thử xác định xem vấn đề có thể được chia thành các vấn đề con hay không. Nếu chúng ta có thể xác định các vấn đề con, có lẽ chúng ta có thể sử dụng Lập trình động

Sau đó, tìm ra sự lặp lại là gì và giải quyết nó. Khi chúng tôi đang cố gắng tìm ra sự lặp lại, hãy nhớ rằng bất kỳ sự lặp lại nào chúng tôi viết đều phải giúp chúng tôi tìm ra câu trả lời. Đôi khi câu trả lời sẽ là kết quả của phép lặp, và đôi khi chúng ta sẽ phải nhận được kết quả bằng cách xem xét một vài kết quả từ phép lặp

Lập trình động có thể giải quyết nhiều vấn đề, nhưng điều đó không có nghĩa là không có giải pháp nào hiệu quả hơn ngoài kia. Giải quyết một vấn đề với Lập trình động giống như phép thuật, nhưng hãy nhớ rằng lập trình động chỉ là một vũ phu thông minh. Đôi khi nó mang lại kết quả tốt, và đôi khi nó chỉ giúp ích một chút

---

Cách giải quyết vấn đề bằng lập trình động



Bây giờ chúng ta đã hiểu Lập trình động là gì và nó hoạt động như thế nào. Hãy xem xét để tạo ra một giải pháp Lập trình động cho một vấn đề. Chúng ta sẽ khám phá quy trình Lập trình động bằng cách sử dụng Bài toán lập lịch theo khoảng thời gian có trọng số

Giả vờ bạn là chủ tiệm giặt khô. Bạn có n khách hàng đến và đưa cho bạn quần áo để giặt sạch. Bạn chỉ có thể giặt đống quần áo [PoC] của một khách hàng tại một thời điểm. Mỗi đống quần áo, i, phải được giặt sạch vào một số thời gian bắt đầu được xác định trước sisi​ và một số thời điểm kết thúc được xác định trước \[f_i\]

Mỗi đống quần áo có một giá trị liên quan, \[v_i\], dựa trên tầm quan trọng của nó đối với doanh nghiệp của bạn. Ví dụ: một số khách hàng có thể trả nhiều tiền hơn để giặt quần áo nhanh hơn. Hoặc một số có thể là khách hàng lặp lại và bạn muốn họ hài lòng

Là chủ sở hữu của tiệm giặt khô này, bạn phải xác định lịch trình quần áo tối ưu để tối đa hóa tổng giá trị của ngày này. Vấn đề này là một diễn đạt lại của vấn đề lịch trình khoảng thời gian trọng số

Bây giờ bạn sẽ thấy 4 bước để giải bài toán Quy hoạch động. Đôi khi, bạn có thể bỏ qua một bước. Đôi khi, vấn đề của bạn đã được xác định rõ ràng và bạn không cần phải lo lắng về những bước đầu tiên

Bước 1. Viết vấn đề ra

Lấy một mảnh giấy. Chép lại

  • Vấn đề là gì?
  • Các vấn đề phụ là gì?
  • Giải pháp đại khái sẽ như thế nào?

Trong bài toán máy giặt khô, hãy viết thành lời các bài toán con. Những gì chúng tôi muốn xác định là lịch trình giá trị tối đa cho mỗi đống quần áo sao cho quần áo được sắp xếp theo thời gian bắt đầu

Tại sao sắp xếp theo thời gian bắt đầu? . Chúng tôi muốn theo dõi các quy trình hiện đang chạy. Nếu chúng ta sắp xếp theo thời gian hoàn thành, điều đó không có ý nghĩa gì trong đầu chúng ta. Chúng tôi có thể có 2 với thời gian kết thúc tương tự, nhưng thời gian bắt đầu khác nhau. Thời gian di chuyển theo kiểu tuyến tính, từ đầu đến cuối. Nếu chúng tôi có đống quần áo bắt đầu lúc 1 giờ chiều, chúng tôi biết mặc vào khi đến 1 giờ chiều. Nếu chúng tôi có một đống quần áo hoàn thành lúc 3 giờ chiều, chúng tôi có thể phải mặc chúng vào lúc 12 giờ đêm, nhưng bây giờ là 1 giờ chiều

Chúng ta có thể tìm lịch biểu giá trị lớn nhất cho cọc \[n-1\] đến n. Và sau đó cho \[n-2\] đến n. Và như thế. Bằng cách tìm ra giải pháp cho từng vấn đề nhỏ, chúng ta có thể tự giải quyết vấn đề ban đầu. Bảng giá trị tối đa cho các cọc từ 1 đến n. Các bài toán con có thể được sử dụng để giải bài toán gốc, vì chúng là các phiên bản nhỏ hơn của bài toán gốc

Với bài toán lập lịch theo khoảng thời gian, cách duy nhất chúng ta có thể giải quyết là sử dụng brute-force tất cả các tập con của bài toán cho đến khi chúng ta tìm được một bài toán tối ưu. Những gì chúng tôi đang nói là thay vì vũ phu từng người một, chúng tôi chia nó ra. Chúng tôi dùng vũ lực từ \[n−1\] đến n. Sau đó, chúng tôi làm tương tự cho \[n−2\] đến n. Cuối cùng, chúng tôi có vô số vấn đề nhỏ hơn mà chúng tôi có thể giải quyết một cách linh hoạt. Chúng tôi muốn xây dựng các giải pháp cho các vấn đề phụ của mình sao cho mỗi vấn đề phụ được xây dựng dựa trên các vấn đề trước đó

2. lặp lại toán học

Tôi biết, toán học hút. Nếu bạn cởi mở với tôi ở đây, bạn sẽ thấy rằng điều này không khó lắm. Phép lặp toán học được sử dụng để

Xác định thời gian chạy của kỹ thuật chia để trị [lập trình động]

Sự lặp lại cũng được sử dụng để xác định các vấn đề. Nếu khó biến các bài toán con của bạn thành toán học, thì đó có thể là bài toán con sai

Có 2 bước để tạo một phép toán lặp lại

1. Xác định trường hợp cơ sở

Các trường hợp cơ sở là mệnh giá nhỏ nhất có thể của một vấn đề

Khi tạo lặp lại, hãy tự hỏi mình những câu hỏi sau

“Tôi đưa ra quyết định gì ở bước 0?”

Nó không nhất thiết phải là 0. Trường hợp cơ sở là mệnh giá nhỏ nhất có thể của một vấn đề. Chúng ta đã thấy điều này với dãy Fibonacci. cơ sở là

  • Nếu n == 0 hoặc n == 1, trả về 1

Điều quan trọng là phải biết trường hợp cơ sở nằm ở đâu, để chúng tôi có thể tạo sự lặp lại. Trong vấn đề của chúng tôi, chúng tôi có một quyết định để thực hiện

  • Mang đống quần áo đó đi giặt

hoặc là

  • Hôm nay đừng giặt đống quần áo đó

If n is 0, that is, if we have 0 PoC then we do nothing. Our base case is

if n == 0, return 0

2. What Decision Do I Make at Step n?

Now we know what the base case is, if we’re at step n what do we do? For each pile of clothes that is compatible with the schedule so far. Compatible means that the start time is after the finish time of the pile of clothes currently being washed. The algorithm has 2 options

  1. Wash that pile of clothes
  2. Don’t wash that pile of clothes

We know what happens at the base case, and what happens else. We now need to find out what information the algorithm needs to go backwards [or forwards]

“If my algorithm is at step i, what information would it need to decide what to do in step i+1?”

To decide between the two options, the algorithm needs to know the next compatible PoC [pile of clothes]. The next compatible PoC for a given pile, p, is the PoC, n, such thatsnsn​[the start time for PoC n] happens after \[f_p\]​ [the finish time for PoC p]. The difference between snsn​ and \[f_p\] should be minimised

In English, imagine we have one washing machine. We put in a pile of clothes at 13. 00. Our next pile of clothes starts at 13. 01. We can’t open the washing machine and put in the one that starts at 13. 00. Đống quần áo tương thích tiếp theo của chúng tôi là đống quần áo bắt đầu sau thời gian kết thúc của đống quần áo đang được giặt

“Nếu thuật toán của tôi đang ở bước i, thì nó cần thông tin gì để quyết định phải làm gì ở bước i-1?”

Thuật toán cần biết về các quyết định trong tương lai. Những cái được tạo cho PoC i đến n để quyết định chạy hay không chạy PoC i-1

Bây giờ chúng tôi đã trả lời những câu hỏi này, chúng tôi đã bắt đầu hình thành một quyết định toán học định kỳ trong tâm trí của chúng tôi. Nếu không, điều đó cũng không sao, việc viết các phép lặp sẽ trở nên dễ dàng hơn khi chúng ta tiếp xúc với nhiều vấn đề hơn

Đây là sự lặp lại của chúng tôi

$$ OPT[i] = \begin{cases} 0, \quad \text{If i = 0} \\ max{v_i + OPT[next[i]], OPT[i+1]},  \quad \text

Hãy khám phá chi tiết điều gì tạo nên sự lặp lại toán học này. OPT[i] biểu thị lịch trình giá trị tối đa cho PoC i đến n sao cho PoC được sắp xếp theo thời gian bắt đầu. OPT[i] là bài toán con của chúng ta từ trước đó

Chúng tôi bắt đầu với trường hợp cơ sở. Tất cả các lần lặp lại cần một nơi nào đó để dừng lại. Nếu chúng tôi gọi OPT[0], chúng tôi sẽ được trả về 0

Để xác định giá trị của OPT[i], có hai tùy chọn. Chúng tôi muốn tận dụng tối đa các tùy chọn này để đạt được mục tiêu của mình. Mục tiêu của chúng tôi là lịch trình giá trị tối đa cho tất cả đống quần áo. Khi chúng tôi chọn tùy chọn mang lại kết quả tối đa ở bước i, chúng tôi ghi nhớ giá trị của nó là OPT[i]

Về mặt toán học, hai tùy chọn - chạy hoặc không chạy PoC i, được biểu diễn dưới dạng

$$v_i + OPT[tiếp theo[n]]$$

Điều này thể hiện quyết định chạy PoC i. Nó thêm giá trị thu được từ PoC i vào OPT[next[n]], trong đó next[n] đại diện cho đống quần áo tương thích tiếp theo sau PoC i. Khi chúng ta cộng hai giá trị này lại với nhau, chúng ta sẽ nhận được lịch trình giá trị lớn nhất từ ​​i đến n sao cho chúng được sắp xếp theo thời gian bắt đầu nếu tôi chạy

Được sắp xếp theo thời gian bắt đầu ở đây vì next[n] là cái ngay sau \[v_i\], do đó, theo mặc định, chúng được sắp xếp theo thời gian bắt đầu

$$OPT[i + 1]$$

Nếu chúng tôi quyết định không chạy i, thì giá trị của chúng tôi là OPT[i + 1]. Giá trị không đạt được. OPT[i + 1] đưa ra lịch trình giá trị lớn nhất cho i+1 đến n, sao cho chúng được sắp xếp theo thời gian bắt đầu

3. Xác định kích thước của mảng ghi nhớ và hướng mà nó sẽ được lấp đầy

Giải pháp cho vấn đề Lập trình động của chúng tôi là OPT[1]. Chúng ta có thể viết ra giải pháp dưới dạng lịch trình giá trị lớn nhất cho PoC 1 đến n sao cho PoC được sắp xếp theo thời gian bắt đầu. Điều này đi đôi với “lịch trình giá trị tối đa cho PoC từ i đến n”

từ bước 2

$$OPT[1] = max[v_1 + OPT[next[1]], OPT[2]]$$

Quay trở lại với các số Fibonacci trước đó, giải pháp Lập trình động của chúng tôi dựa trên thực tế là các số Fibonacci từ 0 đến n - 1 đã được ghi nhớ. Tức là để tìm F[5] ta đã ghi nhớ F[0], F[1], F[2], F[3], F[4]. Chúng tôi muốn làm điều tương tự ở đây

Vấn đề chúng tôi gặp phải là tìm cách điền vào bảng ghi nhớ. Trong bài toán lập lịch, chúng ta biết rằng OPT[1] dựa vào nghiệm của OPT[2] và OPT[next[1]]. PoC 2 và next[1] có thời gian bắt đầu sau PoC 1 do sắp xếp. Chúng ta cần điền vào bảng ghi nhớ từ OPT[in] đến OPT[1]

Chúng ta có thể thấy mảng của chúng ta là một chiều, từ 1 đến n. Nhưng, nếu chúng ta không thể thấy rằng chúng ta có thể giải quyết theo cách khác. Kích thước của mảng bằng với số lượng và kích thước của các biến mà OPT[x] dựa vào. Trong thuật toán của chúng tôi, chúng tôi có OPT[i] - một biến, i. Điều này có nghĩa là mảng của chúng ta sẽ là 1 chiều và kích thước của nó sẽ là n, vì có n đống quần áo

Nếu chúng ta biết rằng n = 5, thì mảng ghi nhớ của chúng ta có thể trông như thế này

memo = [0, OPT[1], OPT[2], OPT[3], OPT[4], OPT[5]]

0 cũng là trường hợp cơ sở. memo[0] = 0, theo sự lặp lại của chúng tôi từ trước đó

4. Mã hóa giải pháp của chúng tôi

Khi tôi mã hóa một giải pháp Lập trình động, tôi muốn đọc phần lặp lại và cố gắng tạo lại nó. Bước đầu tiên của chúng tôi là khởi tạo mảng thành kích thước [n + 1]. Trong Python, chúng ta không cần phải làm điều này. Nhưng bạn có thể cần phải làm điều đó nếu bạn đang sử dụng một ngôn ngữ khác

Bước thứ hai của chúng tôi là thiết lập trường hợp cơ sở

Để tìm kiếm lợi nhuận với việc bao gồm công việc[i]. chúng tôi cần tìm công việc mới nhất không xung đột với công việc[i]. Ý tưởng là sử dụng Tìm kiếm nhị phân để tìm công việc không xung đột mới nhất. Tôi đã sao chép mã từ đây nhưng đã chỉnh sửa

Đầu tiên, hãy định nghĩa “công việc” là gì. Như chúng ta đã thấy, một công việc bao gồm 3 điều

    # Class to represent a job 
class Job: 
    def __init__[self, start, finish, profit]: 
        self.start = start 
        self.finish = finish 
        self.profit = profit 

Thời gian bắt đầu, thời gian kết thúc và tổng lợi nhuận [lợi ích] của việc thực hiện công việc đó

Bước tiếp theo chúng tôi muốn lập trình là lịch trình

# The main function that returns the maximum possible 
# profit from given array of jobs
def schedule[job]: 
    # Sort jobs according to start time 
    job = sorted[job, key = lambda j: j.start] 

    # Create an array to store solutions of subproblems. table[i] 
    # stores the profit for jobs till arr[i] [including arr[i]] 
    n = len[job] 
    table = [0 for _ in range[n]] 

    table[0] = job[0].profit

Trước đó, chúng ta đã biết rằng bảng là 1 chiều. Chúng tôi sắp xếp các công việc theo thời gian bắt đầu, tạo bảng trống này và đặt bảng[0] là lợi nhuận của công việc[0]. Vì chúng tôi đã sắp xếp theo thời gian bắt đầu nên công việc tương thích đầu tiên luôn là công việc[0]

Bước tiếp theo của chúng tôi là điền vào các mục bằng cách sử dụng phép lặp mà chúng tôi đã học trước đó. Để tìm công việc tương thích tiếp theo, chúng tôi đang sử dụng Tìm kiếm nhị phân. Trong mã đầy đủ được đăng sau này, nó sẽ bao gồm điều này. Còn bây giờ thì lo tìm hiểu thuật toán thôi

Nếu công việc tương thích tiếp theo trả về -1, điều đó có nghĩa là tất cả các công việc trước chỉ mục, i, xung đột với nó [vì vậy không thể sử dụng được].

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
4 có nghĩa là chúng tôi đang bao gồm mặt hàng đó trong tập hợp giá trị tối đa. Sau đó, chúng tôi lưu trữ nó trong bảng [i], vì vậy chúng tôi có thể sử dụng lại phép tính này sau

    # Fill entries in table[] using recursive property 
    for i in range[1, n]: 

        # Find profit including the current job 
        inclProf = job[i].profit 
        l = binarySearch[job, i] 
        if [l != -1]: 
            inclProf += table[l]; 

        # Store maximum of including and excluding 
        table[i] = max[inclProf, table[i - 1]] 

Bước cuối cùng của chúng tôi sau đó là trả về lợi nhuận của tất cả các mặt hàng lên đến n-1

    	return table[n-1] 

Mã đầy đủ có thể được nhìn thấy dưới đây

________số 8_______

chúc mừng. 🥳 Chúng tôi vừa viết chương trình động đầu tiên. Bây giờ chúng ta đã bắt tay vào làm việc,  hãy xem qua một loại vấn đề lập trình động khác



vấn đề ba lô

Hãy tưởng tượng bạn là một tên tội phạm. Thông minh ngu xuẩn. Bạn đột nhập vào biệt thự của Bill Gates. Ồ, được. ?. ? . Bạn đã mang theo một chiếc túi nhỏ. Một chiếc ba lô - nếu bạn muốn

Bạn chỉ có thể phù hợp với rất nhiều vào nó. Hãy cho đây là một số tùy ý. Túi sẽ hỗ trợ trọng lượng 15, nhưng không hơn. Những gì chúng tôi muốn làm là tối đa hóa số tiền chúng tôi sẽ kiếm được, b

Cách tiếp cận tham lam là chọn món đồ có giá trị cao nhất có thể vừa với túi. Hãy thử điều đó. Chúng tôi sẽ đánh cắp TV của Bill Gates. £4000? . Nhưng TV của anh ấy nặng 15. Vì vậy… Chúng tôi rời đi với £4000

TV = [£4000, 15]
# [value, weight]

Bill Gates có rất nhiều đồng hồ. Giả sử anh ấy có 2 chiếc đồng hồ. Mỗi chiếc đồng hồ nặng 5 chiếc và mỗi chiếc trị giá £2250. Khi chúng tôi đánh cắp cả hai, chúng tôi nhận được £4500 với trọng lượng là 10

watch1 = [£2250, 5]
watch2 = [£2250, 5]
watch1 + watch2
>>> [£4500, 10]

Theo cách tiếp cận tham lam, chúng tôi sẽ không chọn những chiếc đồng hồ này trước. Nhưng đối với chúng ta là con người, thật hợp lý khi tìm kiếm những món đồ nhỏ hơn có giá trị cao hơn. Phương pháp tham lam không thể giải quyết tối ưu bài toán Ba lô {0,1}. {0, 1} có nghĩa là chúng tôi lấy toàn bộ mặt hàng {1} hoặc chúng tôi không {0}. Tuy nhiên, Quy hoạch động có thể giải bài toán cái ba lô {0, 1} một cách tối ưu

Giải pháp đơn giản cho vấn đề này là xem xét tất cả các tập con của tất cả các mục. Đối với mỗi sự kết hợp đơn lẻ của những thứ của Bill Gates, chúng tôi tính toán tổng trọng lượng và giá trị của sự kết hợp này

Chỉ những người có trọng lượng nhỏ hơn \[w_max\]​ mới được xem xét. Sau đó, chúng tôi chọn kết hợp có giá trị cao nhất. Đây là một thảm họa. Điều này sẽ mất bao lâu? . Trong Big O, thuật toán này mất O[n2] thời gian

Bạn có thể thấy chúng tôi đã có một ý tưởng sơ bộ về giải pháp và vấn đề là gì mà không cần phải viết nó ra trong toán học

Toán đằng sau bài toán chiếc ba lô {0, 1}

Hãy tưởng tượng chúng ta có một danh sách mọi thứ trong nhà của Bill Gates. Chúng tôi đã đánh cắp nó từ một số giấy tờ bảo hiểm. Bây giờ, hãy nghĩ về tương lai. Giải pháp tối ưu cho vấn đề này là gì?

Ta có tập con L là nghiệm tối ưu. L là tập hợp con của S, tập hợp chứa tất cả những thứ của Bill Gates

Hãy chọn một mục ngẫu nhiên, N. L có chứa N hoặc không. Nếu nó không sử dụng N, giải pháp tối ưu cho vấn đề giống như 1,2,…,N−1. Điều này giả định rằng đồ đạc của Bill Gates được sắp xếp theo giá trị/trọng lượng

Giả sử rằng tối ưu của bài toán ban đầu không phải là tối ưu của bài toán con. nếu chúng ta có tối ưu phụ của vấn đề nhỏ hơn thì chúng ta có mâu thuẫn - chúng ta nên có tối ưu cho toàn bộ vấn đề

Nếu L chứa N thì nghiệm tối ưu của bài toán cũng giống như 1,2,3,…,N−1. Chúng tôi biết mục nằm trong, vì vậy L đã chứa N. Để hoàn thành việc tính toán, chúng tôi tập trung vào các mục còn lại. Chúng tôi tìm giải pháp tối ưu cho các hạng mục còn lại

Tuy nhiên, chúng tôi hiện có trọng lượng tối đa mới cho phép là \[W_{max} - W_n\]. Nếu vật phẩm N được chứa trong dung dịch, tổng trọng lượng bây giờ là trọng lượng tối đa lấy đi vật phẩm N [đã có trong ba lô]

Đây là 2 trường hợp. Mục N nằm trong giải pháp tối ưu hoặc không

Nếu trọng lượng của mặt hàng N lớn hơn \[W_max\]​ thì không thể đưa vào nên trường hợp 1 là khả năng duy nhất

Để xác định rõ hơn giải pháp đệ quy này, hãy để \[S_k =1,2,…,k\] và \[S_0 ​= ∅\]

Đặt B[k, w] là tổng lợi ích tối đa thu được khi sử dụng một tập hợp con của SkSk​. Có tổng trọng lượng nhiều nhất là w

Sau đó, chúng tôi xác định B[0, w] = 0 cho mỗi \[w \le W_{max}\]

Giải pháp mong muốn của chúng tôi sau đó là B[n, \[W_{max}\]]

$$OPT[i] = \begin{cases} B[k - 1, w], \quad \text{If w < }w_k \\ max{B[k-1, w], b_k + B[k -

Lập bảng bài toán chiếc ba lô

Được rồi, lấy ra một số bút và giấy. Không, thực sự. Mọi thứ sắp trở nên khó hiểu thật nhanh. Bảng ghi nhớ này là 2 chiều. Chúng tôi có những mặt hàng này

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
0

Trường hợp các bộ dữ liệu là

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
5

Chúng tôi có 2 biến, vì vậy mảng của chúng tôi là 2 chiều. Kích thước đầu tiên là từ 0 đến 7. Chiều thứ hai của chúng tôi là các giá trị

Và chúng tôi muốn trọng số là 7 với lợi ích tối đa

01234567[1, 1][4, 3][5, 4][7, 5]

trọng lượng là 7. Chúng tôi bắt đầu đếm từ 0. Chúng tôi đặt mỗi bộ ở phía bên tay trái. Vâng. Bây giờ để điền vào bảng

01234567[1, 1]0[4, 3]0[5, 4]0[7, 5]0

Các cột có trọng lượng. Ở trọng số 0, chúng ta có tổng trọng lượng là 0. Ở trọng lượng 1, chúng ta có tổng trọng lượng là 1. Rõ ràng, tôi biết. Nhưng đây là một sự khác biệt quan trọng để thực hiện mà sẽ hữu ích sau này

Khi trọng lượng của chúng ta bằng 0, chúng ta không thể mang theo bất cứ thứ gì. Tổng trọng số của mọi thứ tại 0 là 0

01234567[1, 1]01[4, 3]0[5, 4]0[7, 5]0

Nếu tổng trọng lượng của chúng ta là 1, món đồ tốt nhất chúng ta có thể lấy là [1, 1]. Khi chúng tôi đi xuống mảng này, chúng tôi có thể lấy thêm vật phẩm. Tại hàng của [4, 3] chúng ta có thể lấy [1, 1] hoặc [4, 3]. Nhưng hiện tại, chúng ta chỉ có thể lấy [1, 1]. Lợi ích tối đa của chúng tôi cho hàng này sau đó là 1

01234567[1, 1]01111   1   1  1[4, 3]0[5, 4]0[7, 5]0

Nếu tổng trọng lượng của chúng ta là 2, điều tốt nhất chúng ta có thể làm là 1. Chúng tôi chỉ có 1 của mỗi mặt hàng. Chúng tôi không thể sao chép các mục. Vì vậy, bất kể chúng ta đang ở đâu trong hàng 1, điều tuyệt đối tốt nhất chúng ta có thể làm là [1, 1]

Hãy bắt đầu sử dụng [4, 3] ngay bây giờ. Nếu tổng trọng lượng là 1, nhưng trọng lượng của [4, 3] là 3, chúng tôi chưa thể lấy vật phẩm cho đến khi chúng tôi có trọng lượng ít nhất là 3

01234567[1, 1]01111   1   1  1[4, 3]011[5, 4]0[7, 5]0

Bây giờ chúng ta có trọng lượng là 3. Hãy so sánh một số điều. Chúng tôi muốn lấy tối đa

$$MAX[4 + T[0][0], 1]$$

Nếu chúng tôi ở 2, 3, chúng tôi có thể lấy giá trị từ hàng cuối cùng hoặc sử dụng vật phẩm trên hàng đó. Chúng tôi đi lên một hàng và đếm ngược 3 [vì trọng lượng của mặt hàng này là 3]

Trên thực tế, công thức là bất kỳ trọng lượng nào còn lại khi chúng ta trừ đi trọng lượng của mặt hàng trên hàng đó. Trọng số của [4, 3] là 3 và chúng ta đang ở trọng số 3. 3−3=03−3=0. Vì vậy, chúng tôi đang ở

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
6.
def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
7

$$MAX[4 + T[0][0], 1]$$

1 là vì mục trước đó. Tối đa ở đây là 4

01234567[1, 1]01111   1   1  1[4, 3]0114[5, 4]0[7, 5]0

$$max[4 + t[0][1], 1]$$

Tổng trọng lượng là 4, trọng lượng mặt hàng là 3. 4 - 3 = 1. Hàng trước là 0.

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
8

01234567[1, 1]01111   1   1  1[4, 3]01145[5, 4]0[7, 5]0

Tôi sẽ không làm bạn chán với phần còn lại của hàng này, vì không có gì thú vị xảy ra. Chúng tôi có 2 mặt hàng. Và chúng tôi đã sử dụng cả hai để tạo ra 5. Vì không có mục mới, giá trị tối đa là 5

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]0[7, 5]0

Lên hàng tiếp theo của chúng tôi

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 [7, 5]0

Đây là một bí mật nhỏ. Bộ dữ liệu của chúng tôi được sắp xếp theo trọng lượng. Điều đó có nghĩa là chúng ta có thể điền vào các hàng dữ liệu trước đó cho đến điểm trọng số tiếp theo. Chúng tôi biết rằng 4 đã là tối đa, vì vậy chúng tôi có thể điền vào phần còn lại. Đây là nơi ghi nhớ phát huy tác dụng. Chúng tôi đã có dữ liệu, tại sao phải tính toán lại nó?

Chúng tôi đi lên một hàng và lùi lại 4 bước. Điều đó mang lại cho chúng tôi

$$max[4 + T[2][0], 5]$$

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5[7, 5]0

Bây giờ chúng tôi tính toán nó cho tổng trọng lượng 5

$$max[5 + T[2][1], 5] = 6$$

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 56[7, 5]0

Chúng tôi làm điều tương tự một lần nữa

$$max[5 + T[2][2], 5] = 6$$

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 566[7, 5]0

Bây giờ chúng ta có tổng trọng lượng là 7. Chúng tôi chọn tối đa

$$max[5 + T[2][3], 5] = max[5 + 4, 5] = 9$$

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]0

Nếu chúng ta có tổng trọng lượng là 7 và chúng ta có 3 mục [1, 1], [4, 3], [5, 4] thì tốt nhất chúng ta có thể làm là 9

Vì mặt hàng mới của chúng tôi bắt đầu ở trọng số 5, chúng tôi có thể sao chép từ hàng trước đó cho đến khi đạt được trọng số 5

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]01145

Sau đó, chúng tôi thực hiện một mức tối đa khác

Tổng trọng lượng - trọng lượng của mặt hàng mới. Đây là 5−5=0. Chúng tôi muốn hàng trước đó ở vị trí 0

$$max[7 + T[3][0], 6]$$

6 đến từ tốt nhất trên hàng trước cho tổng trọng lượng đó

$$max[7 + 0, 6] = 7$$

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]011457

$$max[7 + T[3][1], 6] = 8$$

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]0114578

$$max[7+T[3][2], 9] = 9$$

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]01145789

9 là giá trị lớn nhất chúng ta có thể nhận được bằng cách chọn các mục từ tập hợp các mục sao cho tổng trọng lượng là≤7≤7

Tìm tập hợp tối ưu cho bài toán chiếc ba lô {0, 1} bằng quy hoạch động

Bây giờ, những mục nào chúng ta thực sự chọn cho bộ tối ưu?

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]01145789

Chúng tôi muốn biết số 9 đến từ đâu. Nó đến từ trên cùng vì số ngay phía trên số 9 ở hàng thứ 4 là 9. Vì nó đến từ trên cùng, mục [7, 5] không được sử dụng trong tập hợp tối ưu

Số 9 này đến từ đâu?

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]01145789

9 này không đến từ hàng trên nó. **Mục [5, 4] phải nằm trong tập hợp tối ưu. **

Bây giờ chúng ta đi lên một hàng và lùi lại 4 bước. 4 bước vì vật phẩm, [5, 4], có trọng lượng 4

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]01145789

4 không đến từ hàng trên. Mục [4, 3] phải nằm trong tập hợp tối ưu

Trọng số của mục [4, 3] là 3. Chúng tôi đi lên và chúng tôi quay lại 3 bước và đạt được

01234567[1, 1]01111   1   1  1[4, 3]01145555[5, 4]011 4 5669[7, 5]01145789

Ngay khi chúng tôi đạt đến điểm có trọng số bằng 0, chúng tôi đã hoàn thành. Hai mục được chọn của chúng tôi là [5, 4] và [4, 3]. Tổng trọng số là 7 và tổng lợi ích của chúng ta là 9. Chúng tôi thêm hai bộ dữ liệu lại với nhau để tìm ra điều này

Hãy bắt đầu viết mã này

Mã hóa {0, 1} Bài toán ba lô trong lập trình động với Python

Bây giờ chúng tôi biết cách thức hoạt động của nó và chúng tôi đã rút ra được phép lặp lại cho nó - không quá khó để viết mã cho nó. Nếu mảng hai chiều của chúng ta là i [hàng] và j [cột] thì chúng ta có

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
1

Nếu trọng lượng j của chúng tôi nhỏ hơn trọng lượng của vật phẩm i [i không đóng góp cho j] thì

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
2

Đây là những gì trái tim cốt lõi của chương trình làm. Tôi đã sao chép một số mã từ đây để giúp giải thích điều này. Tôi sẽ không giải thích nhiều về mã này, vì nó không có gì nhiều hơn những gì tôi đã giải thích. Nếu bạn bối rối vì điều đó, hãy để lại bình luận bên dưới hoặc gửi email cho tôi 😁

def fibonacciVal[n]:
    memo[0], memo[1] = 0, 1
    for i in range[2, n+1]:
        memo[i] = memo[i-1] + memo[i-2]
    return memo[n]
3

Độ phức tạp về thời gian của một vấn đề quy hoạch động

Độ phức tạp thời gian được tính trong Lập trình động như

$$Số \;

Đối với bài toán ban đầu của chúng ta, Bài toán lập lịch theo khoảng thời gian có trọng số, chúng ta có n đống quần áo. Từng đống quần áo được giải quyết trong thời gian liên tục. Độ phức tạp thời gian là

$$O[n] + O[1] = O[n]$$

Tôi đã viết một bài về ký hiệu Big O nếu bạn muốn tìm hiểu thêm về độ phức tạp của thời gian

Với vấn đề Ba lô của chúng tôi, chúng tôi có n số mục. Bảng phát triển tùy thuộc vào tổng sức chứa của chiếc ba lô, độ phức tạp về thời gian của chúng tôi là

$$O[nw]$$

Trong đó n là số lượng vật phẩm và w là sức chứa của ba lô

Tôi sẽ cho bạn biết một bí mật nhỏ. Có thể tìm ra độ phức tạp về thời gian của một thuật toán từ sự lặp lại của nó. Bạn có thể sử dụng thứ gọi là Định lý chính để giải quyết nó. Đây là định lý một cách ngắn gọn

Lấy từ đây /

Bây giờ, tôi sẽ thành thật. Định lý tổng thể xứng đáng có một bài đăng trên blog của riêng nó. Hiện tại, tôi thấy video này rất xuất sắc

Lập trình động vs Phân chia và chinh phục vs Tham lam

Lập trình động & Phân chia và chinh phục là tương tự nhau. Lập trình động dựa trên Chia để trị, ngoại trừ chúng tôi ghi nhớ kết quả

Nhưng, tham lam thì khác. Nó nhằm mục đích tối ưu hóa bằng cách đưa ra lựa chọn tốt nhất tại thời điểm đó. Đôi khi, điều này không tối ưu hóa cho toàn bộ vấn đề. Lấy câu hỏi này làm ví dụ. Chúng tôi có 3 đồng xu

1p, 15p, 25p

Và có người muốn chúng tôi đổi 30p. Với Greedy, nó sẽ chọn 25, sau đó là 5 * 1 với tổng số 6 xu. Giải pháp tối ưu là 2 * 15. Tham lam làm việc từ lớn nhất đến nhỏ nhất. Tại thời điểm nó ở mức 25, sự lựa chọn tốt nhất sẽ là chọn 25

Tham lam so với Chia & Chinh phục so với Lập trình độngTham lamChia & Chinh phụcLập trình độngTối ưu hóa bằng cách đưa ra lựa chọn tốt nhất vào thời điểmTối ưu hóa bằng cách chia nhỏ bài toán con thành các phiên bản đơn giản hơn của chính nó và sử dụng đa luồng & đệ quy để giảiGiống như Chia để trị, nhưng tối ưu hóa bằng cách lưu vào bộ nhớ đệm các câu trả lời . Không phải lúc nào cũng tìm ra giải pháp tối ưu, nhưng rất nhanhLuôn tìm ra giải pháp tối ưu, nhưng chậm hơn Tham lamLuôn tìm ra giải pháp tối ưu, nhưng có thể vô ích trên các tập dữ liệu nhỏ. Hầu như không cần bộ nhớ Cần bộ nhớ để ghi nhớ lời gọi đệ quy Yêu cầu nhiều bộ nhớ để ghi nhớ/lập bảng

Lập bảng [Từ dưới lên] so với Ghi nhớ [Từ trên xuống]

Có 2 loại lập trình động. Lập bảng và ghi nhớ

Ghi nhớ [Từ trên xuống]

Chúng tôi đã tính toán tất cả các bài toán con nhưng không biết thứ tự đánh giá tối ưu là gì. Sau đó, chúng tôi sẽ thực hiện một cuộc gọi đệ quy từ gốc và hy vọng chúng tôi sẽ tiến gần đến giải pháp tối ưu hoặc có được bằng chứng rằng chúng tôi sẽ đi đến giải pháp tối ưu. Ghi nhớ đảm bảo bạn không bao giờ tính toán lại một bài toán con vì chúng tôi lưu trữ các kết quả, do đó các cây con trùng lặp không được tính toán lại

Từ chuỗi Fibonacci của chúng tôi trước đó, chúng tôi bắt đầu tại nút gốc. Cây con F[2] không được tính hai lần

Điều này bắt đầu từ đỉnh của cây và đánh giá các bài toán con từ các lá/cây con ngược trở lại gốc. Ghi nhớ là một cách tiếp cận từ trên xuống

Lập bảng [Từ dưới lên]

Chúng tôi cũng đã thấy Lập trình động được sử dụng làm thuật toán 'điền bảng'. Thông thường, bảng này là đa chiều. Điều này giống như ghi nhớ, nhưng với một sự khác biệt lớn. Chúng ta phải chọn chính xác thứ tự mà chúng ta sẽ thực hiện các phép tính của mình. Bài toán chiếc ba lô chúng tôi đã thấy, chúng tôi đã điền vào bảng từ trái sang phải - trên xuống dưới. Chúng tôi biết thứ tự chính xác để điền vào bảng

Đôi khi 'cái bàn' không giống như những cái bàn mà chúng ta đã thấy. Nó có thể là một cấu trúc phức tạp hơn như cây cối. Hoặc cụ thể cho miền có vấn đề, chẳng hạn như các thành phố trong khoảng cách bay trên bản đồ

Lập bảng & Ghi nhớ - Ưu điểm và Nhược điểm

Nói chung, ghi nhớ dễ viết mã hơn lập bảng. Chúng ta có thể viết hàm bao bọc 'bộ nhớ' tự động thực hiện việc đó cho chúng ta. Với việc lập bảng, chúng ta phải đưa ra một thứ tự

Ghi nhớ có mối quan tâm bộ nhớ. Nếu chúng ta đang tính toán thứ gì đó lớn chẳng hạn như F[10^8], thì mỗi phép tính sẽ bị trì hoãn vì chúng ta phải đặt chúng vào mảng. Và mảng sẽ tăng kích thước rất nhanh

Một trong hai cách tiếp cận có thể không tối ưu về mặt thời gian nếu thứ tự chúng ta thực hiện [hoặc cố gắng] truy cập các bài toán con không tối ưu. Nếu có nhiều hơn một cách để tính toán một bài toán con [thông thường bộ nhớ đệm sẽ giải quyết vấn đề này, nhưng về mặt lý thuyết, bộ nhớ đệm có thể không hoạt động trong một số trường hợp kỳ lạ]. Ghi nhớ thường sẽ thêm vào sự phức tạp về thời gian của chúng ta với sự phức tạp về không gian của chúng ta. Ví dụ: với lập bảng, chúng tôi có nhiều quyền tự do hơn để loại bỏ các phép tính, như sử dụng lập bảng với Fib cho phép chúng tôi sử dụng không gian O[1], nhưng ghi nhớ với Fib sử dụng không gian ngăn xếp O[N]]

Ghi nhớ so với TabulationTabulationMemoisationCode Viết mã khó hơn vì bạn phải biết thứ tựDễ viết mã hơn vì các chức năng có thể đã tồn tại đối với ghi nhớTốc độ Nhanh vì bạn đã biết thứ tự và kích thước của bảngChậm hơn khi bạn đang tạo chúng một cách nhanh chóng  Table completenessThe table is fully computedTable does not have to be fully computed

Phần kết luận

Hầu hết các vấn đề bạn gặp phải trong Lập trình động đã tồn tại ở dạng này hay dạng khác. Thông thường, vấn đề của bạn sẽ được xây dựng từ các câu trả lời cho các vấn đề trước đó.

Tôi hy vọng rằng bất cứ khi nào bạn gặp phải một vấn đề, bạn hãy tự nghĩ “vấn đề này có thể được giải quyết bằng ?”

Cấu trúc dữ liệu tĩnh và động là gì?

Cấu trúc dữ liệu tĩnh có kích thước cố định và bộ nhớ được trình biên dịch cấp phát tại thời điểm biên dịch và giải phóng khi chúng vượt quá phạm vi hoặc chương trình kết thúc. Cấu trúc dữ liệu động có kích thước động và bộ nhớ được cấp phát trong thời gian chạy cho chúng bởi chương trình

Hai loại cấu trúc dữ liệu trong Python là gì?

Cấu trúc dữ liệu tích hợp trong Python có thể được chia thành hai loại lớn. có thể thay đổi và không thể thay đổi . Cấu trúc dữ liệu có thể thay đổi [từ tiếng Latinh mutabilis, "có thể thay đổi"] là những cấu trúc mà chúng ta có thể sửa đổi — ví dụ: bằng cách thêm, xóa hoặc thay đổi các phần tử của chúng.

4 cấu trúc dữ liệu trong Python là gì?

Cấu trúc dữ liệu Python cơ bản trong Python bao gồm danh sách, bộ, bộ và từ điển . Mỗi cấu trúc dữ liệu là duy nhất theo cách riêng của nó.

Tại sao stack được gọi là cấu trúc dữ liệu động?

Ngăn xếp là cấu trúc dữ liệu động tuân theo nguyên tắc Nhập sau xuất trước [LIFO] . Mục cuối cùng được chèn vào ngăn xếp là mục đầu tiên bị xóa khỏi ngăn xếp. Ví dụ: bạn có một chồng khay trên bàn.

Chủ Đề