다음 5개함수 중 가장 잘 쓴 함수라고 생각되는 함수는? 이유는? 틀렸다고 생각되는 함수는? 리스트의 뒤에서 N번째 노드 지운 리스트 리턴하기

  • #3779560
    w 76.***.207.158 956
    
    함수 1.
        ListNode* removeNthFromEnd(ListNode* head, int n) {
            ListNode * start = new ListNode();  
            start -> next = head;
            ListNode* fast = start;
            ListNode* slow = start;     
    
            for(int i = 1; i <= n; ++i)
                fast = fast->next;
        
            while(fast->next != NULL)
            {
                fast = fast->next;
                slow = slow->next;
            }
            
            slow->next = slow->next->next;
            
            return start->next; 
        }
    
    함수 2.
      ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* slow = head;
        ListNode* fast = head;
            
        while (n--)
          fast = fast->next;  
    
        if (fast == nullptr) 
          return head->next;   // return slow->next;  라고 해도 됨. 
    
        while (fast->next) {
          slow = slow->next;
          fast = fast->next;
        }
        slow->next = slow->next->next; 
    
        return head;   
      }
    
    함수 3.
     void removeNthFromEnd(ListNode* head, int n) {
        ListNode* slow = head;
        ListNode* fast = head;
            
        while (n--)
          fast = fast->next;  
    
        if (fast == nullptr) 
           head=head->next;   //  slow->next;  라고 해도 됨. 
    
        while (fast->next) {
          slow = slow->next;
          fast = fast->next;
        }
        slow->next = slow->next->next; 
      }
    
    함수 4.
     void removeNthFromEnd(ListNode** head, int n) {
        ListNode* slow = *head;
        ListNode* fast = *head;
            
        while (n--)
          fast = fast->next;  
    
        if (fast == nullptr) 
           slow=slow->next; 
    
        while (fast->next) {
          slow = slow->next;
          fast = fast->next;
        }
        slow->next = slow->next->next; 
      }
    
    함수 5.
      ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *pre = new ListNode(0, head), *slow = pre, *fast = pre;
        while (fast->next != nullptr && n-- > 0) fast = fast->next;
        while (fast->next != nullptr) {
          fast = fast->next;
          slow = slow->next;
        }
        slow->next = slow->next->next;
        return pre->next;
      }
    
    • 140.***.198.159

      숙제는 스스로

    • w 76.***.207.158

      숙제는 아니고…

    • w 76.***.207.158

      사실은위에 5개 함수 모두 잘 실행되는 함수임.
      다만 잘 실행된다고 다 좋은 함수는 아님. 선호도가 있을수밖에 없음.

    • w 76.***.207.158

      내가 선호하는건 3번. 이유는…

    • w 76.***.207.158

      근데 호감 누른 사람은 호감 누른 이유가 있나요?

    • hive 51.***.149.198

      다 잘 실행된다고 자평하시니 난감이네요.

      버그들

      – 1, 2, 3, 4는 head에 null을 넣으면 크래시하는 버그 있고
      – 1, 2, 3, 4는 list에 3개가 있는 상황에서 7번째 것 삭제하려고 호출하면 모두 크래시 (n = 10000 이렇게 넣어볼 것)
      – 2, 3, 4는 n에 음수 넣으면 크래시하는 버그 있고
      – 1, 5는 메모리를 추가 할당한 후 release를 안 하는 memory leak 버그가 있고
      – 1, 2, 3, 4, 5 전부 다, 삭제된 노드를 release하지 않는 memory leak 버그가 있어요.
      – 3은 void로 정의되어 있는데 값을 리턴해서 컴파일이 안 되는 버그가 있고요
      – 4번은 head가 삭제 대상일 경우 삭제가 되지 않는 논리 버그가 있고요.
      – 5번은 첫번째 while 조건이 잘못돼 있어요. n이 너무 클 경우 slow, fast 간격이 n만큼 유지가 안 돼요.

      디자인

      리턴값 디자인이 ListNode*와 void 사이를 오가고 있는데 뭐가 좋을지 아직 못 정했나요? 이 함수의 경우 chained 형태로 쓰일 수 있으므로 ListNode* 로 디자인하는 게 나아요. 즉 1, 2, 5 형태가 좋아요.

      조언

      5가지 해보는데 시간 많이 걸렸죠? 전체적으로 님이 어려움을 겪는 이유에 대해 남들이 다 연구해서 해결책을 내놓은지 오래됐습니다. TDD를 배우세요. 메모리 릭에 대해서는 memory leak을 검출하는 소프트웨어 사용도 배우시고요.

      지금 c++ 시작하시는 분이면 data structure in c++, algorithm in c++, design pattern에 대한 책을 읽고 이미 검증된 코드를 암기하다시피 반복하여 손에 익히시는 게 좋습니다. 눈으로 이해하고 ok 하지 말고 직접 타이핑 해서 넣을 수 있을 정도로 말입니다.

      • w 76.***.207.158

        n이 너무 클 경우 slow, fast 간격이 n만큼 유지가 안 돼요 –> 투포인터의 전형적인 문제. 이경우는 패스트 포인터를 먼저 츨발시켜 n 번째 노드까지 전진시킴. 그후 슬로우 포인터를 출발시킴. 이때 패스트노드와 슬로우 포인터의 속도가 같으므로 등간격을 유지하게 됨. 여기서 n 은 리스트 노드들의 전체수보다 작거나 같은 양수임. n이 무한정 큰 수가 아님. 리스트 사이즈보다는 작아야지. (그러니 테스트를 하려면 리스트를 빌드하는 과정이 중요하네. 그래야 메인함수에서 n의 한계를 정해줄수있으니. 그리고 제대로 함수를 만들려면 함수매개변수로 리스트의 싸이즈를 포함시켜주면 좋겠네. 아니면 먼저 싸이즈를 재고나서 시작하던지. 그러고보니 제대로 코딩할려면 사소하게 생각되건것들도 고려할게 점점 늘어나네 ㅋ) 메모리리크는 고려되지 않았음. 헤드가 널포인터인 경우도 고려되지 않았음. 헤드가 널이면 이 문제 자체가 성립안하므로 사실 고려하는게 이 문제의 중요한 사항이 아님 그냥 널체킹하기 위한 이프문 하나 더 넣으면 그만임.ㅋㅋ

        리턴타입을 ListNode* 로 명확히 해서 리턴을 명확히 해주는게 읽기에 편하기는 하지만, 3의 경우처럼 보이드 타입으로 쓰는게 장점이 있을때가 있어요. 컴파일러 에러가 나는게 어떤 컴파일러인가요? 포인터나 레퍼런스 함수 매개변수로 썼다고해서 컴파일러 에러가 난건 아닐거에요. 그럴경우는 아마 아래 테스트 코딩의 경우처럼 보이드 리턴을 ( return; ) 확실하게 써줘야 컴파일러에러가 나지 않나보군요. ((업데이트: 구글해보니 Best practice: Do not put a return statement at the end of a non-value returning function. 이런 말이 나오니 return; 을 안써주는게 낫다고 하는데 왜 컴파일 에러가 난다고 하는지 모르겠네요.))

      • w 76.***.207.158

        “눈으로 이해하고 ok 하지 말고 직접 타이핑 해서..” ==> 진짜 옳으신 말씀이시네요. 진짜 이해했다고 생각하는것과 직접 타입하는것과는 다른듯.
        사소한 자실구레한것들을 많이 지적했지만 솔직히 그런것들에서 버그들이 생기니 자질구레하다고 무시하면 안될거 같아요. 메모리리크문제도 항상 꼼꼼히 점검하려하는 자세를 가지려 해야겠고. 4번은 head가 삭제 대상일 경우 삭제가 되지 않는 논리 버그? 논리버그라…. 이건 좀 더 생각해볼 문제같네요.

    • ㄱㄱ 76.***.200.242

      linked list는 동적메모리할당이 기본인데 노드를 삭제하면서 메모리 해제가 아예 없네요.
      심지어 1에서는 new를 쓰고 나서 메모리를 해제하지 않았구요.
      인터넷에 찾아보면 수많은 예제가 있습니다.
      https://www.geeksforgeeks.org/deletion-in-linked-list/

      • w 76.***.207.158

        테스트 버전: (메모리리크는 고려안했음. 컴파일이 보이드 타입 리턴 함수라서 안된다니…컴파일러마다 다른 모양이군요. 위5개 함수 모두 잘 실행됨. –내가 코딩한건 아님 ㅋㅋㅋ 살짝 변형한건 있지만 ㅋㅋㅋ 아주 자주 나오는 문제임 — 물론 리턴타입이 리스트 포인터이면 함수콜링을 살짝 바꿔야 하고…int n 은 리스트 끝에서부터의 앞쪽 n번째 노드를 말하고 있으므로 당연히 >=0 이고 전체 리스트의 엘리먼트보다 작거나 같음. 음수를 왜 고려함?)

        1번과 5번 함수가 안좋은 가장 큰 이유는,
        전혀 필요없는 템포러리 더미 노드를 생성하고 있다는 것이다. 물론 그것이 메모리리크 논란을 일으키고 있지만 메모리리크를 논하기 이전에 이 템포러리 포인터는 전혀 쓰잘데기가 없는데 만들었다. 뭐 그게 꼭 필요했다면 딜리트 한줄 더 늘어난다고 해서 별 대수겠냐만 필요도 없는데 굳이 ….

        
        #include <iostream>
        using namespace std;
        
        	
            
        class LinkedList {
          public:
        	class Node {   // 이 경우는 노드 클래스를 밖으로 안빼내고 그냥 네스트 시켜줌.  난 네스티드 된거 싫더라
        	public:
        		int data;
        		Node* next;
        
                                   Node() : data(0), next(nullptr) {}    // 이 컨스트럭터 첨가해주어도 됨  
        		Node(int d)
        		{
        			data = d;
        			next = NULL;
        		}
        	};
             
        	// Function for inserting a node at the beginning
        	void push(Node*& head, int data)
        	{ 
        		Node* new_node = new Node(data);
        		new_node->next = head;
        		head = new_node;
        	}
        
        	// Function to display the nodes in the list.
        	void display(Node* head)
        	{
        		Node* temp = head;
        		while (temp != NULL) {
        			cout << temp->data << endl;
        			temp = temp->next;
        		}
        	}
        
        	// Function to delete the nth node from the end.
        	void removeNthFromEnd(Node* head, int n)
        	{ 
        		Node* fast = head;
        		Node* slow = head;
        
        		for (int i = 0; i < n; i++) {
        			fast = fast->next;
        		}
        		if (fast == NULL) {
        			head = head->next;
        			return;
        		}
        
        		while (fast->next != NULL) {
        			fast = fast->next;
        			slow = slow->next;
        		}
        		slow->next = slow->next->next;
        		return;
        	}
        };
        
        int main()
        {   
        	LinkedList* l = new LinkedList();  
                LinkedList::Node* head = new LinkedList::Node(5) ; 
         
        	// Create a list 1->2->3->4->5->NULL
        	//l->push(head,5);  // 더블포인터 경우는 l->push(&head,5); 
        	l->push(head,4);
        	l->push(head,3);
        	l->push(head,2);
        	l->push(head,1);
        	cout << "***** Linked List Before deletion *****"
        		<< endl;
        	l->display(head);
        
        	cout << "************** Delete nth Node from the End "
        			"*****"
        		<< endl; 
                     l->removeNthFromEnd(head, 2); // 보이드 리턴 타입 함수의 경우 테스트. 더블포인터 경우는 &head 로 변경
            
        	cout << "*********** Linked List after Deletion *****"
        		<< endl;
        	l->display(head);
        
        	return 0;
        }
        
      • w 76.***.207.158

        보통 예제 솔루션들도 메모리리크문제를 심각하게 고려하지 않아서 나도 메모리리크 문제를 아직까진 잘 고려안했는데 앞으로는 가능하면 제대로 고려해줄려고 노력해야겠네요. 솔직히 메모리리크 자꾸 지적하는것들도 좀 짜증나고…

    • 지나가다 174.***.73.136

      브래드냐

    • 878 108.***.234.21

      이야 먼말인지 모르겟는데 답글들 수준 멋잇다