01/10/2018, 08:17

Hỏi về độ phức tạp tính toán

Mọi người xem giúp mình code sau đây là có độ phức tạp tính toán là O(n^2) hay O(nlogn)???
PS: Đây là bài tập trên khóa Data Structure and Algorithms trên Coursera. Mình thấy thuật toán đúng rồi nhưng thử với các trường hợp số lớn thì lại bị time limited nên không biết sai chỗ nào. Mong mọi người giúp đỡ.

import java.util.*;
import java.io.*;

public class MajorityElement {
    private static int getMajorityElement(int[] a, int left, int right) {
        if (left == right){
            return -1;
        }
        if (left + 1 == right){
            return a[left];
        }
        //write your code here

		int mid = (left + right)/2;
		int majorLeft = getMajorityElement(a, left, mid);
		int majorRight = getMajorityElement(a, mid, right);
		
		//System.out.println("Left: " + majorLeft + " " + mid);
		//System.out.println("Right: " + majorRight + " " + mid);

		if (majorLeft == majorRight){
			return majorLeft;
		}
				
		int countLeft = 0;
		int countRight = 0; 
		
		for (int i=0; i<a.length; i++){	
			
			if (a[i] == majorLeft){
				countLeft ++;
			}
			if (a[i] == majorRight){
				countRight ++;
			}
		}
		
		if (countLeft > a.length/2){
			return majorLeft;
		}
		
		if (countRight > a.length/2){
			return majorRight;
		}

        return -1;
    }

	/*
	private static int[] generate(){
		int[] a = new int[100000];
		for (int i=0; i<100000/2 + 1; i++){
			a[i] = 1000000000;
		}
		
		Random rd = new Random();
		
		for (int i=100000/2 + 1; i< 100000; i++){
			a[i] = rd.nextInt(1000000000);
		}
		return a;
	}
	*/
	
	
    public static void main(String[] args) {
        
		FastScanner scanner = new FastScanner(System.in);
        int n = scanner.nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = scanner.nextInt();
        }
        
		
		//int[] a = generate();
		//long startTime = System.currentTimeMillis();
		
		if (getMajorityElement(a, 0, a.length) != -1) {
            System.out.println(1);
        } else {
            System.out.println(0);
        }
		
		//long endTime = System.currentTimeMillis();
		//long totalTime = endTime - startTime; 
		//System.out.println("Runtime : " + totalTime);
		
		
		//System.out.println(getMajorityElement(a, 0, a.length) );
    }
    static class FastScanner {
        BufferedReader br;
        StringTokenizer st;

        FastScanner(InputStream stream) {
            try {
                br = new BufferedReader(new InputStreamReader(stream));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        String next() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }
}
Gió viết 10:20 ngày 01/10/2018

T(n)=2*T(n/2)+n theo công thức https://en.wikipedia.org/wiki/Master_theorem#Case_2 ở Case 2 thì độ phức tạp là nlogn

rogp10 viết 10:20 ngày 01/10/2018

Chắc tại Java nhưng mà mình thấy case left+1 == right cũng không đúng.

đặt n=2^m và U(m) = T(2^m) thì
U(m) = 2U(m-1) + 2^m = 4U(m-2) + 2^(m-1)2 = 8U(m-3) + 2^(m-2)*4 + 2^m = … = O(2^m * m)
= O(nlogn)

hamhochoi viết 10:32 ngày 01/10/2018

Mình cũng tính ra là nlogn nhưng khi chạy thử test case nó vẫn cứ báo time limited. Mà rõ trong đề nó yêu cầu cài đặt thuật toán nlogn.
Không biết đã có bạn nào học khóa học này trên Coursera chưa để mình thỉnh giáo.

rogp10 viết 10:20 ngày 01/10/2018

Theo mình bạn nên trả về cả majorcount với lại viết lại base case left+1 == right

hamhochoi viết 10:25 ngày 01/10/2018

trả về count làm gì bạn? Base case kia là do lúc gọi mình gọi getMajorityElement(a,0, a.length) chứ không phải là a.length - 1. Đối với các test case nhỏ mình test đúng rồi mà.

Nguyễn Xuân Phúc viết 10:31 ngày 01/10/2018

Mình thấy dpt của nó là O(N2)
Trong mỗi lần đệ quy, bạn duyệt:

for (int i=0; i<a.length; i++)

Như vậy mỗi lần đệ quy là O(N)
Bây giờ nó là O(N2) hay O(NlogN) sẽ do số lần gọi đệ quy quy định.


Ở đây ta lại thấy có N phần tử, trong mỗi lần gọi đệ quy, bạn lại gọi 2 hàm con:

int majorLeft = getMajorityElement(a, left, mid);
int majorRight = getMajorityElement(a, mid, right);

Nếu ta xem mỗi lần gọi hàm là 1 node trên cây thì sẽ dễ thấy đây là 1 cây nhị phân đầy đủ (vì mỗi thằng gọi xuống đúng 2 con). Và số node lá sẽ là N (nếu tính theo số gọi hàm thì sẽ là số dạng 2k nhỏ nhất và ≥ N). Mà đối với cây nhị phân đầy đủ N lá thì sẽ có 2N-1 node. Tức là bạn sẽ gọi tổng cộng 2N-1 lần đệ quy
Vậy thì tổng độ phức tạp là O(N2)

Nguyễn Xuân Phúc viết 10:31 ngày 01/10/2018

anh nhầm ở chỗ vai trò của n rồi
n trong biểu thức là kích thước của đoạn đệ quy (tức left đến right), kích thước này nó giảm dần, hay nói cách khác là các phần tử, chỉ được gọi trong các hàm đệ quy mà bao lấy phần tử đó.
nhưng trong code của bạn kia, thì N (gọi N cho khỏi nhầm) là kích thước của toàn mảng A, tức là mỗi phần tử sẽ được gọi đến trong MỌI hàm đệ quy bất kể left right có phủ lấy nó hay không.

rogp10 viết 10:25 ngày 01/10/2018

Bởi vậy phải chạy thử mới biết big-O là bao nhiêu được

T(n) = 2T(n/2) + N_zero, vậy T(N_zero) = (1+2+4+8+ … + 2^[log(N_zero)]) * N_zero = O(N_zero^2). Chắc phải phân biệt N (đề cho) với n này quá, nguy hiểm thật.

Bi giờ nếu sửa lại thì nó phải như vầy: thớt tìm trội bên trái và trội bên phải, rồi tìm trội bên phải trong nửa trái và trội bên trái trong nửa phải. Một câu hỏi được đặt ra: Nếu trội nửa trái/phải không tồn tại, vậy có khi nào bị sót không? Vì sao?

hamhochoi viết 10:18 ngày 01/10/2018

Cảm ơn bạn nha. Thực ra là mình nhầm ở chỗ bạn nói là i chạy từ 0 đến a.length. Sửa thành

for (int i=left; i< right; i++)

mới đúng.

Bài liên quan
0