여행 경로 계획 프로그램 (Travel Path Planner) < Shortest Path Problem, Dijkstra Algorithm >

Rules

Source Code

main.cpp

cpp
#include "Planner.h"

int main()
{
 Planner test;

 if(!test.Start("input.txt", "output.txt")) cout << "Error Occured.. (no route or input error)";

 return 0;
}

Planner.h

cpp
#ifndef PLANNER_H
#define PLANNER_H

#include <iostream>
#include <fstream>
#include <queue>
#include <string>
#include <stack>
using namespace std;


class Edge // list에 이용될 클래스
{
public:
 int id;
 int cost;
 int time;
 Edge* next;
};

class Planner
{
private:
 Edge* graph; // 여행 경로 그래프
 queue<int> main_route; // 주 여행 경로 (여행지 번호;V)
 queue<Edge*> route; // 최적의 여행 경로 (엣지;E)
 int* found; // 경로 찾기에서 해당되거나 제외될 여행지 번호를 저장

 int number_of_vertices; // 여행지 갯수
 int number_of_edges; // 여행지간 연결 간선의 갯수
 double Wm,Wt; // 소요비용과 시간의 만족도 가중치 (Wm+Wt=1)

 double Utility(Edge* e) { return (e->cost*Wm + e->time*Wt); }
public:
 bool Start(char* filename, char *outname); // 파일을 읽어서 경로를 찾고 출력하는 함수
 bool DijkstraAlgorithm(int start, int end); // shortest(여기선 lowest) path 알고리즘
};
#endif

Planner.cpp

cpp
#include "Planner.h"

bool Planner::Start(char *filename, char *outname) // filename -> input 파일 이름
{
 fstream File,Output;
 char c;
 int n,t,start,end,total,dup;
 int s[4];
 double r;
 Edge* temp;
 string output_route;

 // 파일 읽고 클래스 멤버에 저장, 멤버 초기화
 File.open(filename,ios::in);
 if(!File) return false;

 n = 0;
 //t = 0;
 do
 {
  t=0;
  switch(n)
  {
  case 0:
   File >> number_of_vertices;
   File.get(c);
   n++;
   break;
  case 1:
   do
   {
    File.get(c);
    if(c == ' ') 
    {
     main_route.push(t);
     t = 0;
     continue;
    }
    else if(c == '\n')
    {
     main_route.push(t);
     break;
    }
    else
    {
    t = t * 10;
    t = t + (c - '0');
    }
   } while(1);
   graph = new Edge[number_of_vertices];
   found = new int[number_of_vertices];
   for(int i=0;i<number_of_vertices;i++)
   {
    graph[i].id = i+1;
    graph[i].cost = 0;
    graph[i].time = 0;
    graph[i].next = NULL;
   }
   n++;
   break;
  case 2:
   File >> number_of_edges;
   File.get(c);
   n++;
   break;
  case 3:
   for(int i=0;i<number_of_edges;i++)
   {
    File >> s[0];
    File >> s[1];
    File >> s[2];
    File >> s[3];
    File.get(c);
   
    for(temp=&graph[s[0]-1];temp->next;temp=temp->next) {};    
    temp->next = new Edge();
    temp = temp->next;

    temp->id = s[1];
    temp->cost = s[2];
    temp->time = s[3];
    temp->next = NULL;
   }
   n++;
   break;
  case 4:
   File >> r;
   Wm = r;
   File >> r;
   Wt = r;
   n++;
   break;
  default:
   return false;
  }
 } while(n != 5);
 File.close();
 // 파일 읽기 끝

 // 시작점과 도착점 저장
 start = main_route.front();
 end = main_route.back();
 // 끝


 // 결과 경로 큐에 시작점을 미리 넣어둔다.
 route.push(&graph[start-1]);


 // 도착점을 찾을때까지 경로를 결과루트에 저장
 while(route.back()->id != end)
 {
  // 주요 경로 차례대로 경로를 찾기 위해 큐를 순환시킨다
  main_route.push(main_route.front());
  main_route.pop();
  // 끝

  // 주요경로1 -> 주요경로2 까지의 경로를 찾는다. (함수 내에서 결과 루트 큐에 엣지 포인터를 집어넣음
  if(!this->DijkstraAlgorithm(route.back()->id,main_route.front())) return false;

 }

 // 주요 경로 큐를 올바른 순서로 되돌리려고 사용
 main_route.push(main_route.front());
 main_route.pop();
 //끝

 
 // 출력하기 위해 계산 및 기록하는 부분
 Output.open(outname,ios::out);

 // 방문 배열 초기화~_~
 for(int i=0;i<number_of_vertices;i++)
  found[i] = 0;
 //끝

 total = dup = 0; // total = 총 방문한 도시 수, not_main = 중복 방문 도시 수
 r = 0; // Total utility of final route!!!!!! (double) 
 while(!route.empty()) // 결과 경로 큐가 비어있을때까지 (모든 결과 경로 출력)
 {
  total++;
  r = r + this->Utility(route.front()); // total utility 계산 (누적)
  c = route.front()->id + '0'; // 여행지 번호를 문자로 변환

  // 주요 경로인지 아닌지 확인한다
  // (주 경로도 다시 방문할 수 있으므로 주석처리)
 // is_main = false;
 // for(int i=0,s=main_route.size();i<s;i++)
 // {
 //  if(main_route.front() == route.front()->id) 
 //  {
 //   is_main=true; // 주요 경로이면 true.. (나중에쓰임)
 //   break;
 //  }
//
//   // 주요 경로 큐 순환
  // 주석처리로인해 할 필요 없다.
//   main_route.push(main_route.front());
//   main_route.pop();
//  }
  //끝

  // 중복 방문 도시인지 확인
  if(found[route.front()->id-1])
   dup++;
  else
   found[route.front()->id-1] = 1;
  //끝

  // 경로를 순차적으로 출력하는 부분을 위해 string에 저장
  if(route.front()->cost == 0 && route.front()->time == 0) 
   // cost와 time이 0인 엣지라면 (list에서 graph배열의 엣지 값은 0이다. 즉 출발점;Vertices)
   // graph배열의 next 값 부터 weighted edge..
  {
   output_route = "(";
   output_route = output_route + c;
   output_route = output_route + ")";
   main_route.pop();
  }
  else // 시작점이 아니면 ( 화살표 추가 )
  {
   if(!main_route.empty()&&main_route.front() == route.front()->id) // 주요 경로에 포함되어있다면 괄호 추가 (단 한번만)
   {
    output_route = output_route + "->(";
    output_route = output_route + c;
    output_route = output_route + ")";
    main_route.pop();
   }
   else // 아니면 그냥 출력.
   {
    output_route = output_route + "->";
    output_route = output_route + c;
  
   }
  }

  route.pop(); // 결과 경로 큐에서 삭제
 }

 // 소숫점 둘째자리까지 표시
 Output.precision(2); // 2 digit
 Output.setf(ios::fixed,ios::floatfield);
 cout.precision(2); // 2 digit
 cout.setf(ios::fixed,ios::floatfield);
 //끝

 // 쓰기 (파일&화면)
 Output << r << endl;
 Output << output_route << endl;
 Output << "(" << total << ")-(" << dup << ")=(" << total-dup << ")";
 cout << r << endl;
 cout << output_route << endl;
 cout << "(" << total << ")-(" << dup << ")=(" << total-dup << ")";


 Output.close();
 cout << endl << endl;
 // 출력 끝!


 // 초기화
 while(!main_route.empty())
  main_route.pop();

 while(!route.empty())
  route.pop();
 // 끝

 return true;
}

bool Planner::DijkstraAlgorithm(int start, int end)
{

//    id와 graph 배열의 인덱스는 1만큼 차이가 난다
//   id-1 = index!!!
   

 int minpos,v; //minpos = U가 최소인 vertices의 인덱스
 double min; // U(i)의 최소값
 int* tree; // 백트래킹 배열
 stack<int> box; // 백트래킹 결과를 뒤집어 줄 stack

 // 백트래킹 배열 초기화
 tree = new int[number_of_vertices];

 // graph,fount 초기화
 for(int i=0;i<number_of_vertices;i++)
 {
  found[i] = 0;
  graph[i].time = 99999;
  graph[i].cost = 99999;
 }

 // 주 여행 경로를 클라우드에서 제외한다
 //for(int i=0,s=main_route.size();i<s;i++)
 //{
 // found[main_route.front()-1] = 1;
 // main_route.push(main_route.front());
 // main_route.pop();
 //}
 //끝

 // 시작점 초기화
 found[start-1] = 1; // 클라우드에 시작점을 포함하는 것으로 한다
 graph[start-1].cost = 0;
 graph[start-1].time = 0;
 tree[start-1] = start-1; // 백트래킹 배열 저장
 

 // 도착점 초기화 (start에서 이 함수를 호출하는 방법 때문에 end는 주 여행 경로의 번호임)
 found[end-1] = 0;

 // 시작점과 연결된 여행지에 엣지의 코스트와 타임을 대입
 for(Edge* p=graph[start-1].next;p!=NULL;p=p->next)
 {
  if(!found[p->id-1]) // 주 여행 경로가 아니거나 도착점의 경우
  {
   tree[p->id-1] = start-1;
   graph[p->id-1].cost= p->cost;
   graph[p->id-1].time = p->time;
  }
 }

 // Dijkstra Algorithm을 통한 V 업데이트
 //for(int i=0,prev=start;i<number_of_vertices-static_cast<int>(main_route.size())+2-2;i++)
  // i<n-2.. n=총 여행지 수 - 주 여행지 수 + (시작점+도착점)..
 for(int i=0,prev=start;i<number_of_vertices-2;i++)
 {
  // U가 최소인 여행지 선택
  minpos = -1;
  min = 99999;
  for(int j=0;j<number_of_vertices;j++)
  {
   if(!found[j] && this->Utility(&graph[j]) < min)
   {
    minpos = j;
    min = this->Utility(&graph[j]);    
   }
  }

  // minpos가 존재하지 않는다? input error 또는 경로가 없음
  if(minpos == -1) return false;

  // U가 최소인 여행지 클라우드에 포함시킨다
  found[minpos] = 1;

  // 선택된 여행지와 연결된 경로들로 다른 여행지의 값들을 업데이트 시킨다 
  for(Edge* p=graph[minpos].next;p!=NULL;p=p->next)
  {

   if(this->Utility(&graph[minpos]) + this->Utility(p) < this->Utility(&graph[p->id-1]))
   {
    tree[p->id-1] = minpos; // 백트래킹 배열 저장
    graph[p->id-1].cost = graph[minpos].cost + p->cost;
    graph[p->id-1].time = graph[minpos].time + p->time;
   }

  }

 prev=minpos;

 }

 // 백트래킹 패스를 stack에 넣어 순서를 원래대로 해준다 
 v=end-1;
 while(v!=tree[v])
 {
  box.push(v);
  v=tree[v];
 }
 
 // 경로에 해당되는 엣지 포인터를 결과 경로 큐에 집어넣는다!
 while(!box.empty())
 {
  for(Edge* p=graph[route.back()->id-1].next;p!=NULL;p=p->next)
  {
   if(p->id-1 == box.top())
   {
    route.push(p);
    break;
   }
  }

  box.pop();
 }

 // vertices들을 다시 0으로 초기화한다.. (안하면 Dijkstra algorithm 작동 안됨)
 for(int i=0;i<number_of_vertices;i++)
 {
  graph[i].cost = 0;
  graph[i].time = 0;
 }
 

 //return false;
 return true;
}