절반정도 해서 뒤로 점프!


8. 그래프


그래프는 광범위한 분야에서 활용되는 자료구조!

여러가지 알고리즘과 탐색방법등이 존재한다.


8.1.1 그래프 용어

그래프는 기본적으로 용어에 대한 이해가 없으면 공부가 아예 안된다...


정점(vertex): 노드, 

간선(edge): 두개의 정점을 연결한 선

차수(degree): 정점 a 에 인접한 정점

무방향그래프 : 간선에 방향이 없는 것, (a, b)

방향그래프 : 간선에 방향이 있는 것 <a, b>

- 진입차수(in-degree): 정점으로 들어오는 차수

- 전출차수(out-degree): 정점에서 나가는 차수

경로(path): 시작점부터 끝정점까지의 정점들의 나열

- 단순경로(simple-path) : 경로상의 정점이 모두 다름

- 싸이클 : 시작과 끝이 같음

연결성분: 그래프 중 서로 연결되어있는 부분

가중치(weighted) : 간선에 가중치를 부여(거리, 소요시간 등)

부분그래프(subgraph): 주어진 그래프 중 일부분

- 트리 : 싸이클이 없는 그래프

- 신장트리 : 그래프의 모든 정점을 싸이클 없이 연결하는 그래프


8.1.2 그래프의 자료구조

그래프를 자료구조로 저장하기 위한 방법은 2가지가 있다

인접행렬(Adjacency Matrix)

- 행과 열을 통해 연결되면 1, 아니면 0으로 표현한다

- 만약 노드가 무수히 많을 경우 연결이 되지않은 부분을 0으로 표현하다보니

메모리? 등의 낭비가 있다고 한다. 이런 경우엔 리스트 활용

인접리스트(Adjacency List)

- 연결 된 부분을 리스트로 표현


예) 1번노드와 3번 노드가 연결되어있다면

인접행렬 : a[1][3] = 1

인접리스트 : a = [[],[3]...]

- 1번째 인덱스가 3을 뜻한다 = 1이 3을 가리킨다로 이해




해야지 해야지 하고 이제야 한다.

책 보고 따라치는 정도이지만 자바, 스프링과 최대한 비교하면서 리뷰해야지..


노드는 기본적으로 모듈을 임포트 해서 사용하는데

파이썬, 자바의 경우 import 클래스 한다 하면

노드는 require('모듈명') 으로 하고 이것을 객체로 받는다.


서버를 구성하기 위해선 기본적으로 http 모듈이 필요로 하나 이를 개선한 express를 사용한다.

하지만 http도 같이 씀?!


# 1. 임포트 하고 2.이것을 객체로 받아서 메서드를 사용한다.

var express = require('express'), http = require('http');

var app = express();

이제 app이란 객체를 활용해서 express의 메서드를 사용할 수 있다.


# 서버 생성하기

서버를 생성하기 위해선 http 모듈의 createServer를 사용한다.

기존에 http만 사용할 경우 createServer만 쓰면 됬지만

express 모듈을 같이 사용하므로 그 객체를 같이 넣어준다. 


http.createServer(app).listen(app.get('port'), function(){....

이런식으로.


# app.get('port') 가 가능한 이유는

위에선 언급안했지만 app.set('port', 3000) 으로 저장을 했기 때문


# 위 부분은 자바 스프링에서 서블릿 컨텍스트 설정을 하고 서버를 키는 것과 동일

# xml 등의 부분을 만질 필요가 없다는 점에서 간편한듯?


# 노드는 미들웨어와 라우터가 존재.

미들웨어는 일종의 함수라 생각하면 편할 듯. 즉 자바에서도 어노테이션으로 각 주소를 받고 그에 따른 함수를 지정한 것과 같다 생각하면 편할듯 하다.


# 미들웨어 생성은 use 메서드를 활용한다

app.use(function(req, res, next){... 이렇게


# next() 메서드는 다음 미들웨어로 넘어가기 위함이다.

# 아직 이 부분을 더 깊게 못들어가서 스프링과 비교하기 이름


# 주소 부분에서 ? 이후로의 값을 가져오려면

req.query.?명으로 받아오면 된다.

localhost:3000?name=kkk 라 할 경우 ? 뒤에 name을 받으면 된다

var name = req.query.name; 이런식으로


 

'Node.Js 도전(임시 마감)' 카테고리의 다른 글

Do it node, 세션 처리하기 중..  (0) 2018.05.07

어느덧 벌서 5장, 총 8장까지인데 어느덧 중반을 향하고 있다.


탐색트리는 앞서 했던 트리의 자료구조의 일종이다.

트리 + 이진 탐색을 접목 시킨 구조라 볼 수 있다.


1. 이진탐색(binary Search)

탐색을 하는 방법이다.

기존의 선형구조의 경우 원하는 원소를 찾기 위해선 순차로 검색해야했고

이럴 경우 O(n)의 시간이 소요,

그렇기에 이걸 반씩 나눠 찾자 라는 개념.


즉 1-10까지 있을때 10을 찾기 위해선 10번 해야할껄

5 - 7 - 9 - 10 이런식으로 단축해서 찾을 수 있다.


2. 이진탐색트리

이진탐색의 방법을 트리로 적용 시켜서 만든것.

단순 연결리스트의 중간의 레퍼런스 연결을 바꾸고,

이걸 반복하고.. 말로는 설명하기어렵다.


이진탐색트리의 경우

노드 기준 왼쪽노드 들은 해당 노드보다 작아야 하고 오른쪽은 커야한다.


50

   80

 10 90

위 트리는 10 때문에 안된다. 50보다는 커야하기 때문.


3. 코드


3.1 탐색연산

기본 컨셉은 찾는값(k)과 루트 비교 -> k가 루트보다 크면 오른쪽으로

k가 루트보다 작으면 왼쪽으로,

이 방법을 재귀로 계속...


# self는 클래스 안에 만든 함수라 들어가있다.

def get(self, k):

 return self.get_item(self.root, k)

def get_item(self, n, k):

if n == None: # 루트에 아무것도 없다면 -> 찾는게 없다

return None

if n.key > k : # 찾는값이 작으니 왼쪽으로

return self.get_item(n.left, k)

elif n.key < k: # 찾는값이 크니 오른쪽으로

return self.get_item(n.right, k)

else:

return n.value

어려운 코드는 아니다.


3.2 삽입연산

탐색연산과 유사하지만 return이 다르다.

여기서 상당히 해맸는데, 재귀함수를 사용하는 이유와 결과를 잘 이해해야 한다.

재귀함수는 결국 큰 것을 작은 부분으로 나누어 이해하기 위함이라는 사실을 인지하고 들어가자


def put(self, key, value):

self.root = self.put_item(self.root, key, value)

# 위에 코드와 비교하면 얘는 return을 안하고 self.root, 즉 루트값을 받게끔 되어있다.


def put_item(self,n, key, value):

if n == None:

return Node(key, value) # 결국 아무것도 없는 곳 까지 갔을때 node를 삽입한다. 근데 여기서 끝이 아니다. 이 return 값이 위로 쭉쭉 올라가야한다

if n.key > key:

n.left = self.put_item(n.left, key, value)

elif n.key < key:

n.right = self.put_item(n.right, key, value)

else:

n.value = value

return n


30

20      40

10      25

여기서 5를 넣는다고 하면 얘는 10의 왼쪽으로 가야한다.

재귀적으로 볼때 새로운 값, 10이 하나의 서브트리로 완성 되야하고 이 서브트리를 왼쪽 노드, 25, 20 을 원소로 하는 서브트리가 또 완성되어야 한다.

그리고 이 서브트리는 결국 30, 40으로 이루어진 트리가 되어야한다.

그렇기때문에 값을 넣고(return Node) 한 후에 계속 해서 올라가서 결국은 return n을 해줘야한다...


이거는 직접 코드를 손으로 써본다음에 선 그으면서 봐야한다..


3.3 삭제연산

제일 생각할 것도 많고, 코드도 길고.. 그렇다 암튼.

3가지의 경우의 수가 있다.

우선 삭제할 노드를 n 이라 칭하겠다.

첫번째 : n이 자식이 없을 경우

=> n의 부모노드의 자식을 None으로 만든다

두번째 : n의 자식이 하나만 있을경우

=> n의 부모노드와 n의 자식을 직접 연결한다

세번재 :  n의 자식이 많을 경우..

=> n의 자시에서 중위순회하면서 n을 방문한 직후 방문하는 노드를 n의 자리로 옮긴다

=> 이진탐색에서 n 기준 왼쪽자식은 n보다 작고 오른쪽 자식은 n보다 크다.

그렇기에 n이 삭제되면 그 다음으로 큰 n의 오른쪽 자식이 와야한다.(n보다 큰 다음 노드가 와야 한다는 뜻)


코드는 다음과 같다.


def delete(self, k):

self.root = self.del_node(self.root, k)


def del_noe(self, n, k):

if n == None:

return None

if n.key > k:

n.left = self.del_node(n.left, k)

elif n.key < k:

n.right = self.del_node(n.right, k)

# 여기까지의 코드는 k를 찾으러 가기 위한 과정이다.

else:

if n.right == None:

return n.left;

if n.left == None:

return n.right;

# 위의 코드는 첫번째 경우, 자식이 없을때

target = n #1번

n = self.minimum(target.right) #2번

n.right = self.del_min(target.right) #3번

n.left = target.left #4번

return n


target부분부터 설명을 하자면.

우리는 자식이 많은 노드를 삭제하는 경우를 생각해야한다.

여기서도 이진 탐색트리의 특성을 꼭 기억해야하는데

왼쪽 자식은 작고 오른쪽 자식은 크다는 것이다.


즉 내가 삭제 되더라도 나를 대체할 애를 뎃구와야하고

뎃구 오는 애는 오른쪽 아이들, 그리고 그 아이들에서도 제일 작은 애를 부모로 해야 정렬이 알아서 된다. 그리고 작은 자식을 뎃구 왔으면 당연히 그 친구를 삭제해주고..

그렇기에 #2번을 보면 지우려는 노드의 오른쪽 자식들 중 가장 작은 애를 고른다.

(minumum이란 함수가 있다.. 위에서 작성 안함...ㅠ)

그리고 그 고른 애들 지워준다(#3번), 이때!! 이러한 결과는 노드의 오른쪽에서 이뤄지므로 n.right로 받아준다!!(#3번)

그리고 왼쪽애들을 그대로 붙여준다(#4번)


다시 정리하자면

1. 삭제 전 오른쪽 자식들 중에서 제일 작은 값 선정

2. 제일 작은 값 삭제

3. 오른쪽, 왼쪽 연결

이렇게 된다..





+ Recent posts