0%

오큰수

문제

크기가 N인 수열 A = A1, A2, …, AN이 있다. 수열의 각 원소 Ai에 대해서 오큰수 NGE(i)를 구하려고 한다. Ai의 오큰수는 오른쪽에 있으면서 Ai보다 큰 수 중에서 가장 왼쪽에 있는 수를 의미한다. 그러한 수가 없는 경우에 오큰수는 -1이다.

예를 들어, A = [3, 5, 2, 7]인 경우 NGE(1) = 5, NGE(2) = 7, NGE(3) = 7, NGE(4) = -1이다. A = [9, 5, 4, 8]인 경우에는 NGE(1) = -1, NGE(2) = 8, NGE(3) = 8, NGE(4) = -1이다.

입력

첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째에 수열 A의 원소 A1, A2, …, AN (1 ≤ Ai ≤ 1,000,000)이 주어진다.

출력

총 N개의 수 NGE(1), NGE(2), …, NGE(N)을 공백으로 구분해 출력한다.

예제 입력 1

1
2
4
3 5 2 7

예제 출력 1

1
5 7 7 -1

예제 입력 2

1
2
4
9 5 4 8

예제 출력 2

1
-1 8 8 -1

풀이

스택을 이용하여 문제를 해결한다.

  • 오큰수를 찾지 못하면 해당 위치를 스택에 넣는다.
  • 스택 가장 위에 있는 위치의 A 와 현재 B 라는 수와 비교하여 B 가 클 경우, 스택에 있는 A의 위치를 pop 하고 A 위치에 B 라는 수를 넣는다.
  • 마지막에 스택에 남은 위치에는 큰 수가 없으므로 -1를 채운다.

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Stack;

public class Main {


public static void main(String[] args) throws Exception {
bigNumber();
}

public static void bigNumber() throws Exception {
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));


int n = Integer.parseInt(in.readLine());

String s = in.readLine();


String[] A = s.split(" ");
if (n != A.length) {
return;
}

int[] NGE = new int[n];
Stack<Integer> stack = new Stack<>();

stack.push(0);
for (int i = 1; i < n; i++) {
int a = Integer.parseInt(A[i]);
while (!stack.isEmpty() && Integer.parseInt(A[stack.peek()]) < a) {
NGE[stack.pop()] = a;
}
stack.push(i);
}

while (!stack.isEmpty()) {
NGE[stack.pop()] = -1;
}

for (int ans : NGE) {
out.write(ans + " ");
}
out.write("\n");
out.flush();
out.close();
in.close();
}
}

문제 사이트 : https://www.acmicpc.net/problem/17298

쇠막대기

문제

여러 개의 쇠막대기를 레이저로 절단하려고 한다. 효율적인 작업을 위해서 쇠막대기를 아래에서 위로 겹쳐 놓고, 레이저를 위에서 수직으로 발사하여 쇠막대기들을 자른다. 쇠막대기와 레이저의 배치는 다음 조건을 만족한다.

쇠막대기는 자신보다 긴 쇠막대기 위에만 놓일 수 있다. - 쇠막대기를 다른 쇠막대기 위에 놓는 경우 완전히 포함되도록 놓되, 끝점은 겹치지 않도록 놓는다.
각 쇠막대기를 자르는 레이저는 적어도 하나 존재한다.
레이저는 어떤 쇠막대기의 양 끝점과도 겹치지 않는다.
아래 그림은 위 조건을 만족하는 예를 보여준다. 수평으로 그려진 굵은 실선은 쇠막대기이고, 점은 레이저의 위치, 수직으로 그려진 점선 화살표는 레이저의 발사 방향이다.

이러한 레이저와 쇠막대기의 배치는 다음과 같이 괄호를 이용하여 왼쪽부터 순서대로 표현할 수 있다.

레이저는 여는 괄호와 닫는 괄호의 인접한 쌍 ‘( ) ’ 으로 표현된다. 또한, 모든 ‘( ) ’는 반드시 레이저를 표현한다.
쇠막대기의 왼쪽 끝은 여는 괄호 ‘ ( ’ 로, 오른쪽 끝은 닫힌 괄호 ‘) ’ 로 표현된다.
위 예의 괄호 표현은 그림 위에 주어져 있다.

쇠막대기는 레이저에 의해 몇 개의 조각으로 잘려지는데, 위 예에서 가장 위에 있는 두 개의 쇠막대기는 각각 3개와 2개의 조각으로 잘려지고, 이와 같은 방식으로 주어진 쇠막대기들은 총 17개의 조각으로 잘려진다.

쇠막대기와 레이저의 배치를 나타내는 괄호 표현이 주어졌을 때, 잘려진 쇠막대기 조각의 총 개수를 구하는 프로그램을 작성하시오.

입력

한 줄에 쇠막대기와 레이저의 배치를 나타내는 괄호 표현이 공백없이 주어진다. 괄호 문자의 개수는 최대 100,000이다.

출력

잘려진 조각의 총 개수를 나타내는 정수를 한 줄에 출력한다.

예제 입력 1

1
()(((()())(())()))(())

예제 출력 1

1
17

예제 입력 2

1
(((()(()()))(())()))(()())

예제 출력 2

1
24

풀이

스택을 이용하여 문제를 해결할 수 있다. () 은 레이저이고, ( 의 시작하여 ) 끝은 쇠막대기를 열고 닫는 괄호이며, 쇠막대기 사이에는 () 들이 있다.

ex) (((()())(())()))

  1. ( 가 열릴 때마다 스택에 담는다
  2. ) 가 닫히면 제일 가까이 있는 (를 꺼낸다.
  3. 만약 이전 괄호가 ) 일 경우 1개만 증가하고(쇠막대기 1개를 레이저가 3번 자르면 쇠막대기는 4조각으로 나눠지기 때문에…) 그렇지않으면 스택의 사이즈 만큼 증가한다 result += (before == ')') ? 1 :stack.size();
  4. 이를 계속 반복.

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;

public class Main {


public static void main(String[] args) throws Exception {
ironBar();
}

public static void ironBar() throws Exception {
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

String s = in.readLine();
int result = 0;
char before = ' ';
Stack<Character> stack = new Stack<>();
for (char ch : s.toCharArray()) {
switch (ch) {
case '(':
stack.push(ch);
break;
case ')':
stack.pop();
result += (before == ')') ? 1 :stack.size();
break;
}
before = ch;
}
out.write(result + "\n");
out.flush();
out.close();;
in.close();
}
}

문제 사이트 : https://www.acmicpc.net/problem/10799

단어뒤집기 2(Word Flipping 2)

문제

문자열 S가 주어졌을 때, 이 문자열에서 단어만 뒤집으려고 한다.

먼저, 문자열 S는 아래와과 같은 규칙을 지킨다.

알파벳 소문자(‘a’-‘z’), 숫자(‘0’-‘9’), 공백(‘ ‘), 특수 문자(‘<’, ‘>’)로만 이루어져 있다.
문자열의 시작과 끝은 공백이 아니다.
‘<’와 ‘>’가 문자열에 있는 경우 번갈아가면서 등장하며, ‘<’이 먼저 등장한다. 또, 두 문자의 개수는 같다.
태그는 ‘<’로 시작해서 ‘>’로 끝나는 길이가 3 이상인 부분 문자열이고, ‘<’와 ‘>’ 사이에는 알파벳 소문자와 공백만 있다. 단어는 알파벳 소문자와 숫자로 이루어진 부분 문자열이고, 연속하는 두 단어는 공백 하나로 구분한다. 태그는 단어가 아니며, 태그와 단어 사이에는 공백이 없다.

입력

첫째 줄에 문자열 S가 주어진다. S의 길이는 100,000 이하이다.

출력

첫째 줄에 문자열 S의 단어를 뒤집어서 출력한다.

예제 입력 1

1
baekjoon online judge

예제 출력 1

1
noojkeab enilno egduj

예제 입력 2

1
<open>tag<close>

예제 출력 2

1
<open>gat<close>

예제 입력 3

1
<ab cd>ef gh<ij kl>

예제 출력 3

1
<ab cd>fe hg<ij kl>

예제 입력 4

1
one1 two2 three3 4fourr 5five 6six

예제 출력 4

1
1eno 2owt 3eerht rruof4 evif5 xis6

예제 입력 5

1
<int><max>2147483647<long long><max>9223372036854775807

예제 출력 5

1
<int><max>7463847412<long long><max>7085774586302733229

예제 입력 6

1
<problem>17413<is hardest>problem ever<end>

예제 출력 6

1
<problem>31471<is hardest>melborp reve<end>

예제 입력 7

1
<   space   >space space space<    spa   c e>

예제 출력 7

1
<   space   >ecaps ecaps ecaps<    spa   c e>

풀이

  1. 스택을이용하여 문제를 해결해 나갈 수 있다.
  2. 괄호를 처리하기위해 tag를 스위칭 하여 true이면 스택에 담지않고 출력하고 false 이면 스택에 문자열을 쌓는다.

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;

public class Main {


public static void main(String[] args) throws Exception {
replace();
}

public static void replace() throws Exception {
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

String s = in.readLine();

boolean tag = false;
Stack<Character> stack = new Stack<>();

for (char ch : s.toCharArray()) {
switch (ch) {
case '<':
print(out, stack);
out.write(ch);
tag = true; // 괄호시작
break;
case '>':
out.write(ch);
tag = false; //괄호 종료
break;
default:
if (tag) {
out.write(ch);
} else if (ch == ' ') {
print(out, stack);
out.write(ch);
} else {
stack.push(ch);
}
break;
}
}
print(out, stack);
out.write('\n');
out.flush();;
out.close();
in.close();;
}

public static void print(BufferedWriter out, Stack<Character> stack) throws Exception {
while(!stack.isEmpty()) {
out.write(stack.pop());
}
}
}

문제 사이트 : https://www.acmicpc.net/problem/17413

클래스(Class)

클래스는 프로토타입 기반 객체 지향 패턴을 더 쉽게 사용할 수 있게 할 수 있는 대체제로 클래스 패턴 생성을 더 쉽고 간단하게 생성할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

//클래스 선언
class Person {

//constructor 생성자
constructor(name) {
this._name = name;
}

Hello() {
console.log('Hello! ' + this._name);
}
}

const me = new Person("Paik");
const friends = new Person("Lee");
me.Hello();
friends.Hello();

인스턴스 생성

new 연산자를 이용하여 클래스 인스턴트를 생성한다.

1
2
3
class Foo {}

const foo = new Foo(); //Foo는 Constructor

만약 new 를 사용하지 않고 Foo() 만 입력할 경우 TypeError가 발생한다.

1
2
3
class Foo {}

const foo = new Foo() // Uncaught TypeError: Class constructor Foo cannot be invoked without 'new'

constructor(생성자)

constructor는 인스턴트스를 생성하고 클래스 필드를 초기화 할 수 있는 메소드이다.

  • constructor는 클래스 내에 한 개만 존재할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
//클래스 선언
class Person {
//생성자
constructor(name) {
//this는 클래스가 생성할 인스턴스를 가리킴
//_name은 클래스 필드
this._name = name;
}

}

const me = new Person('Paik');
console.log(me);

constructor는 생략할 수 있고 class Foo {}와 같이 표현한 것과 동일하게 동작하고 빈 객체를 생성한다. 인스턴스에 프로퍼티를 추가하려면 인스턴스를 생성 한 후 프로퍼티를 동적으로 추가해야 한다.

1
2
3
4
5
6
7
8
9
10
class Foo {} 

const foo = new Foo();
console.log(foo); // Foo {}


//동적 프로퍼티 할당 및 초기화

foo.num = 1;
console.log(foo); //Foo {num: 1}

클래스 필드

Class Body에는 메서드만 선언 할 수 있다.

1
2
3
4
5
class Foo {
name = ''; //SyntaxError
constructor() { }

}

필드 선언과 초기화는 constructor 안에서 선언한 클래스 필드를 생성할 인스턴스를 가리키는 this에 바인딩한다. 그러면 클래스 필드는 클래스가 생성할 때 인스턴스의 프로퍼티가 되고, 인스턴스를 통해 클래스 외부에서 언제나 참조가 가능한다. 언제나 public

ES6는 private, public protected 키워드를 지원하지 않는다.

1
2
3
4
5
6
7

class Foo {
constructor(name) {
this.name = name;
}

}
1
2
3
4
5
6
7
8
9

class Foo {
constructor(name = '') {
this.name = name; //public class field
}
}

const foo = new Foo('Paik');
console.log(foo.name); //field는 클래스 외부에서 참조 할 수 있음

Class Field declarations proposal

  • Field declarations
  • Private field
  • Static public fields

이 코드는 최신브라우저(Chrome 72 이상), Node.js (버전 12 이상)에서 항상 작동

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class Person {
age = 1; // Field declaration
#height = 0; // Private field
static y = 20; // Static field;
static #sp = 30; // Static private field

change() {
this.#height = 10; //private 필드 참조
return this.#height;
}
}

const me = new Person();

console.log(me); // {age:10, #height: 0}

console.log(me.age);

console.log(me.y);

console.log(me.change());

getter, setter

getter

getter는 클래스 필드에 접근할 때 클래스 필드의 값을 가져온다. get 키워드를 사용할 수 있고 메서드 이름은 클래스 필드 이름처럼 사용할 수 있다. 즉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class Foo {
//생성자
constructor(arr = []) {
this._arr = arr;
}

get firstElem() {
return this._arr.length ? this._arr[0] : null;
}
}

const foo = new Foo([1,2,3,4,5]);

console.log(foo.firstElem); //get 키워드를 사용한 메서드를 불러올 때 필드명을 가져올 때처럼 사용

setter

setter는 클래스 필드에 해당 값을 할당 할 때 사용하고 set키워드를 사용한다. setter도 메서드 이름을 클래스 필드처럼 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo {
//생성자
constructor(arr = []) {
this._arr = arr;
}

get firstElem() {
return this._arr.length ? this._arr[0] : null;
}
set firstElem(elem) {
this._arr = [elem, ...this._arr]; //... this._arr은 this._arr을 개별 요소로 분리
}
}

const foo = new Foo([1,2,3,4,5]);

foo.firstElem = 100; //set 키워드를 사용한 메서드로 데이터를 할당 할 때 필드명을 사용.

console.log(foo.firstElem); //get 키워드를 사용한 메서드를 불러올 때 필드명을 가져올 때처럼 사용

정적 메서드 (static method)

정적 메서드를 정의 하려면 static 키워드를 사용하면 된다. 정적 메서드는 클래스의 인스턴스가 아닌 클래스 이름으로 호출한다. 그래서 클래스의 인스턴스를 생성하지 않아도 호출할 수 있다.

  • 정적 메서드는 this를 사용할 수 없으며, 정적 메서드 내부에서 클래스 인스턴스를 가리키는 것이 아니라 자기 자신을 기리킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person {
constructor(age) {
this._age = age;
}
static staticMethod() {
/**
정적 메서드는 this를 사용할 수 없으며, 정적 메서드 내부에서 클래스 인스턴스를 가리키는 것이 아니라 자기 자신을 기리킨다.
**/
return 'staticMethod age: ' + this._age;
}

prototypeMethod() {
return this._age
}
}
//정적 메서드는 클래스 이름을 호출
console.log(Person.staticMethod()); //staticMethod age: undefined

const me = new Person(34);

//정적 메서드는 인스턴스로 호출할 수 없다.
console.log(me.staticMethod()); //Uncaught TypeError: me.staticMethod is not a function

클래스도 function이고 기존 prototype 기반 패턴의 Syntatic suger일 뿐이다.

1
2
class Person { }
console.log(typeof Person); //function

[ES5로 표현]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

var Person = (function() {
//constructor
function Person(age) {
this._age = age;
}
Person.staticMethod = function() {
return 'staticMethod age: ' + this._age;
};

Person.prototype.prototypeMethod = function() {
return this._age;
}
return Person;
}());

var me = new Person(34);
console.log(me.prototypeMethod()); //34
console.log(Person.staticMethod()); //staticMethod age: undefined
console.log(me.staticMethod());//Uncaught TypeError: me.staticMethod is not a function
console.log(Person.prototype === me.__proto__); // true
console.log(Person.prototype.constructor === Person); //true

클래스 상속(Class Inheritance)

클래스 상속은 새롭게 정의할 클래스가 기존 클래스와 유사할 경우 상속을 통해 그대로 사용하고 다른 점만 구현할 수 있다. 즉 하위 클래스에서 상속받아 상위 클래스의 정보를 다시 재사용할 수 있는 것이다. 코드의 재사용할 경우 개발 비용을 줄일 수 있다.

extends 키워드

extends는 부모 클래스를 상속받는 자식클래스를 정의 할 때 사용한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//부모클래스 
class Parents {
constructor(name) {
this.name = name;
}
get getName() {
return this.name;
}
set setName(name) {
return this.name = name;
}
}

//자식 클래스 부모 클래스를 상속 받는다.
class Child extends Parents {

constructor(name, age) {
super(name); //부모클래스 생성자
this.age = age;
}

get getAge() {
return this.age;
}
set setAge(age) {
return this.age = age;
}
getInfo() {
return 'My name is ' + this.name + 'and age is ' + this.age;
}
}

const child = new Child('hello', 33);
console.log(child.getInfo()); //My name is helloand age is 33
child.setAge = 20;
console.log(child.getAge); 20
console.log(child.getInfo()); //My name is helloand age is 20
child.setName = 'World';
console.log(child.getName); //World

console.log(child instanceof Child); //true
console.log(child instanceof Parents); //trye

super 키워드

super 는 부모클래스를 참조하거나 생성자를 호출할 때 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Parents {
constructor(name) {
this.name = name;
}
get getName() {
return this.name;
}
set setName(name) {
return this.name = name;
}
}

//자식 클래스 부모 클래스를 상속 받는다.
class Child extends Parents {

constructor(name, age) {
super(name); //부모클래스 생성자
this.age = age;
}

get getAge() {
return this.age;
}
set setAge(age) {
return this.age = age;
}
getInfo() {
return 'My name is ' + super.getName + ' and age is ' + this.age; //super를 통해 getName 참조
}
}

const child = new Child('hello', 33);
console.log(child.getInfo()); //My name is hello and age is 33
child.setAge = 20;
console.log(child.getAge); 20
console.log(child.getInfo()); //My name is hello and age is 20
child.setName = 'World';
console.log(child.getName); //World

console.log(child instanceof Child); //true
console.log(child instanceof Parents); //trye

super 메서드는 자식 클래스의 생성자 내부에서 부모 클래스 생성자(슈퍼클래스)를 호출한다. 즉 부모 클래스의 인스턴스를 생성한다. 그래서 자식 클래스의 생성자에서 super()를 호출하지 않으면 this에 대한 참조 에러(ReferenceError)가 발생한다.

1
2
3
4
5
6
7
8
9
class Parents {}

class Child extends Parents {
constructor() {

}
}

const child = new Child(); //Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

자식 클래스의 생성자에 super()를 추가해야한다.

1
2
3
4
5
6
7
8
9
10

class Parents {}

class Child extends Parents {
constructor() {
super();
}
}

const child = new Child();

참조

https://poiemaweb.com/es6-class

https://jsdev.kr/t/es6/2944

Arrows

Arrows 함수는 function 키워드 대신 화살표(‘=>’) 를 사용하여 함수를 선언하는 축약형 함수이다. Arrows는 표현식 본문(Expression Bodies)와 상태 블럭 본문(Statement block bodies)를 지원한다.

  • 콜백 함수에서 사용하면 간결하게 표현이 가능.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//매개변수 지정 방법
() => {...} // 매개변수가 없는 경우
x => {...} // 매개변수가 한 개인 경우, 소괄호를 생략할 수 있다.
(x,y) => {...} //매개변수가 여러 개의 경우, 소괄호를 생략할 수 없다.

//함수 몸체의 지정 방법
x => { return x * x} //signle line block
x => x * x //함수의 몸체가 한줄구문이라면 중괄호를 생략할 수 있고 암묵적인 리턴이 된다.

() => { return { a:1 }; }
() -> ({a:1}) //위 표현과 동일하고 객체 반환시 소괄호를 사용한다.

//Multi line Block
() => {
const x = 10;
return x * x;
}

화살표 함수는 익명함수로만 사용할 수 있다. 화살표를 호출 하기 위해서는 함수 호출 식을 사용한다.

1
2
3
4
5
6
7
//ES6
var pow = function(x) { return x * x };
console.log(pow(10)); //100;

//ES6
const pow = x => x * x;
console.log(pow(10)); //100;

콜백함수로도 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
//ES5
var arr [1,2,3];
var pow = arr.map(function(x) {
return x * x;
});
console.log(pow); //[1, 4, 9]


//ES6
const arr2 = [1,2,3];
const pow2 arr2.map(x => x * x);
console.log(pow2); //[1, 4, 9]
1
2
3
4
5
6
7
var evens = [2, 4 ,6 ,8];


//Expression Bodies(표현식의 결과가 반환됨)
var odds = evens.map(v => v + 1); //[3, 5, 7, 9]
var nums = evens.map((v, i) => v + i); //[2, 5, 8, 11]
var pairs = evens.map(v => ({even: v, odd: v + 1 })); // {even: 2, odd: 3}

this

일반함수의 this

function 키워드로 생성한 일반함수와 화살표 함수의 가장 큰 차이는 this이다.

자바스크립트는 함추 호출 방식에 의해 this를 바인딩할 어떤 객체가 동적으로 결정된다. 즉 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정

화살표 함수의 this

화살표 함수는 함수를 선언 할 때 this에 바인딩할 객체가 정적으로 결정된다. 즉, 언제나 상위 스코프의 this를 가리킨다. 이를 Lexical this라고 한다.

1
2
3
4
5
6
7
8
9
10
11
function Prefixer(prefix) {
this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
//this는 상위 스코프인 prefixArray 메소드 내의 this를 가리킨다.
return arr.map(x => `${this.prefix} ${x}`);
}

const pre = new Prefixer('Hello');
console.log(pre.prefixArray(['Hong','Paik']));

화살표 함수는 call, apply, bind메소드를 사용하여 this를 변경할 수 없다.

1
2
3
4
5
6
7
8
window.x = 1;
const normal = function() {
return this.x;
}
const arrow = () => this.x;

console.log(normal.call({x: 10})); //10
console.log(arrow.call({x: 10})); //1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

//Statement bodies(내부 블럭을 실행만 함, 반환을 위해선 return을 명시)
nums.forEach(v => {
if (v % 5 === 0)
console.log(v);
});

//Loxical this
//출력결과 : Bob knows John, Brian

var bob = {
_name: "Bob",
_friends: ["John", "Brian"],
printFriends() {
this._friends.forEach(f => console.log(this._name + " knows " + f));
}
}

bob.printFriends();

const materials = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
console.log(materials.map(m => m.length));



const pow = (x) => x * x;
console.log(pow(10));

화살표 함수를 사용해서 안되는 경우

메서드

화살표 함수로 메소드를 정의하는 것은 피해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Bad Code
const person = {
name: 'Paik',
sayHello: () => console.log(`Hello ${this.name}`); // 상위 컨텍스트 전역객체인 window를 가리킴
}
person.sayHello(); //undefined

//Good

const person = {
name: 'Paik',
sayHello: function() {
console.log(`Hello ${this.name}`);
}
}

person.sayHello(); //Hello Paik

prototype

화살표로 정의된 메소드를 prototype에 할당할 경우 일반함수를 할당한다.

생성자 함수

생성자 함수는 prototype 프로퍼티를 가지며 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor를 사용한다. 그러나 화살표 함수는 prototype 프로퍼티를 가지고 있지 않다.

addEventListener 함수의 콜백 함수

addEventListener 함수의 콜백함수를 화살표로 정의 하면 this가 상위 컨택스트인 전역객체 window를 가리킨다. 그래서 function 키워드로 정의한 일반함수를 사용해야 하며, 이런 일반함수로 정의된 addEventListener 함수의 콜백 내부 this는 이벤트 리스너의 바인딩 요소를 가리킨다.

참조

https://poiemaweb.com/es6-arrow-function

https://jsdev.kr/t/es6/2944

[BaekJoon-1158] 요세푸스 문제(Josephus of problem)

문제

요세푸스 문제는 다음과 같다.

1번부터 N번까지 N명의 사람이 원을 이루면서 앉아있고, 양의 정수 K(≤ N)가 주어진다. 이제 순서대로 K번째 사람을 제거한다. 한 사람이 제거되면 남은 사람들로 이루어진 원을 따라 이 과정을 계속해 나간다. 이 과정은 N명의 사람이 모두 제거될 때까지 계속된다. 원에서 사람들이 제거되는 순서를 (N, K)-요세푸스 순열이라고 한다. 예를 들어 (7, 3)-요세푸스 순열은 <3, 6, 2, 7, 5, 1, 4>이다.

N과 K가 주어지면 (N, K)-요세푸스 순열을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N과 K가 빈 칸을 사이에 두고 순서대로 주어진다. (1 ≤ K ≤ N ≤ 5,000)

출력

예제와 같이 요세푸스 순열을 출력한다.

예제 입력 1

1
7 3

예제 출력 1

1
<3, 6, 2, 7, 5, 1, 4>

풀이

큐를 이용하여 문제를 해결 할 수 있다.

N 이 7 이고 K 가 3 일 경우

  1. K번 째가 되기 전 까지 맨 앞 데이터를 꺼내어(poll) 다시 queue에 맨 뒤에 삽입(offer) 한다.
  2. K 번째가 되었을 때 맨 앞 데이터를 꺼내어(poll) list에 담거나, 문자열에 담는다.

끝.

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;

public class Main {


public static void main(String[] args) throws Exception {
josephus();
}

public static void josephus() throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));

String[] input = in.readLine().split(" ");
int n = Integer.parseInt(input[0]);
int k = Integer.parseInt(input[1]);

Queue<Integer> queue = new LinkedList<>();
List<Integer> result = new ArrayList<>();

for (int i = 1; i <=n; i++) {
queue.offer(i);
}
int cnt = 1;
StringBuilder sb = new StringBuilder("<");
while (!queue.isEmpty()) {
if (k == cnt) {
cnt = 1;
sb.append(queue.poll());
if (!queue.isEmpty()) {
sb.append(", ");
}
} else {
cnt++;
queue.offer(queue.poll());
}
}
sb.append(">");
out.write(""+ sb.toString());
out.flush();
out.close();
in.close();
}
}

문제 사이트 : https://www.acmicpc.net/problem/1158

[BaekJoon-1406] 에디터(Editor)

문제

한 줄로 된 간단한 에디터를 구현하려고 한다. 이 편집기는 영어 소문자만을 기록할 수 있는 편집기로, 최대 600,000글자까지 입력할 수 있다.

이 편집기에는 ‘커서’라는 것이 있는데, 커서는 문장의 맨 앞(첫 번째 문자의 왼쪽), 문장의 맨 뒤(마지막 문자의 오른쪽), 또는 문장 중간 임의의 곳(모든 연속된 두 문자 사이)에 위치할 수 있다. 즉 길이가 L인 문자열이 현재 편집기에 입력되어 있으면, 커서가 위치할 수 있는 곳은 L+1가지 경우가 있다.

이 편집기가 지원하는 명령어는 다음과 같다.

L 커서를 왼쪽으로 한 칸 옮김 (커서가 문장의 맨 앞이면 무시됨)
D 커서를 오른쪽으로 한 칸 옮김 (커서가 문장의 맨 뒤이면 무시됨)
B 커서 왼쪽에 있는 문자를 삭제함 (커서가 문장의 맨 앞이면 무시됨) 삭제로 인해 커서는 한 칸 왼쪽으로 이동한 것처럼 나타나지만, 실제로 커서의 오른쪽에 있던 문자는 그대로임
P $ $라는 문자를 커서 왼쪽에 추가함

초기에 편집기에 입력되어 있는 문자열이 주어지고, 그 이후 입력한 명령어가 차례로 주어졌을 때, 모든 명령어를 수행하고 난 후 편집기에 입력되어 있는 문자열을 구하는 프로그램을 작성하시오. 단, 명령어가 수행되기 전에 커서는 문장의 맨 뒤에 위치하고 있다고 한다.

입력

첫째 줄에는 초기에 편집기에 입력되어 있는 문자열이 주어진다. 이 문자열은 길이가 N이고, 영어 소문자로만 이루어져 있으며, 길이는 100,000을 넘지 않는다. 둘째 줄에는 입력할 명령어의 개수를 나타내는 정수 M(1 ≤ M ≤ 500,000)이 주어진다. 셋째 줄부터 M개의 줄에 걸쳐 입력할 명령어가 순서대로 주어진다. 명령어는 위의 네 가지 중 하나의 형태로만 주어진다.

출력

첫째 줄에 모든 명령어를 수행하고 난 후 편집기에 입력되어 있는 문자열을 출력한다.

예제 입력 1

1
2
3
4
5
abcd
3
P x
L
P y

예제 출력 1

1
abcdyx

예제 입력 2

1
2
3
4
5
6
7
8
9
10
11
abc
9
L
L
L
L
L
P x
L
B
P y

예제 출력 2

1
yxabc

예제 입력 3

1
2
3
4
5
6
7
8
9
10
11
12
13
dmih
11
B
B
P x
L
B
B
B
P y
D
D
P z

예제 출력 3

1
yxz

풀이

커서(cursor : |)를 기준으로 왼쪽 스택(left) 오른쪽 스택(right)로 나누어서 문제를 해결할 수 있다.

스택을 2개 만들고 문자열을 left에 초기화로 넣고 문제를 해결해 나가면 된다.

  1. L 일 경우 왼쪽 스택 데이터를 Pop 하여 오른쪽 스택으로 Push 한다.
  2. D 일 경우 오른족 스택 데이터를 Pop 하여 왼쪽으로 움긴다.
  3. B 일 경우 왼쪽에 있는 스택 데이터를 하나 삭제(Pop)한다.
  4. P $ 일 경우 $를 스택 왼쪽에 추가(Push) 하면된다.

알고리즘 분류를 보면 연결리스트라고 되어 있는데 연결리스트를 통해 문제를 풀면 시간초과로 Fail이 되버린다.

<연결리스트 소스코드 시간초과>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;

public class Main {

public static void main(String[] args) throws Exception {
editorLinkedList();
}

public static void editorLinkedList() throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));


String read = in.readLine();
if (read.length() > 100000) {
return;
}
int m = Integer.parseInt(in.readLine());

if (m < 1 || m > 500000) {
return;
}

List<Character> list = new LinkedList<>();
for (char ch : read.toCharArray())
list.add(ch);

int idx = list.size();
for (int i = 0; i < m; i++) {
String input = in.readLine();
char[] chs = input.toCharArray();
switch (chs[0]) {
case 'L':
idx = (idx == 0) ? 0 : --idx;
break;
case 'D':
idx = (idx == list.size()) ? idx : ++idx;
break;
case 'B':
if (idx != 0)
list.remove(idx- 1);
idx = (idx == 0) ? 0 : --idx;
break;
case 'P':
list.add(idx++, chs[2]);
break;
}
}

for (char ch : list) {
out.write(ch + "");
}

out.flush();

out.close();
in.close();
}
}

최종 소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.*;

public class Main {

public static void main(String[] args) throws Exception {
editor();
}

public static void editor() throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));


String read = in.readLine();
if (read.length() > 100000) {
return;
}
int m = Integer.parseInt(in.readLine());

if (m < 1 || m > 500000) {
return;
}

Stack<Character> left = new Stack<>();
Stack<Character> right = new Stack<>();
for (char ch : read.toCharArray())
left.push(ch);

for (int i = 0; i < m; i++) {
String input = in.readLine();
switch (input.charAt(0)) {
case 'L':
if (!left.isEmpty())
right.push(left.pop());
break;
case 'D':
if (!right.isEmpty())
left.push(right.pop());
break;
case 'B':
if (!left.isEmpty())
left.pop();
break;
case 'P':
left.push(input.charAt(2));
break;

}
}

while (!left.isEmpty()) {
right.push(left.pop());
}
while (!right.isEmpty()) {
out.write("" + right.pop());
}
out.flush();

out.close();
in.close();
}
}

문제 사이트 : https://www.acmicpc.net/problem/1406

[BaekJoon-1874] 스택수열(Stack Sequence)

문제

스택 (stack)은 기본적인 자료구조 중 하나로, 컴퓨터 프로그램을 작성할 때 자주 이용되는 개념이다. 스택은 자료를 넣는 (push) 입구와 자료를 뽑는 (pop) 입구가 같아 제일 나중에 들어간 자료가 제일 먼저 나오는 (LIFO, Last in First out) 특성을 가지고 있다.

1부터 n까지의 수를 스택에 넣었다가 뽑아 늘어놓음으로써, 하나의 수열을 만들 수 있다. 이때, 스택에 push하는 순서는 반드시 오름차순을 지키도록 한다고 하자. 임의의 수열이 주어졌을 때 스택을 이용해 그 수열을 만들 수 있는지 없는지, 있다면 어떤 순서로 push와 pop 연산을 수행해야 하는지를 알아낼 수 있다. 이를 계산하는 프로그램을 작성하라.

입력

첫 줄에 n (1 ≤ n ≤ 100,000)이 주어진다. 둘째 줄부터 n개의 줄에는 수열을 이루는 1이상 n이하의 정수가 하나씩 순서대로 주어진다. 물론 같은 정수가 두 번 나오는 일은 없다.

출력

입력된 수열을 만들기 위해 필요한 연산을 한 줄에 한 개씩 출력한다. push연산은 +로, pop 연산은 -로 표현하도록 한다. 불가능한 경우 NO를 출력한다.

예제 입력 1

1
2
3
4
5
6
7
8
9
8
4
3
6
8
7
5
2
1

예제 출력 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+
+
+
+
-
-
+
+
-
+
+
-
-
-
-
-

예제 입력 2

1
2
3
4
5
6
5
1
2
5
3
4

예제 출력 2

1
NO

힌트

1부터 n까지에 수에 대해 차례로 [push, push, push, push, pop, pop, push, push, pop, push, push, pop, pop, pop, pop, pop] 연산을 수행하면 수열 [4, 3, 6, 8, 7, 5, 2, 1]을 얻을 수 있다.

풀이

  • 1부터 N까지의 수를 스택에 넣었다가 뽑음으로 하나의 수열을 만듬
  • push 순서 오르차순
  • 임의 수열이 주어졌을 때 풀 수 있는지 없는지 있을 경우 Push (+) pop(-) // 풀수없을 경우 NO 출력

예제입력 seq = [4, 3, 6, 8, 7, 5, 2, 1] 가 주어 졌을 때
출력 + + + + - - + + - + + - - - - - 이 된다.

  1. 일단 1부터 N까지의 순차적으로 루프를 돌며 seq[i] 번째 데이터와 일치할 때 가지 루프를 돌며 스택에 데이터를 push한다.
  2. seq[i] 번째 값이 스택에 있으면 pop해 나간다.

소스코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Stack;

public class Main {

public static void main(String[] args) throws Exception {
stackSequence();
}

public static void stackSequence() throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
int n = Integer.parseInt(in.readLine());
int[] seq = new int[n];
for (int i = 0; i < n; i++) {
seq[i] = Integer.parseInt(in.readLine());
}

Stack<Integer> stack = new Stack<>();
StringBuilder sb = new StringBuilder();
int m = 0;
for (int data : seq) {
if (m < data) {
while (m < data) {
stack.push(++m);
sb.append("+\n");
}
stack.pop();
sb.append("-\n");
} else {
if (stack.peek() != data) {
System.out.println("NO");
return;
}
stack.pop();
sb.append("-\n");
}
}

out.write(sb.toString());
out.flush();
out.close();
in.close();
}
}

문제 사이트 : https://www.acmicpc.net/problem/1874

큐(Queue)

정의

한쪽 끝에서 자료를 넣으면 다른 한쪽 끝에서 자료를 뺄 수 있는구조로 쉽게 말해서 먼저들어온 데이터가 먼저 나오는 구조라고해서 FIFO(First In First Out)라고 부른다.

큐의 연산

  • enqueue : 데이터를 큐에 삽입
  • dequeue : 제일 첫 번째 들어온 데이터를 제거
  • 큐의 전단은 front, 후단은 rear로 이루어짐
  • 새로운 데이터가 들어오면 rear가 하나씩 증가
  • 데이터를 빼내면 front가 다음 queue에 저장되어 있는 데이터를 가리킴

enqueue

1
2
3
4

void enqueue(Object[] queue, int data) {
queue[rear++] = data;
}

dequeue

1
2
3
4
Object dequeue(Object[] queue) {
queue[front++] = data;

}

배열에서 단순 큐의 문제점

  • 배열에서 큐가 차면 데이터를 빼내야 하고, front 앞의 배열에는 공백이 생긴다(가용용량이 줄어들어버림). 또 다른경우 데이터를 빼낼 때 기존 데이터를 배열의 첫 번 째 위치로 이동해야하는 연산이 생길 수 있다.
  • 이런 문제를 해결하기 위해 Circular Queue 를 활용한다.

원형 큐(Circular Queue)

  • 배열의 끝(rear)과 시작(front)부분을 이어 순환시키도록 하는 것.
  • 배열의 rear에 데이터를 삽입하면서 rear의 다음이 front와 만나면 배열이 꽉차게 됨.

CircularQueue.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "CircularQueue.h"

void createQueue(Queue **queue, int capacity) {
(*queue) = (Queue*)malloc(sizeof(Queue));
(*queue)->capacity = capacity;
(*queue)->front = 0;
(*queue)->rear = 0;
(*queue)->nodes = (Node*)malloc(sizeof(Node)* (capacity+1));
}
void enqueue(Queue *queue, element data) {
int rear = (queue->rear) % queue->capacity;
queue->nodes[rear].data = data;
queue->rear = rear + 1;
}
void dequeue(Queue *queue) {
int front = (queue->front) % queue->capacity;
queue->nodes[front].data;
queue->front = front+1;
}
int isEmpty(Queue *queue) {
if (queue->front == queue->rear) {
return TRUE;
}
return FALSE;
}
int isFull(Queue *queue) {
if (queue->rear % queue->capacity == queue->front) {
return TRUE;
}
else if (queue->front == (queue->rear+1) {
return TRUE;
}
return FALSE;
}

CircularQueue.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>


#ifndef CIRCULARQUEUE_H
#define CIRCULARQUEUE_H

#define TRUE 1
#define FALSE 0

typedef int element;
typedef struct _Node {
element data;
}Node;

typedef struct _Queue {
int capacity;
int front;
int rear;
Node* nodes;
}Queue;

void createQueue(Queue **queue, int capacity);
void enqueue(Queue *queue, element data);
void dequeue(Queue *queue);
int isEmpty(Queue *queue);
int isFull(Queue *queue);

#endif //CIRCULARQUEUE_H

ArrayList Queue java version

Queue.java

1
2
3
4
5
6
public interface Queue<T> {
public void enqueue(T t);
public T dequeue();
public boolean isFull();
public boolean isEmpty();
}

ArrayQueue.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class ArrayQueue<T> implements Queue<T> {

private Object[] queue;
private int front;
private int rear;
private int capacity;
private int length;
private static final int DEFAULT_CAPACITY = 5;

public ArrayQueue() {
this.capacity = DEFAULT_CAPACITY;
this.front = 0;
this.rear = 0;
this.length = 0;
this.queue = new Object[capacity];
}
public ArrayQueue(int capacity) {
this.capacity = capacity;
this.front = 0;
this.rear = 0;
this.length = 0;
this.queue = new Object[capacity];
}

@Override
public void enqueue(T data) {
if (isFull()) {
throw new IndexOutOfBoundsException("queue is Full...");
}
int rear = (this.rear) % this.capacity;
queue[rear] = data;
this.rear = rear+ 1;
this.length++;
System.out.println("enqueue : rear : "+ rear +" front : "+ front);
}

@Override
public T dequeue() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("queue is empty...");
}
int front = ((this.front) % this.capacity);
Object data = queue[front];
queue[front] = null;
this.front = front + 1;
this.length--;
System.out.println("dequeue : rear : "+ rear +" front : "+ front);
return (T)data;
}

@Override
public boolean isFull() {
if (length == capacity) {
return true;
}
return false;
}

@Override
public boolean isEmpty() {

if ( length == 0 || front == rear) {
return true;
}
return false;
}

@Override
public String toString() {
if (length == 0) {
return "[ ]";
} else {
StringBuilder sb = new StringBuilder("[");
for(int i =0; i<length; i++) {
sb.append(queue[i]);
if (i != length-1) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
}

}

연결리스트 큐 (LinkedList Queue)

  • 연결리스트 큐를 이용하면 용량상태를 확인할 필요가 없으며 용량의 제한이 없어서 가득찬다는 개념이 존재하지 않음
  • front에서 데이터를 빼낼 때 next Node를 연결 해제 해주면 되므로 삽입 삭제가 편리

LinkedQueue.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

#include <stdio.h>
#include <stdlib.h>

#ifndef LINKQUEUE_H
#define LINKQUEUE_H

#define TRUE 1
#define FALSE 0

typedef int element;
typedef struct _Node {
element data;
struct _Node* next;
}Node;

typedef struct _Queue {
Node* front;
Node* rear;
int count;
}LinkedQueue;

Node *createNode(element data);
void createQueue(LinkedQueue **queue);
void enqueue(LinkedQueue *queue, Node* newNode);
void dequeue(LinkedQueue *queue);
int isEmpty(LinkedQueue *queue);

#endif //LINKQUEUE_H

LinkQueue.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "LinkQueue.h"

void createQueue(LinkedQueue **queue) {
(*queue) = (LinkedQueue*)malloc((sizeof(LinkedQueue)));
(*queue)->front = NULL;
(*queue)->rear = NULL;
(*queue)->count = 0;
}
Node *createNode(element data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->next = NULL;
}
void enqueue(LinkedQueue *queue, Node* newNode) {
if (queue->front == NULL) {
queue->front = newNode;
queue->rear = newNode;
queue->count++;
} else {
queue->rear->next = newNode;
queue->rear = newNode;
queue->count++;
}
}
void dequeue(LinkedQueue *queue) {
Node* front = queue->front;
if (queue->front->next == NULL) {
queue->front = NULL;
queue->rear = NULL;
} else {
queue->front =queue->front->next;
}
queue->count--;
}
int isEmpty(LinkedQueue *queue) {
if (queue->count == 0) {
return TRUE;
}
return FALSE;
}

Linked Queue java version

LinkedQueue.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class LinkedQueue<T> implements Queue<T> {

private Node front;
private Node rear;
private static int length;
private class Node {
private T data;
private Node next;
public Node() {
this.data = null;
this.next = null;
}
public Node(T data) {
this.data =data;
this.next = null;
}
}

public LinkedQueue() {
this.length = 0;
}
@Override
public void enqueue(T data) {
Node newNode = new Node(data);
if (front == null) {
front = newNode;
rear = newNode;
} else {
rear.next = newNode;
rear = newNode;
}
length++;
}

@Override
public T dequeue() {
if (isEmpty()) {
throw new IndexOutOfBoundsException("is empty");
}
Node remove = front;
front = front.next;
remove.next = null;
length--;
return remove.data;
}


@Override
public boolean isEmpty() {
if (length == 0) {
return true;
}
return false;
}
@Override
public boolean isFull() {
// TODO Auto-generated method stub
return false;
}

@Override
public String toString() {
Node temp = front;
if (temp == null) {
return "[ ]";
} else {
// StringBuilder 클래스를 이용하여 데이터를 출력
StringBuilder sb = new StringBuilder("[");
sb.append(temp.data);
temp = temp.next;
while (temp != null) {
sb.append(", ");
sb.append(temp.data);
temp = temp.next;
}
sb.append("]");
return sb.toString();
}
}
}

Queue.java

1
2
3
4
5
6
public interface Queue<T> {
public void enqueue(T t);
public T dequeue();
public boolean isFull();
public boolean isEmpty();
}

소스코드

github 이동 (Click)

연결리스트(Linked List)

개념과 구조

  • 데이터가 연속적인 리스트

  • 새로운 노드를 삽입하거나 삭제가 간편

  • 링크라는 개념을 통해 물리 메모리를 연속적으로 사용하지 않아도 된다.

  • 데이터를 구조체로 묶어 포인터로 연결

Linked List에는 기본적으로 Node와 Link라는 용어를 사용

HeadNode에는 데이터를 저장하지 않는다. 단지 LinkedList의 시작부분임을 나타낸다.(ex:기관차에서 headNode는 승객이 타지 않음)

LinkedList의 마지막 부분을 나타내는 노드도 있다. End Node or Tail Node라고 불리며, 데이터를 저장하지 않는다. 즉, Head, Tail(End) 노드는 데이터를 저장하지 않음(저장할 수 없다는 것이 아니라 묵시적으로 데이터를 저장하지 않는다는 것)

  • 링크에 화살표가 표시되어 있는 방향은 Head Node 부터 시작해 연결된 다음 노드들을 계속 가리키다보면 D에는 EndNode를 가리키고 EndNode는 아무것도 가리키지 않는 상태가 된다. 이와 같이 자신의 노드에서 다음 노드만 가리킬 수 있는 형태가 전형적이 LinkedList 의 형태이다.

연결리스트의 구조체

c

1
2
3
4
typedef struct _Node {
char data; //데이터
struct _Node *next; //다음노드 꼬리 Link
} Node;

java

1
2
3
4
private class Node {
private Object data;
private Node next;
}

연결리스트의 장 단점

장점

  • 새로운 노드의 추가, 삽입, 삭제가 쉽고 빠름, 배열은 삽입 삭제가 어렵 - 현재 노드의 다음 노드를 얻어오는 연산에 대해 비용이 발생하지 않음

단점

  • 다음 노드를 가리키려는 포인터 때문에 각 노드마다 4byte의 메모리가 추가로 필요
  • 특정 위치에 있는 노드(중간삽입)를 얻는데 비용이 크며 속도가 느림
  • 노드의 개수가 n개면 최악의 경우 n개의 노드 탐색 루프를 실행해야 특정 위치에 있는 노드를 찾을 수 있는 반면 배열은 상수 시간에 노드를 얻을 수 있음

연결리스트의 삽입과 삭제

Q. 배열을 사용하지 않고 LinkedList를 사용하는 이유

  • 배열은 생성할 때 데이터를 저장하는데 필요한 모든 메모리를 한번에 확보해 사용할 수 있게 해주므로 프로그램이 실행되는 중간에 배열의 크기를 바꿀 수 없다.

  • 배얼 안에 저장되어 있는 값들을 정렬할 때에도 메모리에 저장되어 있는 각각의 값을 바꿔줘야한다.

  • 배열은 연속적인 메모리를 사용하지만 연결리스트는 반드시 연속적이라고 불 수 없다

  • 연결리스트는 연속적이지 않은 데이터들을 링크로 서로 연결시키는 개념으로 볼 수 있다.

배열에서의 삽입

배열에서 B와 D 사이에 C 데이터 추가하려면 배열의 끝에서부터 한 칸씩 뒤로 이동해야 한다 즉 E와 D가 한칸씩 이동하고 B 와 D 사이에 C가 들어간다.

배열 데이터 삽입(C언어)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void insertArray(char* array, char data,int length) {
int i, cur;
char tmp, tmp2;
for (i=0; i< length; i++) {
if (array[i] > data) {
break;
}
}
tmp = array[i];
array[i] = data;
i++;

for (; i<length; i++) {
tmp2 = array[i];
array[i] = tmp;
tmp = tmp2;
}
}

연결리스트의 삽입

ㅁㅁ -> A -> B - C - D -> E -> ㅁㅁ
(B와 D 사이에 새로운 노드 C 삽입)

B와 D 노드에 새로 삽입되는 노드 C가 있다면 C가 노드 D를 가리키도록 하고 원래 노드 D를 가리키던 B노드는 C 노드를 가리키도록 해야한다.

연결리스트 삽입(C언어)

1
2
3
4
5
6
7
8
9
10
void insertNode(Node *newNode) {
Node *idxNode;
for (idxNode = head; idxNode != end; idxNodx = idxNode->Next) {
if (idxNode->next->data > newNode->data) {
break;
}
}
newNode->next = idxNode->next;
idxNode->next = newNode;
}

연결리스트에서 1개의 노드를 삽입하는 과정

  1. 새로운 노드를 생성한다
    Node *noewNode = (Node*)malloc(sizeof(Node));
  2. 새로운 노드가 삽입될 위치를 검색한다.

    1
    2
    3
    4
    5
    for (idxNode = head; idxNode != end; idxNodx = idxNode->Next) {
    if (idxNode->next->data > newNode->data) {
    break;
    }
    }
  3. 새로운 노드의 Next를 새로운 노드가 삽입될 다음 노드로 연결

    newNode->next = idxNode->next

  4. 새로운 노드가 삽입될 위치의 이전노드의 Next가 새로운 노드를 가리키도록 한다.

    idxNode->next = newNode

연결리스트에서 노드 삭제하는 과정

  1. 이전 노드를 가리킬 포인터와 삭제할 노드를 가리킬 포인터를 선언

    Node idxNode
    Node removeNode

  2. 삭제할 노드 검색

    1
    2
    3
    4
    5
    6
    for(idxNode = head; idxNode != end; idxNode = idxNode->next) {
    if (idxNode->next->data == removeNode->data) {
    removeNode = idxNode->next;
    break;
    }
    }
  3. 이전 노드가 삭제할 노드를 건너뛰고 다음 노드를 가리키도록 새로 설정

    idxNode->next = idexNode->next->next

  4. free() 함수로 노드를 메모리에서 삭제

    free(removeNode)

연결리스트 c version

LinkedList.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef LINKEDLIST_H
#define LINKEDLIST_H

#include <stdio.h>
#include <stdlib.h>


typedef struct _Node {
int data;
struct _Node *next;
}Node;

Node* createNode(int data);

void add(Node** head, Node* newNode);
void addAfter(Node* cur, Node* newNode);
void addHead(Node** head, Node* newHead);
int removeNode(Node** head, Node* remove);
Node* getNode(Node* head, int idx);
void destoryNode(Node *node);

int getLength(Node* head) {
int cnt = 0;
Node* cur = head;
while(cur != NULL) {
cur= cur->next;
cnt++;
}
return cnt;
}

#endif

LinkedList.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

#include "LinkedList.h"

Node* createNode(int data) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data =data;
newNode->next = NULL;
return newNode;
}

void destoryNode(Node *node) {
free(node);
}

void add(Node** head, Node* newNode) {
if ((*head) == NULL) {
*head = newNode;
} else {
Node* tail = (*head);
while(tail->next != NULL) {
tail = tail->next;
}
tail->next = newNode;
}
}

void addAfter(Node* cur, Node* newNode) {
newNode->next = cur->next;
cur->next = newNode;
}
void addHead(Node** head, Node* newHead) {
if (*head == NULL) {
(*head) = newHead;
} else {
newHead->next = (*head);
(*head) = newHead;
}
}
int removeNode(Node** head, Node* remove) {
if (*head == remove) {
*head = remove->next;
} else {
Node* cur = *head;
while(cur != NULL && cur->next != remove) {
cur= cur->next;
}
if (cur != NULL) {
cur->next = remove->next;
}
}
destoryNode(remove);
}
Node* getNode(Node* head, int idx) {
Node* cur = head;
while(cur != NULL) {
cur= cur->next;
}
return cur;
}

연결리스트 java version

List.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface List<T> {
// 임의의 위치에 데이터 삽입
public boolean add(T data, int index);

// 맨 첫번째에 데이터 삽입
public boolean addFirst(T data);

// 맨 마지막에 데이터 삽입
public boolean addLast(T data);

// 맨 끝에 데이터 삽입
public boolean add(T data);

// 인덱스 값을 통해 임의 위치 값 줄력
public T get(int index);

// 리스트의 크기 값
public int size();

// 마지막 리스트 의 값 출력
public T getLast();

// 맨 첫번째 리스트의 값 출력
public T getFirst();

// 임의 위치에 삭제
public T remove(int index);

// 첫번째 위치 삭제
public T removeFirst();

// 마지막 위치 삭제
public T removeLast();

// 마지막 위치 삭제
public T remove();
}

LinkedList.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191

public class LinkedList<T> implements List<T> {
private Node header;
private int length;

public LinkedList() {
header = new Node();
length = 0;
}

private class Node {
private T data;
private Node next;

Node() {
data = null;
next = null;
}

Node(final T data) {
this.data = data;
this.next = null;
}
}

private Node getNode(int index) {
if (header == null && (length == 0 || index <= 0 || index > length)) {
throw new IndexOutOfBoundsException();
}
Node cur = header;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur;
}

@Override
public boolean add(T data, int index) {
if (header != null) {
if (index == 0) {
addFirst(data);
return true;
}

Node prev = getNode(index - 1);
Node next = getNode(index);
Node newNode = new Node(data);

newNode.next = next;
prev.next = newNode;
length++;
return true;
}
return false;
}

@Override
public boolean addFirst(T data) {
if (header != null) {
Node newNode = new Node(data);
if (header.next == null) {
header.next = newNode;
length++;
return true;
} else {
newNode.next = header.next;
header.next = newNode;
length++;
return true;
}
}
return false;
}

@Override
public boolean addLast(T data) {
if (header != null) {
if (length == 0) {
addFirst(data);
return true;
}
Node next = getNode(length - 1);
Node newNode = new Node(data);
next.next = newNode;
length++;
return true;
}
return false;
}

@Override
public boolean add(T data) {
addLast(data);
return false;
}

@Override
public T get(int index) {
if (header == null && length == 0) {
throw new IndexOutOfBoundsException();
}
Node cur = header;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.data;
}

@Override
public int size() {
return this.length;
}

@Override
public T getLast() {
return get(length);
}

@Override
public T getFirst() {
return get(0);
}

@Override
public T remove(int index) {
if (header != null) {
if (length < index) {
throw new IndexOutOfBoundsException();
} else if (length <= 0 || index < 0) {
throw new IndexOutOfBoundsException();
} else if (index == 0) {
return removeFirst();
} else {
Node prev = getNode(index - 1);
Node remove = getNode(index);
if (remove.next == null) {
prev.next = null;
length--;
return remove.data;
} else {
prev.next = remove.next;
length--;
return remove.data;
}
}
}
return null;
}

@Override
public T removeFirst() {
if (header == null && length ==0 ) {
throw new IndexOutOfBoundsException();
}

Node remove = header.next;
header.next = remove.next;
length--;
return remove.data;
}

@Override
public T removeLast() {
return remove(length - 1);
}

@Override
public T remove() {
return removeLast();
}

@Override
public String toString() {
Node temp = header.next;
if (temp == null) {
return "[ ]";
} else {
// StringBuilder 클래스를 이용하여 데이터를 출력
StringBuilder sb = new StringBuilder("[");
sb.append(temp.data);
temp = temp.next;
while (temp != null) {
sb.append(", ");
sb.append(temp.data);
temp = temp.next;
}
sb.append("]");
return sb.toString();
}
}
}

Circular Linked List(원형 연결 리스트)

  • head와 tail로 연결되어 있는 연결 리스트

  • tail의 다음 노드 포인터가 헤드를 가리킴

    • 장점으로는 시작과 끝을 알 수 있다.

노드추가

1
2
3
4
5
typedef struct _Node {
int data;
struct _Node *prev;
struct _Node *next;
}Node;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void add(Node** head, Node* newNode) {
if ((*head) == NULL) {
*head = newNode;
(*head)->next = *head;
(*head)->prev = *head;
}
else {
Node* tail = (*head)->prev;
tail->next->prev = newNode;
tail->next = newNode;
newNode->nextNode = (*head)
newNode->prevNode = tail;
}
}

노드 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void remove(Node** head, Node* remove) {
if ((*head)->next == remove) {
(*head)->prev->next = remove->next;
(*head)->next->prev = remove->prev;
*head = remove->nextNode;

remove->prev = NULL;
remove->next = NULL;
}
else {
Node* tmp = remove;
remove->prev->next = temp->next;
remove->next->prev = temp->prev;

remove->prev = NULL;
remove->next = NULL;
}
}

Circular Linked List for Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

public class CircularLinkedList<T> implements List<T> {

private Node header;
private int length;

private class Node {
private T data;
private Node prev;
private Node next;

public Node(T data) {
this.data = data;
this.prev = null;
this.next = null;
}

public Node() {
this.data = null;
this.prev = null;
this.next = null;
}
}

public CircularLinkedList() {
header = new Node();
header.prev = header;
header.next = header;
this.length = 0;

}

@Override
public boolean add(T data, int index) {
if (header != null) {
if (index == 0) {
return addFirst(data);

} else if (index == length - 1) {
return addLast(data);
} else {
Node prev = getNode(index - 1);
Node newNode = new Node(data);

newNode.next = prev.next;
newNode.prev = prev;
prev.next.prev = newNode;
prev.next = newNode;
length++;
return true;
}
}
return false;
}

@Override
public boolean addFirst(T data) {
if (header != null) {
Node newNode = new Node(data);
Node nextNode = header.next;
newNode.next = nextNode;
newNode.prev = header;
nextNode.prev = newNode;
header.next = newNode;
length++;
return true;
}
return false;
}

@Override
public boolean addLast(T data) {
if (header != null) {
Node newNode = new Node(data);
Node tailNode = header.prev;
tailNode.next = newNode;
newNode.prev = tailNode;
newNode.next = header;
header.prev = newNode;
length++;
return true;
}
return false;
}

@Override
public boolean add(T data) {
return addLast(data);
}

@Override
public T get(int index) {
if (header == null && length == 0) {
throw new IndexOutOfBoundsException();
}
T data = null;
if (index == 0) {
data = getFirst();
} else if (index == length - 1) {
data = getLast();
} else {
Node cur = header;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
}
return data;
}

@Override
public int size() {
return this.length;
}

@Override
public T getLast() {
return header.prev.data;
}

@Override
public T getFirst() {
return header.next.data;
}

@Override
public T remove(int index) {
if (length < index) {
throw new IndexOutOfBoundsException();
} else if (length <= 0 || index < 0) {
throw new IndexOutOfBoundsException();
}
if (header != null) {
if (index == 0) {
return removeFirst();
} else if (index == length - 1) {
return removeLast();
} else {
Node remove = getNode(index);
Node prev = remove.prev;
prev.next = remove.next;
remove.next.prev = prev;
remove.next = null;
remove.prev = null;
length--;
return remove.data;
}
}
return null;
}

@Override
public T removeFirst() {
if (header == null && length == 0) {
throw new IndexOutOfBoundsException();
}

Node remove = header.next;
if (remove.next == header) {
header.next = header;
header.prev = header;
} else {
header.next = remove.next;
remove.next.prev = header;
}
remove.next = null;
remove.next = null;
length--;
return remove.data;
}

@Override
public T removeLast() {
if (header == null && length <= 0) {
throw new IndexOutOfBoundsException();
}
Node remove = header.prev;

if (remove.prev == header) {
header.next = header;
header.prev = header;
} else {
remove.prev.next = header;
header.prev = remove.prev;
}
remove.next = null;
remove.prev = null;
length--;
return remove.data;
}

@Override
public T remove() {
return removeLast();
}

@Override
public String toString() {
Node temp = header.next;
if (temp == header) {
return "[ ]";
} else {
StringBuilder sb = new StringBuilder("[");
sb.append(temp.data);
temp = temp.next;
while (temp != header) {
sb.append(", ");
sb.append(temp.data);
temp = temp.next;
}
sb.append("]");
return sb.toString();
}
}

private Node getNode(int index) {
if (header == null && (length == 0 || index <= 0 || index > length)) {
throw new IndexOutOfBoundsException();
}
Node cur = header;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur;
}
}

Double Linked List(더블 연결 리스트)

정의

  • Double Linked List는 연결리스트의 탐색 기능을 개선한 자료구조이다.
  • 연결리스트는 노드를 찾으려면 head에서 tail 방향으로만 탐색할 수 있지만 Double Linked List는 양방향 탐색이가능하다.
1
2
3
4
5
typedef struct _Node {
int data; //데이터 필드
struct _Node* prev; //이전 노드를 가리키는 포인터
struct _Node* next; //다음 노드를 가리키는 포인텅
}Node;

생성/삽입/삭제

노드생성

1
2
3
4
5
6
7
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}

노드 삽입(순서대로)

1
2
3
4
5
6
7
8
9
10
11
12
13
void add(Node** head, Node* newNode) {
if ((*head) == NULL) {
(*head)->next = newNode;
newNode->prev = (*head);
} else {
Node* tail = (*head);
while( tail->next != NULL) {
tail = tail ->next;
}
tail->next = newNode;
newNode->prev = tail;
}
}

노드 중간 삽입

  1. newNode->next는 nextNode를 연결
  2. newNode->prev는 prevNode를 연결
  3. nextNode->prev는 newNode를 연결
  4. prevNode->next는 newNode를 연결
1
2
3
4
5
6
7
8
void addAfter(Node* cur, Node* newNode) {
newNode->next = cur->next;
newNode->prev = cur;
if (cur->next != NULL) {
cur->next->prev = newNode;
}
cur->next = newNode;
}

노드 삭제

  1. prevNode->next에 nextNode를 연결
  2. nextNode->prev에 prevNode를 연결
  3. removeNode는 메모리 해제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void removeNode(Node** head, Node* remove) {
if (*head == remove) {
*head = remove->next;
if ((*head) != NULL) {
(*head)->prev = NULL;
}
remove->prev = NULL;
remove->next = NULL;
} else {
Node* tmp = remove;
remove->prev->next = tmp->next;
if (remove->next != NULL) {
remove->next->prev = temp->prev;
}
remove->next = NULL;
remove->prev = NULL;
}
free(remove);
}

Double Linked List (java version)

List.java (interface)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package algorithm;

public interface List<T> {
// 임의의 위치에 데이터 삽입
public boolean add(T data, int index);

// 맨 첫번째에 데이터 삽입
public boolean addFirst(T data);

// 맨 마지막에 데이터 삽입
public boolean addLast(T data);

// 맨 끝에 데이터 삽입
public boolean add(T data);

// 인덱스 값을 통해 임의 위치 값 줄력
public T get(int index);

// 리스트의 크기 값
public int size();

// 마지막 리스트 의 값 출력
public T getLast();

// 맨 첫번째 리스트의 값 출력
public T getFirst();

// 임의 위치에 삭제
public T remove(int index);

// 첫번째 위치 삭제
public T removeFirst();

// 마지막 위치 삭제
public T removeLast();

// 마지막 위치 삭제
public T remove();
}

DLinkedList.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207

public class DLinkedList<T> implements List<T> {
private Node header;
private int length;

public DLinkedList() {
header = new Node();
length = 0;
}

private class Node {
private T data;
private Node next;
private Node prev;

Node() {
data = null;
next = null;
prev = null;
}

Node(final T data) {
this.data = data;
this.next = null;
this.prev = null;
}
}

private Node getNode(int index) {
if (header == null && (length == 0 || index <= 0 || index > length)) {
throw new IndexOutOfBoundsException();
}
Node cur = header;
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur;
}

@Override
public boolean add(T data, int index) {
if (header != null) {
if (index == 0) {
addFirst(data);
return true;
}

Node prev = getNode(index - 1);
Node newNode = new Node(data);

newNode.next = prev.next;
newNode.prev = prev;
prev.next.prev = newNode;
prev.next = newNode;
length++;
return true;
}
return false;
}

@Override
public boolean addFirst(T data) {
if (header != null) {
Node newNode = new Node(data);
if (header.next == null) {
header.next = newNode;
newNode.prev = header;
length++;
return true;
} else {
newNode.next = header.next;
newNode.prev = header;
header.next.prev = newNode;
header.next = newNode;
length++;
return true;
}
}
return false;
}

@Override
public boolean addLast(T data) {
if (header != null) {
if (length == 0) {
addFirst(data);
return true;
}
Node next = getNode(length - 1);
Node newNode = new Node(data);
next.next = newNode;
newNode.prev = next;
length++;
return true;
}
return false;
}

@Override
public boolean add(T data) {
addLast(data);
return false;
}

@Override
public T get(int index) {
if (header == null && length == 0) {
throw new IndexOutOfBoundsException();
}
Node cur = header;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.data;
}

@Override
public int size() {
return this.length;
}

@Override
public T getLast() {
return get(length);
}

@Override
public T getFirst() {
return get(0);
}

@Override
public T remove(int index) {
if (header != null) {
if (length < index) {
throw new IndexOutOfBoundsException();
} else if (length <= 0 || index < 0) {
throw new IndexOutOfBoundsException();
} else if (index == 0) {
return removeFirst();
} else {
Node prev = getNode(index - 1);
Node remove = getNode(index);
if (remove.next == null) {
prev.next = null;
remove.next = null;
remove.prev = null;
length--;
return remove.data;
} else {
prev.next = remove.next;
remove.next.prev = prev;
remove.next = null;
remove.prev = null;
length--;
return remove.data;
}
}
}
return null;
}

@Override
public T removeFirst() {
if (header == null && length ==0 ) {
throw new IndexOutOfBoundsException();
}

Node remove = header.next;
header.next = remove.next;
remove.next.prev = header;
remove.next = null;
remove.next = null;
length--;
return remove.data;
}

@Override
public T removeLast() {
return remove(length - 1);
}

@Override
public T remove() {
return removeLast();
}

@Override
public String toString() {
Node temp = header.next;
if (temp == null) {
return "[ ]";
} else {
// StringBuilder 클래스를 이용하여 데이터를 출력
StringBuilder sb = new StringBuilder("[");
sb.append(temp.data);
temp = temp.next;
while (temp != null) {
sb.append(", ");
sb.append(temp.data);
temp = temp.next;
}
sb.append("]");
return sb.toString();
}
}
}

소스코드

github 이동 (Click)