Làm thế nào có thể xây dựng một đống có độ phức tạp thời gian O (n)?


494

Ai đó có thể giúp giải thích làm thế nào có thể xây dựng một đống là phức tạp O (n)?

Chèn một mục vào một đống là O(log n)và việc chèn được lặp lại n / 2 lần (phần còn lại là các lá và không thể vi phạm thuộc tính của heap). Vì vậy, điều này có nghĩa là sự phức tạp nên O(n log n), tôi sẽ nghĩ.

Nói cách khác, đối với mỗi mục chúng tôi "heapify", nó có khả năng phải lọc xuống một lần cho mỗi cấp cho heap cho đến nay (đó là mức log n).

Tôi đang thiếu gì?


chính xác ý bạn là gì khi "xây dựng" một đống?
mfrankli

Như bạn sẽ làm trong một heapsort, lấy một mảng chưa được sắp xếp và lọc từng phần tử nửa trên cho đến khi nó tuân theo quy tắc của một heap
GBa

2
Điều duy nhất tôi có thể tìm thấy là liên kết này: Độ phức tạp của Buildheap dường như là Θ (n lg n) - n gọi tới Heapify với chi phí (lg n) mỗi cuộc gọi, nhưng kết quả này có thể được cải thiện thành (n) cs.txstate.edu/~ch04/webtest/teaching/cifts/5329/lectures/ chủ
GBa

2
@Gba xem video này từ MIT: Anh ấy giải thích rõ về cách chúng tôi nhận được O (n), với một chút toán học youtube.com/watch?v=B7hVxCmfPtM
CodeShadow 7/07/2016

2
Liên kết trực tiếp đến lời giải thích @CodeShadow đã đề cập: youtu.be/B7hVxCmfPtM?t=41m21s
sha1

Câu trả lời:


435

Tôi nghĩ rằng có một số câu hỏi chôn trong chủ đề này:

  • Làm thế nào để bạn thực hiện buildHeapđể nó chạy trong thời gian O (n) ?
  • Làm thế nào để bạn chỉ ra rằng buildHeapchạy trong thời gian O (n) khi được thực hiện chính xác?
  • Tại sao logic tương tự không hoạt động để làm cho heap sort chạy trong thời gian O (n) chứ không phải O (n log n) ?

Làm thế nào để bạn thực hiện buildHeapđể nó chạy trong thời gian O (n) ?

Thông thường, câu trả lời cho những câu hỏi này tập trung vào sự khác biệt giữa siftUpsiftDown. Việc lựa chọn chính xác giữa siftUpsiftDownrất quan trọng để có được hiệu suất O (n)buildHeap , nhưng không giúp được gì cho người ta hiểu sự khác biệt giữa buildHeapheapSortnói chung. Thật vậy, thực hiện đúng cả hai buildHeapheapSortsẽ chỉ sử dụng siftDown. Các siftUphoạt động chỉ là cần thiết để thực hiện chèn vào một đống hiện, vì vậy nó sẽ được sử dụng để thực hiện một hàng đợi ưu tiên sử dụng một đống nhị phân, ví dụ.

Tôi đã viết điều này để mô tả cách hoạt động của một heap tối đa. Đây là loại heap thường được sử dụng để sắp xếp heap hoặc cho hàng đợi ưu tiên trong đó các giá trị cao hơn biểu thị mức độ ưu tiên cao hơn. Một đống tối thiểu cũng hữu ích; ví dụ: khi truy xuất các mục có khóa số nguyên theo thứ tự tăng dần hoặc chuỗi theo thứ tự bảng chữ cái. Các nguyên tắc hoàn toàn giống nhau; chỉ cần chuyển đổi thứ tự sắp xếp.

Các bất động sản đống quy định cụ thể mà mỗi nút trong một đống nhị phân ít nhất phải lớn như cả hai con của nó. Đặc biệt, điều này ngụ ý rằng mục lớn nhất trong heap là ở gốc. Sift xuống và sàng lên về cơ bản là cùng một hoạt động theo hướng ngược lại: di chuyển một nút vi phạm cho đến khi nó thỏa mãn thuộc tính heap:

  • siftDown hoán đổi một nút quá nhỏ với con lớn nhất của nó (do đó di chuyển nó xuống) cho đến khi nó lớn nhất bằng cả hai nút bên dưới nó.
  • siftUp hoán đổi một nút quá lớn với cha của nó (do đó di chuyển nó lên) cho đến khi nó không lớn hơn nút phía trên nó.

Số lượng thao tác cần thiết siftDownsiftUptỷ lệ thuận với khoảng cách mà nút có thể phải di chuyển. Đối với siftDown, đó là khoảng cách đến dưới cùng của cây, vì vậy rất siftDowntốn kém cho các nút ở đầu cây. Với siftUp, công việc tỷ lệ thuận với khoảng cách đến ngọn cây, vì vậy rất siftUptốn kém cho các nút ở dưới cùng của cây. Mặc dù cả hai hoạt động là O (log n) trong trường hợp xấu nhất, trong một đống, chỉ có một nút ở trên cùng trong khi một nửa các nút nằm ở lớp dưới cùng. Vì vậy, không quá ngạc nhiên khi chúng ta phải áp dụng một thao tác cho mọi nút, chúng ta sẽ thích siftDownhơn siftUp.

Các buildHeapchức năng phải mất một mảng các mặt hàng không được phân loại và di chuyển chúng cho đến khi tất cả họ đều thoả mãn tính chất heap, do đó tạo ra một đống hợp lệ. Có hai cách tiếp cận người ta có thể thực hiện khi buildHeapsử dụng siftUpvà các siftDownthao tác chúng tôi đã mô tả.

  1. Bắt đầu ở đầu heap (phần đầu của mảng) và gọi siftUptừng mục. Ở mỗi bước, các mục đã sàng trước đó (các mục trước mục hiện tại trong mảng) tạo thành một đống hợp lệ và sàng các mục tiếp theo lên đặt nó vào một vị trí hợp lệ trong heap. Sau khi chọn từng nút, tất cả các mục thỏa mãn thuộc tính heap.

  2. Hoặc, đi theo hướng ngược lại: bắt đầu ở cuối mảng và di chuyển về phía trước. Ở mỗi lần lặp, bạn chọn một mục xuống cho đến khi nó ở đúng vị trí.

Việc thực hiện nào cho buildHeaphiệu quả hơn?

Cả hai giải pháp này sẽ tạo ra một đống hợp lệ. Không có gì đáng ngạc nhiên, hoạt động hiệu quả hơn là hoạt động thứ hai sử dụng siftDown.

Đặt h = log n đại diện cho chiều cao của heap. Công việc cần thiết cho siftDowncách tiếp cận được tính bằng tổng

(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).

Mỗi thuật ngữ trong tổng có khoảng cách tối đa mà một nút ở độ cao đã cho sẽ phải di chuyển (0 cho lớp dưới cùng, h cho gốc) nhân với số lượng nút ở độ cao đó. Ngược lại, tổng số để gọi siftUptrên mỗi nút là

(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).

Cần phải rõ ràng rằng tổng thứ hai là lớn hơn. Chỉ riêng thuật ngữ đầu tiên là hn / 2 = 1/2 n log n , vì vậy cách tiếp cận này có độ phức tạp cao nhất là O (n log n) .

Làm thế nào để chúng tôi chứng minh tổng cho siftDowncách tiếp cận thực sự là O (n) ?

Một phương pháp (có những phân tích khác cũng hoạt động) là biến tổng hữu hạn thành một chuỗi vô hạn và sau đó sử dụng chuỗi Taylor. Chúng tôi có thể bỏ qua thuật ngữ đầu tiên, đó là số không:

Dòng Taylor cho độ phức tạp buildHeap

Nếu bạn không chắc chắn tại sao mỗi bước đó hoạt động, đây là lời biện minh cho quy trình bằng từ ngữ:

  • Các thuật ngữ đều dương, vì vậy tổng hữu hạn phải nhỏ hơn tổng vô hạn.
  • Sê-ri bằng với chuỗi lũy thừa được đánh giá ở x = 1/2 .
  • Chuỗi lũy thừa đó bằng (một thời gian không đổi) đạo hàm của chuỗi Taylor cho f (x) = 1 / (1-x) .
  • x = 1/2 nằm trong khoảng hội tụ của chuỗi Taylor đó.
  • Do đó, chúng ta có thể thay thế chuỗi Taylor bằng 1 / (1-x) , phân biệt và đánh giá để tìm giá trị của chuỗi vô hạn.

Vì tổng vô hạn chính xác là n , nên chúng tôi kết luận rằng tổng hữu hạn không lớn hơn và do đó, O (n) .

Tại sao sắp xếp heap yêu cầu thời gian O (n log n) ?

Nếu có thể chạy buildHeaptrong thời gian tuyến tính, tại sao sắp xếp heap yêu cầu thời gian O (n log n) ? Vâng, sắp xếp heap bao gồm hai giai đoạn. Đầu tiên, chúng ta gọi buildHeapvào mảng, đòi hỏi thời gian O (n) nếu được thực hiện tối ưu. Giai đoạn tiếp theo là liên tục xóa mục lớn nhất trong heap và đặt nó ở cuối mảng. Bởi vì chúng tôi xóa một mục khỏi heap, luôn có một vị trí mở ngay sau khi kết thúc heap nơi chúng tôi có thể lưu trữ mục đó. Vì vậy, sắp xếp heap đạt được một thứ tự được sắp xếp bằng cách loại bỏ liên tiếp mục lớn nhất tiếp theo và đưa nó vào mảng bắt đầu ở vị trí cuối cùng và di chuyển về phía trước. Đó là sự phức tạp của phần cuối cùng này chiếm ưu thế trong sắp xếp đống. Vòng lặp trông như thế này:

for (i = n - 1; i > 0; i--) {
    arr[i] = deleteMax();
}

Rõ ràng, vòng lặp chạy O (n) lần ( n - 1 để chính xác, mục cuối cùng đã được đặt đúng chỗ). Độ phức tạp của deleteMaxmột heap là O (log n) . Nó thường được thực hiện bằng cách loại bỏ gốc (mục lớn nhất còn lại trong heap) và thay thế nó bằng mục cuối cùng trong heap, đó là một chiếc lá, và do đó là một trong những mục nhỏ nhất. Root mới này gần như chắc chắn sẽ vi phạm thuộc tính heap, vì vậy bạn phải gọi siftDowncho đến khi bạn chuyển nó trở lại vị trí chấp nhận được. Điều này cũng có tác dụng di chuyển vật phẩm lớn nhất tiếp theo lên đến gốc. Lưu ý rằng, trái ngược với buildHeapnơi mà hầu hết các nút mà chúng ta đang gọi siftDowntừ dưới cùng của cây, chúng ta hiện đang gọi siftDowntừ đỉnh cây trên mỗi lần lặp!Mặc dù cây đang co lại, nhưng nó không co lại đủ nhanh : Chiều cao của cây không đổi cho đến khi bạn loại bỏ nửa nút đầu tiên (khi bạn xóa hoàn toàn lớp dưới cùng). Sau đó cho quý tiếp theo, chiều cao là h - 1 . Vì vậy, tổng công việc cho giai đoạn thứ hai này là

h*n/2 + (h-1)*n/4 + ... + 0 * 1.

Lưu ý công tắc: bây giờ trường hợp công việc bằng 0 tương ứng với một nút và trường hợp công việc h tương ứng với một nửa các nút. Tổng này là O (n log n) giống như phiên bản không hiệu quả buildHeapđược triển khai bằng siftUp. Nhưng trong trường hợp này, chúng tôi không có lựa chọn nào vì chúng tôi đang cố gắng sắp xếp và chúng tôi yêu cầu mục lớn nhất tiếp theo sẽ bị xóa tiếp theo.

Tóm lại, công việc sắp xếp heap là tổng của hai giai đoạn: O (n) thời gian cho buildHeap và O (n log n) để loại bỏ từng nút theo thứ tự , vì vậy độ phức tạp là O (n log n) . Bạn có thể chứng minh (sử dụng một số ý tưởng từ lý thuyết thông tin) rằng đối với loại sắp xếp dựa trên so sánh, O (n log n) là cách tốt nhất bạn có thể hy vọng dù sao đi nữa, vì vậy không có lý do gì để thất vọng về điều này hoặc mong đợi sắp xếp đống để đạt được O (n) ràng buộc thời gian mà buildHeap.


2
Tôi đã chỉnh sửa câu trả lời của mình để sử dụng heap tối đa vì dường như hầu hết những người khác đang đề cập đến điều đó và đó là lựa chọn tốt nhất cho việc sắp xếp heap.
Jeremy West

28
Đây là điều làm cho nó rõ ràng bằng trực giác với tôi: "chỉ có một nút ở trên cùng trong khi một nửa các nút nằm ở lớp dưới cùng. Vì vậy, không quá ngạc nhiên rằng nếu chúng ta phải áp dụng một thao tác cho mọi nút, chúng ta sẽ thích siftDown hơn siftUp. "
Vicky Chijwani

3
@JeremyWest "Một là bắt đầu ở đầu heap (phần đầu của mảng) và gọi siftUp trên mỗi mục." - Ý của bạn là bắt đầu từ cuối đống?
aste123

4
@ aste123 Không, nó đúng như văn bản. Ý tưởng là duy trì một rào cản giữa một phần của mảng thỏa mãn thuộc tính heap và phần chưa được sắp xếp của mảng. Bạn có thể bắt đầu từ đầu di chuyển về phía trước và gọi siftUpvào từng mục hoặc bắt đầu ở cuối di chuyển lùi và gọi siftDown. Bất kể bạn chọn cách tiếp cận nào, bạn đang chọn mục tiếp theo trong phần chưa sắp xếp của mảng và thực hiện thao tác thích hợp để di chuyển nó vào vị trí hợp lệ trong phần được sắp xếp của mảng. Sự khác biệt duy nhất là hiệu suất.
Jeremy West

2
Đây là câu trả lời tốt nhất tôi từng thấy cho bất kỳ câu hỏi nào trên thế giới. Nó đã được giải thích rất tốt, tôi giống như là nó thực sự có thể ... cảm ơn rất nhiều.
HARSHIL JAIN

314

Phân tích của bạn là chính xác. Tuy nhiên, nó không chặt chẽ.

Thật không dễ để giải thích tại sao xây dựng một đống là một hoạt động tuyến tính, bạn nên đọc nó tốt hơn.

Một phân tích tuyệt vời của thuật toán có thể được nhìn thấy ở đây .


Ý tưởng chính là trong build_heapthuật toán, heapifychi phí thực tế không O(log n)dành cho tất cả các yếu tố.

Khi heapifyđược gọi, thời gian chạy phụ thuộc vào khoảng cách một phần tử có thể di chuyển xuống cây trước khi quá trình kết thúc. Nói cách khác, nó phụ thuộc vào chiều cao của phần tử trong heap. Trong trường hợp xấu nhất, phần tử có thể đi xuống tận cấp độ lá.

Hãy để chúng tôi đếm công việc được thực hiện theo cấp độ.

Ở cấp độ cuối cùng, có 2^(h)các nút, nhưng chúng tôi không gọi heapifybất kỳ nút nào trong số này, vì vậy công việc là 0. Ở cấp độ tiếp theo có 2^(h − 1)các nút và mỗi nút có thể di chuyển xuống 1 cấp. Ở cấp độ 3 từ dưới lên, có 2^(h − 2)các nút và mỗi nút có thể di chuyển xuống 2 cấp độ.

Như bạn có thể thấy không phải tất cả các hoạt động heapify là O(log n), đây là lý do tại sao bạn đang nhận được O(n).


17
Đây là một lời giải thích tuyệt vời ... nhưng tại sao sau đó heap-sort chạy trong O (n log n). Tại sao không cùng lý do áp dụng cho heap-sort?
hba

49
@hba Tôi nghĩ rằng câu trả lời cho câu hỏi của bạn nằm trong việc hiểu hình ảnh này từ bài viết này . HeapifyO(n)khi thực hiện với siftDownnhưng O(n log n)khi thực hiện với siftUp. Do đó, việc sắp xếp thực tế (kéo các mục từ đống một) phải được thực hiện với siftUpnhư vậy O(n log n).
The11

3
Tôi thực sự thích lời giải thích trực quan của tài liệu bên ngoài của bạn ở phía dưới.
Lukas Greblikas

1
@hba Câu trả lời dưới đây của Jeremy West giải quyết câu hỏi của bạn chi tiết hơn, dễ hiểu hơn, giải thích thêm câu trả lời nhận xét của The111 tại đây.
cellepo

Một câu hỏi. Dường như với tôi, các phép so sánh # được thực hiện cho một nút ở độ cao itừ đáy của cây có chiều cao h cũng phải 2* log(h-i)so sánh và cũng nên được tính đến @ The111. Bạn nghĩ sao?
Sid

94

Trực giác:

"Độ phức tạp phải là O (nLog n) ... đối với mỗi mục chúng tôi" heapify ", nó có khả năng phải lọc xuống một lần cho mỗi cấp cho heap cho đến nay (đó là mức log n)."

Không hẳn. Logic của bạn không tạo ra một ràng buộc chặt chẽ - nó vượt qua ước tính độ phức tạp của từng heapify. Nếu được xây dựng từ dưới lên, chèn (heapify) có thể ít hơn nhiều O(log(n)). Quá trình này như sau:

(Bước 1) Các n/2phần tử đầu tiên đi vào hàng dưới cùng của heap. h=0, vì vậy heapify là không cần thiết.

(Bước 2) Các phần tử tiếp theo đi trên hàng 1 từ dưới lên. , heapify bộ lọc xuống 1 cấp.n/22h=1

(Bước i ) Các phần tử tiếp theo đi lên từ dưới lên. , heapify cấp lọc xuống.n/2iih=ii

( Nhật ký bước (n) ) Phần tử cuối cùng đi lên từ dưới lên. , heapify cấp lọc xuống.n/2log2(n) = 1log(n)h=log(n)log(n)

THÔNG BÁO: sau bước một, 1/2các yếu tố (n/2)đã có trong heap và chúng tôi thậm chí không cần gọi heapify một lần. Ngoài ra, lưu ý rằng chỉ có một yếu tố duy nhất, gốc, thực sự phát sinh toàn bộ log(n)sự phức tạp.


Về mặt lý thuyết:

Tổng số bước Nđể xây dựng một đống kích thước n, có thể được viết ra bằng toán học.

Ở độ cao i, chúng tôi đã chỉ ra (ở trên) rằng sẽ có các yếu tố cần gọi heapify và chúng tôi biết heapify ở độ cao là . Điều này mang lại:n/2i+1iO(i)

nhập mô tả hình ảnh ở đây

Giải pháp cho phép tính tổng cuối cùng có thể được tìm thấy bằng cách lấy đạo hàm của cả hai vế của phương trình chuỗi hình học nổi tiếng:

nhập mô tả hình ảnh ở đây

Cuối cùng, cắm x = 1/2vào các phương trình trên mang lại 2. Cắm phương trình này vào phương trình đầu tiên sẽ cho:

nhập mô tả hình ảnh ở đây

Do đó, tổng số bước có kích thước O(n)


35

Sẽ là O (n log n) nếu bạn xây dựng heap bằng cách chèn liên tục các phần tử. Tuy nhiên, bạn có thể tạo một heap mới hiệu quả hơn bằng cách chèn các phần tử theo thứ tự tùy ý và sau đó áp dụng thuật toán để "heapify" chúng theo thứ tự phù hợp (tùy thuộc vào loại heap dĩ nhiên).

Xem http://en.wikipedia.org/wiki/Binary_heap , "Xây dựng một đống" để biết ví dụ. Trong trường hợp này, về cơ bản bạn làm việc từ cấp độ dưới cùng của cây, hoán đổi các nút cha và con cho đến khi các điều kiện heap được thỏa mãn.


12

Đã có một số câu trả lời tuyệt vời nhưng tôi muốn thêm một chút giải thích trực quan

nhập mô tả hình ảnh ở đây

Bây giờ, hãy nhìn vào hình ảnh, có
n/2^1 các nút màu xanh lá câychiều cao 0 (ở đây 23/2 = 12)
n/2^2 nút màu đỏchiều cao 1 (ở đây 23/4 = 6)
n/2^3 nút màu xanhchiều cao 2 (ở đây 23/8 = 3)
n/2^4 Các nút màu tímchiều cao 3 (ở đây 23/16 = 2)
vì vậy có n/2^(h+1)các nút cho chiều cao h
Để tìm độ phức tạp thời gian cho phép đếm số lượng công việc được thực hiện hoặc tối đa không lặp lại được thực hiện bởi mỗi nút
bây giờ có thể nhận thấy rằng mỗi nút có thể thực hiện (tối đa) lần lặp == chiều cao của nút

Green  = n/2^1 * 0 (no iterations since no children)  
red    = n/2^2 * 1 (heapify will perform atmost one swap for each red node)  
blue   = n/2^3 * 2 (heapify will perform atmost two swaps for each blue node)  
purple = n/2^4 * 3 (heapify will perform atmost three swaps for each purple node)   

vì vậy, đối với bất kỳ nút nào có chiều cao h, công việc tối đa được thực hiện là n / 2 ^ (h + 1) * h

Bây giờ tổng số công việc được thực hiện là

->(n/2^1 * 0) + (n/2^2 * 1)+ (n/2^3 * 2) + (n/2^4 * 3) +...+ (n/2^(h+1) * h)  
-> n * ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

bây giờ với bất kỳ giá trị nào của h , chuỗi

-> ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

sẽ không bao giờ vượt quá 1
Do đó, độ phức tạp thời gian sẽ không bao giờ vượt quá O (n) để xây dựng đống


7

Như chúng ta biết chiều cao của heap là log (n) , trong đó n là tổng số phần tử. Hãy biểu thị nó là h
   Khi chúng ta thực hiện thao tác heapify, thì các phần tử ở cấp độ cuối ( h ) sẽ không di chuyển dù chỉ một bươc.
   Số lượng phần tử ở cấp độ cuối cùng thứ hai ( h-1 ) là 2 h-1 và chúng có thể di chuyển ở mức tối đa 1 (trong heapify).
   Tương tự, đối với cấp thứ i , chúng ta có 2 phần tử i có thể di chuyển vị trí hi .

Do đó tổng số lần di chuyển = S = 2 h * 0 + 2 h-1 * 1 + 2 h-2 * 2 + ... 2 0 * h

                                               S = 2 h {1/2 + 2/2 2 + 3/2 3 + ... h / 2 h } ----------------------- -------------------------- 1
đây là loạt AGP , để giải quyết việc chia cả hai bên cho 2
                                               S / 2 = 2 h {1/2 2 + 2/2 3 + ... h / 2 h + 1 } --------------------------------- ---------------- 2
trừ phương trình 2 từ 1 cho
                                               S / 2 = 2 h { 1/2 + 1/2 2 + 1/2 3 + ... + 1 / 2 h + h / 2 h + 1 }
                                               S = 2 h + 1 {1/2 + 1/2 2 + 1/2 3 + ... + 1/2 h + h / 2 h + 1 }
tại 1/2 + 1/2 2 + 1/2 3 + ... + 1/2 h đang giảm GP có tổng nhỏ hơn 1 (khi h có xu hướng vô cùng, tổng có xu hướng 1). Trong phân tích sâu hơn, hãy lấy giới hạn trên của tổng là 1.
Điều này cho S = 2 h + 1 {1 + h / 2 h + 1 }
                    = 2 h + 1 + h
                    ~ 2 h + h
h = log (n) , 2 h = n

Do đó S = n + log (n)
T (C) = O (n)


6

Trong khi xây dựng một đống, giả sử bạn đang thực hiện một cách tiếp cận từ dưới lên.

  1. Bạn lấy từng phần tử và so sánh nó với các phần tử con của nó để kiểm tra xem cặp này có tuân theo quy tắc heap không. Vì vậy, do đó, các lá được bao gồm trong đống miễn phí. Đó là vì họ không có con.
  2. Di chuyển lên trên, trường hợp xấu nhất cho nút ngay phía trên các lá sẽ là 1 so sánh (Tối đa chúng sẽ được so sánh với chỉ một thế hệ trẻ em)
  3. Tiến xa hơn, cha mẹ ngay lập tức của họ có thể được so sánh tối đa với hai thế hệ trẻ em.
  4. Tiếp tục theo cùng một hướng, bạn sẽ có các so sánh log (n) cho gốc trong trường hợp xấu nhất. và log (n) -1 cho con ngay lập tức của nó, log (n) -2 cho con ngay lập tức của họ và cứ thế.
  5. Vì vậy, tổng hợp tất cả, bạn đến một cái gì đó như log (n) + {log (n) -1} * 2 + {log (n) -2} * 4 + ..... + 1 * 2 ^ {( logn) -1} không có gì ngoài O (n).

2

Trong trường hợp xây dựng heap, chúng ta bắt đầu từ height, logn -1 (trong đó logn là chiều cao của cây của n phần tử). Đối với mỗi phần tử có ở độ cao 'h', chúng tôi sẽ giảm chiều cao tối đa (logn -h) xuống.

    So total number of traversal would be:-
    T(n) = sigma((2^(logn-h))*h) where h varies from 1 to logn
    T(n) = n((1/2)+(2/4)+(3/8)+.....+(logn/(2^logn)))
    T(n) = n*(sigma(x/(2^x))) where x varies from 1 to logn
     and according to the [sources][1]
    function in the bracket approaches to 2 at infinity.
    Hence T(n) ~ O(n)

1

Chèn liên tiếp có thể được mô tả bởi:

T = O(log(1) + log(2) + .. + log(n)) = O(log(n!))

Bằng cách xấp xỉ sao n! =~ O(n^(n + O(1))), do đó,T =~ O(nlog(n))

Hy vọng điều này sẽ giúp, cách tối ưu O(n)là sử dụng thuật toán heap build cho một tập hợp nhất định (thứ tự không thành vấn đề).


1

Về cơ bản, công việc chỉ được thực hiện trên các nút không có lá trong khi xây dựng một đống ... và công việc được thực hiện là lượng hoán đổi để đáp ứng điều kiện heap ... nói cách khác (trong trường hợp xấu nhất) số lượng tỷ lệ thuận với chiều cao của nút ... tất cả trong sự phức tạp của vấn đề tỷ lệ thuận với tổng chiều cao của tất cả các nút không có lá..có gì (2 ^ h + 1 - 1) -h-1 = nh-1 = Trên)


1

@bcorso đã chứng minh bằng chứng về phân tích phức tạp. Nhưng vì lợi ích của những người vẫn đang học phân tích phức tạp, tôi có thêm điều này:

Cơ sở của sai lầm ban đầu của bạn là do giải thích sai về ý nghĩa của tuyên bố, "việc chèn vào một đống mất thời gian O (log n)". Chèn vào một heap thực sự là O (log n), nhưng bạn phải nhận ra rằng n là kích thước của heap trong quá trình chèn .

Trong ngữ cảnh chèn n đối tượng vào một heap, độ phức tạp của phần chèn thứ i là O (log n_i) trong đó n_i là kích thước của heap như khi chèn i. Chỉ phần chèn cuối cùng có độ phức tạp là O (log n).


1

Giả sử bạn có N phần tử trong một đống. Sau đó, chiều cao của nó sẽ là Log (N)

Bây giờ bạn muốn chèn một phần tử khác, thì độ phức tạp sẽ là: Log (N) , chúng ta phải so sánh tất cả các cách UP với root.

Bây giờ bạn đang có các yếu tố N + 1 & height = Nhật ký (N + 1)

Sử dụng kỹ thuật cảm ứng có thể chứng minh rằng độ phức tạp của chèn sẽ là ∑logi .

Hiện đang sử dụng

đăng nhập a + log b = log ab

Điều này đơn giản hóa thành: ∑logi = log (n!)

mà thực sự là O (NlogN)

Nhưng

chúng tôi đang làm điều gì đó sai ở đây, như trong tất cả các trường hợp chúng tôi không đạt đến đỉnh cao. Do đó, trong khi thực hiện hầu hết các lần chúng ta có thể thấy điều đó, chúng ta thậm chí sẽ không đi được nửa đường lên cây. Từ đâu, ràng buộc này có thể được tối ưu hóa để có một ràng buộc chặt chẽ hơn bằng cách sử dụng toán học được đưa ra trong các câu trả lời ở trên.

Nhận thức này đến với tôi sau một chi tiết & thử nghiệm trên Heaps.


0

Tôi thực sự thích lời giải thích của Jeremy west .... một cách tiếp cận khác rất dễ hiểu được đưa ra ở đây http://cifts.washington.edu/css343/zander/NotesProbs/heapcomplexity

kể từ đó, buildheap phụ thuộc vào việc sử dụng phụ thuộc vào heapify và cách tiếp cận shiftdown được sử dụng phụ thuộc vào tổng chiều cao của tất cả các nút. Vì vậy, để tìm tổng chiều cao của các nút được đưa ra bởi S = tổng từ i = 0 đến i = h của (2 ^ i * (hi)), trong đó h = logn là chiều cao của cây giải s, chúng ta nhận được s = 2 ^ (h + 1) - 1 - (h + 1) kể từ, n = 2 ^ (h + 1) - 1 s = n - h - 1 = n- logn - 1 s = O (n), và độ phức tạp của buildheap là O (n).


0

"Giới hạn thời gian tuyến tính của Heap xây dựng, có thể được hiển thị bằng cách tính tổng chiều cao của tất cả các nút trong vùng heap, là số lượng đường đứt nét tối đa. Đối với cây nhị phân hoàn hảo có chiều cao h chứa N = 2 ^ ( h + 1) - 1 nút, tổng chiều cao của các nút là N - H - 1. Do đó, nó là O (N). "


0

Bằng chứng về O (n)

Bằng chứng không lạ mắt, và khá đơn giản, tôi chỉ chứng minh trường hợp cho một cây nhị phân đầy đủ, kết quả có thể được khái quát cho một cây nhị phân hoàn chỉnh.


0

Chúng ta có được thời gian chạy để xây dựng heap bằng cách tìm ra mức di chuyển tối đa mà mỗi nút có thể thực hiện. Vì vậy, chúng ta cần biết có bao nhiêu nút trong mỗi hàng và mỗi nút có thể đi được bao xa.

Bắt đầu từ nút gốc, mỗi hàng tiếp theo có gấp đôi các nút so với hàng trước đó, do đó, bằng cách trả lời tần suất chúng ta có thể nhân đôi số nút cho đến khi không còn nút nào, chúng ta sẽ có được chiều cao của cây. Hoặc theo thuật ngữ toán học, chiều cao của cây là log2 (n), n là chiều dài của mảng.

Để tính toán các nút trong một hàng, chúng tôi bắt đầu từ phía sau, chúng tôi biết n / 2 nút ở dưới cùng, vì vậy bằng cách chia cho 2, chúng tôi nhận được hàng trước đó và cứ thế.

Dựa trên điều này, chúng tôi có được công thức này cho phương pháp Siftdown: (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)

Thuật ngữ trong phép so sánh cuối cùng là chiều cao của cây nhân với một nút nằm ở gốc, thuật ngữ trong phép so sánh đầu tiên là tất cả các nút ở hàng dưới cùng nhân với độ dài mà chúng có thể di chuyển, 0. Công thức tương tự trong thông minh: nhập mô tả hình ảnh ở đây

môn Toán

Mang n trở lại trong chúng ta có 2 * n, 2 có thể bị loại bỏ bởi vì nó là một hằng số và chúng ta có thời gian chạy tồi tệ nhất của phương pháp Siftdown: n.


-6

nghĩ rằng bạn đang phạm sai lầm. Hãy xem cái này: http://golang.org/pkg/container/heap/ Xây dựng một đống không phải là O (n). Tuy nhiên, chèn là O (lg (n). Tôi giả sử khởi tạo là O (n) nếu bạn đặt kích thước heap b / c, heap cần phân bổ không gian và thiết lập cấu trúc dữ liệu. Nếu bạn có n mục để đặt vào heap thì có, mỗi lần chèn là lg (n) và có n mục, vì vậy bạn nhận được n * lg (n) như bạn đã nêu


2
không, nó không chặt chẽ phân tích chặt chẽ hơn về năng suất heap xây dựng O (n)
emre nevayeshirazi

có vẻ như đó là một ước tính. Câu trích dẫn trong thearticle mà ông tham khảo là "Trực giác là hầu hết các lời kêu gọi heapify đều ở những đống rất ngắn" Tuy nhiên điều này đang đưa ra một số giả định. Có lẽ, đối với một đống lớn, trường hợp xấu nhất vẫn là O (n * lg (n)), ngay cả khi thông thường bạn có thể đến gần O (n). Nhưng tôi có thể sai
Mike Schachter

Vâng, đó cũng là câu trả lời trực quan của tôi, nhưng các tài liệu tham khảo như trạng thái wikipedia "Heaps với n phần tử có thể được xây dựng từ dưới lên trong O (n)."
GBa

1
Tôi đã nghĩ về một cấu trúc dữ liệu được sắp xếp đầy đủ. Tôi quên các thuộc tính cụ thể của một đống.
Mike Schachter
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.