자료구조와 알고리듬 With Java/[zerobase] Algorithm

Chapter 02. 선형 자료구조

계란💕 2022. 5. 30. 13:20

2.1 자료구조 소개

  Def) 자료 구조(Data Structure): 자료를 효율적으로 관리하기 위한 구조

    - 목적에 맞게 사용한 자료구조는 실행시간 단축이나 메모리 용량 절감 효과가 있다.

    - 선형 자료구조 / 비선형 자료구조

 

  Note) 자료구조의 구현

    - 추상 자료형(absract Data type, ADT)

      -> 자료 형태와 자료에 대한 연산을 정의한 것

      -> 구체적인 구현 방법은 명시하지 않는다. (추상 클래스, 인터페이스)

 

 

2.2 배열

  Note) 배열

    - 데이터가 메모리 상에 연속적으로 저장된다 .

    - 데이터와 인덱스가 1:1 대응으로 구성된다.

    - 단점

      -> 데이터 추가/ 삭제가 번거롭다.

      -> 미리 최대 길이를 정해서 생성한다.

      -> 가변 길이 배열은 배열의 크기를 변경할 때마다 새로운 배열을 생성한다.

      -> 데이터 삭제 시, 인덱스를 유지하기 위해 빈 공간을 유지한다.

  Ex) 

<hide/>
// Practice
// 기본 배열 자료형을 이용한 배열의 생성, 삽입, 삭제 기능 구현

import java.util.Arrays;

class MyArray {

    int[] arr;

//  배열의 초기 사이즈 설정
    MyArray(int size){
        this.arr = new int[size];
    }

    
//  배열에 데이터 삽입
    public void insertData(int index, int data){
        if(index < 0 || index > this.arr.length){   // 연속적이지 않은 경우 예외처리
            System.out.println("Index Error!");
            return;
        }
        int[] arrDup = this.arr.clone();
        this.arr = new int[arr.length + 1];

        for(int i = 0;i < index; ++i){
            this.arr[i] = arrDup[i];

        }
        for(int i = index + 1; i < this.arr.length; ++i){
            this.arr[i] = arrDup[i - 1];
        }
        this.arr[index] = data;

    }



//  배열에서 특정 데이터 삭제

    public void removeData(int data){
        int targetIndex = -1;

        for(int i = 0; i < this.arr.length; ++i){
            if(this.arr[i] == data){
                targetIndex = i;
                break;
            }
        }
        if(targetIndex == -1){
            System.out.println("해당 데이터가 없습니다.");
        }else{
            int[] arrDup = this.arr.clone();
            this.arr = new int[this.arr.length - 1];

            for(int i = 0 ; i < targetIndex; ++i){
                this.arr[i] = arrDup[i];

            }
            for(int i = targetIndex; i < this.arr.length; ++i){
                this.arr[i] = arrDup[i + 1];
            }
        }
    }
}

public class Practice {
    public static void main(String[] args) {

//      Test code
        int size = 5;
        MyArray myArray = new MyArray(size);

        for (int i = 0; i < size; i++) {
            myArray.arr[i] = i + 1;
        }
        System.out.println(Arrays.toString(myArray.arr));   // [1, 2, 3, 4, 5]

        myArray.arr[0] = 10;
        System.out.println(Arrays.toString(myArray.arr));   // [10, 2, 3, 4, 5]

        myArray.insertData(2, 20);
        System.out.println(Arrays.toString(myArray.arr));   // [10, 2, 20, 3, 4, 5]

        myArray.insertData(6, 60);
        System.out.println(Arrays.toString(myArray.arr));   // [10, 2, 20, 3, 4, 5, 60]

        myArray.insertData(-1, 0);  // Index Error

        myArray.removeData(4);
        System.out.println(Arrays.toString(myArray.arr));   // [10, 2, 20, 3, 5, 60]

        myArray.removeData(5);
        System.out.println(Arrays.toString(myArray.arr));   // [10, 2, 20, 3, 60]

        myArray.removeData(99); // 해당 데이터가 없습니다.
    }
}

  Note) 실행 결과

 

 

2.3 배열 practice

  Ex 1) 주어진 배열에 대해 홀수 데이터의 평균과 짝수 데이터의 평균을 구하라

<hide/>
// Practice1
// 배열 arr 의 모든 데이터에 대해서,
// 짝수 데이터들의 평균과 홀수 데이터들의 평균을 출력하세요.

// 입출력 예시)
// 배열 arr: 1, 2, 3, 4, 5, 6, 7, 8, 9
// 결과:
// 짝수 평균: 5.0
// 홀수 평균: 5.0

import java.lang.reflect.Array;
import java.util.Arrays;

public class Practice1 {
    public static void main(String[] args) {
        int[] arr= {1, 2, 3, 4, 5, 6, 7, 8, 9};
        float sumEven = 0;
        float sumOdd = 0;
        int evenCnt = 0;
        int oddCnt = 0;

        for(int item : arr){
            if(item % 2 == 0){
                sumEven += item;
                ++evenCnt;
            }else{
                sumOdd += item;
                ++oddCnt;
            }
        }
        System.out.println("짝수 평균: "+ sumEven / evenCnt);
        System.out.println("홀수 평균: "+ sumOdd / oddCnt);
    }
}

  Note) 실행 결과

 

  Ex 2) 주어진 배열에서 target에 해당하는 값의 인덱스를 출력하라

<hide/>
// Practice2
// 배열 arr 에서 target 에 해당하는 값의 인덱스를 출력
// 해당 값이 여러 개인 경우 가장 큰 인덱스 출력

// 입출력 예시)
// 배열 arr: 1, 1, 100, 1, 1, 1, 100
// target : 100
// 결과: 6

public class Practice2 {
    public static void main(String[] args) {

        int[] arr= {1, 1, 100, 1, 1, 1, 100};
        int target = 100;
        int result = -1;

        for(int i = arr.length - 1;i >= 0 ; --i){
            if(arr[i] == target){
                result = i;
                break;
            }
        }
        System.out.println(result);
    }
}

  Note) 실행 결과: 6

 

 

  Ex 3) 주어진 배열에 대해 데이터 순서를 거꾸로 변경하여 출력하라

<hide/>
// Practice3
// 배열 arr 의 데이터 순서를 거꾸로 변경하세요.
// 추가 배열을 사용하지 않고 구현

// 입출력 예시)
// arr: 1, 3, 5, 7, 9
// 결과: 9, 7, 5, 3, 1

import java.util.Arrays;
public class Practice3 {
    public static void main(String[] args) {

        int [] arr  = { 1, 3, 5, 7, 9};
        for(int i = 0; i < arr.length / 2; ++i){
            int tmp = arr[i];
            arr[i] = arr[arr.length - 1 - i];
            arr[arr.length - 1 - i] = tmp;
        }
        System.out.println(Arrays.toString(arr));
    }
}

  Note) 실행 결과

 

 

  Ex 4) 배열에서 peek값을 출력하라

    - peek값이란? 좌우보다 큰 값을 말한다. 

<hide/>// Practice4
// 배열 arr 에서 peek 값 모두 출력
// 입출력 예시)
// arr: 3, 1, 2, 6, 2, 2, 5, 1, 9, 10, 1, 11
// 결과: 3, 6, 5, 10, 11

import java.util.Arrays;
public class Practice4 {
    public static void main(String[] args) {
        int[] arr = {3, 1, 2, 6, 2, 2, 5, 1, 9, 10, 1, 11};
        for(int i = 0; i < arr.length; ++i){
            if(i == 0 && arr[i] > arr[i + 1]){
                System.out.print(arr[i]+ " ") ;
            }else if(i == arr.length - 1 && arr[i] > arr[i -1]){
                System.out.print(arr[i]+ " ") ;
            }else if(arr[i - 1] < arr[i] && arr[i] > arr[i + 1]){
                System.out.print(arr[i]+ " ") ;
            }
        }
       System.out.println();
    }
}

  Note) 실행 결과

 

 

  Ex 5) 배열의 데이터를 오름차순으로 출력한다.

<hide/>
// Practice5
// 배열 arr 의 값을 오름차순으로 출력
import java.util.*;

// 입출력 예시)
// arr: 5, 3, 1, 4, 6, 1
// 결과: 1, 1, 3, 4, 5, 6

public class Practice5 {
    public static void main(String[] args) {

        int [] arr = {5, 3, 1, 4, 6, 1};
        int [] visited = new int[arr.length];
        int visitCnt = 0;
        int minVal = Integer.MAX_VALUE;
        int minIdx = -1;

        while(visitCnt < arr.length){
            for(int i = 0; i < arr.length; ++i){
                if(arr[i] < minVal && visited[i] == 0){
                    minVal = arr[i];
                    minIdx = i;
                }
            }
            if(minIdx != - 1){
                System.out.print(minVal + " ");
                visited[minIdx]  = 1;
                ++visitCnt;
            }
            minVal = Integer.MAX_VALUE;
            minIdx = - 1;
        }
        System.out.println();
    }
}

  Note) 실행 결과

 

 

  Ex 6) 배열에서 중복된 데이터를 없애고 새 배열을 만들어라

<hide/>
// Practice6
// 배열 arr 에서 중복 값을 제거한 새 배열을 만드시오.
// 입출력 예시)
// arr: 1, 5, 3, 2, 2, 3, 1, 4, 1, 2, 3, 5
// 결과: 1, 5, 3, 2, 4

import java.util.Arrays;
public class Practice6 {
    public static void main(String[] args) {

        int[] arr = {1, 5, 3, 2, 2, 3, 1, 4, 1, 2, 3, 5};
        int[] arrResult= new int[arr.length];
        int cnt = 0;
        for(int i = 0; i < arr.length; ++i){
            boolean dupFlag = false;
            for(int j = 0; j < cnt; ++j){
                if(arr[i] == arrResult[j]){ // 중복된 값이므로
                    dupFlag = true;
                }
            }
            if(dupFlag == false){
                arrResult[cnt++] = arr[i];
            }
        }
        for(int i = 0;i < cnt; ++i){
            System.out.print(arrResult[i] + " ");
        }
        System.out.println();
    }
}

  Note) 실행 결과

 

 

  Ex 7) 2차원 배열을 시계방향으로 90도 회전시킨 결과를 출력하라

 Sol) 

<hide/>
// Practice7
// 2차원 배열 arr 을 시계방향 90도 회전시킨 결과를 출력하세요.

// 입출력 예시:
// arr:
// 1 2 3 4 5
// 6 7 8 9 10
// 11 12 13 14 15
// 결과:
// 11 6 1
// 12 7 2
// 13 8 3
// 14 9 4
// 15 10 5

public class Practice7 {

        static void printArr(int [][] arr){
            for(int [] item1D : arr){
                for(int item : item1D){
                    System.out.print(item + " ");
                }
                System.out.println();
            }
        }

        public static void main(String[] args) {

            int[][] arr = {{ 1, 2, 3, 4, 5},
                            {6, 7, 8, 9, 10},
                            {11, 12, 13, 14, 15}};
            int [][] arr90 = new int[arr[0].length][arr.length];

            for(int i = 0; i < arr.length; ++i) {      // j = 0 ~ 4
                for (int j = 0; j < arr[i].length; ++j) {    // i = 2 ~ 0
                    int r = arr.length - 1 - i;
                    arr90[j][r] = arr[i][j];
                }
            }
            printArr(arr90);
        }
}

 MySol)

<hide/>
// Practice7
// 2차원 배열 arr 을 시계방향 90도 회전시킨 결과를 출력하세요.

// 입출력 예시:
// arr:
// 1 2 3 4 5
// 6 7 8 9 10
// 11 12 13 14 15
// 결과:
// 11 6 1
// 12 7 2
// 13 8 3
// 14 9 4
// 15 10 5

public class Practice7 {
        public static void main(String[] args) {

            int[][] arr = {{ 1, 2, 3, 4, 5},
                            {6, 7, 8, 9, 10},
                            {11, 12, 13, 14, 15}};

            for(int j = 0;j < arr[0].length; ++j){      // j = 0 ~ 4
                for(int i = arr.length - 1; i >= 0 ; --i){    // i = 2 ~ 0
                    System.out.print(arr[i][j] +  " ");
                }
                System.out.println();
            }
        }
}

  Note) 실행 결과

 

 

2.4 연결리스트(LinkedList)

  Def) 연결 리스트:  데이터를 링크로 연결해서 관리하는 자료구조

    - 자료의 순서는 정해져있지만 메모리상 연속성이 보장되지는 않는다.

  

  Note) 장단점

  - 장점: 데이터 공간을 미리 할당할 필요가 없다. (리스트 길이가 가변적 - 데이터 추가, 삭제 용이)

 

  - 단점: 연결 구조를 위한 별도 데이터 공간 필요

    -> 연결 정보를 찾는 시간이 필요 (상대적으로 속도가 느리다.)

    ->  데이터 추가, 삭제 시 앞뒤 데이터의 연결을 재구성하는 작업이 필요하다. (head/ 중간/ tail)

  Note) Java에서는 Garbage Collection이 해당 메모리를 더이상 참조하지 않으면 자동으로 delete_Node를 지워준다.

 

  Note) 연결 리스트의 기본 구조

    Def) 노드(Node): 데이터 저장 단위로 값과 포인터로 구성된다.

    - 포인터(pointer): 다음 노드나 이전 노드의 연결 정보

    - 가장 앞에 있는 노드를 head, 가장 뒤에 있는 노드를 tail이라고 한다.

 

  Ex) 

<hide/>
// 선형 자료구조 - 연결 리스트
// 단순 연결 리스트 (simple ver.) 기본 구조 구현

// 노드
class Node {
    int data;
    Node next;

    Node(){}
    Node(int data, Node next){
        this.data = data;
        this.next = next;
    }
}

class LinkedList {
    Node head;
    LinkedList(){}
    LinkedList(Node node){
        this.head = node;
    }

    //  연결 리스트 비어있는지 확인
    public boolean isEmpty(){
        if(this.head == null){
            return  true;
        }
        return  false;
    }

    //  연결 리스트의 맨 뒤에 데이터 추가
    public void addData(int data){
        if(this.head == null){
            this.head = new Node(data, null);
        }else{
            Node cur = this.head;
            while(cur.next != null){
                cur = cur.next;
            }
            cur.next = new Node(data, null);
        }

    }

    //  연결 리스트의 맨 뒤의 데이터 삭제
    public void removeData(){
        if(this.isEmpty()){
            System.out.println("List is Empty!");
            return;
        }
        Node cur = this.head;
        Node prev = cur;

        while(cur.next != null){
            prev = cur;
            cur = cur.next;
        }
        if(cur == this.head){
            this.head = null;
        }else{
            prev.next = null;
        }
    }

    //  연결 리스트에서 데이터 찾기
    public void findData(int data){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        Node cur = this.head;
        while(cur != null){
            if(cur.data == data){
                System.out.println("Data exists!");
                return;
            }
            cur = cur.next;
        }
        System.out.println("Data not found!");
    }

    //  연결 리스트의 모든 데이터 출력
    public void showData(){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        Node cur = this.head;
        while(cur != null){
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }
}


public class Main {

    public static void main(String[] args) {

//      Test Code
        LinkedList myList = new LinkedList(new Node(1, null));
        myList.showData();      // 1

        myList.addData(2);
        myList.addData(3);
        myList.addData(4);
        myList.addData(5);
        myList.showData();      // 1 2 3 4 5

        myList.findData(3);     // Data exist!
        myList.findData(100);   // Data not found!

        myList.removeData();
        myList.removeData();
        myList.removeData();
        myList.showData();      // 1 2

        myList.removeData();
        myList.removeData();
        myList.removeData();    // List is empty
    }
}

  Note) 실행 결과

 

 

  Ex) 단방향 연결 리스트

<hide/>
// Practice1
// 단순 연결 리스트 구현 완성

class LinkedList2 extends LinkedList{
    LinkedList2(Node node){
        this.head = node;
    }
    // 연결 리스트에 데이터 추가
    // before_data가 null인 경우, 가장 뒤에 추가
    // before_data에 값이 없는 경우 해당 갑을 가진 노드 앞에 추가

    public void addData(int data, Integer beforeData){

        if(this.head == null){
            this.head = new Node(data, null);

        }else if(beforeData == null){
            Node cur = this.head;
            while(cur.next != null){
                cur = cur.next;
            }
            cur.next = new Node(data, null);
        }else{  // 리스트 내에서
            Node cur = this.head;
            Node pre = cur;
            while(cur != null){
                if(cur.data == beforeData){
                    if(cur == this.head){
                        this.head = new Node(data, this.head);
                    }else{  // 중간에 들어갈 때
                        pre.next = new Node(data, cur);
                    }
                    break;
                }
                pre = cur;
                cur = cur.next;
            }
        }
    }
    public void removeData(int data){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        Node cur = this.head;
        Node pre = cur;
        while(cur != null){
            if(cur.data == data){
                if(cur == this.head){
                    this.head = cur.next;
                }else{
                    pre.next = cur.next;
                }
                break;
            }
            pre = cur;
            cur = cur.next;
        }
    }
}

public class Practice1 {
    public static void main(String[] args) {

//      Test code
        LinkedList2 myList = new LinkedList2(new Node(1, null));
        myList.showData();         // 1

        myList.addData(2);
        myList.addData(3);
        myList.addData(4);
        myList.addData(5);
        myList.showData();         // 1 2 3 4 5

        myList.addData(100, 1);
        myList.addData(200, 2);
        myList.addData(300, 3);
        myList.addData(400, 4);
        myList.addData(500, 5);
        myList.showData();         // 100 1 200 2 300 3 400 4 500 5

        myList.removeData(300);
        myList.removeData(100);
        myList.removeData(500);
        myList.removeData(200);
        myList.removeData(400);
        myList.showData();         // 1 2 3 4 5

        myList.removeData(3);
        myList.removeData(1);
        myList.removeData(5);
        myList.removeData(2);
        myList.removeData(4);
        myList.showData();         // List is empty!
    }
}

  Note) 실행 결과

 

 

  Ex) 양방향 연결 리스트

<hide/>
// Practice2
// 양방향 연결 리스트 (Doubly Linked List) 구현

class NodeBi {

    int data;
    NodeBi next;
    NodeBi prev;

    NodeBi(int data, NodeBi next, NodeBi prev){
        this.data = data;
        this.next = next;
        this.prev = prev;
    }

}
class DoublyLinkedList extends LinkedList{
    NodeBi head;
    NodeBi tail;

    DoublyLinkedList(NodeBi node){
        this.head = node;
        this.tail = node;
    }
    public boolean isEmpty(){
        if(this.head == null){
            return  true;
        }
        return  false;
    }
    public void addData(int data, Integer beforeData){
        if(this.head == null){
            this.head = new NodeBi(data,null, null);
            this.tail = this.head;
        }else if(beforeData == null){
            this.tail.next = new NodeBi(data, null, this.tail);
            this.tail = this.tail.next;
        }else{// before데이터가 있는 경우
            NodeBi cur = this.head;
            NodeBi pre = cur;
            while(cur != null){
                if(cur.data == beforeData){
                    if(cur == this.head){
                        this.head = new NodeBi(data, this.head, null);
                        this.head.next.prev = this.head;
                    }else{// 헤드가 아닐 때
                        pre.next = new NodeBi(data, cur, pre);
                        cur.prev = pre.next;
                    }
                    break;
                }
                pre = cur;
                cur = cur.next;
            }
        }
    }
    public void removeData(int data){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        NodeBi cur = this.head;
        NodeBi pre = cur;

        while(cur != null){
            if(cur.data == data){
                if(cur == this.head && cur == this.tail){   // 노드가 하나인 경우
                    this.head = null;
                    this.tail = null;
                }else if(cur == this.head){
                    this.head = cur.next;
                    this.head.prev = null;  // 기존의 헤드는 없어진다.
                }else if(cur == this.tail){
                    this.tail = this.tail.prev; // 가장 끝의 태일을 기존 태일의 이전이 되도록 만든다.
                    this.tail.next = null; // 기존의 끝 부분
                }else{  // 중간 노드 삭제
                    pre.next = cur.next;
                    cur.next.prev = pre;    // 중간 노드는 자동으로 없어진다
                }
                break;
            }
            pre = cur;
            cur = cur.next;

        }
    }
    public void showData(){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        NodeBi cur = this.head;
        while(cur != null){
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }
    public void showDataFromTail(){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        NodeBi cur = this.tail;
        while(cur != null){
            System.out.print(cur.data + " ");
            cur = cur.prev;
        }
        System.out.println();
    }

}


public class Practice2 {
    public static void main(String[] args) {

//      Test code
        DoublyLinkedList myList = new DoublyLinkedList(new NodeBi(1, null, null));
        myList.showData();          // 1

        myList.addData(2, null);
        myList.addData(3, null);
        myList.addData(4, null);
        myList.addData(5, null);
        myList.showData();          // 1 2 3 4 5
        myList.showDataFromTail();  // 5 4 3 2 1

        myList.addData(100, 1);
        myList.addData(200, 2);
        myList.addData(300, 3);
        myList.addData(400, 4);
        myList.addData(500, 5);
        myList.showData();          // 100 1 200 2 300 3 400 4 500 5
        myList.showDataFromTail();  // 5 500 4 400 3 300 2 200 1 100

        myList.removeData(300);
        myList.removeData(100);
        myList.removeData(500);
        myList.removeData(200);
        myList.removeData(400);
        myList.showData();          // 1 2 3 4 5
        myList.showDataFromTail();  // 5 4 3 2 1

        myList.removeData(3);
        myList.removeData(1);
        myList.removeData(5);
        myList.removeData(2);
        myList.removeData(4);
        myList.showData();          // List is empty!
        myList.showDataFromTail();  // List is empty!
    }
}

  Note) 실행 결과

 

 

  Ex) practice3  원형 연결 리스트

<hide/>
// Practice3
// 원형 연결 리스트 (Circular Linked List) 구현

class CircularLinkedList {

    NodeBi head;
    NodeBi tail;

    CircularLinkedList(NodeBi node){
        this.head = node;
        this.tail = node;
        node.next = this.head;
        node.prev = this.head;
    }
    public boolean isEmpty(){
        if(this.head == null){
            System.out.println("List is empty!");
            return true;
        }
        return  false;
    }

    // 연결 리스트에 데이터 추가
    // before_data 가 null 인 경우, 가장 뒤에 추가
    // before_data 에 값이 있는 경우, 해당 값을 가진 노드 앞에 추가
    public void addData(int data, Integer beforeData){
        if(this.head == null){  // 처음에 노드 하나일 때
            NodeBi newNodeBi = new NodeBi(data, null, null);
            this.head = newNodeBi;
            this.tail = newNodeBi;
            newNodeBi.next = newNodeBi;
        }else if(beforeData == null){
            NodeBi newNodeBi = new NodeBi(data, this.head, this.tail);
            this.tail.next = newNodeBi;
            this.head.prev = newNodeBi;
            this.tail = newNodeBi;
        }else {
            NodeBi cur = this.head;
            NodeBi pre = cur;
            do{
                if(cur.data == beforeData) {
                    if (cur == this.head) {
                        NodeBi newNodeBi = new NodeBi(data, this.head, this.tail);
                        this.tail.next = newNodeBi;
                        this.head.prev = newNodeBi;
                        this.head = newNodeBi;
                    }else{// 노드가 중간에 추가되는 경우
                        NodeBi newNodeBi = new NodeBi(data,cur, pre);
                        pre.next = newNodeBi;
                        cur.prev = newNodeBi;
                    }
                    break;
                }
                pre = cur;
                cur = cur.next;
            }while(cur != this.head);
        }
    }

    //  연결 리스트에서 특정 값을 가진 노드 삭제
    public void removeData(int data){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        NodeBi cur = this.head;
        NodeBi pre = cur;
        while(cur != null){
            if(cur.data == data){
                if(cur == this.head && cur == this.tail){   // 노드가 하나뿐일 때,
                    this.head = null;
                    this.tail = null;
                }else if(cur == this.head){
                    cur.next.prev = this.head.prev;
                    this.head = cur.next;
                    this.tail.next = this.head;
                }else if(cur == this.tail){
                    pre.next = this.tail.next;
                    this.tail = pre;
                    this.head.prev = this.tail;
                }else{  // 노드가 중간일 때
                    pre.next = cur.next;
                    cur.next.prev = pre;
                }
                break;
            }
            pre = cur;
            cur = cur.next;
        }
    }
    public void showData(){
        if(this.isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        NodeBi cur = this.head;
        while(cur.next != this.head){   // next가 자신이 되지 않을 때까지
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println(cur.data);
    }


}

public class Practice3 {
    public static void main(String[] args) {
        // Test code
        CircularLinkedList myList = new CircularLinkedList(new NodeBi(1, null, null));
        myList.addData(2, null);
        myList.addData(3, null);
        myList.addData(4, null);
        myList.addData(5, null);
        myList.showData();  // 1 2 3 4 5

        myList.addData(100, 1);
        myList.addData(200, 2);
        myList.addData(300, 3);
        myList.addData(400, 4);
        myList.addData(500, 5);
        myList.showData();  // 100 1 200 2 300 3 400 4 500 5

        myList.removeData(300);
        myList.removeData(100);
        myList.removeData(500);
        myList.removeData(200);
        myList.removeData(400);
        myList.showData();          // 1 2 3 4 5

        myList.removeData(3);
        myList.removeData(1);
        myList.removeData(5);
        myList.removeData(2);
        myList.removeData(4);
        myList.showData();          // List is empty!
    }
}

  Note) 실행 결과

 

 

2.5 단방향 연결 리스트 practice

  Ex) practice1. 단방향 연결 리스트에서 중복 데이터를 찾아 삭제하라.

    - findData메서드의 반환형을  boolean으로 바꿔서 false인 경우에만 addData를 한다.

<hide/>
// Practice1
// 단방향 연결 리스트에서 중복 데이터를 찾아 삭제하세요.

// 입출력 예시)
// 입력 연결 리스트: 1, 3, 3, 1, 4, 2, 4, 2
// 결과 연결 리스트: 1, 3, 4, 2


class Node {
    int data;
    Node next;

    Node() {}
    Node(int data, Node next) {
        this.data = data;
        this.next = next;
    }
}

class LinkedList {
    Node head;

    LinkedList() {}
    LinkedList(Node node) {
        this.head = node;
    }

    public boolean isEmpty() {
        return this.head == null;
    }

    public void addData(int data) {
        if (this.head == null) {
            this.head = new Node(data, null);
        } else {
            Node cur = this.head;
            while (cur.next != null) {
                cur = cur.next;
            }
            cur.next = new Node(data, null);
        }
    }

    public void removeData(int data) {
        if (this.isEmpty()) {
            System.out.println("List is empty");
            return;
        }

        Node cur = this.head;
        Node pre = cur;
        while (cur != null) {
            if (cur.data == data) {
                if (cur == this.head) {
                    this.head = cur.next;
                } else {
                    pre.next = cur.next;
                }
                break;
            }

            pre = cur;
            cur = cur.next;
        }
    }

    public boolean findData(int data) {
        if (this.isEmpty()) {
            System.out.println("List is empty");
        }

        Node cur = this.head;
        while (cur != null) {
            if (cur.data == data) {
   //             System.out.println("Data exist!");
                return true;
            }
            cur = cur.next;
        }
        return false;
     //   System.out.println("Data not found!");
    }

    public void showData() {
        if (this.isEmpty()) {
            System.out.println("List is empty!");
            return;
        }

        Node cur = this.head;
        while (cur != null) {
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }
}


public class Practice1 {
    public static LinkedList removeDup(LinkedList listBefore) {
        // 노드 하나마다 처음에서 부터 자기 인덱스의 값 까지 하나씩 비교해서
        LinkedList listAfter = new LinkedList();
        Node cur = listBefore.head;

        while(cur != null) {
           if (listAfter.findData(cur.data) == false) {
                listAfter.addData(cur.data);
           }
            cur = cur.next;
       }
        return listAfter;
    }

    public static void main(String[] args) {
        // Test code
        LinkedList linkedList = new LinkedList();
        linkedList.addData(1);
        linkedList.addData(3);
        linkedList.addData(3);
        linkedList.addData(1);
        linkedList.addData(4);
        linkedList.addData(2);
        linkedList.addData(4);
        linkedList.addData(2);
        linkedList.showData();  // 1 3 3 1 4 2 4 2

        linkedList = removeDup(linkedList);
        linkedList.showData();  // 1 3 4 2

    }
}

  Note) 실행 결과

 

 

  Ex) practice2. 팰린드롬 연결 리스트: pallindrome 문자열이 맞는지 아닌지 확인하라

    - 단방향 연결 리스트로 팰린드롬 문자열 확인하기

<hide/>
// Practice2
// Palindrome 연결 리스트
// 추가 자료구조 없이 연결 리스트만으로 풀어보기
// Palindrome: 앞으로 읽어도 뒤로 읽어도 같은 문자열

// 입력 예시)
// 입력 연결 리스트: 1, 3, 5, 3, 1
// 결과: true

// 입력 연결 리스트: 3, 5, 5, 3
// 결과: true

// 입력 연결 리스트: 1, 3, 5, 1
// 결과: false


public class Practice2 {
    public static boolean checkPalindrome(LinkedList list) {

        Node cur = list. head;
        Node left = list. head;
        Node right = null;
        int cnt = 0;

        while(cur != null){
            ++cnt;
            right = cur;
            cur = cur.next;
        }
        Node prevRight = right;

        for(int i = 0; i < cnt / 2; ++i){
            if(left.data != right.data){
                return false;
            }
            //양끝데이터가 같은 경우
            left = left.next;
            right = left;	// 일단은 left로 잡은 후,

            // prevRight를 만날 때까지 right를 이동시킨다.
            while(right.next != prevRight){
                right = right.next;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        // Test code
        LinkedList linkedList = new LinkedList();
        linkedList.addData(1);
        linkedList.addData(3);
        linkedList.addData(5);
        linkedList.addData(3);
        linkedList.addData(1);
        System.out.println(checkPalindrome(linkedList));

        LinkedList linkedList2 = new LinkedList();
        linkedList2.addData(3);
        linkedList2.addData(5);
        linkedList2.addData(5);
        linkedList2.addData(3);
        System.out.println(checkPalindrome(linkedList2));

        LinkedList linkedList3 = new LinkedList();
        linkedList3.addData(1);
        linkedList3.addData(3);
        linkedList3.addData(5);
        linkedList3.addData(1);
        System.out.println(checkPalindrome(linkedList3));

    }
}

  Note) 실행 결과

 

 

  Ex) practice3  연결리스트 부분 뒤집기 - 주어진 연결 리스트에서 시작 위치부터 끝 위치 사이의 노드들을 뒤집으ㄹ시오

<hide/>
// Practice3
// 연결 리스트 부분 뒤집기
// 주어진 연결 리스트에서 시작 위치부터 끝 위치 사이의 노드들을 뒤집으시오.

// 입력 예시)
// 입력 연결 리스트: 1, 2, 3, 4, 5
// 시작 위치: 2
// 끝 위치: 4
// (처음 위치는 1부터라고 가정)
// 결과 연결 리스트: 1, 4, 3, 2, 5


public class Practice3 {
    public static LinkedList reverseList(LinkedList list, int left, int right) {

        Node cur1 = null;
        Node pre1 = null;
        cur1 = list.head;

        for(int i = 0; i < left - 1; ++i){
            pre1 = cur1;
            cur1 = cur1.next;
        }

        Node cur2 = cur1;
        Node pre2 = pre1;
        Node after = null;

        for(int i = left; i <= right; ++i){
            after = cur2.next;
            cur2.next = pre2;
            pre2 = cur2;
            cur2 = after;
        }

        pre1.next  = pre2;
        cur1.next = cur2;

        return list;
    }
    
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.addData(1);
        linkedList.addData(2);
        linkedList.addData(3);
        linkedList.addData(4);
        linkedList.addData(5);
        linkedList.showData();

        linkedList = reverseList(linkedList, 2, 4);
        linkedList.showData();


        LinkedList linkedList2 = new LinkedList();
        linkedList2.addData(1);
        linkedList2.addData(2);
        linkedList2.addData(3);
        linkedList2.addData(4);
        linkedList2.addData(5);
        linkedList2.addData(6);
        linkedList2.addData(7);
        linkedList2.showData();

        linkedList2 = reverseList(linkedList2, 3, 5);
        linkedList2.showData();

    }
}

  Note) 실행 결과

 

 

  Ex) practic4 주어진 문자열 배열을 연결 리스트 배열로 관리하는 코드를 작성한다.

    - 각각의 리스트에는 접근이 불가능하므로 해당 위치에 하나씩 객체를 만들어줘야한다.

<hide/>
package P4;
// Practice4
// 연결 리스트 배열 사용 연습

// 주어진 문자열 배열을 연결 리스트 배열로 관리하는 코드를 작성하시오.
// 관리 규칙은 다음과 같다.
// 각 문자열의 첫 글자가 같은 것끼리 같은 연결 리스트로 관리하기


import java.util.ArrayList;
import java.util.HashSet;

class Node {
    String data;
    Node next;

    Node() {}
    Node(String data, Node next) {
        this.data = data;
        this.next = next;
    }
}

class LinkedList {
    Node head;
    char alphabet;

    LinkedList() {}
    LinkedList(Node node, char alphabet) {
        this.head = node;
        this.alphabet = alphabet;
    }

    public boolean isEmpty() {
        return this.head == null;
    }

    public void addData(String data) {
        if (this.head == null) {
            this.head = new Node(data, null);
        } else {
            Node cur = this.head;
            while (cur.next != null) {
                cur = cur.next;
            }
            cur.next = new Node(data, null);
        }
    }

    public void removeData(int data) {
        if (this.isEmpty()) {
            System.out.println("List is empty");
            return;
        }

        Node cur = this.head;
        Node pre = cur;
        while (cur != null) {
            if (cur.data.equals(data)) {
                if (cur == this.head) {
                    this.head = cur.next;
                } else {
                    pre.next = cur.next;
                }
                break;
            }

            pre = cur;
            cur = cur.next;
        }
    }

    public boolean findData(int data) {
        if (this.isEmpty()) {
            System.out.println("List is empty");
            return false;
        }

        Node cur = this.head;
        while (cur != null) {
            if (cur.data.equals(data)) {
                System.out.println("Data exist!");
                return true;
            }
            cur = cur.next;
        }
        System.out.println("Data not found!");
        return false;
    }

    public void showData() {
        if (this.isEmpty()) {
            System.out.println("List is empty!");
            return;
        }

        Node cur = this.head;
        while (cur != null) {
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }
}

public class Practice4 {

    public static void dataCollect(String[] data) {

        HashSet<Character> set = new HashSet<>();

        for(String item : data){
            set.add(item.toCharArray()[0]); // 첫글자를 set에 넣는다.
        }
        System.out.println(set);

        Character[] arr = set.toArray(new Character[0]);    // set을 배열로 바꾼다.
        LinkedList[] linkedList = new LinkedList[set.size()];   // 아직 각각의 위치에는 접근이 불가능하다.

        for(int i = 0; i < linkedList.length; ++i){
            linkedList[i] = new LinkedList(null, arr[i]);   // 해당 위치에 객체를 만들어줘야한다.
        }
        for(String item : data){
            for(LinkedList list : linkedList){
                if(list.alphabet == item.toCharArray()[0]){
                    list.addData(item);
                }
            }
        }
        for(LinkedList list: linkedList){
            System.out.print(list.alphabet + ": ");
            list.showData();
        }
    }

    public static void main(String[] args) {
        // Test code
        String[] input = {"apple", "watermelon", "banana", "apricot", "kiwi", "blueberry", "cherry", "orange"};
        dataCollect(input);

        System.out.println();
        String[] input2 = {"ant", "kangaroo", "dog", "cat", "alligator", "duck", "crab", "kitten", "anaconda", "chicken"};
        dataCollect(input2);

    }
}

  Note) 실행 결과

 

 

2.6 스택(Stack)

  - 후입 선출구조(LIFO, last in first out / FILO, first in last out)

  ex) 함수 콜 스택, 수식 계산, 인터럽트(갑자기 발생한 일)처리

 

 

2.7 스택(Stack) 문제 풀이

  Ex 1)  ArrayList를 이용한 Stack 구현하기

<hide/>
// Practice1
// ArrayList 를 이용한 스택 구현

import java.util.ArrayList;

class MyStack1 {
    ArrayList list;

    MyStack1() {
        this.list = new ArrayList();
    }

    public boolean isEmpty() {
        if(this.list.size() == 0){
            return true;
        }else{
            return false;
        }
    }

    public void push(int data) {
        this.list.add(data);

    }

    public Integer pop() {
        if(this.isEmpty()){
            System.out.println("Stack is empty!");
            return null;
        }
        // arraylist가 제네릭으로 되어 있지 않아서 형변환을 해줘야 오류가 나지 않는다.
        int data  = (int) this.list.get(this.list.size() - 1);    // 가장 마지막 값을 가져온다.
        this.list.remove(this.list.size() - 1);
        return data;
    }

    public Integer peek() {
        if(this.isEmpty()){
            System.out.println("Stack is empty!");
            return null;
        }
        int data  = (int) this.list.get(this.list.size() - 1);    // 가장 마지막 값을 가져온다.
        return data;
    }

    public void printStack() {
        System.out.println(this.list);
    }
}

public class Practice1 {
    public static void main(String[] args) {
        // Test code
        MyStack1 myStack = new MyStack1();
        System.out.println(myStack.isEmpty());
        myStack.push(1);
        myStack.push(2);
        myStack.push(3);
        myStack.push(4);
        myStack.push(5);
        myStack.printStack();               // 1, 2, 3, 4, 5

        System.out.println(myStack.peek()); // 5
        myStack.printStack();               // 1, 2, 3, 4, 5

        System.out.println(myStack.pop());  // 5
        System.out.println(myStack.pop());  // 4
        myStack.printStack();               // 1, 2, 3

        System.out.println(myStack.pop());  // 3
        System.out.println(myStack.pop());  // 2
        System.out.println(myStack.pop());  // 1
        myStack.printStack();
    }
}

  Note) 실행 결과

 

 

  Ex 2) 배열을 이용한 Stack 구현하기

<hide/>
// Practice2
// 배열을 이용한 기본 스택 직접 구현

class MyStack2 {
    int[] arr;
    int top = -1;

    MyStack2(int size) {
        arr = new int[size];
    }

    public boolean isEmpty() {
        if(top == -1){
            return true;
        }
        return false;
    }

    public boolean isFull() {
        if(this.top == arr.length - 1){
            return true;
        }
        return false;
    }

    public void push(int data) {

       if(isFull()){
           System.out.println("Stack is full!");
           return;
       }
        arr[++top] = data;
    }

    public Integer pop() {
        if(isEmpty()){
            System.out.println("Stack is empty!");
            return null;
        }
        return  arr[top--];
    }

    public Integer peek() {
        if(isEmpty()){
            System.out.println("Stack is empty!");
            return null;
        }
        return arr[top];
    }

    public void printStack() {
        for(int i = 0; i < top + 1; ++i){
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

public class Practice2 {
    public static void main(String[] args) {
        // Test code
        MyStack2 myStack = new MyStack2(5);
        System.out.println(myStack.isEmpty());
        myStack.push(1);
        myStack.push(2);
        myStack.push(3);
        myStack.push(4);
        myStack.push(5);
        myStack.push(6);
        myStack.printStack();               // 1, 2, 3, 4, 5

        System.out.println(myStack.peek()); // 5
        myStack.printStack();               // 1, 2, 3, 4, 5

        System.out.println(myStack.pop());  // 5
        System.out.println(myStack.pop());  // 4
        myStack.printStack();               // 1, 2, 3

        System.out.println(myStack.pop());  // 3
        System.out.println(myStack.pop());  // 2
        System.out.println(myStack.pop());  // 1
        myStack.printStack();
    }
}

  Note) 실행 결과

 

 

2.7 Stack 문제 풀이

  Ex 1) 문자열 뒤집기

 

  MySol)

    - for문(char c :  .. ) 대신에 for(String s : str.split("")) 을 쓸 수도 있다.

<hide/>
// Practice1
// 문자열 뒤집기

// 입출력 예시)
// 입력: "Hello"
// 출력: "OlleH"

// 입력: 1 3 5 7 9
// 출력: 9 7 5 3 1

import java.util.Stack;

public class Practice1 {
    public static String reverseString(String str) {

        Stack<Character> stack = new Stack<>();
        String answer = "";

        for(char c : str.toCharArray()){
            stack.push(c);
        }

        while(!stack.isEmpty()){
            answer += stack.pop();
        }
        return answer;

    }

    public static void main(String[] args) {
        // Test code
        String result = reverseString("Hello");
        System.out.println("result = " + result);

        result = reverseString("1 3 5 7 9");
        System.out.println("result = " + result);
    }
}

  Note) 실행 결과

 

 

  Ex 2) 괄호 짝 검사

<hide/>
// Practice2
// 괄호 짝 검사

// 입출력 예시)
// 입력: "("
// 출력: Fail

// 입력: ")"
// 출력: Fail

// 입력: "()"
// 출력: Pass

import java.util.Stack;

public class Practice2 {
    public static void checkParenthesis(String str) {

        Stack stack = new Stack();

        for(char x : str.toCharArray()){
            if(x == '('){
                stack.push(x);
            }else{
                if(stack.isEmpty()){
                    System.out.println("FAIL!");
                    return;
                }
                stack.pop();
            }
        }

        if(stack.isEmpty()){
            System.out.println("PASS!");
            return;
        }
        System.out.println("FAIL!");
    }

    public static void main(String[] args) {
        // Test code
        checkParenthesis("(");          // FAIL!
        checkParenthesis(")");          // FAIL!
        checkParenthesis("()");         // PASS!
        checkParenthesis("()()()");     // PASS!
        checkParenthesis("(())()");     // PASS!
        checkParenthesis("(((()))");    // FAIL!
    }
}

  Note) 실행 결과

 

 

  Ex 3) 후위 표기법 연산

<hide/>
// Practice3
// 후위표기법 연산
// 참고 설명) 전위/중위/후위 표기법

// 입출력 예시)
// 입력: "2 2 +"
// 출력: 4

// 입력: "2 2 -"
// 출력: 0

import java.util.Stack;

public class Practice3 {
    public static double calculate(String string) {

        Stack<Double> stack = new Stack();
        
        for(String x : string.split(" ")){
                if(x.equals("+")){
                    Double a = stack.pop();
                    Double b = stack.pop();
                    stack.push(b + a);
                // System.out.print(a + "+" + b + "=" + answer);

                }else if(x.equals("-")){
                    Double a = stack.pop();
                    Double b = stack.pop();
                    stack.push(- a + b);
                    //  System.out.print(b + "-" + a + "=" + answer);

                }else if(x.equals("*")){
                    Double a = stack.pop();
                    Double b = stack.pop();
                    stack.push(a * b);
                 
                }else if(x.equals("/")){
                    Double a = stack.pop();
                    Double b = stack.pop();
                    stack.push(1 / a * b);
                 }else{
                    stack.push(Double.parseDouble(x));
                 }
        }
        return stack.pop();
    }

    public static void main(String[] args) {
        // Test code
        System.out.println(calculate("2 2 +"));    // 4
        System.out.println(calculate("2 2 -"));    // 0
        System.out.println(calculate("2 2 *"));    // 4
        System.out.println(calculate("2 2 /"));    // 1
        System.out.println(calculate("1 1 + 2 * 3 * 2 / 5 -"));    // 1
        System.out.println(calculate("5 2 * 3 - 8 * 4 /"));        // 14
    }
}

  Note) 실행 결과

 

 

  Ex 4) 두 문자열 비교, '#'은 backspace로 바로 이전의 문자를 삭제하는 기능이라고 가정한다.

<hide/>
// Practice4
// 두 문자열 비교
// 단, #은 backspace 로 바로 이전의 문자를 삭제하는 기능이라고 가정

// 입출력 예시
// 입력: s1 = "tree", s2 = "th#ree"
// 출력: true

// 입력: s1 = "ab#a", s2 = "aab#"
// 출력: true

// 입력: s1 = "wo#w", s2 = "ww#o"
// 출력: false


import java.util.Stack;

public class Practice4 {

    public static boolean stringCompare(String s1, String s2) {
        Stack stack1 = new Stack();
        Stack stack2 = new Stack();

        for(char c : s1.toCharArray()){
            if(c == '#'){
                stack1.pop();
                continue;
            }
            stack1.push(c);
        }
        
       for(char c : s2.toCharArray()){
            if(c == '#'){
                stack2.pop();
                continue;
            }
            stack2.push(c);
        }
        
        return  String.valueOf(stack1).equals(String.valueOf(stack2));
    }

    public static void main(String[] args) {
        // Test code
        String s1 = "tree";
        String s2 = "th#ree";
        System.out.println(stringCompare(s1, s2));

        s1 = "ab#a";
        s2 = "aab#";
        System.out.println(stringCompare(s1, s2));

        s1 = "wo#w";
        s2 = "ww#o";
        System.out.println(stringCompare(s1, s2));
    }
}

  Note) 실행 결과

 

 

2.8 Queue

  - 선입선출(FIFO, first in first out) 

    ex) 프린터 출력 대기열, BFS

  - 데이터 추가(Enqueue), 꺼내기(Dequeue), 큐 공간 확인 동작으로 이뤄진다.

  - Queue는 Stack과 다르게 인터페이스로 구현되어 있기 때문에 객체를 만들 수 없다.

    -> <다형성>

    -> Queue queuue = new LinkedList();를 이용한다.

 

  Ex 1) ArrayList를 이용해서 Queue 구현하기

    - list.remove(0); 지우려는 데이터의 index를 매개변수로 넣는다.

<hide/>
// Practice1
// ArrayList 를 이용한 큐 구현

import java.util.ArrayList;

class MyQueue1 {
    ArrayList list;

    MyQueue1() {
        this.list = new ArrayList();
    }

    public boolean isEmpty() {
        return  list.size() == 0;
    }

    public void push(int data) {
        list.add(data);
    }

    public Integer pop() {
        if(list.isEmpty()){
            System.out.println("List is empty!");
            return null;
        }
        int data  = (int) list.get(0);
        list.remove(0);
        return data;
    }

    public Integer peek() {
        if(list.isEmpty()){
            System.out.println("List is empty!");
            return null;
        }
        return (int) list.get(0);
    }

    public void printQueue() {
        System.out.println(list);

    }
}

public class Practice1 {
    public static void main(String[] args) {
        // Test code
        MyQueue1 myQueue = new MyQueue1();
        myQueue.push(1);
        myQueue.push(2);
        myQueue.push(3);
        myQueue.push(4);
        myQueue.push(5);

        myQueue.printQueue();   // 1, 2, 3, 4, 5

        System.out.println(myQueue.peek()); // 1
        myQueue.printQueue();   // 1, 2, 3, 4, 5

        System.out.println(myQueue.pop());  // 1
        myQueue.printQueue();   // 2, 3, 4, 5

        System.out.println(myQueue.pop());  // 2
        myQueue.printQueue();   // 3, 4, 5

        System.out.println(myQueue.pop());
        System.out.println(myQueue.pop());
        System.out.println(myQueue.pop());
        myQueue.printQueue();
    }
}

  Note) 실행 결과

 

 

  Ex 2) 배열을 이용해서 Circle Queue 구현하기 - 원형 큐

    - 인덱스 0에는 값을 비워둔다.

    - 원형 큐 관리를 위해 배열의 사이즈는  매개변수로 들어온 값에 1을 더한 값으로 정한다.(5 + 1)

   - enqueue를 할 때는 rear값만 변하고 front값은 변하지 않는다. 

<hide/>
// Practice2
// 배열을 이용한 기본 큐 직접 구현 (원형 큐)

class MyQueue2 {
    int[] arr;
    int front = 0;
    int rear = 0;

    MyQueue2(int size) {
        arr = new int[size + 1];
        // 원형 큐 관리를 위해서 front자리를 비워둔다.
    }

    public boolean isEmpty() {
        return rear == front;
    }

    public boolean isFull() {

        return (rear + 1) % arr.length == front;
    }

    public void enqueue(int data) {
        if(isFull()){
            System.out.println("Queue is full!");
            return;
        }
        rear = (rear + 1) % arr.length;
        arr[rear] = data;
    }

    public Integer dequeue() {
        if(isEmpty()){
            System.out.println("Queue is empty!");
            return  null;
        }
        front = (front + 1) % arr.length;
        return arr[front];
    }

    public void printQueue() {
        int start = (front + 1) % arr.length;
        int end = (rear + 1) % arr.length;

        for(int i = start; i != end; i = (i + 1) % arr.length){
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

public class Practice2 {
    public static void main(String[] args) {
        // Test code
        MyQueue2 myQueue = new MyQueue2(5);
        myQueue.enqueue(1);
        myQueue.enqueue(2);
        myQueue.enqueue(3);
        myQueue.enqueue(4);
        myQueue.enqueue(5);
        myQueue.enqueue(6); // Queue is full!

        myQueue.printQueue();   // 1, 2, 3, 4, 5

        System.out.println(myQueue.dequeue());  // 1
        myQueue.printQueue();   // 2, 3, 4, 5

        System.out.println(myQueue.dequeue());  // 2
        myQueue.printQueue();   // 3, 4, 5

        myQueue.enqueue(6);
        myQueue.enqueue(7);
        myQueue.printQueue();   // 3, 4, 5, 6, 7

        System.out.println(myQueue.dequeue());  // 3
        System.out.println(myQueue.dequeue());  // 4
        System.out.println(myQueue.dequeue());  // 5
        myQueue.printQueue();   // 6, 7
        System.out.println(myQueue.dequeue());  // 6
        System.out.println(myQueue.dequeue());  // 7
        myQueue.dequeue();      // Queue is empty!
    }
}

  Note) 실행 결과

 

 

2.9 Queue 문제 풀이

  Ex 1) 카드 섞기  - 1 ~ N 까지 번호로 구성된 N개의 카드가 있다. (1번: 위, N번: 가장 아래)

    (1) 가장 위의 카드는 버린다.  

    (2) 그 다음 위의 카드는 가장아래에 다시 넣는다. 카드가 한 장 남을 때까지 반복했을 때, 마지막 카드 구하기

    - intstream을 이용해서 하나씩 큐에 넣을 수 있다. 

    - 반환할 때, poll()이 아닌 remove()를 쓸 수도 있따.

IntStream.range(1, N + 1).forEach(x -> queue.add(x));
<hide/>
// Practice1
// 카드 섞기
// 1부터 N 까지의 번호로 구성된 N장의 카드가 있다.
// 1번 카드가 가장 위에 그리고 N번 카드는 가장 아래의 상태로 카드가 순서대로 쌓여있다.
// 아래의 동작을 카드 한 장만 남을 때까지 반복했을 때, 가장 마지막 남는 카드 번호를 출력하시오.
// 1. 가장 위의 카드는 버린다.
// 2. 그 다음 위의 카드는 쌓여 있는 카드의 가장 아래에 다시 넣는다.

// 예시 입력)
// N = 4
// 결과: 4

// N = 7
// 결과: 6


import javax.swing.plaf.LabelUI;
import java.util.LinkedList;
import java.util.Queue;
import java.util.stream.IntStream;

public class Practice1 {
    public static int findLastCard(int N) {

        Queue queue = new LinkedList();
        IntStream.range(1, N + 1).forEach(x -> queue.add(x));
    
        while(queue.size() != 1){
            queue.poll();
            queue.add(queue.poll());
        }
        return  (int) queue.poll();
    }

    public static void main(String[] args) {
        // Test code
        System.out.println(findLastCard(4));    // 4
        System.out.println(findLastCard(7));    // 6
        System.out.println(findLastCard(9));    // 2
    }
}

  Note) 실행 결과

 

 

  Ex 2) 요세푸스 순열 - Josephus Permutation

  MySol)

<hide/>
// Practice2
// 요세푸스 문제
// N과 K가 주어졌을 때 (N, K) 요세푸스 순열을 구하시오.
// N과 K는 N >= K 를 만족하는 양의 정수이다.
// 1부터 N 번까지 N명이 순서대로 원을 이루어 모여 있다.
// 이 모임에서 원을 따라 순서대로 K번째 사람을 제외한다.
// 모든 사람이 제외될 때까지 반복하며 이 때, 제외되는 순서가 요세푸스 순열이다.

// 예시 입력
// 입력: N = 5, K = 2
// 결과: 2, 4, 1, 5, 3

// 입력: N = 7, K = 3
// 결과: 3, 6, 2, 7, 5, 1, 4


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.stream.IntStream;

public class Practice2 {

    public static ArrayList getJosephusPermutation(int N, int K) {

        Queue queue = new LinkedList();
        ArrayList<Integer> result = new ArrayList<>();
        IntStream.range(1, N + 1).forEach(x -> queue.add(x));
        while(!queue.isEmpty()){
            for(int i = 1; i < K; ++i){
                queue.add(queue.poll());
            }
            result.add((int) queue.poll());
        }
        return result;
    }

    public static void main(String[] args) {
        // Test code
        System.out.println(getJosephusPermutation(5, 2));
        System.out.println(getJosephusPermutation(7, 3));
    }
}

  Note) 실행 결과

 

 

 

 

2.10 Deque(Doubly ended Queue, 데크, 디큐)

  Def)  Deque: 양쪽에서 삽입과 삭제가 모두 가능한 자료 구조 (Stack + Queue)

    - 일부 기능을 제한하여 용도에 맞게 변형 가능

    - add / remove -> 공간이 부족하거나 제거할 데이터가 없을 때, 예외를 발생시킨다.

    - offer / poll -> 공간이 부족하거나 제거할 데이터가 없을 때, null이나  false를 반환시킨다.

 

    1) Scroll(입력 제한 데트): 한 쪽(rear 또는 front)의 입력을 제한한 데크

    2) Shelf(출력 제한 데크): 한 쪽의 출력을 제한한 데크

 

 

 

 

  Ex) Java Deque

<hide/>
// 선형 자료구조 - 데크


import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;

public class Main {
    public static void main(String[] args) {

        Deque deque = new ArrayDeque();

        // Front 부분 입력
        deque.addFirst(1);
        deque.addFirst(2);
        deque.addFirst(3);
        System.out.println(deque);

        // Rear 부분 입력
        deque.addLast(10);
        deque.addLast(20);
        deque.addLast(30);
        System.out.println(deque);

        // Front 부분 출력

        System.out.println(deque.removeFirst());
        System.out.println(deque);

        // Rear 부분 출력
        System.out.println(deque.removeLast());
        System.out.println(deque);
        System.out.println(deque.removeLast());
        System.out.println(deque.removeLast());
        System.out.println(deque.removeLast());
        System.out.println(deque.removeLast());
        System.out.println(deque);

        System.out.println(deque.pollLast());
        System.out.println(deque.pollLast());
    }
}

  Note) 실행 결과

 

 

  Ex 1) ArrayList를 이용한 Deque

<hide/>
// Practice1
// ArrayList 를 이용한 데크 구현

import java.util.ArrayList;

class MyDeque1 {
    ArrayList list;

    MyDeque1() {
        list = new ArrayList();
     }

    public boolean isEmpty() {
        if(list.size() == 0){
            System.out.println("Deque is empty!");
            return true;
        }
        return false;
    }

    public void addFirst(int data) {
        list.add(0, data);
    }

    public void addLast(int data) {
        list.add(list.size(), data);
    }

    public Integer removeFirst() {
        if(list.isEmpty()){
            System.out.println("Deque is empty!");
            return null;
        }
        int data = (int) list.get(0);
        list.remove(0);
        return  data;

    }

    public Integer removeLast() {
        if(this.isEmpty()){
            System.out.println("Deque is empty!");
            return null;
        }
        int data = (int) list.get(list.size() - 1);
        list.remove(list.size() - 1);
        return data;
      }

    public void printDeque() {

        System.out.println(list);

    }

}

public class Practice1 {
    public static void main(String[] args) {
        // Test code
        MyDeque1 myDeque = new MyDeque1();
        // Front 부분 입력
        myDeque.addFirst(1);
        myDeque.addFirst(2);
        myDeque.addFirst(3);
        myDeque.printDeque();    // 3 2 1

        // Rear 부분 입력
        myDeque.addLast(10);
        myDeque.addLast(20);
        myDeque.addLast(30);
        myDeque.printDeque();    // 3 2 1 10 20 30

        // Front 부분 출력
        System.out.println(myDeque.removeFirst());  // 3
        myDeque.printDeque();    // 2 1 10 20 30

        // Rear 부분 출력
        System.out.println(myDeque.removeLast());   // 30
        myDeque.printDeque();    // 2 1 10 20

        System.out.println(myDeque.removeLast());   // 20
        System.out.println(myDeque.removeLast());   // 10
        System.out.println(myDeque.removeLast());   // 1
        System.out.println(myDeque.removeLast());   // 2
        myDeque.printDeque();
    }
}

  Note) 실행 결과

 

 

  Ex 2)  배열을 이용한 Deque

<hide/>
// Practice2
// 배열을 이용한 기본 데크 직접 구현

class MyDeque2 {

    int [] arr;
    int front = 0;
    int rear = 0;

    MyDeque2(int size) {
        arr = new int[size + 1];    // 원형으로 만들것이기 때문에 + 1
    }

    public boolean isEmpty() {
        return front == rear;
    }

    public boolean isFull() {
        return (rear + 1) % arr.length == front;
    }

    public void addFirst(int data) {
        if(this.isFull() == true) {
            System.out.println("Deque is full!");
            return;
        }
        arr[front] = data;
        front = (front - 1 + arr.length) % arr.length;  // 나머지가 음수가 되는 것을 막는다.
        return;
    }

    public void addLast(int data) {
        if(this.isFull()){
            System.out.println("Deque is full!");
            return;
        }
        rear = (rear + 1) % arr.length;
        arr[rear] = data;
    }

    public Integer removeFirst() {
        if(this.isEmpty()){
            System.out.println("Deque is empty!");
            return null;
        }
        front = (front + 1) % arr.length;
        return  arr[front];
    }

    public Integer removeLast() {
        if(this.isEmpty()){
            System.out.println("Deque is empty!");
            return null;
        }
        int data = arr[this.rear];
        rear = (rear - 1 + arr.length) % arr.length; // 조정해준다.
        return data;
    }

    public void printDeque() {
        int start =  (front + 1)  % arr.length;
        int end =  (rear + 1)  % arr.length;

        for(int i = start; i != end; i = (i + 1) % arr.length ){
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

public class Practice2 {
    public static void main(String[] args) {
        // Test code
        MyDeque2 myDeque = new MyDeque2(5);
        // Front 부분 입력
        myDeque.addFirst(1);
        myDeque.addFirst(2);
        myDeque.addFirst(3);
        myDeque.printDeque();   // 3 2 1

        // Rear 부분 입력
        myDeque.addLast(10);
        myDeque.addLast(20);
        myDeque.addLast(30);    // Deque is full!
        myDeque.printDeque();        // 3 2 1 10 20

        // Front 부분 출력
        System.out.println(myDeque.removeFirst());  // 3
        myDeque.printDeque();   // 2 1 10 20

        // Rear 부분 출력
        System.out.println(myDeque.removeLast());   // 20
        myDeque.printDeque();    // 2 1 10

        System.out.println(myDeque.removeLast());   // 10
        System.out.println(myDeque.removeLast());   // 1
        System.out.println(myDeque.removeLast());   // 2
        System.out.println(myDeque.removeLast());   // Deque is empty!
    }
}

 

  Note) 실행 결과

 

 

2.11 Deque 문제 풀이

  Ex 1)  데이터 재정렬

<hide/>
// Practice1
// 데이터 재정렬
// D_0 -> D_1 -> ... -> D_n-1 -> D_n 순으로 되어 있는 데이터를
// D_0 -> D_n -> D_1 -> D_n-1 -> ... 순이 되도록 재정렬 하시오.

// 입력 예시)
// 입력 데이터: 1 -> 2 -> 3 -> 4 -> 5
// 출력 데이터: 1 -> 5 -> 2 -> 4 -> 3


import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.stream.IntStream;

public class Practice1 {
    public static void reorderData(int[] arr) {

        Deque deque = new ArrayDeque();
        ArrayList result = new ArrayList();

        IntStream.of(arr).forEach(x -> deque.addLast(x));
        System.out.println(deque);

        while(!deque.isEmpty()){
            result.add(deque.removeFirst());

            if(!deque.isEmpty()){
                result.add(deque.removeLast());
            }
        }
        System.out.println("== 정렬 전 ==");
        printData(arr);
        System.out.println("== 정렬 후 ==");
        printData(result.stream().mapToInt(x -> (int) x).toArray());
    }
    public static void printData(int[] arr){

        for(int i = 0; i < arr.length - 1; ++i){
            System.out.print(arr[i] + " -> ");
        }
        System.out.println(" " + arr[arr.length - 1]);
    }

    public static void main(String[] args) {
        // Test code
        int[] arr = {1, 2, 3, 4, 5};
        reorderData(arr);   // 1 -> 5 -> 2 -> 4 -> 3

        int[] arr2 = {1, 2, 3, 4, 5, 6, 7};
        reorderData(arr2);  // 1 -> 7 -> 2 -> 6 -> 3 -> 5 -> 4
    }
}

  Note) 실행 결과

 

  Ex 2) 팰린드롬

    - deque를 이용한 팰린드롬

    - remove가 아닌 pollFirst()와 pollLast()를 이용해야 데이터가 비어 있을 때, null을 반환한다.

<hide/>
// Practice2
// Palindrome 찾기
// Palindrome 이면 true, 아니면 false 를 반환하세요.
// Palindrome: 앞으로 읽어도 거꾸로 읽어도 같은 문자열

// 입출력 예시)
// 입력: a
// 결과: true

// 입력: madam
// 결과: true

// 입력: abab
// 결과: false


import java.util.ArrayDeque;
import java.util.Deque;

public class Practice2 {
    public static boolean checkPalindrome(String str) {

        Deque deque = new ArrayDeque();
        boolean isFront = true;
        boolean isPalindrome = true;

        for(String s : str.split("")){
            deque.addFirst(s);
        }
        while(!deque.isEmpty()){
            String s1 = (String) deque.pollFirst();     // 데이터가 없으면 null을 반환한다.
            String s2 = (String) deque.pollLast();

            if(!s1.equals(s2) && s1 != null && s2 != null){
                isPalindrome = false;
                break;
            }
        }
        return  isPalindrome;
    }

    public static void main(String[] args) {
        // Test code
        System.out.println(checkPalindrome("a"));       // true
        System.out.println(checkPalindrome("aba"));     // true
        System.out.println(checkPalindrome("abba"));    // true
        System.out.println(checkPalindrome("abab"));    // false
        System.out.println(checkPalindrome("abcba"));   // true
        System.out.println(checkPalindrome("abccba"));  // true
        System.out.println(checkPalindrome("madam"));  // true
    }
}

  Note) 실행 결과

 

 

  Ex 3)  중간에 데이터 추가하기

<hide/>
// Practice3
// 데크 변형
// 기본 데크 구조에서 중간에 데이터를 추가하는 기능을 구현하세요.
// 단, 추가적인 자료구조 생성하지 말고 구현

// 입력 예시)
// 초기 데크 상태 (size: 5)
// -> 1, 2, 3, 4
// 중간 입력: 10
// 결과 데크
// -> 1, 2, 10, 3, 4


class MyDeque {
    int[] arr;
    int front = 0;
    int rear = 0;

    MyDeque(int size) {
        this.arr = new int[size + 1];
    }

    public boolean isEmpty() {
        return this.rear == this.front;
    }

    public boolean isFull() {
        return (this.rear + 1)  % this.arr.length == this.front;
    }

    public void addFirst(int data) {
        if (this.isFull()) {
            System.out.println("Deque is full!");
            return;
        }

        this.arr[front] = data;
        this.front = (this.front - 1 + this.arr.length) % this.arr.length;
    }

    public void addLast(int data) {
        if (this.isFull()) {
            System.out.println("Deque is full!");
            return;
        }

        this.rear = (this.rear + 1) % this.arr.length;
        this.arr[this.rear] = data;
    }

    public void addMiddle(int data) {
        if (this.isFull()) {
            System.out.println("Deque is full!");
            return;
        }
        int elements = rear - front;    //데이터가 있는 부분
        if (elements < 0) {
            elements = elements + arr.length;
        }
        int mid = (rear - elements / 2 + arr.length) % arr.length + 1;

        int start = (rear + 1) % arr.length;
        int end = (rear - elements / 2 + arr.length) % arr.length;

        for (int i = start; i != end; i = (i - 1 + arr.length) % arr.length) {
            arr[i] = arr[(i - 1 + arr.length) % arr.length];
        }
        arr[mid] = data;
        rear = (rear + 1) % arr.length;
    }

    public Integer removeFirst() {
        if (this.isEmpty()) {
            System.out.println("Deque is empty!");
            return null;
        }
        this.front = (this.front + 1) % this.arr.length;
        return this.arr[this.front];
    }

    public Integer removeLast() {
        if (this.isEmpty()) {
            System.out.println("Deque is empty!");
            return null;
        }
        int data = this.arr[this.rear];
        this.rear = (this.rear - 1 + this.arr.length) % this.arr.length;
        return data;
    }

    public void printDeque() {
        int start = (this.front + 1) % this.arr.length;
        int end = (this.rear + 1) % this.arr.length;

        for (int i = start; i != end; i = (i + 1) % this.arr.length) {
            System.out.print(this.arr[i] + " ");
        }
        System.out.println();
    }
}

public class Practice3 {
    public static void main(String[] args) {
        // Test code
        MyDeque myDeque1 = new MyDeque(5);
        myDeque1.addLast(1);
        myDeque1.addLast(2);
        myDeque1.addLast(3);
        myDeque1.addLast(4);
        myDeque1.printDeque();

        myDeque1.addMiddle(10);
        myDeque1.printDeque();

        MyDeque myDeque2 = new MyDeque(5);
        myDeque2.addLast(10);
        myDeque2.addLast(10);
        myDeque2.addLast(10);
        myDeque2.addLast(10);
        myDeque2.addLast(10);
        myDeque2.removeFirst();
        myDeque2.removeFirst();
        myDeque2.removeFirst();
        myDeque2.removeFirst();
        myDeque2.addLast(11);
        myDeque2.addLast(12);
        myDeque2.addLast(13);
        myDeque2.printDeque();

        myDeque2.addMiddle(100);
        myDeque2.printDeque();
    }
}

 

  Note) 실행 결과

 

 

  Ex 4)  데크 리사이즈

<hide/>
// Practice4
// 데크 리사이즈
// 기본 데크 구조에서 데크 공간이 full 일 때 데이터를 추가하는 경우,
// 데크 공간을 2배 씩 늘려주는 코드를 작성하세요.

class MyDeque2 {
    int[] arr;
    int front = 0;  // 첫 데이터 앞에 빈 공간
    int rear = 0;   // 데이터 중 가장 마지막 데이터

    MyDeque2(int size) {
        this.arr = new int[size + 1];
    }

    public boolean isEmpty() {
        return this.rear == this.front;
    }

    public boolean isFull() {
        return (this.rear + 1)  % this.arr.length == this.front;
    }

    public void increaseSize() {

        System.out.println("MyDeque2.increaseSize");

        int[] arrtmp = this.arr.clone();
        arr = new int[arr.length * 2];

        int start = (this.front + 1) % arrtmp.length;
        int end = (this.rear + 1) % arrtmp.length;

        int idx = 1;

        for(int i =  start; i != end; i = (i + 1) % arrtmp.length){
            this.arr[idx++] = arrtmp[i];

        }
            front = 0;
            rear = idx - 1;

    }

    public void addFirst(int data) {
        if (this.isFull()) {
            increaseSize();
        }

        this.arr[front] = data;
        this.front = (this.front - 1 + this.arr.length) % this.arr.length;
    }

    public void addLast(int data) {
        if (this.isFull()) {
            increaseSize();
        }

        this.rear = (this.rear + 1) % this.arr.length;
        this.arr[this.rear] = data;
    }

    public Integer removeFirst() {
        if (this.isEmpty()) {
            System.out.println("Deque is empty!");
            return null;
        }

        this.front = (this.front + 1) % this.arr.length;
        return this.arr[this.front];
    }

    public Integer removeLast() {
        if (this.isEmpty()) {
            System.out.println("Deque is empty!");
            return null;
        }

        int data = this.arr[this.rear];
        this.rear = (this.rear - 1 + this.arr.length) % this.arr.length;
        return data;
    }

    public void printDeque() {
        int start = (this.front + 1) % this.arr.length;
        int end = (this.rear + 1) % this.arr.length;

        for (int i = start; i != end; i = (i + 1) % this.arr.length) {
            System.out.print(this.arr[i] + " ");
        }
        System.out.println();
    }
}

public class Practice4 {
    public static void main(String[] args) {
        // Test code
        MyDeque2 myDeque = new MyDeque2(5);

        myDeque.addLast(1);
        myDeque.addLast(2);
        myDeque.addLast(3);
        myDeque.addLast(4);
        myDeque.addLast(5);
        myDeque.printDeque();

        myDeque.addLast(6);
        myDeque.addLast(7);
        myDeque.addLast(8);
        myDeque.addLast(9);
        myDeque.addLast(10);
        myDeque.printDeque();

        myDeque.removeLast();
        myDeque.removeLast();
        myDeque.addFirst(100);
        myDeque.addFirst(200);
        myDeque.printDeque();

        myDeque.addFirst(300);
        myDeque.addFirst(400);
        myDeque.addFirst(500);
        myDeque.printDeque();
    }
}

 

  Note) 실행 결과

 

 

2.12 해시 테이블(Hash Table)

  Def) Hash Table(해시 맵, 해시 표): 키와 값을 대응시켜 저장하는 데이터 구조, 키를 통해 데이터에 빠르게 접근 가능하다

    - 해싱: 키를 특정 계산식에 넣어 나온 결과를 사용하여 값에 접근하는 과정

    - 키(key): 해시 테이블 접근을 위한 입력 값

    - 해시 함수:  키를 해시 값으로 매핑하는 연산

    - 해시 값: 해시 테이블의 인덱스

    - 해시 테이블: 키와 값을 연관시켜 저장하는 데이터 구조

 

    Note) 해시 충돌: 해시테이블의 같으 ㄴ공간에 서로 다른 값을 저자하려는 경우

    - 서로 다은 키의 해시 함수를 통해 해시 값이 동일한 경우

    -  해시 충돌 해결 방법으로는 크게 개방 주소법과 분리 연결법이 있다.

 

    1) 개방 주소법(Open Address): 충돌 시 테이블에서 비어 있는 공간의 hash를 찾아 데이터를 저장한다.

      -> hash와 value가 1:1 관계를 유지한다.

      -> 비어 있는 공간 탐색 방법에 따른 분류(선형 탐사법, 제곱 탐사법, 이중 해싱)

 

      (1) 선형 탐사법(Linear Probing): 빈 공간을 순차적으로 탐사하는 방법

        - 충돌 발생 지점부터 이후의 빈 공간을 순서대로 탐사

        - 일차 군집화 문제 발생 (반복된 충돌 발생 시 해당 지점 주변에 데이터가 몰리는 경우 발생한다.)

  

      (2) 제곱 탐사법(Quarratic Probing): 빈 공간을 n제곱만큼의 간견을 두고 탐사하는 방법

        - 충돌 발생 지점 부터 이후의 빈 공간을 n제곱 간격으로 탐사한다.

        - 일차 군집화 문제 일부 보완

        - 이차 군집화 문제 발생 가능성

 

      (3) 이중 해싱(Double Hashing): 해시 함수를 이중으로 사용

        - 해시 함수1(최초 해시를 구할 때, 사용) / 해시함수2(충돌 발생 시, 탐사 이동 간격을 구할 때 사용한다.) 

        - 선형탐사, 제곱탐사에 비해 데이터가 골고루 분포된다. 

 

    2) 분리 연결법(Seperate Chaining): 해시 테이블을 연결 리스트로 구성한다.

        - 충돌 발생 시, 테이블 내의 다른 위치를 탐색하는 것이 아닌 연결 리스트를 이용해서 해당 테이블에 데이터를 연결한다.

 

 

  Ex)  hash table

<hide/>
// 선형 자료구조 - 해시 테이블

import java.util.Hashtable;
import java.util.Map;

public class Main {
    // 해시 함수
    public static  int getHash(int key){
        return key % 5;
    }

    public static void main(String[] args) {
        // 기본 해시 테이블 사용 방법
        Hashtable<String, Integer> ht = new Hashtable();
        ht.put("key1", 10);
        ht.put("key2", 20);
        ht.put("key3", 30);
        ht.put("key3", 50);

        for(Map.Entry<String, Integer> item: ht.entrySet()){
            System.out.println(item.getKey() + " - " + item.getValue());
        }
        // 해시 충돌 케이스 (해시 함수 사용)
        Hashtable<Integer, Integer> ht2 = new Hashtable();
        ht2.put(getHash(1), 10);
        ht2.put(getHash(2), 20);
        ht2.put(getHash(3), 30);
        ht2.put(getHash(4), 40);
        ht2.put(getHash(5), 50);

        System.out.println("== 충돌 전 ==");
        for(Map.Entry<Integer, Integer> item: ht2.entrySet()){
            System.out.println(item.getKey() + " - " + item.getValue());
        }

        System.out.println("== 충돌 후 ==");
        ht2.put(getHash(6), 60);
        for(Map.Entry<Integer, Integer> item: ht2.entrySet()){
            System.out.println(item.getKey() + " - " + item.getValue());
        }
    }
}

  Note) 실행 결과

 

 

  Ex 1) 해시테이블을 배열로 구현하기

<hide/>
// Practice1
// 해시 테이블 배열로 직접 구현

class MyHashTable{
    Integer[] table;
    int elemCnt;

    MyHashTable(){}
    MyHashTable(int size){
        table = new Integer[size];
        elemCnt = 0;
    }

    public int getHash(int key){
        return key % table.length;
    }

    public void setValue(int key, int data){
        int idx  = this.getHash(key);
        this.table[idx] = data;
        ++this.elemCnt;
    }

    public int getValue(int key){
        int idx = getHash(key);
        return  table[idx];
    }

    public void removeValue(int key) {
        int idx = getHash(key);
        table[idx] = null;
        --elemCnt;
    }
    public  void printHashTable(){
        System.out.println("== Hash Table ==");
        for(int i = 0;i < table.length; ++i){
            System.out.println(i + " : " + table[i]);
        }
    }
}

public class Practice1 {

    public static void main(String[] args) {
        // Test code
        MyHashTable ht = new MyHashTable(7);
        ht.setValue(1, 1);
        ht.setValue(2, 2);
        ht.setValue(3, 3);
        ht.setValue(4, 4);
        ht.setValue(5, 5);
        ht.printHashTable();
        ht.setValue(8, 6);
        ht.printHashTable();
    }
}

  Note) 실행 결과

    - 8을 7로   나눈 나머지는  1이기 때문에

    - setvalue(8, 6)은 hash 1에 6을 넣는 것과 같은 충돌이 일어난다. 데이터가 바뀐다.

 

 

  Ex 2) 선형 탐사법

<hide/>
// Practice2
// 해시 충돌 해결 - 개방 주소법 (선형 탐사법)

class MyHashTable2 extends MyHashTable {

    MyHashTable2(int size) {
        super(size);
    }

    public void setValue(int key, int data) {
        int idx = this.getHash(key);
        if(this.elemCnt == this.table.length){
            System.out.println("Hash table is full!");
            return;
        }else if(this.table[idx] == null){
            this.table[idx] = data;
        }else{
            int newIdx = idx;
            while(true) {
                newIdx = (newIdx + 1) % this.table.length;
                if (this.table[newIdx] == null) {
                    break;
                }
            }
            this.table[newIdx] = data;
        }
        ++elemCnt;
    }
}

public class Practice2 {
    public static void main(String[] args) {
        // Test code
        MyHashTable2 ht = new MyHashTable2(5);
        ht.setValue(1, 1);
        ht.setValue(3, 3);
        ht.printHashTable();

        ht.setValue(1, 10);
        ht.printHashTable();

        ht.setValue(1, 20);
        ht.setValue(1, 30);
        ht.setValue(1, 40);
        ht.printHashTable();
    }
}

  Note) 실행 결과

 

 

  Ex 3) 해시 충돌 해결 - 개방 주소법(제곱 탐사법)

<hide/>
// Practice3
// 해시 충돌 해결 - 개방 주소법 (제곱 탐사법)

class MyHashTable3 extends MyHashTable {

    MyHashTable3(int size) {
        super(size);
    }

    public void setValue(int key, int data) {
        int idx = this.getHash(key);
        if(this.elemCnt == this.table.length){
            System.out.println("Hash table is full!");
            return;
        }else if(this.table[idx] == null){
            this.table[idx] = data;
        }else{
            int newIdx = idx;
            int cnt = 0;
            while(true){
                newIdx = (newIdx + (int) Math.pow(2, cnt)) % this.table.length;
                if(this.table[newIdx] == null){
                    break;
                }
                ++cnt;  // 충돌할 때마다 증가
            }
            this.table[newIdx] = data;
        }
        ++this.elemCnt;
    }
}

public class Practice3 {
    public static void main(String[] args) {
        // Test code
        MyHashTable3 ht = new MyHashTable3(11);
        ht.setValue(1, 10);
        ht.setValue(2, 20);
        ht.setValue(4, 40);
        ht.printHashTable();

        ht.setValue(1, 100);
        ht.printHashTable();

        ht.setValue(1, 200);
        ht.setValue(1, 300);
        ht.setValue(1, 400);
        ht.printHashTable();
    }
}

  Note) 실행 결과

 

 

  Ex 4) 해시 충돌 해결 - 개방 주소법 (이중 해싱)

<hide/>
// Practice4
// 해시 충돌 해결 - 개방 주소법 (이중 해싱)

class MyHashTable4 extends MyHashTable {

    int c ; // 즉, getHashC의 결과값
    MyHashTable4(int size){
        super(size);        // table = new Integer[size]
        this.c = this.getHashC(size);       // 11보다 작은 최대의 소수 7 = c
    }
    
    public int getHashC(int size){      //getHaahC(11) = 7
        int c = 0;
        if(size <= 2){
            return  size;
        }
        for(int i = size - 1; i > 2; --i){
            boolean isPrime = true;
            for(int j = 2; j < i; ++j){
                if(i % j == 0){         // i가 합성수일 때 안쪽 for문 빠져나가고 아래 if문 건너뛰고 다음 i에 대해 바깥 for문을 실행한다.
                    isPrime = false;
                    break;
                }
            }
            if(isPrime == true){    // i가 소수일 때, 안쪽 for문을 돌고 여기 if문까지 들어온다.
                c = i;
                break;  // 바깥 for문을 빠져나온다.
            }
        }
        return  c;  // 즉, c는 i보다 작은 수 중에서 가장 큰 소수이다.
    }

    public int getHash2(int key){       // size가 11일 때, getHash2(key)는 key를 7로 나눈 나머지에 1을 더한 값이다.
        int hash = 1 + key % this.c;
        return  hash;
    }
    public void setValue(int key, int data) {
        int idx = getHash(key);

        if(elemCnt == table.length){
            System.out.println("Table is full!");
            return;
        }else if(table[idx] == null) {
            table[idx] = data;
        }else{
            int newIdx = idx;       // 초기값 idx = 1;
            int cnt = 1;
            while(true){
                System.out.printf("%nkey = %d, data = %d %n", key, data);
                System.out.printf("newIdx = (%d + %d * %d) %% %d %n", newIdx, getHash2(newIdx), cnt, table.length);
                newIdx = (newIdx + getHash2(newIdx) * cnt) % table.length;
                System.out.printf("newIdx = %d %n", newIdx );
                if(table[newIdx] == null){
                    System.out.printf("                              result: table[%d] = %d %n", newIdx, data);
                    break;
                }
                ++cnt;
            }
            table[newIdx] = data;
        }
        ++elemCnt;
    }
}

public class Practice4 {
    public static void main(String[] args) {
        // Test code
        MyHashTable4 ht = new MyHashTable4(11); // 11보다 작은 가장 큰 소수 getHashC(11) = 7
        ht.setValue(1, 10);
        ht.setValue(2, 20);
        ht.setValue(3, 30);
        ht.printHashTable();
        ht.setValue(1, 100);
        ht.setValue(1, 200);
        ht.setValue(1, 300);
        ht.printHashTable();
    }
}

  Note) 실행 결과

 

 

  Ex 5) 해시 충돌 해결 - 분리 연결법

<hide/>
// Practice5
// 해시 충돌 해결 - 분리 연결법

import java.util.List;

class Node {
    int data;
    int key;
    Node next;

    Node() {}
    Node(int key, int data, Node next) {
        this.key = key;
        this.data = data;
        this.next = next;
    }
}

class LinkedList {
    Node head;

    LinkedList() {}
    LinkedList(Node node) {
        this.head = node;
    }

    public boolean isEmpty() {
        return  this.head == null;
    }

    public void addData(int key, int data) {       
        if(isEmpty()){
            this.head =  new Node(key, data, null);
            return;
        }
        Node cur = this.head;
        while(cur.next != null){
            cur = cur.next;
        }
        cur.next = new Node(key, data, null);
    }

    public void removeData(int data) {      // 키를 가지고 찾아서 데이터를 지운다.
        if(isEmpty()){
            System.out.println("List is empty!");
            return;
        }
        Node cur = this.head;
        Node pre = cur;

        while(cur != null){
           if(cur.key == data){         // 삭제하려는 값을 찾은 경우
               if(cur == this.head){    // 찾으려는 데이터가 리스트 가장 첫 원소
                   this.head = cur.next;
               } else {
                   pre.next = cur.next;
               }
               break;
           }
           pre = cur;   
           cur = cur.next;
        }
    }

    public Integer findData(int key) {      // key를 넣으면 data를 반환한다.
        // removeData랑 findData는 isEmpty()
        // key 값이 있는지 확인해보고 있으면 노드의 data를 반환한다.

        if(isEmpty()){
            System.out.println("List is empty!");
            return null;
        }
        Node cur = this.head;
        while(cur != null){
            if(cur.key == key){
                return  cur.data;
            }
            cur = cur.next;
        }
        System.out.println("Data not found!");
        return  null;
    }
    public void showData() {
        if(isEmpty()){
            System.out.println("List is empty!");
            return;
        }
       Node cur = this.head;
       while(cur != null){
           System.out.print(cur.data + " " );
           cur = cur.next;
        }
        System.out.println();
    }
}

class MyHashTable5 {
    LinkedList[] table;
    MyHashTable5(int size){
        table = new LinkedList[size];
        for(int i = 0; i < table.length; ++i){
            table[i] = new LinkedList(null);
        }
    }
    public int getHash(int key){
        return  key % table.length;     // 11로 나눠서 나머지 구하기
    }

    public void setValue(int key, int data) {
        int idx = getHash(key);
        table[idx].addData(key, data);
    }
    public int getValue(int key){
        int idx = getHash(key);
        return table[idx].findData(key);
    }
    public void removeValue(int key){   // 헷갈리는 부분..
        int idx = getHash(key);
        table[idx].removeData(key);
        
    }
    public void printHashTable(){
        System.out.println("== Hash Table ==");
        for(int i = 0;i < table.length; ++i){
            System.out.print(i + " : ");
            table[i].showData();
        }
    }
}


public class Practice5 {
    public static void main(String[] args) {
        // Test code
        MyHashTable5 ht = new MyHashTable5(11); // ht = new LinkedList[11]
        ht.setValue(1, 10);
        ht.setValue(2, 20);
        ht.setValue(3, 30);
        ht.printHashTable();


        ht.setValue(12, 11);    // getHash(key)값이 1인 데이터를 리스트에 넣는다.
        ht.setValue(23, 12);
        ht.setValue(34, 13);

        ht.setValue(13, 21);    // getHash(key)값이 2인 데이터를 리스트에 넣는다.
        ht.setValue(24, 22);
        ht.setValue(35, 23);

        ht.setValue(5, 1);		// getHash(key)값이 5인 데이터를 리스트에 넣는다.
        ht.setValue(16, 2);
        ht.setValue(27, 3);
        ht.printHashTable();

        System.out.println("== key 값으로 해당 데이터 가져오기 ==");
        System.out.println(ht.getValue(1)); // 데이터 반환
        System.out.println(ht.getValue(12));

        System.out.println("== 데이터 삭제 ==");
        ht.removeValue(1);
        ht.removeValue(5);
        ht.removeValue(16);
        ht.printHashTable();
    }
}

  Note) 실행 결과

 

 

2.13 hash table 문제 풀이

 

  Ex 1) Hash Table

<hide/>
// Practice1
// 해시 테이블을 이용한 수 찾기
// 주어진 첫 번째 배열을 이용하여 해시 테이블을 초기화 한 후
// 두 번째 배열이 주어졌을 때 해당 배열 내 데이터가 해시 테이블에 있는지 확인하는 코드를 작성하세요.

// 입출력 예시)
// 배열1: 1, 3, 5, 7, 9
// 배열2: 1, 2, 3, 4, 5
// 출력: True, False, True, False, True

import java.util.Hashtable;

public class Practice1 {
    public static void solution(int[] arr1, int[] arr2) {
        Hashtable<Integer, Integer> ht = new Hashtable<>();

        for(int i = 0; i < arr1.length; ++i){
            ht.put(arr1[i], arr1[i]);

        }
        for(int i = 0; i < arr2.length; ++i){
            if(ht.containsKey(arr2[i])){
                System.out.println("True");
            }else{
                System.out.println("False");
            }
        }
    }

    public static void main(String[] args) {
        // Test code
        int[] arr1 = {1, 3, 5, 7, 9};
        int[] arr2 = {1, 2, 3, 4, 5};
        solution(arr1, arr2);
    }
}

  Note) 실행 결과

 

 

  Ex 2) Hash Table 

    - 정수형 배열 numbers가 주어졌을 때, target을 구할 수 있는지 확인하는 프로그램 작성하라

    - target에서 뺀 값을 key로 넣는다. (나중에 if문에서 containsKey로 비교하기 위해)

<hide/>
// Practice2
// 정수형 배열 nums 와 target 이 주어졌을 때,
// nums 에서 임의의 두 수를 더해 target 을 구할 수 있는지 확인하는 프로그램을 작성하세요.
// 두 수 의 합으로 target 을 구할 수 있으면 해당 값의 index 를 반환하고,
// 없는 경우 null 을 반환하세요.

// 입출력 예시
// nums: 7, 11, 5, 3
// target: 10
// 출력: 0, 3

// nums: 8, 3, -2
// target: 6
// 출력: 0, 2


import java.util.Arrays;
import java.util.Hashtable;

public class Practice2 {
    public static int[] solution(int[] numbers, int target) {
            int[] result = new int[2];
            int idx = 0;
            Hashtable<Integer, Integer> ht = new Hashtable<>();

            for(int i = 0; i < numbers.length; ++i) {
                if (ht.containsKey(numbers[i])) {
                    result[0] = ht.get(numbers[i]);
                    result[1] = i;
                    return result;
                }
                ht.put(target - numbers[i], i);
            }
        return null;
    }

    public static void main(String[] args) {
        // Test code
        int[] nums = {7, 11, 5, 3};
        System.out.println(Arrays.toString(solution(nums, 10)));

        nums = new int[]{8, 3, -2};
        System.out.println(Arrays.toString(solution(nums, 6)));

        nums = new int[]{1, 2, 3};
        System.out.println(Arrays.toString(solution(nums, 12)));
    }
}

  Note) 실행 결과

 

 

  Ex 3) HashTable과 HashMap 비교하기

    - 공통점: 둘 다 map인터페이스를 구현한 것이다.

    - 차이점: key에 null 사용 여부(hashmap: 가능, hashtable: 불가능)

      -> Thread safe(스레드 세이프): hashtable(o) - 멀티 스레드 환경에서 우수하다, hashmap(x) - 싱글스레드 환경에서 우수하다 .

      Def) Thread safe(스레드 세이프): 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수 혹은 객체가 여러 스레드로부터 동시에 접근이 이뤄져도 프로그램의 실행에 문제가 없다.

      ex) SyncronizedMap, ConcurrentHashMap -> 스레드 세이프한 HashMap으로 두 가지의 장점을 모두 가진다.

<hide/>
// Practice3
// 참고 - Hashtable? HashMap?

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class Practice3 {
    public static void main(String[] args) {
        //hashtable
        Hashtable<Integer, Integer> ht = new Hashtable();
        ht.put(0, 10);
        System.out.println(ht.get(0));

        // hashmap
        HashMap<Integer, Integer> hm = new HashMap();
        hm.put(0, 10);
        System.out.println(hm.get(0));

        Map<Integer, Integer> map1 = ht;
        Map<Integer, Integer> map2 = hm;
        System.out.println(map1.get(0));
        System.out.println(map2.get(0));

//      ht.put(null, -999);
//      System.out.println(ht.get(null));   // hashtable에는 null을 넣을 수 없다.

        hm.put(null, -999);
        System.out.println(hm.get(null));
    }
}

  Note) 실행 결과

 

 

2.14 선형 자료구조 연습문제 풀이

 

  Ex 1) modification함수를 이용해서 배열 내 데이터 순서를 변경한다. modification 함수를 되돌리는 코드를 작성하라.

<hide/>
import java.util.Arrays;

public class Practice1 {

    public static int[] solution(int[] arr) {
        int[] arrNew = new int[arr.length];
        int index = 0;
        for (int i = 0; i < arr.length; ++i) {
            for (int j = 0; j < arr.length; ++j) {
                if (arrNew[index] == 0) {
                    break;
                }
                index = (index + 1) % arr.length;   // 인덱스를 오른쪽으로 하나씩 이동
            }
            arrNew[index] = arr[i];
            index = (index + arr[i]) % arr.length;
        }
        return arrNew;
    }

    public static int[] modification(int[] arr) {
        int[] arrNew = new int[arr.length];

        int idx = 0;
        int cnt = 0;
        int val = arr[idx];

        while (cnt < arr.length) {
            while (val == 0) {
                idx = (idx + 1) % arr.length;
                val = arr[idx];
            }
            arrNew[cnt++] = val;
            arr[idx] = 0;
            idx = (val + idx) % arr.length;
            val = arr[idx];
        }
        return arrNew;
    }


    public static void main(String[] args) {
        // Test code
        // Modification test
        int[] arr = {1, 3, 7, 9, 5};
        int[] arrNew = modification(arr);
        System.out.println(Arrays.toString(arrNew));

        // Revert data
        arr = new int[]{1, 3, 5, 7, 9};
        int[] arrOrigin = solution(arr);
        System.out.println(Arrays.toString(arrOrigin));

        arr = new int[]{3, 2, 6, 8};
        arrOrigin = solution(arr);
        System.out.println(Arrays.toString(arrOrigin));
    }
}

  Note) 실행 결과

 

 

  Ex 2) 정수로 주어진 M * N 행렬에 대해 행렬의 원소 중에서 0이 있을 경우, 해당 원소가 위치하는 행과 열 전체 데이터를 0으로 작성하라.

 

  Sol)

    - 행렬 값이 0인 데이터에 대해 각 열과 행에 대해 boolean 값(fczero, frzero)를 true로 저장한다.

    - 1행 ~ 마지막 행 , 1열~ 마지막 열을 먼저 for문으로 0으로 바꾸는 작업을 한다.

    - 그 다음 0행, 0열은 firstRowZero/ firstColumnZero 를 이용해서 작업하면 된다. 

<hide/>
import java.util.Arrays;

public class Practice2 {

    public static void solution(int[][] matrix) {
        // fisrtColumnZero의 약자

        boolean fcZero = false;
        boolean frZero = false;

        for(int i = 0; i < matrix.length; ++i){
            for(int j = 0; j < matrix[i].length; ++j){
                if(matrix[i][j] == 0){
                    if(i == 0){         // 0행에 하나라도 0이 있으면
                        frZero = true;  // 2번 케이스의 경우 둘 다 트루
                    }
                    if(j == 0){         // 0열에 하나라도 0이 있으면
                        fcZero = true;  // 1번 케이스는 둘 다 거짓
                     }
                    matrix[i][0] = 0;   // 인덱스 위치에 0으로 표시를 해준다.
                    matrix[0][j] = 0;
                }
            }
        }

        // 0행이나 0열에 (주변 애들을 바꿀 수 있는) 0이 있는 경우, 해당 줄을 모두 0으로 바꾼다.
        for(int i = 1; i < matrix.length;++i){
            for(int j = 1; j< matrix[i].length; ++j){
                if(matrix[i][0] == 0 || matrix[0][j] == 0){
                        matrix[i][j] = 0;
                }
            }
        }
        // 0번째 행과 0번째 열은 앞으로 데이터를 놔둘건지, 0으로 모두 바꿀 것인지 정보가 있다.
        // 0열의 행렬값을 모두 0으로 만든다.
        if(fcZero){
            for(int i = 0; i < matrix.length; ++i){
                matrix[i][0] = 0;
            }
        }

        // 0행의 행렬값을 모두 0으로 만든다.
        if(frZero){
            for(int j = 0; j < matrix[0].length; ++j){
                matrix[0][j] = 0;
            }
        }
        // 출력
        for (int i = 0; i < matrix.length; ++i) {
                for (int j = 0; j < matrix[0].length; ++j) {

                    System.out.print(matrix[i][j] + " ");
                }
                System.out.println();
            }
    }

    public static void main(String[] args) {
        // Test code
        int[][] matrix = {{1, 1, 1},
                          {1, 0, 1},
                          {1, 1, 1}};
        solution(matrix);

        System.out.println();
        matrix = new int[][]{{1, 1, 0}, {1, 1, 1}, {0, 1, 1}};
        solution(matrix);
    }
}

 

  MySol) 만약 0이 같은 행에 두 개 있거나 같은 열에 두 개 넘게 있는 경우에는 오류가 생길지도 모르겠다.. 확인할 부분

<hide/>
import java.util.Arrays;

public class Practice2 {

    public static void solution(int[][] matrix) {
        boolean[][] visited = new boolean[matrix.length][matrix.length];


        for(int i = 0; i < matrix.length; ++i) {
            for (int j = 0; j < matrix[0].length; ++j) {
                if (matrix[i][j] == 0 && visited[i][j] == false) {
                    visited[i][j] = true;
                    for (int x = i; x < i + 1; ++x) {
                        for (int y = 0; y < matrix[0].length; ++y) {
                            visited[x][y] = true;
                            matrix[x][y] = 0;
                        }
                    }
                    for (int x = 0; x < matrix.length; ++x) {
                        for (int y = j; y < j + 1; ++y) {
                            visited[x][y] = true;
                            matrix[x][y] = 0;
                        }
                    }
                }
            }
        }

            for (int i = 0; i < matrix.length; ++i) {
                for (int j = 0; j < matrix[0].length; ++j) {

                    System.out.print(matrix[i][j] + " ");
                }
                System.out.println();
            }
    }

    public static void main(String[] args) {
        // Test code
        int[][] matrix = {{1, 1, 1}, {1, 0, 1}, {1, 1, 1}};
        solution(matrix);

        System.out.println();
        matrix = new int[][]{{1, 1, 0}, {1, 1, 1}, {0, 1, 1}};
        solution(matrix);
    }
}

 

  Note) 실행 결과

 

 

  Ex 3) 1~N번 까지 N개의 풍선이 원형으로 놓여 있고 i번 풍선의 오른쪽에는 i + 1번 풍선이 있고 왼쪽에는i -1번 풍선이 있다. 

    - 처음에는 1번 풍선을 터뜨리고 풍선안에 있는 종이를 꺼내서 그 종이에 적힌 값만큼 이동하여 다음 풍선을 터뜨린다. 

    - 이미 터뜨린 풍선은 제외하고 이동한다.

    - 풍선이 터지는 순서대로 인덱스 출력

<hide/>

class Node{
    int data;
    int cmd;    // 어디로 이동할 지

    boolean visited;
    Node next;
    Node prev;

    public Node(int data, int cmd, boolean visited, Node next, Node prev) {
        this.data = data;
        this.cmd = cmd;
        this.visited = visited;
        this.next = next;
        this.prev = prev;
    }
}
// 원형 연결 리스트
class LinkedListC{
    Node head;

    public void add(int data, int cmd){
        if(this.head == null){
            this.head = new Node(data, cmd, false, null, null );
            this.head.next = this.head;
            this.head.prev = this.head;

        }else{
           Node cur = this.head;
           while(cur.next != this.head){
               cur = cur.next;
           }
           cur.next = new Node(data, cmd, false, cur.next, cur);
           this.head.prev = cur.next;
        }
    }
}


public class Practice3 {
    public static void solution(int[] data) {
        LinkedListC linkedList =  new LinkedListC();
        for(int i = 0; i < data.length ; ++i ){
            linkedList.add(i + 1, data[i]);
        }
        Node cur = linkedList.head;
        int visitCnt = 0;   // 몇개가 터졌는지
        int cmd = 0;

       while(visitCnt != data.length){     // (첫 번째, 두 번째, .. , 다섯 번째 연산)
            int cnt = 0;
            while( cnt != Math.abs(cmd)){   // cmd의 절댓값 만큼 이동한다.
                if(cmd > 0){
                    cur = cur.next;         // 이동 명령(배열의 원소)이 양수일 때, 우측 이동
                }else{
                    cur = cur.prev;
                }
                if(cur.visited == false) {  // 방문하지 않은 곳만 
                    ++cnt;                  // n번째 연산에서 몇 칸 이동했는지 센다.
                }
            }   
            // cnt == |cmd|가 됐을 때
            System.out.println(cur.data + " ");    // 방문한 곳의 data
            cur.visited = true;
            ++visitCnt;
            cmd = cur.next.cmd;
        }
    }

    public static void main(String[] args) {
        int[] balloon = {3, 2, 1, -3, -1};
        solution(balloon);

        System.out.println();
        balloon = new int[]{3, 2, -1, -2};
        solution(balloon);
    }
}

  Note) 실행 결과

 

 

  Ex 4) 괄호 짝 검사, 세 종류의 괄호가 있다.

    - 여는 괄호의 순서에 맞춰 닫는 괄호의 순서도 맞아야한다.

    - 스택 하나만 가지고 풀 수 있다.

    - stack과 map의 성질을 함께 이동한다.

<hide/>
import java.util.HashMap;
import java.util.Stack;

public class Practice4 {
    public static void solution(String str) {
        Stack<String> stack = new Stack<>();
        boolean checkFlag = true;
        HashMap<String, String> map = new HashMap<>();

        map.put("(", ")");
        map.put("{", "}");
        map.put("[", "]");

        for(String s : str.split("")){
            if(map.containsKey(s)){         // 앞 괄호가 있으면
                stack.push(s);
            }else if(map.containsValue(s)){     // 뒷 괄호가 있으면
                if (stack.isEmpty()) {
                    checkFlag = false;
                    break;
                }else{
                    String cur = stack.pop();
                    if(!s.equals(map.get(cur))){
                        checkFlag = false;
                        break;
                    }
                }
            }
        }
          if( checkFlag && stack.isEmpty()){
                System.out.println("PASS");
                return;
          }
        System.out.println("FAIL");
 }

    public static void main(String[] args) {
        // Test code
        solution("[yyyy]-[mm]-[dd]");               // Pass
        solution("System.out.println(arr[0][1))");  // FAIL
        solution("Support [Java or Python(3.x)]");  // PASS
        solution("([[{}])");                        // FAIL
    }
}

  Note) 실행 결과

 

 

  Ex 5) Dummy - 도스게임

<hide/>
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class Practice5 {
    public static Integer solution(int n, int k, int l, ArrayList<ArrayList> apples, Queue<ArrayList> moves) {

        int[][] board =  new int[n + 1][n + 1];
        // 사과 정보를 업데이트
        for(ArrayList item: apples){
              board[(int)item.get(0)][(int)item.get(1)] = 1;
        }

        // 스네이크의 초기 위치값
        Queue<ArrayList> snake = new LinkedList<>();
        snake.add(new ArrayList(Arrays.asList(1, 1)));  // 객체 생성을 먼저 하지않고 일단 add에 넣는다.

        // list로 된 list direcion에 네 가지 방향을 넣는다.
        // 일단 처음에는 +0행, +1열 만큼씩, 즉 오른쪽으로 이동한다.
        // 나중에 나오는 didx를 연산하기위해서 아래 방향리스트 네개의 순서를 고정시킨다.
        ArrayList<ArrayList> direction = new ArrayList<>();
        direction.add(new ArrayList(Arrays.asList(0, 1)));
        direction.add(new ArrayList(Arrays.asList(1, 0)));
        direction.add(new ArrayList(Arrays.asList(0, -1)));
        direction.add(new ArrayList(Arrays.asList(-1, 0)));

        int dIdx = 0;
        int score = 0;
        int x = 1;  // 출발하는 위치
        int y = 1;

        // score 회 만큼 while문을 실행하는 건가???
        while(true){    // 스네이크 정보 업데이트
            score += 1; // 시간 체크    //????????

            // direction 리스트 중에서 didx 번째 리스트의 행과 열 => x, y
            x += (int) direction.get(dIdx).get(0);  // 행
            y += (int) direction.get(dIdx).get(1);  // 열

            if(1 <= x && x <= n && 1 <= y && y <= n){   // 벽에 부딪히지 않는 범위 내
                ArrayList cur = new ArrayList(Arrays.asList(x, y));     // 현재 위치 정보
                if(snake.contains(cur)){    // 현재 이동한 좌표가 Queue에도 있다는 건 이미 몸체라는 뜻 => 게임 종료
                    return score;
                }
                snake.add(cur);         // 새로운 위치를 queue에 더하기
                if(board[x][y] == 0){   // 사과 없을 때
                    snake.poll();       // 위에서 add한 cur을 poll()한다.
                }else {                 // 사과 있을 때
                    board[x][y] = 0;    // 사과 없어진다.
                }
            }else{          // 벽에 부딪혔을 때
                return score;
            }

            // moves 큐에 원소가 있으면 계속 실행
            if(moves.size() > 0  &&  score == (int)moves.peek().get(0)) {
                // while문에서 score를 1씩 더하다가 moves의 0열 값이랑 같은 경우에만 실행한다.

                if((char) moves.peek().get(1) == 'D') { //방향 바꿔준다.
                    dIdx = (dIdx + 1) % 4;  //오른쪽 90도로 움직인다.
                    moves.poll();
                }else if((char) moves.peek().get(1) == 'L'){ // 왼쪽인 경우
                    dIdx = (dIdx - 1) % 4;  //
                    moves.poll();
                }
            }

        }// while문 종료
    }

    public static void main(String[] args) {
        // Test code
        int n = 6;
        int k = 3;
        int l = 3;
        ArrayList<ArrayList> apples = new ArrayList();
        apples.add(new ArrayList<>(Arrays.asList(3, 4)));
        apples.add(new ArrayList<>(Arrays.asList(2, 5)));
        apples.add(new ArrayList<>(Arrays.asList(5, 3)));

        Queue<ArrayList> moves = new LinkedList();
        moves.add(new ArrayList(Arrays.asList(3, 'D')));
        moves.add(new ArrayList(Arrays.asList(15, 'L')));
        moves.add(new ArrayList(Arrays.asList(7, 'D')));
        System.out.println((solution(n, k, l, apples, moves)));

        n = 10;
        k = 4;
        l = 4;
        apples.clear();
        apples.add(new ArrayList<>(Arrays.asList(1, 2)));
        apples.add(new ArrayList<>(Arrays.asList(1, 3)));
        apples.add(new ArrayList<>(Arrays.asList(1, 4)));
        apples.add(new ArrayList<>(Arrays.asList(1, 5)));

        moves.clear();
        moves.add(new ArrayList(Arrays.asList(8, 'D')));
        moves.add(new ArrayList(Arrays.asList(10, 'D')));
        moves.add(new ArrayList(Arrays.asList(11, 'D')));
        moves.add(new ArrayList(Arrays.asList(13, 'L')));
        System.out.println((solution(n, k, l, apples, moves)));

        n = 10;
        k = 5;
        l = 4;
        apples.clear();
        apples.add(new ArrayList<>(Arrays.asList(1, 5)));
        apples.add(new ArrayList<>(Arrays.asList(1, 3)));
        apples.add(new ArrayList<>(Arrays.asList(1, 2)));
        apples.add(new ArrayList<>(Arrays.asList(1, 6)));
        apples.add(new ArrayList<>(Arrays.asList(1, 7)));

        moves.clear();
        moves.add(new ArrayList(Arrays.asList(8, 'D')));
        moves.add(new ArrayList(Arrays.asList(10, 'D')));
        moves.add(new ArrayList(Arrays.asList(11, 'D')));
        moves.add(new ArrayList(Arrays.asList(13, 'L')));
        System.out.println((solution(n, k, l, apples, moves)));
    }
}

  Note) 실행 결과

 

 

  Ex 6) 프린터 중요도 인쇄

<hide/>

import java.util.LinkedList;
import java.util.Queue;

class Doc{
    int no;
    int priority;
    public Doc(int no, int priority){
        this.no = no;
        this.priority = priority;
    }
}

public class Practice1 {
    public static void solution(int docs, int target, int[] priority) {
        Queue<Doc> queue = new LinkedList<>();
		
		// 자바에 내장된 게 아닌 직접 만든 클래스이므로 Doc객체를 하나씩 만들어서 Queue에 넣어야한다.
        for(int i = 0;i < docs ; ++i){
            queue.add(new Doc(i, priority[i])); // 1 1 9 1 1 1
            // Doc(인덱스, 우선순위)
        }
        int cnt = 1;

        while(true){
            Doc cur = queue.poll();
            Doc[] highP = queue.stream().filter(x -> x.priority > cur.priority).toArray(Doc[]::new);
            System.out.printf("cur.no : %d  cur.priority: %d %n", cur.no, cur.priority);
            
            if(highP.length > 0){   // 현재 문서 보다 우선순위 높은 게 뒤에 있으니 poll해서 뒤로 보낸다.
                queue.add(cur);

            }else{  // cur이 최고 우선순위일 때

                System.out.printf("target: %d cur.no: %d  cur.priority: %d %n", target, cur.no, cur.priority);
                if(target == cur.no){   // 출력대상 번호가 현재 문서의 번호일 때
                    System.out.println("result cnt : " + cnt);
                    break;
                }
                ++cnt;  
                // cur이 최고 우선순위이지만 출력하려는 target이 아닐 때
                // cur을 인쇄하므로 cnt증가시키고 다음 순서로 넘어간다.
                System.out.println("cnt: " + cnt );
            }
        }
    }

    public static void main(String[] args) {
        // Test code
        int docs = 1;
        int target = 0;
        int[] priority = {5};
        solution(docs, target, priority);

        docs = 4;
        target = 2;
        priority = new int[]{1, 2, 3, 4};
        solution(docs, target, priority);

        docs = 6;
        target = 0;
        priority = new int[]{1, 1, 9, 1, 1, 1};
        solution(docs, target, priority);
    }
}

  Note) 실행 결과

 

 

  Ex 7) 스택 수열 만들기

    - 1~n까지의 숫자를 (오름차순으로) 스택에 넣었다가 뽑아 늘어놓음으로써 하나의 수열을 만든다. 

    - 임의의 수열이 주어졌을 때, 스택을 이용해 그 수열을 만들 수 있는지 없는지

    - 원하는 수가 나왔을 때, push를 한 다음에 뺀다.

<hide/>
import java.util.ArrayList;
import java.util.Stack;

public class Practice2 {
    public static void solution(int[] nums) {

        Stack<Integer> stack = new Stack<>();
        ArrayList<String> result = new ArrayList<>();

        int idx = 0;
        for(int i = 0; i < nums.length; ++i){
            int num = nums[i];

            if(num > idx){
                for(int j = idx + 1; j < num + 1; ++j){
                    stack.push(j);
                    result.add("+");
                }
                idx = num;

            }else if(stack.peek() != num){
                System.out.println("NO");
                return;
            }
            stack.pop();
            result.add("-");
        }
        for(String item: result){
            System.out.println(item);
        }

    }

    public static void main(String[] args) {
        int[] nums = {4, 3, 6, 8, 7, 5, 2, 1};
        solution(nums);

        System.out.println("=====");
        nums = new int[]{1, 2, 5, 3, 4};
        solution(nums);
    }
}

  Note) 실행 결과

 

 

  Ex 8) 베스트 앨범 출시

<hide/>
import java.sql.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Map;

class Song{
    int no;
    int play;
    
    public Song(int no, int play){
        this.no = no;
        this.play = play;
    }
}

public class Practice3 {
    public static void solution(String[] genres, int[] plays) {
        Hashtable<String, ArrayList<Song>> ht = new Hashtable<>();

        for(int i = 0; i < genres.length;++i){
            if(ht.containsKey(genres[i])){
                ArrayList<Song> cur = ht.get(genres[i]);
                int idx = -1;

                for(int j = 0; j < cur.size(); ++j){
                    if(plays[i] > cur.get(j).play ||
                       plays[i] == cur.get(j).play && i < cur.get(j).no){
                        idx = j;    // 초기화
                        break;
                    }
                }

                if(idx == -1){// 재생횟수가 작을 때
                    cur.add(new Song(i, plays[i])); // 리스트의 가장 뒤에 붙인다.
                }else{
                    cur.add(idx, new Song(i, plays[i]));    // 리스트의 idx 위치에 붙인다.
                }
                ht.put(genres[i], cur);
            }else{  // 해당 키가 해시테이블에 없는 경우
                Song s = new Song(i, plays[i]);
                ht.put(genres[i], new ArrayList<>(Arrays.asList(s)));
            }
        }

        Hashtable<String, Integer> htPlay = new Hashtable<>();
        for(String item : ht.keySet()){

            int sum = 0;
            ArrayList<Song> cur = ht.get(item);
            for(int i = 0; i < cur.size(); ++i){

                sum += cur.get(i).play;
            }
            htPlay.put(item, sum);
        }
        ArrayList<Map.Entry<String, Integer>> htPlaySort = new ArrayList<>(htPlay.entrySet());
        // 우변의 value들이 좌변에 저장..
        htPlaySort.sort((x1, x2) -> (x2.getValue() - x1.getValue()));

        for(Map.Entry<String, Integer> item: htPlaySort){

            ArrayList<Song> songs = ht.get(item.getKey());  //

            for(int i = 0; i < songs.size(); ++i){
                System.out.print(songs.get(i).no + " ");
                if(i == 1){
                    break;
                }
            }
        }
        System.out.println();
    }
    
    public static void main(String[] args) {
        // Test code
        String[] genres = {"classic", "pop", "classic", "classic", "pop"};
        int[] plays = {500, 600, 150, 800, 2500};
        solution(genres, plays);
    }
}

  Note) 실행 결과: 4 1 3 0

 

 

  Ex 9) 완주하지 못한 선수

    - 동명이인 있는 경우가 있다.

<hide/>

import java.util.Arrays;
import java.util.Hashtable;

public class Practice4 {
    public static String solution(String[] participant, String[] completion) {
        String answer = "";
        Hashtable<String, Integer> ht = new Hashtable<>();

        for(String item: participant){
            if(ht.containsKey(item)){
                ht.put(item, ht.get(item) + 1);
            }else{
                ht.put(item, 1);
            }
        }
        for(String item : completion){
            ht.put(item, ht.get(item) - 1);
        }

        for(String item : participant){
            if(ht.get(item) > 0){
                answer = item;
                break;
            }
        }
        return answer;
    }

    public static void main(String[] args) {
        // Test code
        String[] participant = {"leo", "kiki", "eden"};
        String[] completion = {"eden", "kiki"};
        System.out.println(solution(participant, completion));

        participant = new String[]{"marina", "josipa", "nikola", "vinko", "filipa"};
        completion = new String[]{"josipa", "filipa", "marina", "nikola"};
        System.out.println(solution(participant, completion));

        participant = new String[]{"mislav", "stanko", "mislav", "ana"};
        completion = new String[]{"stanko", "ana", "mislav"};
        System.out.println(solution(participant, completion));
    }
}

  Note) 실행 결과

 

 

  Ex 10) 어피치 진열된 모든 종류의 보석을 적어도 1개 이상 포함하는 가장 짧은 구간을 찾아서 구매

<hide/>

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.stream.Stream;

public class Practice5 {
    public static ArrayList<Integer> solution(String[] gems) {

        ArrayList<ArrayList<Integer>> result = new ArrayList<>();

        HashSet<String> set = new HashSet<>();

        Stream.of(gems).forEach(x -> set.add(x));
        int n = set.size();

        if(n == 1){
            result.add(new ArrayList(Arrays.asList(1, 1)));
            return  result.get(0);
        }
        Hashtable<String, Integer> ht = new Hashtable();
        boolean isCandidatee  = false;

        int left  = 0;
        int right = 0;
        ht.put(gems[0], 1);

        while(true){
            if(isCandidatee == false){
                right += 1;
                if(right >= gems.length){
                    break;
                }
                if(ht.containsKey(gems[right]) == false){
                    ht.put(gems[right], 1);

                }else{
                    ht.put(gems[right], ht.get(gems[right]) + 1);
                }
                if(ht.size() == n){
                    isCandidatee = true;
                    result.add(new ArrayList<>(Arrays.asList(left + 1, right + 1)));
                }
            }else{  //
                left  += 1;
                int cnt = ht.get(gems[left - 1]) - 1;

                if(cnt == 0){
                    ht.remove(gems[left - 1]);  // 해당 위치에 보석을 뺀다.
                    isCandidatee = false;   // 다시 while문으로 들어가서

                }else{
                    ht.put(gems[left - 1], cnt);
                    result.add(new ArrayList<>(Arrays.asList(left + 1, right + 1)));
                }
            }
        }
        int minIdx = 0;
        int minNum = Integer.MAX_VALUE;
        for(int i = 0; i < result.size(); ++i){
            ArrayList<Integer> cur = result.get(i);
            left = cur.get(0);
            right = cur.get(1);

            if(right - left < minNum){
                minNum = right - left;
                minIdx = i;
            }
        }
        return result.get(minIdx);
    }

    public static void main(String[] args) {
        // Test code
        String[] gems = {"DIA", "RUBY", "RUBY", "DIA", "DIA", "EMERALD", "SAPPHIRE", "DIA"};
        System.out.println(solution(gems));

        gems = new String[]{"AA", "AB", "AC", "AA", "AC"};
        System.out.println(solution(gems));

        gems = new String[]{"XYZ", "XYZ", "XYZ"};
        System.out.println(solution(gems));

        gems = new String[]{"ZZZ", "YYY", "NNNN", "YYY", "BBB"};
        System.out.println(solution(gems));
    }
}

  Note) 실행 결과