다항식 표현 방법 polynomial a

1. 선형 리스트
1.1 선형 리스트, 원소 삽입
1.2 선형 리스트, 원소 삭제
2. 선형 리스트
2.1 1차원 배열의 순차 표현
2.2 2차원 배열의 순차 표현
2.3 3차원 배열의 순차 표현
3. 다항식의 순차 자료구조 표현
3.1 ADT Polynomial
3.2 addPoly 알고리즘
3.3 addPoly 알고리즘 구현


1. 선형 리스트

데이터를 구조화시키는 기본 표현 방식은 다음과 같다.

  • 순차 자료구조 : 논리적인 순서와 물리적인 순서가 같음
  • 연결 자료구조 : 주소를 이용해 접근, 원소의 논리적인 순서와 물리적인 순서가 같을 필요가 없음
    단, 주소를 저장할 공간이 추가적으로 필요, 데이터와 주소를 ‘노드’ 단위로 저장

본 포스팅에서는 선형 리스트를 순차 자료구조 방식으로 구현하는 것을 다룰 것이다.

  • 리스트 : List, 데이터를 나열하는 것
    • 데이터를 구조화시키는 가장 기본적인 방법
  • 선형 리스트 : Linear List, 나열한 원소들 간에 순서를 가지고 있는 리스트
    • = 순서 리스트, Ordered List
    • 리스트에서 각 원소들이 번호를 단 꼴이다.

      다항식 표현 방법 polynomial a

      선형 리스트의 예

리스트를 표현하는 일반적인 방법은 다음과 같다.

리스트이름 = (원소, 원소2, … , 원소 n)

괄호 안에 나열한 원소들을 하나의 리스트로 그룹화, 리스트 이름을 정한다.
선형 리스트의 경우, 나열한 순서대로 원소들의 순서가 결정된다.

원소가 하나도 없는 리스트는 공백 리스트(empty list)라고 하고, 괄호 내에 원소가 없다.

선형 리스트는 원소들 간의 논리적인 순서와 메모리에 저장하는 물리적인 순서가 같은 구조로 되어 있다.
이러한 구조를 순차 자료구조 라고 한다.
순차 자료구조에서 원소의 순서대로 데이터가 메모리에 연속 저장된다.

1.1 선형 리스트, 원소 삽입

선형 리스트에 새로운 원소를 삽입하려먼 먼저 삽입할 자리를 만든 후에 그 자리에 원소를 삽입해야 한다.
원소를 삽입할 자리를 만들기 위해서는 삽입할 위치 이후에 있는 원소들을 모두 한자리씩 뒤로 밀어내야 한다.

위 경우, 새로운 원소를 2번 자리에 삽입하기 위해 원소들을 4회 이동하였다.
(n+1)개의 원소로 이루어진 선형 리스트에 k번 자리에 새로운 원소를 삽입하려면,
(n+1-k)개의 원소를 모두 한자리씩 이동시켜야 한다.
따라서 빈 자리를 만들기 위한 이동횟수는 (n-k+1) 이다.

1.2 선형 리스트, 원소 삭제

선형 리스트에서 원소를 삭제하면 원소가 있던 자리는 빈자리가 된다.
선형 리스트는 논리 순서와 같은 순서대로 메모리들이 연속하여 저장되어야 하는 자료구조이기 때문에 중간에 빈자리가 있어서는 안된다.
따라서 삭제된 원소 이후에 있는 원소들을 모두 한자리씩 앞으로 옮겨 빈자리를 채워야 한다.

(n+1)개의 원소로 이루어진 선형 리스트에 k번 자리에 있던 원소를 삭제하면,
(n+1-(k+1))개의 원소를 모두 한자리씩 이동시켜야 한다.
따라서 빈 자리를 채우기 위한 이동횟수는 (n-k) 이다.


2. 선형 리스트 구현

선형 리스트를 순차 구조로 표현하기 위해 배열 을 사용한다.
배열은 <인덱스, 원소>의 쌍으로 구성되어 메모리에 연속적으로 할당된다.
배열은 이같은 특징을 지니고 있기 때문에, 배열을 그대로 사용하여 선형 리스트를 구현할 수 있다.

2.1 1차원 배열의 순차 표현

1차원 배열 : 인덱스를 하나만 사용하는 배열

아래 코드는 분기별 상품 판매량을 출력하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
package io.github.dudri63.dataStructure;

public class LinearList1 {

public static void main(String args[]) {
int sale[] = new int[] {100, 120, 130, 110};

for(int i=0; i<4; i++) {
System.out.println("sale [" + (i+1) + "] = " + sale[i]);
}
}
}

다항식 표현 방법 polynomial a


2.2 2차원 배열의 순차 표현

분기별 판매량 뿐만 아니라, 연도별 판매량을 나타내고 싶을때는 인덱스가 2개 필요하다.
아래는 그것을 2차원 형태인 표로 도식화한 것이다.

다항식 표현 방법 polynomial a


이러한 경우 2차원 배열을 사용한다.

2차원 배열은 위 표와 같은 형태로 행,열을 구분하여 논리적으로 묘사할 수 있다.
그러나 실제로 메모리에 저장될 때에는 1차원의 순서로 저장이 될 것이다.
2차원 논리적 순서과 1차원 물리적 순서로 변환되는 방법은 다음과 같다.

행 우선 순서(Row Major Order) 열 우선 순서(Column Major Order)

행 우선 순서 방법은 행을 기준으로 1행의 첫번째 열부터 마지막 열까지, 2행의 첫번째 열부터 마지막 열까지, 순서대로 저장하여 마지막 행의 첫번째 열부터 마지막 열까지 저장하는 방법이다.
예를 들어, sale[0][0], sale[0][1], sale[0][2], sale[1][0], sale[1][1], sale[1][2]순서대로 저장한다.

열 우선 순서 방법은 열을 기준으로 1열의 첫번째 행부터 마지막 행까지, 2열의 첫번째 행부터 마지막 행까지, 순서대로 저장하여 마지막 열의 첫번째 행부터 마지막 행까지 저장하는 방법이다.
예를 들어, sale[0][0], sale[1][0], sale[0][1], sale[1][1], sale[0][2], sale[1][2]순서대로 저장한다.

논리 순서를 물리 순서로 변환하는 방법은 프로그래밍 언어의 컴파일러에 의해서 결정된다.

다음은 2년 동안의 분기별 선형 리스트를 2차원 배열로 구현한 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package io.github.dudri63.dataStructure;

public class LinearList2 {
public static void main(String args[]) {
int sale[][] = new int[2][4];

for(int i=0; i<2; i++) {
for (int j=0; j<4; j++) {
sale[i][j] = 100*i + 20*j;
}
}

for(int i=0; i<2; i++) {
for (int j=0; j<4; j++) {
System.out.printf("sale[%d][%d] = %d %n", i+1, j+1, sale[i][j]);
}
}
}
}


다항식 표현 방법 polynomial a


2.3 3차원 배열의 순차 표현

2년 동안의 분기별 선형 리스트는 2차원 배열로 표현할 수 있었다.
그렇다면, 각 팀별 2년 동안의 분기별 선형 리스트는 3차원 배열로 표현할 수 있다.
왜냐하면 이러한 경우에는 인덱스가 3개가 필요하기 때문이다.

다항식 표현 방법 polynomial a


3차원 배열의 논리적 구조는 메모리상에는 2차원 배열과 마찬가지로 1차원 선형 구조 형태로 저장된다.
3차원 논리 구조를 1차원 물리 구조로 변환하는 방법은 다음과 같다.

  • 면 우선 순서 방법
  • 열 우선 순서 방법

다음은 1팀, 2팀의 2년 동안의 분기별 선형 리스트를 3차원 배열로 구현한 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package io.github.dudri63.dataStructure;

public class LinearList3 {
public static void main(String args[]) {
int sale[][][] = new int[][][] {{{100,200,150,180},{180,190,200,210}},
{{150,180,170,200},{220,210,150,180}}};
for(int i=0;i<2;i++) {
System.out.printf("%d팀 %n", i+1);
for(int j=0;j<2;j++) {
for(int k=0;k<4;k++) {
System.out.printf("sale[%d][%d] = %d %n", j+1,k+1,sale[i][j][k]);
}
System.out.printf("----------------------------%n");
}
}
}
}

다항식 표현 방법 polynomial a


순차 자료구조는 원소들의 순서를 따로 표시할 필요없다는 장점이 있다.
또한 인덱스를 사용하여 특정 원소를 쉽게 접근할 수 있다.
그러나 원소를 삽입하거나 삭제할 경우, 물리적으로 원소들을 밀어내거나 당겨야 하기 때문에 추가적인 오버헤드가 발생한다.
삽입, 삭제가 많이 필요한 문제에서 순차 자료구조를 사용하는 것은 비효율적이다.


3. 다항식의 순차 자료구조 표현

순차 자료구조를 사용하여 다항식 표현 및 연산을 수행 가능하다.

3.1 ADT polynomial

다음은 다항식 연산에 필요한 연산을 추상자료형(ADT)으로 정의한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ADT polynomial
데이터 : <ei,ai>의 집합으로 표현된 다항식 p(x), 단, ei, ai는 각각 지수, 계수
연산 : p, p1, p2 ∈ Polynomial; a ∈ Coefficient; e ∈ Exponent; // p,p1,p2는 다항식, a는 계수, e는 지수
zeroP() ::= return polynomial P(x) = 0; // 다항식을 0으로 만드는 연산
isZeroP() ::= if (p) then false
else return true; // 다항식 p가 0인지를 검사하는 연산
coef(p,e) ::= if (<e,a> ∈ p) then return a;
else return 0; // 다항식 p에서 지수가 e인 항의 계수 a를 구하는 연산
maxExp(p) ::= return max(p.Exponent); // 다항식 p에서 최대 지수를 구하는 연산
addTerm(p,a,e) ::= if (e ∈ p.Exponent) then return error
else return p after // 다항식 p에서 지수가 e인 항이 없는 경우, 새로운 항 <e,a>를 추가하는 연산
delTerm(p,e) ::= if (e ∈ p.Exponent) then return p after removing the term <e,a>
else return error; // 다항식 p에서 지수가 e인 항 <e,a>를 삭제하는 연산
multiTerm(p,a,e) ::= return (p * ax^e) // 다항식 p의 모든 항에 ax^e하을 곱하는 연산
addPoly(p1,p2) ::= return (p1 + p2); // 두 다항식 p1, p2의 합을 구하는 연산
multiPoly(p1,p2) ::= return (p1 * p2); // 두 다항식 p1과 p2의 곱을 구하는 연산
End Polynomial

예를 들어, 위 추상 자료형의 정의에 따른다면, A(x) = 4x3 + 3x2 + 2는 p1 = ((3,4), (2,3), (0,2))와 같이 각 항의 지수와 계수를 한 쌍으로 하는 3개의 항을 선형 리스트 p1의 원소로 정의할 수 있다.
차수가 n인 다항식은 (n+1)개의 원소를 가지는 배열을 사용하여 순차 자료구조로 표현할 수 있다.
이때 배열의 인덱스 i는 지수가 (n-i)인 항을 가리키고, 해당 항의 계수를 인덱스 i에 대한 배열 요소에 저장한다.

다항식 표현 방법 polynomial a

n차 다항식 P(x)의 순차 자료구조 표현

다음은 3차 다항식을 순차 자료구조로 표현한 것에 대한 예시이다.

다항식 표현 방법 polynomial a

3차 다항식의 순차 자료구조 표현

다항식의 각 항이 계수를 정해진 인덱스의 배열요소에 저장하여 사용하는 방법은 지수를 따로 저장하지 않기 때문에 표현하기가 쉽고 간단하다. 그러나 항의 계수가 ‘0’인 항이 많을 경우 메모리의 낭비가 심해지게 된다.
예를 들어 다항식 B(x) = 3x1000 + x + 4의 경우가 그러한 경우이다.
이러한 다항식을 ‘희소 다항식’이라고 하는데, 이러한 경우에는 차수에 따라 배열을 생성하기보다는 항의 개수에 따라 배열 크기를 결정하는 것이 메모리 사용면에서 효율적이다.
단, 이러한 방법을 차용하기 위해서는 지수를 표현할 수 있도록 인덱스를 하나 더 추가하여 <지수,계수> 쌍을 2차원 배열에 저장한다.

다항식 표현 방법 polynomial a

희소 다항식의 순차 자료구조 표현

2차원 배열에서 각 행의 0번 열에 지수, 1번 열에 계수를 저장한다.

3.2 addPoly 알고리즘

추상자료형으로 정의한 다항식 연산 중 두 다항식의 덧셈 연산인 addPloy(p1,p2)에 대한 알고리즘을 정의한 바는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
addPloy(A, B)     // 주어진 두 다항식 A, B를 더하여 결과 다항식 C를 반환하는 알고리즘
C <- zeroP(); // 임의의 다항식 C를 0으로 만듬
while (not isZeroP(A) and not isZeroP(B)) do { // 다항식 A, B가 0이 아니면,
case {
maxExp(A) < maxExp(B) : // 다항식 B의 차수가 다항식 A의 차수보다 큰 경우
C <- addTerm(C, coef(B, maxExp(B)), maxExp(B)); // 다항식 C에 다항식 B의 최고차항을 새로운 항으로서 추가
B <- delTerm(B, maxExp(B)); // 다항식 B의 최고차항을 제거
maxExp(A) = MaxExp(B) : // 다항식 B의 차수와 다항식 A의 차수가 같은 경우
sum <- coef(A, maxExp(A)) + coef(B, maxExp(B)); // 다항식 A, B의 최고차항의 계수를 더한 뒤, 변수 sum에 저장
if (sum ≠ 0) then, C <- addTerm(C, sum, maxExp(A)); // sum이 0이 아니라면, 다항식 C에 다항식 A의 최대 지수를 지수로, sum을 계수로 하는 새로운 항을 추가
A <- delTerm(A, maxExp(A)); // 다항식 A의 최고차항을 삭제
B <- delTerm(B, maxExp(B)); // 다항식 B의 최고차항을 삭제
maxExp(A) > maxExp(B) : // 다항식 A의 차수가 다항식 B의 차수보다 큰 경우
C <- addTerm(C, coef(A, maxExp(A)), maxExp(A)); // 다항식 C에 다항식 A의 최고차항을 새로운 항으로서 추가
A <- delTerm(A, maxExp(A)); // 다항식 A의 최고차항을 삭제
}
}
if (not isZeroP(A)) then A의 나머지 항들을 C에 복사 // A에 항이 남이 있으면
else if (not isZero(B)) then B의 나머지 항들을 C에 복사 // B에 항이 남아 있으면
return C;
End addPoly()
}

3.3 addPoly 알고리즘 구현

addPoly 알고리즘을 자바 프로그램으로 구현한 결과는 다음과 같다.

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 Polynomial {
private int degree;
private float coef[] = new float[20];

Polynomial (int degree, float coef[]) {
this.degree = degree;
this.coef = coef;
}
Polynomial (int degree) {
this.degree = degree;
for(int i=0; i<=degree; i++) {
this.coef[i] =0;
}
}
public int getDegree() {
return this.degree;
}
public float getCoef(int i) {
return this.coef[i];
}
public void setCoef(int i, float coef) {
this.coef[i] = coef;
}
public void printPoly() {
int temp = this.degree;
for(int i=0; i<=this.degree; i++) {
System.out.printf("%3.0fx^%d ", this.coef[i], temp--);
}
}
}

public class OperatePoly {
private int degree_A=0, degree_B=0, degree_C=0, index_A=0, index_B=0, index_C=0;
private int expo_A, expo_B;

public Polynomial addPoly(Polynomial A, Polynomial B) {
degree_A = A.getDegree();
degree_B = B.getDegree();
expo_A = degree_A;
expo_B = degree_B;
if(degree_A >= degree_B) degree_C = degree_A;
else degree_C = degree_B;
Polynomial C = new Polynomial(degree_C);
while (index_A <= degree_A && index_B <= degree_B) {
if(expo_A < expo_B) {
C.setCoef(index_C++, B.getCoef(index_B++));
expo_B--;
}
else if(expo_A == expo_B) {
C.setCoef(index_C++, A.getCoef(index_A++)+B.getCoef(index_B++));
expo_A--;
expo_B--;
}
else {
C.setCoef(index_C++, A.getCoef(index_A++));
expo_A--;
}
}
return C;
}
}

public class Ex_addPoly {
public static void main(String args[]) {
float a[] = new float[] {1,2,3,4};
float b[] = new float[] {1,2,3,4,5};
Polynomial A = new Polynomial(3, a);
Polynomial B = new Polynomial(4, b);
OperatePoly optPoly1 = new OperatePoly();
Polynomial C = optPoly1.addPoly(A,B);
System.out.printf("A(x)=");
A.printPoly();
System.out.println();
System.out.printf("B(x)=");
B.printPoly();
System.out.println();
System.out.printf("C(x)=");
C.printPoly();
}
}

Reference

  • 이지영, 『자바로 배우는 쉬운 자료 구조』. 서울: (주)한빛아카데미, 2013