1. 程式人生 > 其它 >20202301 2021-2022-1 《資料結構與面向物件程式設計》實驗九報告

20202301 2021-2022-1 《資料結構與面向物件程式設計》實驗九報告

課程:《程式設計與資料結構》
班級: 2023
姓名: 賈奕琦
學號:20202301
實驗教師:王志強
實驗日期:2021年12月19日
必修/選修: 必修

1.實驗內容

(1) 初始化:根據螢幕提示(例如:輸入1為無向圖,輸入2為有向圖)初始化無向圖和有向圖(可用鄰接矩陣,也可用鄰接表),圖需要自己定義(頂點個數、邊個數,建議先在草稿紙上畫出圖,然後再輸入頂點和邊數)(2分)
(2) 圖的遍歷:完成有向圖和無向圖的遍歷(深度和廣度優先遍歷)(4分)
(3) 完成有向圖的拓撲排序,並輸出拓撲排序序列或者輸出該圖存在環(3分)
(4) 完成無向圖的最小生成樹(Prim演算法或Kruscal演算法均可),並輸出(3分)
(5) 完成有向圖的單源最短路徑求解(迪傑斯特拉演算法)(3分)

PS:本題12分。目前沒有明確指明圖的頂點和連通邊,如果雷同或抄襲,本次實驗0分。
實驗報告中要根據所編寫的程式碼解釋圖的相關演算法

2. 實驗過程及結果

(1) 初始化:根據螢幕提示(例如:輸入1為無向圖,輸入2為有向圖)初始化無向圖和有向圖(可用鄰接矩陣,也可用鄰接表),圖需要自己定義(頂點個數、邊個數,建議先在草稿紙上畫出圖,然後再輸入頂點和邊數)(2分)

System.out.println("輸入1為無向圖,輸入2為有向圖:");
System.out.print("請輸入頂點數:");
int n, m;
char[] a = new char[100];
char[] b=new char[100];
int [] w=new int[100];
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
System.out.print("請輸入邊數:");
m = scanner.nextInt();
char[] vexs = new char[n];
for (int i = 0; i < n; i++) {
System.out.print("請輸入第" + (i + 1) + "個頂點");
vexs[i] = scanner.next().charAt(0);
System.out.println();
}
EData[] bian = new EData[m];
for (int j = 0; j <= m-1; j++) {
System.out.print("請輸入第" + (j + 1) + "條邊的資訊");
a[j]= scanner.next().charAt(0);
b[j]= scanner.next().charAt(0);
w[j]= scanner.nextInt();
bian[j]= new EData(a[j], b[j], w[j]);
}
Sorting list;
list = new Sorting(vexs, bian);


實現:
public Sorting(char[] dingdian, EData[] bian) {

int lenv = dingdian.length;//頂點的數量
int elen = bian.length;//邊的數量

// 初始化頂點
Node = new N[lenv];
for (int i = 0; i < Node.length; i++) {
Node[i] = new N();
Node[i].dingdian = dingdian[i];
Node[i].firstX = null;
}

// 初始化邊
Edge = elen;
for (int i = 0; i < elen; i++) {
char c1 = bian[i].start;
char c2 = bian[i].end;
int weight = bian[i].weight;
int p1 = pG(c1);
int p2 = pG(c2);
Bian node1 = new Bian();
node1.i = p2;
node1.w = weight;
if (Node[p1].firstX == null)
Node[p1].firstX = node1;
else
Connect(Node[p1].firstX, node1);
Bian node2 = new Bian();
node2.i = p1;
node2.w = weight;
if (Node[p2].firstX == null)
Node[p2].firstX = node2;
else
Connect(Node[p2].firstX, node2);
}
}

private void Connect(Bian list, Bian node) {
Bian p = list;

while (p.nextX != null)
p = p.nextX;
p.nextX = node;
}

private int pG(char ch) {
for (int i = 0; i < Node.length; i++)
if (Node[i].dingdian == ch)
return i;
return -1;
}

(2) 圖的遍歷:完成有向圖和無向圖的遍歷(深度和廣度優先遍歷)(4分)

程式碼:

//深度優先
private void DFS(int i, boolean[] BL) {
Bian node;

BL[i] = true;
System.out.printf("%c ", Node[i].dingdian);
node = Node[i].firstX;
while (node != null) {
if (!BL[node.i])
DFS(node.i, BL);
node = node.nextX;
}
}

public void DFS() {
boolean[] BL = new boolean[Node.length];
for (int i = 0; i < Node.length; i++)
BL[i] = false;


for (int i = 0; i < Node.length; i++) {
if (!BL[i])
DFS(i, BL);
}
System.out.printf("\n");
}
先遍歷一支,將節點直接輸出,再返回上一個節點,尋找下一個分支,重新遍歷輸出

/*
廣度優先
*/
public void BFS() {
int head = 0;
int rear = 0;
int[] queue = new int[Node.length];
boolean[] BL = new boolean[Node.length];
for (int i = 0; i < Node.length; i++)
BL[i] = false;


for (int i = 0; i < Node.length; i++) {
if (!BL[i]) {
BL[i] = true;
System.out.printf("%c ", Node[i].dingdian);
queue[rear++] = i; // 入佇列
}

while (head != rear) {
int j = queue[head++]; // 出佇列
Bian node = Node[j].firstX;
while (node != null) {
int k = node.i;
if (!BL[k]) {
BL[k] = true;
System.out.printf("%c ", Node[k].dingdian);
queue[rear++] = k;
}
node = node.nextX;
}
}
}
System.out.printf("\n");
}

一層層遍歷輸出


(3) 完成有向圖的拓撲排序,並輸出拓撲排序序列或者輸出該圖存在環(3分)

//Kahn演算法
private static class KahnTopo {
private List<Node> result; // 用來儲存結果集
private Queue<Node> setOfZeroIndegree; // 用來儲存入度為0的頂點
private Graph graph;

//建構函式,初始化
public KahnTopo(Graph di) {
this.graph = di;
this.result = new ArrayList<Node>();
this.setOfZeroIndegree = new LinkedList<Node>();
// 對入度為0的集合進行初始化
for(Node iterator : this.graph.vertexSet){
if(iterator.pathIn == 0){
this.setOfZeroIndegree.add(iterator);
}
}
}

//拓撲排序處理過程
private void process() {
while (!setOfZeroIndegree.isEmpty()) {
Node v = setOfZeroIndegree.poll();

// 將當前頂點新增到結果集中
result.add(v);

if(this.graph.adjaNode.keySet().isEmpty()){
return;
}

// 遍歷由v引出的所有邊
for (Node w : this.graph.adjaNode.get(v) ) {
// 將該邊從圖中移除,通過減少邊的數量來表示
w.pathIn--;
if (0 == w.pathIn) // 如果入度為0,那麼加入入度為0的集合
{
setOfZeroIndegree.add(w);
}
}
this.graph.vertexSet.remove(v);
this.graph.adjaNode.remove(v);
}

// 如果此時圖中還存在邊,那麼說明圖中含有環路
if (!this.graph.vertexSet.isEmpty()) {
System.out.println("Has Cycle !");

}
}

//結果集
public Iterable<Node> getResult() {
return result;
}
}
對入度為0的節點進行刪除與入隊操作,再對該節點的所有邊進行刪除,再對入度為0的節點進行刪除和入隊,重複上述操作,直到全部入隊


(4) 完成無向圖的最小生成樹(Prim演算法或Kruscal演算法均可),並輸出(3分)


public void kruskal(int num) {
int index = 0;
int[] vends = new int[num]; // 用於儲存"已有最小生成樹"中每個頂點在該最小樹中的終點。
EData[] rets = new EData[num]; // 結果陣列,儲存kruskal最小生成樹的邊
EData[] edges; // 圖對應的所有邊

// 獲取"圖中所有的邊"
edges = getEdges();
// 將邊按照"權"的大小進行排序(從小到大)
sortEdges(edges, num);

for (int i=0; i<num; i++) {
int p1 = gPs(edges[i].start); // 獲取第i條邊的"起點"的序號
int p2 = gPs(edges[i].end); // 獲取第i條邊的"終點"的序號

int m = getEnd(vends, p1); // 獲取p1在"已有的最小生成樹"中的終點
int n = getEnd(vends, p2); // 獲取p2在"已有的最小生成樹"中的終點
// 如果m!=n,意味著"邊i"與"已經新增到最小生成樹中的頂點"沒有形成環路
if (m != n) {
vends[m] = n; // 設定m在"已有的最小生成樹"中的終點為n
rets[index++] = edges[i]; // 儲存結果
}
}
// 統計並列印"kruskal最小生成樹"的資訊
int length = 0;
for (int i = 0; i < index; i++)
length += rets[i].weight;
System.out.printf("Kruskal=%d: ", length);
for (int i = 0; i < index; i++)
System.out.printf("(%c,%c) ", rets[i].start, rets[i].end);
System.out.printf("\n");
}
// 獲取i的終點
private int getEnd(int[] vends, int p1) {
while (vends[p1] != 0)
p1 = vends[p1];
return p1;
}
private void sortEdges(EData[] edges, int elen) {

for (int i = 0; i < elen-1; i++) {
for (int j = i + 1; j < elen; j++) {

if (edges[i].weight > edges[j].weight) {
// 交換"邊i"和"邊j"
EData tmp = edges[i];
edges[i] = edges[j];
edges[j] = tmp;
}
}
}
}
private EData[] getEdges() {
int index = 0;
EData[] edges;

edges = new EData[Edge];
for (int i = 0; i < Node.length; i++) {

Bian node = Node[i].firstX;
while (node != null) {
if (node.i > i) {
edges[index++] = new EData(Node[i].dingdian, Node[node.i].dingdian, node.w);
}
node = node.nextX;
}
}

return edges;
}

private int getWeight(int start, int end) {

if (start == end)
return 0;

Bian node = Node[start].firstX;
while (node != null) {
if (end == node.i)
return node.w;
node = node.nextX;
}

return INF;
}
private int gPs(char ch) {
for(int i=0; i<Node.length; i++)
if(Node[i].dingdian==ch)
return i;
return -1;
}

對所有邊的權值進行比較,將最小的邊的兩頂點存入陣列,重複上述操作,直至所有頂點全部進入陣列,且沒有環


(5) 完成有向圖的單源最短路徑求解(迪傑斯特拉演算法)(3分)

public void dijkstra(int s, int[] q, int[] t) {
// flag[i]=true→最短路徑獲取。
boolean[ ] flag = new boolean[Node.length];

// 初始化
for (int i = 0; i < Node.length; i++) {
flag[i] = false;
q[i] = 0; // 頂點i的前一個頂點為0。
t[i] = getWeight(s, i);
}


flag[s] = true;
t[s] = 0;


int k = 0;
for (int i = 1; i < Node.length; i++) {
// 尋找當前最小的路徑;
int min = INF;
for (int j = 0; j < Node.length; j++) {
if (flag[j] == false && t[j] < min) {
min = t[j];
k = j;
}
}
// 獲取到最短路徑
flag[k] = true;
for (int j = 0; j < Node.length; j++) {
int tmp = getWeight(k, j);
tmp = (tmp == INF ? INF : (min + tmp));
if (flag[j] == false && (tmp < t[j])) {
t[j] = tmp;
q[j] = k;
}
}
}

//print
System.out.printf("dijkstra(%c): \n", Node[s].dingdian);
for (int i = 0; i < Node.length; i++)
System.out.printf(" (%c, %c)=%d\n", Node[s].dingdian, Node[i].dingdian, t[i]);
}

初始化dis[start]=0
找出一個與start點距離dis最小的未確定最短路徑的點x,標記它為已經確定的點
遍歷所有以x為起點的邊得到(x, y, d),如果dis[y] > dis[x] + d, 則更新dis[y] = dis[x] + d
重複2,3步直到所有的點都被標記為確定最短路徑的點

鄰接表:

public linjiebiao_yes(char[] vertexs, char[][] edges) {
size = vertexs.length;
this.vertexLists = new LinkedList[size];

for (int i = 0; i < size; i++) {
this.vertexLists[i] = new LinkedList<Character>();
vertexLists[i].add(vertexs[i]);
}

for (char[] c : edges) {
int p = getPosition(c[0]);
this.vertexLists[p].add(c[1]);
}

}

private int getPosition(char ch) {
for (int i = 0; i < size; i++)
if (vertexLists[i].get(0) == ch)
return i;
return -1;
}

public void print() {
for (int i = 0; i < size; i++) {
LinkedList<Character> temp = vertexLists[i];
for (int j = 0; j < temp.size(); j++) {
System.out.print(temp.get(j));
if (j + 1 < temp.size())
System.out.print("→");
}
System.out.println();
}
}

3. 實驗過程中遇到的問題和解決過程

1.拓撲排序的成環情況時,出現斷點。後將最開始使用的人為丟擲異常

throw new IllegalArgumentException("Has Cycle !");

方法,改為直接輸出

其他(感悟、思考等)

在這次實驗進行過程中,我學會了一個很重要的道理,女人何苦為難自己!!!

換個思路說不定他就過了!

最後一次實驗了,做的依然很痛苦。。。

我以後的路還有很長,未來的痛苦還有很多,這門課結束了,但學生必將謹記老師教的所有道理,不管是做事或求學,都要學會,該偷懶的偷懶,該努力的絕不偷懶。

願未來一路順利。

參考資料



- 《Java程式設計教程(第九版)》

- 《Java軟體結構與資料結構(第四版)》