2015년 2월 15일 일요일

데이터 구조와 알고리즘 3장

연결 리스트

 - 연결리스트란?
데이터의 집합을 저장하기 위해 사용되는 데이터 구조인데, 다음과 같은 속성을 갖는다.
 연속되는 항목들이 포인터로 연결된다.
 마지막 항목은 NULL로 포인트한다.
 프로그램이 수행되는 동안 크기가 커지거나 작아질 수 있다,
 (메모리가 허락하는 한) 필요한 만큼 길어질 수 있다.
 메모리 공간을 낭비하지 않는다(포인터를 위한 추가의 메모리를 필요로 한다).



 - 연결 리스트 ADT

연결 리스트의 주요 연산들
 삽입: 항목을 리스트에 추가한다.
 삭제: 지정된 위치의 항목을 리스트로부터 삭제하며 리턴한다.

연결 리스트의 보조적 연산들
 리스트 삭제: 리스트의 모든 항목을 삭제한다.(리스트도 삭제).
 개수 세기: 리스트의 항목의 개수를 리턴한다.
 리스트의 끝으로부터 n번째 항목 찾기


- 연결리스트와 배열 비교

배열 개요
배열의 항목을 저장하기 위해 메모리 블록 하나가 할당된다. 배열의 항목은 특정 항목의 인덱스를 첨자로 사용하여 일정한 시간으로 접근할 수 있다.
why?
처음에 데이터형에 따른 항목의 크기(int: 4바이트, float: 8바이트)가 계산되고, 그것이 항목의 인덱스에 곱해져서 기본 주소에 더해질 값이 된다.
이 두 연산이 일정한 시간이 걸므로 배열 접근은 일정한 시간으로 수행된다고 할 수 있다.

 - 배열의 장점
간단하고 사용하기 쉽다.
항목에의 접근이 더 빠르다.

 - 배열의 단점
고정된 크기: 배열의 크기는 정적이다.
한 블록의 할당: 처음에 배열을 할당할 때 전체 배열을 위한 메모리를 얻지 못할 때도 있다.
                  (배열의 크기가 클 경우)
복잡한 위치 기반의 삽입: 주어진 위치에 항목을 삽입하려면 기존의 항목들을 이동해야 할
수 있다. 이렇게 해야 원하는 위치에 새 항목을 추가할 자리가 생긴다. 만약 새 항목을 추가할 자리가 가장 앞이면 다른 항목들의 이동 연산이 더욱 오래 걸리게 된다.


 - 동적 배열
동적 배열은 랜덤 접근하는, 크기가 변하는 리스트 데이터 구조로 새로운 항목들이 추가되거나 삭제될 수 있다. 동적 배열을 구현하는 한 가지 간단한 방법은 처음에 고정된 크기의 배열로 시작하는 것이다. 이 배열이 가득 차면, 원래 배열의 두 배 크기의 새 배열을 만든다. 마찬가지로 배열의 항목의 수가 절반 이하가 되면 배열 크기를 반으로 줄인다.


- 연결 리스트의 장점
일정한 시간으로 확장 가능하다는 것이다. 배열을 만들기 위해서는 특정한 숫자의 항목을 위해 메모리를 할당해야만 한다. 배열에 항목을 추가하기 위해서는 새 배열을 만들어 원래 배열에서 새 배열로 항목들을 복사해야 한다. 그러나 이 방법은 시간이 오래 걸린다.
 이것을 막기 위해 처음에 많은 공간을 할당할 수도 있다. 하지만 이렇게 하면 필요한 것보다 더 많이 할당하게 되어 메모리를 낭비하게 된다. 이 때 연결 리스트를 사용하면 하나의 항목을 위한 공간으로 시작해서 복사나 재할당 없이 새 항목을 쉽게 추가할 수 있다.

 - 연결 리스트의 단점

1. 개별 항목에 접근하는 접근 시간이 길다는 것
   배열은 랜덤 접근이 가능하므로 배열의 항목에 접근하는 데 O(1)의 시간이 걸리다. 연결    리스트는 리스트의 항목에 접근하는 데 최악의 경우 O(n)의 시간이 걸린다.

2. 연결리스트를 변경하기 까다로운 경우
   마지막 항목이 삭제되면, 가장 끝에서 하나 전의 항목의 포인터가 NULL을 가리키도록      변경되어야만 한다.

3. 추가적인 참조 포인터를 위한 메모리 공간이 낭비된다.

연결리스트 배열 동적배열 비교



--단일 연결 리스트


public class ListNode {
   private int data;
   private ListNode next;
   public ListNode(int data){
      this.data = data;
}
   public void setData(int data){
       this.data = data;
}
   public int getData(){
      return data;
}
   public void setNext(ListNode next){
       this.next = next;
}
   public ListNode getNext(){
        return this.next;
}
}

 - 리스트의 기본 연산
 리스트 탐색하기
 리스트에 항목 삽입하기
 리스트에서 항목 삭제하기

 - 리스트 탐색하기
 '머리' 노드 포인터가 리스트의 첫 번째 노드를 가리킨다고 가정하자. 리스트를 탐색하기 위해 다음을 수행한다.
 1. 포인터를 따라간다.
 2. 탐색하면서 노드의 값을 표시한다
 3. '다음' 포인터가 NULL을 가리키면 멈춘다.

int ListLength(ListNode headNode) {
    int length = 0;
    ListNode currentNode = headNode;
    while(currentNode != null) {
       length++;
       currentNode = currentNode.getNext();
    }
    return length;
}

시간 복잡도: 크기가 n인 전체 리스트를 스캔하는 데 O(n)
공간 복잡도: 하나의 임시 변수를 만드는 데 O(1)


 - 단일 연결 리스트의 삽입

단일 연결 리스트에 삽입하는 경우는 세 가지가 있다.

1. 새 노드를 '머리' 노드 포인터 앞에 삽입하기
   새 노드의 '다음' 포인터를 현재의 '머리'를 가리키도록 업데이트
   '머리' 노드 포인터가 새 노드를 가리키도록 업데이트

2. 새 노드를 '꼬리' 노드 포인터 뒤에 삽입하기
   새 노드의 '다음' 포인터는 NULL을 가리킨다.
   마지막 노드의 '다음' 포인터는 새 노드를 가리킨다.

3. 새 노드를 리스트의 중간에 삽입하기.
    위치 노드의 '다음' 포인터가 새 노드를 가리키게 한다.
    새 노드의 '다음' 포인터가 위치 노드의 다음 노드를 가리키게 한다.

ListNode InsertInLinkedList (ListNode headNode, ListNode nodeToInsert, int position) {
       if(headNode == null)
           return nodeToInsert;
       int size = ListLength(headNode);
       if(position > size + 1 || position < 1) {
           System.out.println("Position of node to insert is invalid. The valid inputs are                                     1 to " + (size+1));
             return headNode;
           }
        if(position == 1){
             nodeToInsert.setNext(headNode);
             return nodeToInsert;
         }else{
            ListNode previousNode = headNode;
            int count = 1;
            while(count < position-1){
                 previousNode = previousNode.getNext();
                  count++;
             }
            ListNode currentNode = previousNode.getNext();
            nodeToInsert.setNext(currentNode);
           previousNode.setNext(nodeToInsert);
        }
        return headNode;
     }

시간 복잡도: 최악의 경우에 노드를 리스트의 가장 끝에 추가 해야 하므로 O(n)
공간 복잡도: 한 개의 임시 변수만을 생성하므로 O(1)


 - 단일 연결 리스트의 노드 삭제

1. 첫 번째 노드 삭제하기  
   임시 노드를 만들어 '머리' 포인터와 같은 노드를 가리키게 한다.
   '머리' 노드 포인터를 다음 노드로 옮기고 임시 노드를 삭제한다.

2. 마지막 노드 삭제하기
   리스트를 탐색하면서 마지막 하나 전 노드의 주소도 저장한다. 리스트의 가장 끝에 도달    했을 때 마지막 노드를 가리키는 포인터와 마지막 하나 전 노드를 가리키는 포인터, 두      개의 포인터를 갖고 있게 된다.
   마지막 노드 하나 전 노드의 '다음' 포인터를 NULL을 가리키도록 업데이트한다.
   마지막 노드를 제거한다.

3. 중간 노드 삭제하기
   리스트를 탐색하면서 삭제 할 노드 하나 전의 노드도 저장한다. 삭제할 노드를 찾았을 때    하나 전 노드의 '다음' 포인터를 삭제할 노드의 '다음' 포인터의 값으로 바꾼다.
   삭제할 현재 노드를 제거한다.

ListNode DeleteNodeFromLinkedList(ListNode headNode, int position) {
   int size = getLinkedListLength(headNode);
   if(position > size || position < 1) {
     System.out.println("Position of node to delete is invalid. The valid inputs are 1                                 to" + size);
       return headNode;
   }
    if(position == 1) {
    ListNode currentNode = headNode.getNext();
    headNode = null;
    return currentNode;
 } else {
    ListNode previousNode = headNode;
    int count = 1;
    while(count < position - 1) {
          previousNode = previousNode.getNext();
          count++;
     }
    ListNode currentNode = previousNode.getNext();
    previousNode.setNext(currentNode.getNext());
    currentNode = null;
  }
  return headNode;
}

시간 복잡도: O(n). 최악의 경우에 리스트 맨 마지막의 노드를 삭제해야 할 수도 있다.
공간 복잡도: O(1). 하나의 임시 변수만 생성하기 때문이다.

 - 단일 연결 리스트 삭제하기
 현재 노드의 값을 임시 변수에 저장하고 현재 노드의 메모리 할당을 해제하면 된다.
 현재 노드의 메모리 할당을 해제한 뒤에 임시 변수의 값을 이용해 다음 노드로 이동한 뒤    전체 노드에서 이 과정을 반복한다.

void DeleteLinkedList(ListNode head) {
    ListNode auxilaryNode, iterator = head;
    while(iterator != null) {
          auxilarayNode = iterator.getNext();
          iterator = null;
          iterator = auxilaryNode;
    }
}


-- 이중 연결 리스트

 - 장점: 리스트의 특정 노드로부터 양방향으로 탐색할 수 있다.
          //거꾸로 탐색 가능
 - 단점: 각 노드가 포인터를 하나 씩 더 필요로 하기 때문에 저장 공간이 더 필요하다.
          삽입, 삭제 연산이 조금 더 오래 걸린다.

 public class DLLNode{
      private int data;
      private DLLNode next;
      private DLLNode previous;
      public DLLNode(int data){
           this.data = data;
     }
     public void setData(int data){
         this.data = data;
     }
     public int getData(){
          return data;
     }
     public void setNext(DLLNode next){
        this.next = next;
     }
     public DLLNode getNext() {
        return this.next;
     }
     public void setPrevious(DLLNode previos) {
          this.previous = previous;
     }
     public DLLNode getPrevious(){
           return this.previous;
     }
 }


 - 이중 연결 리스트의 삽입

 1. 새 노드를 '머리' 노드 포인터 앞에 삽입하기
    새 노드의 '다음' 포인터가 현재의 '머리' 노드를 가리키도록 아래 그림의 점선을 업데이     트하고 새 노드의 '이전' 포인터는 NULL을 가리키게 한다.
    '머리' 노드의 '이전' 포인터가 새 노드를 가리키게 하고 새 노드를 '머리' 노드가 되게 한     다.

 2. 이중 연결 리스트의 가장 끝에 노드 삽입하기
     새 노드의 '다음' 포인터가 NULL을 가리키게 하고 '이전' 포인터가 리스트의 맨 마지막      노드를 가리키게 한다.
     리스트 맨 마지막 노드의 '다음' 포인터가 새 노드를 가리키게 한다.

 3. 이중 연결 리스트의 중간에 노드 삽입하기
     새 노드의 '다음' 포인터가 우리가 새 노드를 삽입하려고 하는 '위치' 노드의 다음 노드      를 가리키게 한다. 또한 새 노드의 '이전' 포인터가 '위치' 노드를 가리키게 한다.
     '위치' 노드의 '다음' 포인터가 새 노드를 가리키게 하고 '위치'노드 다음 노드의 '이전'      포인터도 새 노드를 가리키게 한다.

  DLLNode DLLInsert(DLLNode headNode, DLLNode nodeToInsert, int position) {
      if(headNode == null)   // 시작 부분에 삽입한다.
         return nodeToInsert;
      int size = getDLLLength(headNode);
      if(position > size + 1 || position < 1) {
         System.out.println("Position of nodeToInsert is invalid. " +
                               "The valid inputs are 1 to " + (size + 1));
         return headNode;
      }
      if(position == 1) {   //머리 부분에 노드를 삽입한다.
          nodeToInsert.setNext(headNode);
          headNode.setPrevious(nodeToInsert);
          return nodeToInsert;
     } else {   //끝이 될 때까지 중간에 노드를 삽입한다.
           DLLNode previousNode = headNode;
           int count = 1;
           while(count < position -1) {
               previousNode = previousNode.getNext();
               count++;
            }
            DLLNode currentNode = previousNode.getNext();
            nodeToInsert.setNext(currentNode);
            if(currentNode != null)
                currentNode.setPrevious(nodeToInsert);
            previousNode.setNext(nodeToInsert);
            nodeToInsert.setPrevious(previousNode);
        }
        return headNode;
  }

시간 복잡도:O(n). 최악의 경우에 리스트 가장 끝에 노드를 삽입해야 한다.
공간 복잡도:O(1). 하나의 임시 변수만 생성하기 때문이다.

 - 이중 연결 리스트의 노드 삭제

 1. 임시 노드를 만들어 '머리' 노드 포인터와 같은 노드를 가리키게 한다.
    이제, '머리' 노드 포인터를 다음 노드로 옮기고 '머리' 노드의 '이전' 포인터를 NULL을        가리키게 한다. 그런 다음 임시 노드를 삭제한다.

 2. 이중 연결 리스트의 마지막 노드 삭제하기
    리스트의 끝가지 탐색한다. 리스트의 끝에 도달했을 때는 마지막 노드의 '이전' 포인터       로 마지막 노드 하나 전 노드를 알 수 있다.
    마지막 노드 하나 전 노드의 '다음' 포인터를 NULL을 가리키게 한다.
    마지막 노드를 제거한다.

 3. 이중 연결 리스트에서 중간 노드 삭제하기
    리스트의 끝까지 탐색한다. 삭제할 노드를 찾으면 삭제할 노드 하나 전 노드의 '다음' 포     인터를 삭제할 노드 다음 노드를 가리키도록 한다. 그리고 삭제할 노드 다음 노드의 '이     전' 포인터를 삭제할 노드 하나 전 노드를 가리키도록 한다.
    현재 삭제할 노드를 제거한다.

  DLLNode DLLDelete(DLLNode headNode, int position) {
     int size = getDLLLength(headNode);
      //위치가 주어진 연결 리스트의 사이즈보다 클 경우 폐기한다.
     if(position > size || position < 1) {
       System.out.println("Position of node to delete is invalid.
               The valid inputs are 1 to " + size);
        return headNode;
      }
     if(position == 1) {   //시작 노드를 삭제
        DLLNode currentNode = headNode.getNext();
        headNode = null;
        currentNode.setPrevious(null);
       return currenNode;
     }else{   //끝이 될 때까지 내부의 노드를 삭제
        DLLNode previousNode = headNode;
        int count = 1;
        while(count < position-1) {
          previousNode = previousNode.getNext();
          count++;
        }
       DLLNode currentNode = previousNode.getNext();
       DLLNode laterNode = currentNode.getNext();
       previousNode.setNext(laterNode);
       if(laterNode != null)
          //마지막 노드가 NULL이 아닐 경우엔 이전 노드를 NULL로 설정
          laterNode.setPrevious(previousNode);
       currentNode = null;
   }
   return headNode;
 }

시간 복잡도: 크기 n인 리스트 전체를 탐색하므로 O(n)
공간 복잡도: 하나의 임시 변수만을 만들기 때문에 O(1)


-- 원형 연결 리스트



- 원형 연결 리스트의 노드 개수 세기
  노드의 개수를 세려면 '머리'라고 표시된 노드로부터 시작해서 '현재'라는 임시 노드를 하   나 사용하여 '현재' 노드가 시작점인 '머리'에 도달할 때까지 탐색한다.

int CircluarListLength(CLLNode headNode) {
    int length = 0;
    CLLNode currentNode = headNode;
    while(currentNode != null) {
       length++;
       currentNode =currentNode.getNext();
       if(currentNode == headNode)
          break;
     }
     return length;
 }

시간 복잡도: O(n), 크기 n인 전체 리스트를 탐색
공간 복잡도: O(1), 하나의 임시 변수만을 생성


 - 원형 연결 리스트의 내용 프린트하기
   값을 프린트하고 다음 노드로 이동하고 다시 프린트하기를 '머리' 노드에 다시 도달할 때    까지 계속 한다.

void PrintCircularListData(CLLNode headNode) {
   CLLNode CLLNode = headNode;
   while(CLLNode != null) {
        System.out.print(CLLNode.getData()+"->");
        CLLNode = CLLNode.getNext();
        if(CLLNode == headNode) break;
   }
   System.out.println("(" + CLLNode.getData() + ")headNode");
}

시간 복잡도: O(n), 크기 n인 전체 리스트를 탐색
공간 복잡도: O(1), 하나의 임시 변수만을 생성

 - 원형 연결 리스트의 가장 끝에 노드 삽입하기
    새 노드를 만들어 '다음 포인터를 일단 자기 자신을 가리키게 한다.
    새 노드의 '다음' 포인터가 '머리' 노드를 가리키게 한 다음, '꼬리' 노드까지 리스트를       탐색한다. 이 말은 원형 연결 리스트에서는 다음 노드가 '머리'노드에서 멈춰야 한다는       것이다.
    '머리'노드 이전 노드의 '다음 포인터가 새 노드를 가리키도록 업데이트를 하면 다음과       같은 리스트를 얻게 된다.

void InsertAtEndInCLL (LLNode headNode, LLNode nodeToInsert) {
     CLLNode currentNode = headNode;
     while(currentNode.getNext() != headNode) {
        currentNode.setNext(currentNode.getNext());
     }
     nodeToInsert.setNext(nodeToInsert);
      if(headNode == null) headNode = nodeToInsert;
     else{
          nodeToInsert.setNext(heaedNode);
          currentNode.setNext(nodeToInsert);
      }
   }

시간 복잡도: O(n), 크기 n인 전체 리스트를 탐색
공간 복잡도: O(1), 하나의 임시 변수만을 생성

 - 원형 연결 리스트의 가장 처음에 노드 삽입하기
    새 노드를 만들어 '다음' 포인터를 일단 자기 자신을 가리키게 한다.
    새 노드의 '다음' 포인터가 새 노드를 가리키도록 업데이트한다,
    새 노드를 '머리' 노드가 되게 한다.

void InsertAtBeginInCLL(LLNode headNode, LLNode nodeToInsert) {
     CLLNode currentNode = headNode;
     while(currentNode.getNext() != headNode) {
         currentNode.setNext(currentNode.getNext());
      }
     nodeToInsert.setNext(nodeToInsert);
     if(headNode == null)
         headNode = nodeToInsert;
     else {
          nodeToInsert.setNext(headNode);
          currentNode.setNext(nodeToInsert);
          headNode = nodeToInsert;
      }
 }

시간 복잡도: O(n), 크기 n인 전체 리스트를 탐색
공간 복잡도: O(1), 하나의 임시 변수만을 생성

- 원형 연결 리스트의 마지막 노드 삭제하기
  리스트를 탐색하며 마지막 노드와 그 전 노드를 찾는다.
  마지막 노드 하나 전 노드의 '다음' 포인터가 '머리'노드를 가리키게 한다.
  마지막 노드를 제거한다.

void DeleteLastNodeFromCLL(CLLNode head) {
   CLLNode temp = head;
   CLLNode currentNode = head;
   if(head == null) {
      System.out.println("List Empty");
      return;
    }
    while(currentNode.getNext() != headNode) {
       temp = currentNode;
       currentNode = currentNode.getNext();
    }
    currentNode = null;
    return;
 }

시간 복잡도: 크기 n인 전체 리스트를 탐색하므로 O(n)
공간 복잡도: 하나의 임시 변수만을 생성하므로 O(1)

 - 원형 연결 리스트의 첫 번째 노드 삭제하기
   리스트를 탐색하여 '꼬리' 노드를 찾는다. '꼬리'노드는 우리가 삭제할 '머리'노드의 하나    전 노드이다.
   '머리' 노드를 가리키는 임시 포인터를 만든다. 또한 '꼬리'노드의 '다음' 포인터가 '머리'    노드의 다음 노드를 가리키게 한다.
   이제, '머리' 포인터를 다음 노드로 옮긴다. 임시 포인터가 가리키는 노드를 제거한다.

 void DeleteFrontNodeFromCLL(CLLNode head) {
     CLLNode temp = head;
     CLLNode current = head;
     if(head == null) {
         Systen.out.println("List Empty");
         return;
     }
     while(current.getNext() != head)
           current.setNext(current.getNext());
     current.setNext(head.getNext());
     head = head.getNext();
     temp = null;
     return;
}

시간 복잡도: O(n), 크기 n인 전체 리스트를 탐색
공간 복잡도: O(1), 하나의 임시 변수만을 생성


 - 메모리-효율적인 이중 연결 리스트

일반적인 노드 정의
 class DLLNode {
     private int data;
     private DLLNode next;
     private DLLNode previous;
     ............
}

새로운 노드 정의
 public class ListNode {
     private int data;
     private ListNode ptrdiff;
     ............
}
ptrdiff 포인터는 다음 노드를 가리키는 포인터와 이전 노드를 가리키는 포인터의 차이를 갖고 있다. 포인터 차이는 배타적 논리합(Exclusive OR, \oplus)연산을 사용해서 계산된다.

ptrdiff = 이전 노드를 가리키는 포인터 \oplus 다음 노드를 가리키는 포인터

시작 노드('머리' 노드)의 ptrdiff는 NULL \oplus 다음 노드('머리' 노드의 다음 노드)이다.
이와 유사하게 마지막 노드의 ptrdiff는 이전 노드(마지막 노드의 이전 노드) \oplus NULL이다.

어떻게 이것이 가능한가?

\oplus X = 0
\oplus 0 = X
\oplus Y = Y \oplus X (대칭성)
(X \oplus Y) \oplus Z = X \oplus (Y \oplus Z) (전이성)

앞의 예에서, 우리가 노드 C에 있는데 B로 이동하고 싶다고 가정하자. C의 ptrdiff는 B \oplus D임을 알고 있다. B로 이동하고 싶다면, C의 ptrdiff와 D에 \oplus를 수행하면 된다.

(B \oplus D) \oplus D = B (D \oplus D = 0이므로)
만약 D로 이동하고 싶다면, C의 ptrdiff와 B에 \oplus를 적용하면 D를 알 수 있다.

(B \oplus D) \oplus B = D (B \oplus B = 0이므로)
앞의 논의에서 하나의 포인터만을 사용하여 리스트의 앞이나 뒤로 이동할 수 있다는 것을 알 수 있다. 이중 연결 리스트의 메모리-효율적인 구현은 시간 효율성을 아주 많이 잃지 않고도 실현 가능하다.


2015년 2월 10일 화요일

데이터 구조와 알고리즘 2장

재귀와 백트래킹

재귀: 자기 자신을 호출하는 함수를 재귀적이라고 부른다.
      *재귀가 확실히 종료되게 해야 한다.

 - 재귀 함수의 형식
 재귀 함수는 하위 작업을 수행하도록 자기 자신을 호출하여 작업을 수행한다. 어느 단계에 이르러서는, 자기 자신을 호출하지 않고도 수행할 수 있는 하위 작업을 수행한다. 이렇게 함수가 재귀 호출하지 않는 것을 기본 경우라고 하고, 함수가 자기 자신을 호출해서 하위 작업을 수행하는 것을 재귀 경우라고 한다.

if (기본 경우인지 테스트)
   return 기본 경우 값
else if (또다른 기본 경우 테스트)
     return 다른 기본 경우 값
//재귀 경우
else
    return (어떤 작업) 그런 다음 (재귀 호출)

ex) 팩토리얼 함수
//양의 정수의 팩토리얼을 계산한다.
int Fact(int n) {
   // 기본 경우: 0이나 1의 팩토리얼은 1이다
    if (n == 1)
       return 1;
    else if (n == 0)
         return 1;
// 재귀 경우: (n - 1) 팩토리얼에 n을 곱한다.
    else
         return n*Fact(n - 1);
}


 - 재귀와 메모리(시각화)
재귀 호출될 때마다 메서드의 복사본이 메모리에 만들어진다. 메서드가 종료할 때, 리턴하는 메서드의 복사본은 매모리에서 삭제된다.

ex) n = 4일 때, 팩토리얼 함수 표현


 재귀
기본 경우에 도달하면 종료한다.
각 재귀 호출은 메모리에 부가 공간을 필요로 한다.
무한 재귀에 들어가게 되면 메모리 용량을 초과해서 스택 오버플로우를 초래하게 된다.
어떤 문제들의 해답은 재귀적인 수식으로 만들기 쉽다.

 반복
조건이 거짓이 될 때 종료한다.
각 반복이 부가 공간을 필요로 하지 않는다.
무한 루프는 추가 메모리가 필요하지 않으므로 무한히 반복된다.
반복적 해법은 재귀적 해법에 비해 간단하지 않을 때가 있다.


 - 백트래킹
백트래킹은 분할 정복을 이용한 완전 검색 기법이다,

어떤 경우에는 문제를 푸는 최선의 알고리즘이 모든 경우의 수를 다 살펴보는 것이다.
이 방법은 항상 느리지만 도움이 될 수 있는 표준적인 도구들이 있다.
ex) 도구: 기본 오브젝트를 생성하는 알고리즘들
         이진 문자열(n-bit 문자열에 대해 2^n 확률)
         치환(n!), 조합(n!/r!*(n-r)!)
         일반화된 문자열(길이가 n인 k-ary 문자열은 k^n 확률)
백트래킹은 가지치기를 이용해 완전 검색을 빠르게 한다.


데이터 구조와 알고리즘 1장 2

 - 중요 사항
 최선의 경우, 최악의 경우, 평균의 경우를 분석할 때 상한, 하한, 평균 수행 시간을 구하려 한다. 그 중에서 집중해야하는 부분은 상한이다. 왜냐하면 알고리즘의 하한을 아는 것은 실용적으로 중요하지 않기 때문이다.

 - 점근적 분석 가이드라인 //중요!!!
    (알고리즘의 수행 시간을 계산하는 데 도움이 되는 몇 가지 일반적인 규칙이 있다.)

1) 루프: 루프의 수행 시간은 루프 안의 구문들의 수행 시간(조건문 수행 시간 포함해서) 곱 하기 반복 횟수가 최대 값이 된다.

//n번 수행
for( i=1; i<=n; i++)
      m = m + 2; //일정한 시간, c

전체 시간 = 상수 c*n = O(n).

2) 중복 루프: 안쪽에서 바깥쪽 순서로 분석한다. 전체 수행 시간은 각각의 루프의 수행 시간을 곱해서 구한다.

// 바깥 루프는 n번 수행
for(i=1; i<=n; i++) {
    // 안쪽 루프 n번 수행
    for(j=1; j<=n; j++)
          k=k+1;
}

전체 시간 = c*n*n = O(n^2)

3) 연속된 구문들: 각 구문의 복잡도를 더한다.

x = x + 1; //일정한 시간
//n번 수행
for(i=1; i<=n; i++)
     m = m + 2; //일정한 시간
//바깥 루프 n번 수행
for(i=1; i<=n; i++) {
     //안쪽 루프 n번 수행
      for(j=1; j<=n; j++)
          k = k+1; //일정한 시간
}

전체 시간 = c0 + c1*n + c2*n^2 = O(n^2)

4) If-then-else 구문: 최악의 경우 수행 시간은 조건문 수행 시간에 then 부분 또는 else 부분 중에 더 오래 걸리는 쪽 시간을 더한 경우이다.

//조건문: 상수
if(length()==0) {
    return false; //then 부분 : 상수
}
else { //else 부분: (상수 + 상수) * n
       for(int n=0; n < length(); n++) {
           // 또 다른 if: 상수 + 상수 (else 부분 없음)
           if(!list[n].equals(otherList.list[n]))
           // 상수
                   return false;
        }
}

전체 시간 = c0 + c1 + (c2 + c3)*n = O(n)

5) 로그형 복잡도: 어떤 알고리즘의 문제의 크기를 일부(보통은 1/2)를 줄이는 데 일정한 시간이 걸린다면 O(logn)이다.

for( i=1; i<=n; )
     i = i*2;
// i의 값이 매번 두 배가 된다.
   k번째 단계엔 2^k = n이 되고 루프를 빠져나온다.
 log(2^k) = logn
 k*log2 = logn
 k = logn  // 2를 베이스로 한다고 가정하면

 전체 시간 = O(logn)

또 다른 예
for(i=n; i<=2; )
     i = i/2;

이진 검색(n페이지의 사전에서 단어 찾기)

사전의 중앙을 찾는다.
단어가 중앙의 왼쪽인가? 오른쪽인가?
왼쪽이나 오른쪽 부분을 가지고 단어를 찾을 때까지 앞의 과정을 반복한다.


- 각 표기법의 특성
 이행성: f(n) = Θ(g(n))이고, g(n) = Θ(h(n))이면 => f(n) = Θ(h(n))이다.
             (O와 Ω에 대해서도 성립)
 반사성: f(n) = Θ(f(n))이다. (O와 Ω에 대해서도 성립)
 대칭성: g(n) = Θ(f(n))일 경우에만 (iff, if and only if) f(n) = Θ(g(n))이다.
 전치 대칭성: g(n) = Ω(f(n))일 경우에만 (iff, if and only if) f(n) = O(g(n))이다.


 - 자주 사용되는 로그 함수와 급수

 조화급수

∑(1/k) = 1 + 1/2 + ... + 1/n ≈ log n

∑(log k) ≈ n*log n

∑(k^p) = 1^p + 2^p + ... + n^p ≈ (1/p+1)*(n^(p+1))



 - 분할 정복을 위한 마스터 정리

재귀 관계식이 T(n) = aT(n/b) + Θ((n^k)*(log n)^p)의 형태로 a >= 1 , b > 1, k >= 0이며 p가 실수라면 다음과 같다.

마스터 정리 1. a > b^k 이면 T(n) = Θ(n^(logba))

마스터 정리 2. a = b^k일 경우
   a. p > -1이면 T(n) = Θ(((n^(logba))*((log n)^(p+1)))
   b. p = -1이면 T(n) = Θ(((n^(logba))*(log(log n)))
   c. p < -1이면 T(n) = Θ((n^(logba))
마스터 정리 3. a < b^k일 경우
   a. p >= 0이면 T(n) = Θ((n^k)*((log n)^p))
   b. p < 0이면 T(n) = Θ(n^k)

 // a, b, k, p 값을 잘 구하는 것이 중요


 - 차감 정복 점화식을 위한 마스터 정리

어떤 상수 c, a > 0, b > 0, k >= 0과 함수 f(n)에서 다음과 같은 속성을 갖는다고 하자.
T(n) = c                   if n =< 1
     = a*(T(n-b) + f(n)), if n > 1

f(n)이 O(n^k) 안에 있다면

        O(n^k),     if a < 1
T(n) = O(n^(k+1)), if a = 1
        O((n^k)*(a^(n/b))), if a > 1


 - 상각 분석
상각 분석은 작업 시퀀스의 시간평균화된 수행 시간을 계산하는 것을 말한다.
//작업 시퀀스에 대한 최악의 경우 분석
상각 분석은 대부분의 작업은 단순하지만 몇몇 작업이 시간이 많이 걸리는 작업으로 구성된 작업 시퀀스의 분석에 주로 이용된다.
시간이 많이 걸리는 작업의 빈도가 특별히 낮다는 것을 증명할 수만 있다면, 단순한 작업의 수행 시간으로 이런 작업의 시간을 커버하고 단순한 작업의 한계만 계산할 수 있다.
일반적인 접근 방법은 작업 시퀀스의 각 작업에 가공의 비용을 할당하는데, 이 가공의 비용의 총합이 시퀀스의 실제 비용의 총합을 넘지 않도록 하는 것이다. 이 가공의 비용이 작업의 상각 비용이라고 불린다.



2015년 2월 8일 일요일

데이터 구조와 알고리즘 1장 1

- 데이터 구조
1) 선형 데이터 구조
 항목들이 순차적 차례에 따라 접근되지만 순차적으로 저장되어야 하는 것은 아니다.
ex) 연결 리스트, 스택, 큐

2) 비선형 데이터 구조
 항목들이 비선형의 차례로 저장/접근된다.
ex) 트리, 그래프


- 추상 데이터형(Abstract Data Type)
 보통의 사용자 정의 데이터형은 연산과 함께 정의된다. 문제를 푸는 과정을 단순화시키기 위해 데이터 구조와 연산을 합쳐 놓은 것을 추상 데이터형이라고 하는데, ADT는 두 부분으로 구성된다.
 1. 데이터의 선언
 2. 연산의 선언
주로 사용되는 ADT에는 연결 리스트,스택,큐,우선순위 큐,이진 트리, 딕셔너리,서로 소 집합, 해시 테이블, 그래프 등 다수가 있다.
    ex) 스택은 데이터를 데이터 구조에 저장할 때 LIFO방식을 사용한다. 스택에 가장 나중에 집어넣은 항목이 제일 먼저 꺼내지는 항목이 되는 것이다. 스택에서 주로 사용되는 연산에는 스택 만들기, 스택에 항목 집어넣기, 스택에서 항목 꺼내기, 스택의 맨 위에 있는 항목 찾기, 스택 안의 항목 개수 찾기 등이 있다.
 ADT를 정의할 때는 구체적인 구현은 신경 쓰지 않아도 된다. 실제 사용할 때 구현이 중요하다. 사용 용도에 따라 그에 알맞는 ADT들이 쓰이며 몇몇 ADT는 특정 용도에 최적화되어 있다.


 - 알고리즘: 주어진 문제를 풀기 위한 단계별 지시사항들이다.

 - 왜 알고리즘을 분석하는가?
한 가지 문제를 푸는 데 여러 가지 알고리즘이 있을 수 있다. 알고리즘 분석은 시간과 공간적으로 어느 것이 가장 효율적인지 알 수 있게 해준다.

 - 알고리즘 정렬의 목적
알고리즘 정렬의 목적은 알고리즘을 비교하는 것인데, 주로 수행 시간으로 비교하지만 다른 요인들로 비교할 때도 있다.

 - 수행 시간 분석이란 무엇인가?
문제의 크기가 증가함에 따라 처리 시간이 얼마나 증가하는지를 분석하는 것이다. 입력 크기는 입력되는 항목의 개수인데 문제의 종류에 따라 입력의 종류도 달라진다. 일반적으로 다음과 같은 종류의 입력들을 볼 수 있다.
 -배열의 크기
 -다항식의 차수
 -행렬의 항목 개수
 -이진으로 표현된 입력의 비트 수
 -그래프에서의 정점과 간선

 - 어떻게 알고리즘을 비교하는가?
// 컴퓨터 프로그램의 구조와 해석(1) 1장 2 자람 차수 부분 참조
                                                    여기서는 '증가율'이라 표현한다.

자주 사용되는 증가율 목록
// 모르는 거는 그냥 넘어가기


 - 분석의 종류

최악의 경우
 알고리즘이 오래 걸리는 경우이다.
 알고리즘이 느리게 수행되도록 하는 것을 입력으로 한다.

최선의 경우
 알고리즘이 제일 적은 시간이 걸리게 하는 경우이다.
 알고리즘이 가장 빨리 수행되도록 하는 것을 입력으로 한다.

평균의 경우
 알고리즘의 예상 수행 시간을 제시한다.
 입력이 무작위라고 가정한다.
 (하한 시간=< 평균 시간 =< 상한 시간)

ex)
f(n) = n^2 + 500, 최악의 경우
f(n) = n + 100*n + 500, 최선의 경우
* 평균의 경우에는 입력을 정의한 후 수식을 만든다.


 - 점근적 표기법
최선, 평균, 최악의 경우에 대한 수식이 있을 때, 이 세개의 경우 모두에 대한 상한과 하한을 찾아야 한다. 이러한 상한과 하한을 표현하기 위해 필요한 문법을 알아야한다.

 1) 빅-오 표기법
이 표기법은 주어진 함수에서 엄밀한 상한을 찾게 해준다. 일반적으로 f(n) = O(g(n))으로 표현된다. 이 것은 n의 값이 클 때, f(n)의 상한이 g(n)이라는 말이다.
ex)
f(n) = n^4 + 100*(n^2) + 10*n + 50 일 때 n^4이 g(n)이다.

빅-오 표기법의 정의
 O(g(n)) = {f(n): n > n0 인 모든 n에 대해 0 =< f(n) =< c*(g(n))을 만족하는 양의 상수 c와 n0 이 존재한다.}


빅-오 시각화
O(g(n))은 g(n)의 증가율보다 작거나 같은 함수들의 집합이다.


빅-오 예제
예제1) f(n) = 3*n + 8 의 상한을 구하라.
         n >= 8인 모든 n에 대하여 3*n + 8 =< 4*n이다.
         따라서 3*n + 8 = O(n)이며 c = 4, n0 = 8이다.

예제2) f(n) = n^2 + 1의 상한을 구하라.
예제3) f(n) = n^4 + 100*n^2 + 50의 상한을 구하라.
예제4) f(n) = 2*n^4 - 2*n^2의 상한을 구하라.
예제5) f(n) = n의 상한을 구하라.


 2) 오메가 표기법
이 표기법은 주어진 알고리즘에 대해 엄밀한 하한을 찾게 해주며 f(n) = Ω(g(n))으로 표현된다. 이는 n의 값이 클 때, f(n)의 엄밀한 하한이 g(n)이라는 말이다.

오메가 표기법 정의
Ω(g(n)) = {f(n): n >= n0 인 모든 n에 대해 0 =< c*g(n) =< f(n)을 만족하는 양의 상수 c와 n0이 존재한다}



 오메가 예제
예제1) f(n) = 5*n^2의 하한을 구하라.
        0 =< c*n =< 5*n^2 => c*n =< 5*n^2 => c=1, n0=1

예제2) f(n) = 100*n + 5 =/= Ω(n^2)을 증명하라
          0 =< c*n^2 =< 100*n + 5
          n >= 1인 임의의 n에 대해 100*n + 5 =< 100*n + 5*n = 105*n
         c*n^2 =< 105*n => n*(c*n - 105) =< 0
         n이 양수이므로 => c*n -105 =< 0 => n =< 105/c
          => n이 어떤 상수보다 작을 수 없으므로 모순이 된다.

예제3) n! = Ω(2^n), n^3 = Ω(n^2), n = Ω(logn) 


 - 세타 표기법
이 표기법은 주어진 함수(알고리즘)의 상한과 하한이 같은지 아닌지를 결정한다. 알고리즘의 평균 수행 시간은 항상 하한과 상한 사이에 존재한다. 만역 상한(O)과 하한(Ω)이 같다면 세타(Θ)표기법 역시 같은 증가율을 갖게 된다.


세타 표기법의 정의
Θ(g(n)) = {f(n): n >= n0인 모든 n에 대해 0 =< c1*g(n) =<f(n) =< c2*g(n)을 만족하는 양의 상수 c1, c2와 n0이 존재한다}

세타 예제
예제1) f(n) = (n^2)/2 - n/2의 Θ한계를 구하라.
          n >= 1인 모든 n에 대하여 n^2 =< (n^2)/2 - n/2 =< n^2이다.
          따라서 (n^2)/2 - n/2 = Θ(n^2)이며 c1 = 1/5, c2 = 1이고 n0 = 1이다.

예제2)  n =/= Θ(n^2)임을 증명하라.
          c1*n^2 =< n =< c2*n^2 => n =< 1/c1일 때만 참이다.
          따라서 n =/= Θ(n^2)이다. 

예제3) 6*n^3 =/= Θ(n^2)임을 증명하라.
          c1*n^2 =< 6*n^3 =< c2*n^2 => n =< c2/6일 때만 참이다.
          따라서 6*n^3 =/= Θ(n^2)이다. 
예제4) n =/= Θ(logn)임을 증명하라.
        c1*logn =< n =< c2*logn => n >= n0인 임의의 n에 대하여 c >= n/logn 는 불가능하다.



2015년 2월 3일 화요일

아두이노 (모터 제어)

* 무조건적으로 풀업 저항을 달고 시작할 것!!!!!
* 회로를 혼자 만들 수 있는 수준의 사람이라고 가정하고 써놓음


-서보 모터
서보는 지속적으로 회전하는 대신 어떤 위치로 이동하는 방식으로 작동되기 때문에 물리적인 이동을 정밀하게 제어하는 데 유용하다(특히 0도 ~ 180도 사이에서)

서보 모터 내부 구조

위치 피드백이 끊어져 있는 연속 회전 서보 모터를 사용하면 계속 회전할 수 있다.
                                               // 모터 쉴드를 사용하지 않아도 된다.

서보 모터는 펄스의 지속 시간에 반응한다.
펄스의 지속 시간에 따른 회전방향

-----------------------ㅣ---------------------------ㅣ------------------------>
                                             1ms                                                  2ms
    한쪽 끝으로 회전                 펄스폭에 비례해서 회전               반대쪽으로 회전



 -솔레노이드와 릴레이
솔레노이드는 전원이 공급되었을 때 선형 이동을 제공한다. 솔레노이드에는 전류가 코일을 통과할 때 생성되는 자기자에 의해 움직이는 금속 코어가 있다. 기계식 릴레이는 전기 접점을 연결하거나 끊어 주는 일종의 솔레노이드다.


-브러시드 및 브러시리스 모터


-스텝퍼 모터
스텝퍼는 제어 펄스에 반응해서 정해진 각도로 회전하는 모터다. 단계별 회전 각도는 모터에 따라 다르며, 단계당 1~2도부터 시작해서 30도 이상까지 다양하다.


 *모터를 선택할 때 가장 중요하게 고려하는 특성은 토크다. 
토크에 따라 모터의 최대 작업 용량이 결정된다. 일반적으로 토크가 높은 모터가 저토크 모터에 비해 크고 무거울 뿐만 아니라 전류도 많이 사용한다.


서보 모터 예제

#include <Servo.h>

Servo myservo;   // 서보를 제어할 서보 오브젝트를 만든다.

int angle =0;   // 서보 위치를 저장할 변수

void setup(){
  myservo.attach(9);   // 핀 9의 서보를 서보 오브젝트에 연결한다.
}

void loop(){
  for(angle =0; angle<180; angle+=1){   // 0도에서 180도로 이동한다.
    myservo.write(angle);                       // 'angle' 변수의 위치로 서보를 이동시킨다.
    delay(20);                                         // 서보 명령 간에 20ms를 기다린다.
  }
  for(angle = 180; angle >= 1; angle-=1){ // 180도에서 0도로 이동한다.
    myservo.write(angle);                         // 서보를 반대 방향으로 이동한다.
    delay(20);                                           // 서보 명령 간에 20ms를 기다린다.
  }
}

myservo.attach(pin, min, max);

min: 서보의 최소 각도(0도)에 해당하는 펄스 폭이며, 기본값은 554이다.

max: 서보의 최대 각도(180도)에 해당하는 펄스 폭이며, 기본값은 2400이다.

 2개 이하의 서보 제어하기(회전방향, 속도)
#include <Servo.h>

Servo myservo;

int potpin = 0;   //포텐셔미터를 연결하는 데 사용되는 아날로그 핀
int val;

void setup(){
  myservo.attach(9);
}

void loop(){
 val = analogRead(potpin);   // 포텐셔미터의 값을 읽는다.
 val = map(val, 0, 1023, 0, 180);
 myservo.write(val);
 delay(15);
}

연속 회전 서보의 속도 제어하기
#include <Servo.h>

Servo myservo1;
Servo myservo2;

int angle=0;

void setup(){
  myservo1.attach(9);
  myservo2.attach(10);
}
void loop(){
  for(angle=90;angle<180;angle+=1){
    myservo1.write(angle);
    myservo2.write(180-angle);
    delay(20);
  }
  for(angle=180;angle>=90;angle-=1){
    myservo1.write(angle);
    myservo2.write(180-angle);
  }
}

컴퓨터 명령으로 서보 제어하기
#include<Servo.h>

#define SERVOS 4
int servoPins[SERVOS] = {7, 8, 9, 10};   // 핀 7부터 핀 10까지 서보를 연결한다.

Servo myservo[SERVOS];

void setup(){
  Serial.begin(9600);
  for(int i=0; i < SERVOS; i++)
  myservo[i].attach(servoPins[i]);
}

void loop(){
  serviceSerial();
}

// serviceSerial 함수는 시리얼 포트를 검사한 후 수신된 데이터를 사용하여 위치를 갱신한다.
// 이 함수에서는 다음 형식의 서보 데이터를 원한다.
//
// "180a"는 서보 a에 180을 기록한다.
// "90b"는 서보 b에 90을 기록한다.
//

void serviceSerial(){
  static int pos = 0;
  
  if(Serial.available()) {
    char ch = Serial.read();
    
    if(isDigit(ch))                                                    // ch가 숫자라면:
    pos = pos*10 + ch - '0';                                  // 값을 누적시킨다.
    else if(ch >= 'a' && ch <= 'a'+SERVOS)           // ch가 서보에 해당하는 문자라면:
    myservo[ch - 'a'].write(pos);                         // 위치 배열에 위치를 저장한다.
  }
}

솔레노이드
int solenoidPin = 2;

void setup(){
  pinMode(solenoidPin, OUTPUT);
}

void loop(){
  long interval = 1000 * 60 * 60;   // 1시간
  
  digitalWrite(solenoidPin, HIGH);
  delay(1000);
  digitalWrite(solenoidPin, LOW);
  delay(interval);
}

대부분의 솔레노이드는 아두이노 핀에서 제공해주는 것보다 많은 전원이 필요하므로 트랜지스터를 사용해서 솔레노이드를 활성화하는 데 필요한 전류를 공급한다.

센서 + 진동 모터
const int motorPin = 3;
const int sensorPin = 0;
int sensorAmbient = 0;                        // 초기 밝기
const in thresholdMargin = 100;         // 진동 발생 조건에 해당하는 밝기 차이

void setup(){
  pinMode(motorPin, OUTPUT);
  sensorAmbient = analogRead(sensorPin);
}

void loop(){
  int sensorValue = analogRead(sensorPin);
  if( sensorValue > sensorAmbient + thresholdMargin){
    digitalWrite(motorPin, HIGH);
  }else{
    digitalWrite(motorPin, LOW);
  }
}

바이폴라 스텝퍼 모터 구동하기(아두이노 모터 실드가 필요하다)
#include <Stepper.h>

#define STEPS 24      //사용 중인 모터의 단계 수에 맞게 이 값을 변경한다.

Stepper stepper(STEPs, 2, 3, 4, 5);
// 스텝퍼 클래스의 인스턴스를 만들면서 모터의 단계 수와 모터를 연결할 핀을 지정한다.

int steps =0;

void setup(){
  stepper.setSpeed(30);  // 모터 속도를 30RPM으로 설정한다.
  Serial.begin(9600);
}

void loop(){
  if(Serial.available()){
    char ch = Serial.read();
    
    if(isDigit(ch)){
      steps = steps * 10 + ch - '0';
    }else if(ch =='+'){
      stepper.step(steps);
      steps = 0;
    }else if(ch == '-'){
      stepper.step(steps*-1);
      steps = 0;
    }
  }
}
   // 예를 들어 시리얼창에 3+, 7-로 입력한다.



바이폴라 스텝퍼 모터 구동하기
const int dirPin = 2;
const int stepPin = 3;

int speed = 100;   //원하는 속도(초당 단계 수)
int steps = 0;        // 단계 수

void setup(){
  pinMode(dirPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  Serial.begin(9600);
}
void loop(){
  if(Serial.available()){
    char ch = Serial.read();
    
    if(isDigit(ch)){     // ch가 숫자라면?
      steps = steps * 10 + ch - '0';    // 값을 누적시킨다.
    }else if(ch == '+'){
      step(steps);
      steps = 0;
    }else if(ch == '-'){
      step(-steps);
      steps = 0;
    }else if(ch == 's'){
      speed = steps;
      Serial.print("Setting speed to ");
      Serial.println(steps);
      steps = 0;
    }
  }
}

void step(int steps){
  int stepDelay = 1000/speed;
  int stepsLeft;
  if(steps > 0){
    digitalWrite(dirPin,HIGH);
    stepsLeft = steps;
  }
  if(steps < 0){
    digitalWrite(dirPin,LOW);
    stepsLeft = -steps;
  }
  while(stepsLeft > 0){
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(1);
    digitalWrite(stepPin, LOW);
    delay(stepDelay);
    stepsLeft--;
  }
}



2015년 2월 2일 월요일

아두이노 (센서로부터 입력받기)

무조건적으로 풀업 저항을 달고 시작할 것!!!!!
* 회로를 혼자 만들 수 있는 수준의 사람이라고 가정하고 써놓음

센서에서 정보를 제공하는 방법

1) 디지털 켜기/끄기
 ex) tilt seneor , motion sensor

2) 아날로그
 감지된 값에 비려하는 전압을 제공하는 센서도 있다.
 ex) 온도 ,조명 ,동작, 진도, 소리
analogRead 명령어를 사용한다.

3) 펄스 폭
 거리값에 비례하는 펄스 지속 시간을 사용하여 데이터를 제공한다.
 ex) PING))
pulseIn 명령을 사용하여 펄스의 지속 시간을 측정한다.

4) 시리얼
시리얼 프로토콜을 사용하여 값을 제공한다.
ex) GPS

5) 동기 프로토콜 : I2C와 SPI
이 장이 아닌 또다른 장에서 자세히 다룰 것임

주의점:
1) 센서를 사용하기 전에 센서의 데이터시트를 잘 참조해라
2) 센서의 출력값을 일반인들이 알아볼 수 있는 단위로 변경하는 코드를 짜넣어라
3) 원하는 신호와 주위의 잡음을 분리할 수 있어야 한다.


 흔들림 감지하기(디지털로 신호를 주는 센서 처리 방법)
/*tilt sketch*/
const int tiltSensorPin = 2;   //틸트 센서가 연결된 핀
const int firstLEDPin = 11;   // LED 핀 중 하나
const int secondLEDPin = 12;    //또 하나의 LED 핀

void setup()
{
  pinMode(tiltSensorPin,INPUT);   // 2번 핀을 읽는다
  pinMode(tiltSensorPin,HIGH);     //풀업 저항을 사용

  pinMode(firstLEDPin, OUTPUT);   // 11 핀을 제어한다.
  pinMode(secondLEDPin,OUTPUT);   // 12 핀을 제어한다.
}

void loop()
{
  if(digitalRead(tiltSensorPin)) {   //핀이 high인지 검사한다.
    digitalWrite(firstLEDPin,HIGH);   //high라면 firstLED를 켠다.
    digitalWrite(secondLEDPin,LOW);   //secondLED를 켠다
  }else{
    digitalWrite(firstLEDPin,LOW);   //반대로 수행한다.
    digitalWrite(secondLEDPin,HIGH);
  }
}
=> 틸트 센서의 기울어진 방향에 따라 LED 중 하나가 켜짐


/* shaken sketch*/

const int tiltSensorPin = 2;  //틸트 센서가 연결된 핀
const int ledPin = 13;   //led가 연결된 핀
int tiltSensorPreviousValue = 0;   //틸트센서의 과거값
int tiltSensorCurrentValue = 0;   //틸트센서의 현재값
long lastTimeMoved = 0;   /마지막으로 흔들렸던 시간
int shakeTime = 50;   //흔들림의 기준

void setup()
{
  pinMode(tiltSensorPin,INPUT);  // 2번 핀을 읽는다
  digitalWrite(tiltSensorPin,HIGH);   //풀업 저항 사용
  pinMode(ledPin,OUTPUT);   // 13 핀을 제어한다.
}
void loop()
{
  tiltSensorCurrentValue = digitalRead(tiltSensorPin);  // 2번 핀의 값을 현재값에 대입한다
  if(tiltSensorPreviousValue != tiltSensorCurrentValue) {  //과거값과 현재값이 같지 않으면
    lastTimeMoved = millis();  
  //millis: 프로그램이 시작된 후의 시간을 밀리세컨드 단위로 리턴한다.
    tiltSensorPreviousValue = tiltSensorCurrentValue;    //현재값을 과거값에 대입
  }

  if(millis() - lastTimeMoved < shakeTime) {  // 흔들렸으면
    digitalWrite(ledPin,HIGH);
  }
  else {   //흔들리지 않았으면
    digitalWrite(ledPin,LOW);
  }
}

흔들림을 판단하는 메커니즘

------------------------------------------------------------------------->
millis

               l             l                           l        l                l                 l          l       >
lastTimeMoved

               --------  ----------------  ----   --------   ----------  ------
millis - lasttimeMoved =>이게 50 millisecond 이되지 않으면 흔들린 것으로 간주


  조명 감지하기( 아날로그 신호를 주는 센서들 처리 방법)
/*light sensor*/
const int ledPin = 13;
const int sensorPin =0;

void setup()
{
  pinMode(ledPin,OUTPUT);
}

void loop()
{
  int rate = analogRead(sensorPin); // 0 ~ 1023 사이의 값으로 나온다.
  digitalWrite(ledPin, HIGH);
  delay(rate);
  digitalWrite(ledPin,LOW);
  delay(rate);
}


거리 측정하기(펄스 폭 신호를 주는 센서들 처리방법)

/*EZ1 Rangefinder Distance Sensor*/

const int sensorPin = 5;
const int ledPin = 13;

long value = 0;
int cm = 0;
int inches =0;

void setup()
{
  value = pulseIn(sensorPin, HIGH);
  cm = value / 58;                            //펄스 폭이 cm당 58마이크로다.
  inches = value / 147;                     //인치당 147마이크로초
  Serial.print(cm);
  Serial.print(',');
  Serial.println(inches);
  
  digitalWrite(ledPin, HIGH);
  delay(cm*10);                               //1센티미터당 지연 시간을 10밀리초 추가한다.
  digitalWrite(ledPin, LOW);
  delay(cm*10);
  
  delay(20);
}


정확한 거리 측정하기
/*infrared-ray_distance sketch*/

const int ledPin = 13;
const int sensorPin  = 0;
const long referenceMv = 5000;    //곱하기 연산 시 오버플로우를 방지하기 위한 long형 정수

void setup()
{
  Serial.begin(9600);
  pinMode(ledPin,OUTPUT);
}

void loop()
{
  int val = analogRead(sensorPin);
  int mV = (val * referenceMv) / 1023;
  
  Serial.print(mV);
  Serial.print(",");
 int cm = getDistance(mV);
 Serial.println(cm);

 digitalWrite(ledPin,HIGH);
 delay(cm * 10);
 digitalWrite(ledPin,LOW);
 delay(cm * 10);

 delay(100);
}

const int TABLE_ENTRIES = 12;
const int INTERVAL = 250;

static int distance[TABLE_ENTRIES] = {150,140,130,100,60,50,40,35,30,25,20,15};

int getDistance(int mV)
{
 if(mV > INTERVAL * TABLE_ENTRIES-1)   // 1)
return distance[TABLE_ENTRIES -1];
else
{
  int index = mV / INTERVAL;   // 2)

  float frac = (mV % 250) / (float)INTERVAL;   // 3)
  return distance[index] + ((distance[index]) * (frac*100));
}
}
/* IR 센서의 출력은 거리에 비례하지 않는다
 * 이 때 적용되는 기술: 보간(interpolating)
 * 전압을 거리로 변환하는 작업을 수행하는 함수
 *  int getDistance(int mv)
 * 1) 값이 테이블에 지정된 범위 내에 있는 지 검사한다. 값이 범위 내에 없으면 가장 짧은    * 유효 거리가 리턴된다.
 * 2) 값이 테이블 범위 내에 있으면 정수 나누기 연산을 통해 판독값보다 낮으면서 가장 가    * 까운 항목이 계산된다.
 * 3) 판독값이 두 항목 사이에 있을 경우에는 모듈로 연산자를 사용해서 소수 값이 계산된다


소리 감지하기
/* microphone sketch*/
const int ledPin =13;              // 핀 13의 LED가 켜진다.
const int middleValue = 512;   // 아날로그 값 범위의 중간값
const int numberOfSamples = 128;   // 한 번에 읽어올 판독값의 개수

int sample;                   // 마이크로폰에서 읽어온 값
long signal;                  // DC 오프셋을 제거한 이후의 판독값
long averageReading;   // 해당 루프 동안 읽어온 값의 평균

long runningAverage = 0;   // 계산된 값의 실행 평균
const int averagedOver = 16;   // 새 값이 실행 평균에 영향을 미치는 속도
                                            // 숫자가 클수록 느려진다.

const int threshold = 400;     // 조명레벨

void setup() {
  pinMode(ledPin,OUTPUT);
  Serial.begin(9600);
}
void loop() {
  long sumOfSquares = 0;
  for (int i = 0; i < numberOfSamples; i++) {   // 많은 값을 읽어와서 평균을 구한다.
    sample = analogRead(0);                        // 값을 읽어온다
    signal = (sample - middleValue);             // 오프셋을 적용한다.
    signal *= signal;                                     // 양수가 되도록 값을 제곱한다.
    sumOfSquares += signal;                       // 합계를 추가한다.
  }
  averageReading = sumOfSquares/numberOfSamples;   //실행 평균을 계산한다.
  runningAverage=(((averagedOver-1)*runningAverage)+averageReading)/averagedOver;
  
  if(runningAverage>threshold) {   // 평균이 임계값보다 큰가?
    digitalWrite(ledPin,HIGH);       // 그렇다면 LED를 켠다.
  }else{
    digitalWrite(ledPin,LOW);       // 그렇지 않다면 LED를 끈다.
  }
  Serial.println(runningAverage);   // 값을 확인하기 위해 인쇄한다.

}


RFID 태그 판독하기
/*RFID sketch*/
const int startByte = 10;    // 각 태그 앞에 아스키 줄 바꿈 문자를 사용한다.
const int endByte = 13;     // 각 태그 끝에 아스키 캐리지 리턴 문자를 사용한다.
const int tagLength = 10;   // 태그의 자릿수
const int totalLength = tagLength + 2;   // 태그 길이 + 시작 및 종료 바이트
char tag[tagLength + 1];     // 태그와 종료 널 포함

int bytesread = 0;

void setup()
{
  Serial.print(2400);        // RFID 판독기의 전송 속도로 설정한다.
  pinMode(2,OUTPUT);   // RFID ENABLE 핀에 연결된다. 
  digitalWrite(2,LOW);    // RFID 판독기를 실행한다.
}

void loop()
{
  if(Serial.available() >= totalLength)   // 충분한 데이터가 있는지 검사한다.
  {
    if(Serial.read() == startByte)
    {
      bytesread = 0;   //태그의 시작이므로 count를 0으로 재설정한다.
      while(bytesread < tagLength)   // 10자리 코드를 읽는다.
      {
        int val = Serial.read();
        if((val == startByte)||(val == endByte))  // 코드의 끝을 검사한다.
        break;
        tag[bytesread] = val;
        bytesread = bytesread + 1;   // 다음 자릿수의 코드를 읽기 위해 준비한다.
      }
      if(Serial.read() == endByte)   // 올바른 종료 문자인지 검사한다.
      {
        tag[bytesread] = 0;   // 문자열을 종료한다.
        Serial.print("RFID tag is: ");
        Serial.println(tag);
      }
    }
  }

}


로터리 이동 추적하기
/*rotary-movement sensor*/
const int encoderPinA = 4;   // 인코더가 회전하고 있는 지의 여부
const int encoderPinB = 2;   // 인코더가 회전하고 있는 방향
const int encoderStepsPerRevolution = 16;   // 회전당 단계수
int angle = 0;

int val;

int encoderPos = 0;
boolean encoderALast = LOW;   // 이전 핀의 상태를 기억한다.

void setup()
{
  pinMode(encoderPinA,INPUT);
  pinMode(encoderPinB,INPUT);
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);
  Serial.begin(9600);
}

void loop()
{
  boolean encoderA = digitalRead(encoderPinA);  // 인코더 핀 하나를 읽는다.
  
  if ((encoderALast == HIGH) && (encoderPinA == LOW));  
  {
    if(digitalRead(encoderPinB) == LOW)
    {
      encoderPos--;
    }
    else
   {
    encoderPos++;
   }
  angle=(encoderPos % encoderStepsPerRevolution)*360/encoderStepsPerRevolution; 
   Serial.print(encoderPos);
   Serial.print(" ");
   Serial.println(angle);
  }
  encoderALast = encoderA;
}