江蘇科技大學
《資料結構》實驗報告
(2024/2025學年第1學期)
學生姓名:
學生學號:
院 系: 計算機學院
專 業:
考核得分:
2024 年 12 月
實驗一 線性表的操作
一、實驗目的
掌握線性表的基本操作在儲存結構上的實現,其中以單連結串列的操作作為重點。
二、實驗題目
1.以單連結串列作為儲存結構,實現線性表的就地逆置。
2. 建立一個非遞減序(有重複值)的單連結串列,實現刪除值相同的多餘結點。
三、實驗預習的核心演算法虛擬碼
· 單連結串列就地逆置
reverseList(head):
prev = NULL
curr = head
while curr != NULL:
next = curr.next
curr.next = prev
prev = curr
curr = next
head = prev
刪除重複節點
removeDuplicates(head):
current = head
while current != NULL:
runner = current
while runner.next != NULL:
if runner.next.value == current.value:
runner.next = runner.next.next
else:
runner = runner.next
current = current.next
四、實驗源程式
單連結串列逆置
#include
using namespace std;
struct Node {
int data;
Node* next;
Node(int value) : data(value), next(nullptr) {}
};
// 逆置單連結串列
void reverseList(Node*& head) {
Node* prev = nullptr;
Node* curr = head;
Node* next = nullptr;
while (curr != nullptr) {
next = curr->next; // 儲存當前節點的下一個節點
curr->next = prev; // 反轉當前節點的指標
prev = curr; // prev向前移動
curr = next; // curr向前移動
}
head = prev; // 更新頭指標
}
// 列印連結串列
void printList(Node* head) {
Node* temp = head;
while (temp != nullptr) {
cout << temp->data << " ";
temp = temp->next;
}
cout << endl;
}
int main() {
// 建立一個簡單連結串列:1 -> 2 -> 3 -> 4
Node* head = new Node(1);
head->next = new Node(2);
head->next->next = new Node(3);
head->next->next->next = new Node(4);
cout << "Original list: ";
printList(head);
reverseList(head);
cout << "Reversed list: ";
printList(head);
return 0;
}
刪除重複節點
#include
using namespace std;
struct Node {
int data;
Node* next;
Node(int value) : data(value), next(nullptr) {}
};
// 刪除重複節點
void removeDuplicates(Node* head) {
Node* current = head;
while (current != nullptr) {
Node* runner = current;
while (runner->next != nullptr) {
if (runner->next->data == current->data) {
// 刪除重複節點
Node* temp = runner->next;
runner->next = runner->next->next;
delete temp;
} else {
runner = runner->next;
}
}
current = current->next;
}
}
// 列印連結串列
void printList(Node* head) {
Node* temp = head;
while (temp != nullptr) {
cout << temp->data << " ";
temp = temp->next;
}
cout << endl;
}
int main() {
// 建立帶有重複值的連結串列:1 -> 1 -> 2 -> 2 -> 3
Node* head = new Node(1);
head->next = new Node(1);
head->next->next = new Node(2);
head->next->next->next = new Node(2);
head->next->next->next->next = new Node(3);
cout << "Original list: ";
printList(head);
removeDuplicates(head);
cout << "List after removing duplicates: ";
printList(head);
return 0;
}
五、實驗結果與分析(分析出錯的原因與除錯過程等)
- 單連結串列逆置:
- 透過遍歷連結串列並反轉每個節點的指向,最終實現連結串列順序的逆轉。逆置操作時需要小心指標的更新,確保每個節點都正確地指向其前驅。
- 若指標操作不當(例如錯誤更新prev、curr或next),可能導致連結串列斷裂或程式崩潰。
- 刪除重複節點:
- 使用雙指標法,current指標用來遍歷連結串列,runner指標用來檢查是否有重複節點。如果有重複元素,直接刪除該節點。
- 在刪除節點時,要注意更新指標,否則可能導致連結串列斷裂。
六、實驗心得(個性化總結實驗收穫,此處切忌套路或參考,實在覺得沒有任何收穫,可不寫;另,學有餘力的同學,在該部分可以給出與該次實驗內容相關的自主擴充內容與結果。)
· 連結串列操作的基礎:
- 本實驗幫助我加深了對單連結串列的理解,特別是如何透過指標操作改變連結串列結構。逆置操作中的指標交換以及刪除重複節點中的指標更新是連結串列操作中的關鍵。
· 除錯過程的挑戰:
- 在實驗過程中,我遇到了指標操作不當導致連結串列結構出錯的問題。例如,在刪除節點時,如果忘記更新指標,就會導致連結串列斷裂,無法正常遍歷。
· 自主擴充:
- 透過學習連結串列的基本操作,我意識到連結串列結構的強大。未來我可以進一步研究如何最佳化連結串列的操作,比如在刪除重複節點時使用雜湊表以減少遍歷的時間複雜度。
· 對資料結構的理解:
- 透過這次實驗,我更加理解了連結串列的動態性和連結串列操作的基本原理。在今後的學習中,我會繼續深入研究其他連結串列相關的操作,如合併連結串列、連結串列排序等。
實驗二 棧和佇列的操作
一、實驗目的
掌握棧和佇列的儲存結構、操作特性及實現方法。
二、實驗題目
1.設從鍵盤輸入一個整數序列:a1, a2, …,an,編寫程式實現:採用鏈棧結構儲存輸入的整數,當ai ≠-1時,將ai進棧;當ai=-1時,輸出棧頂整數並出棧。演算法應對異常情況給出相應的資訊。
2.設以不帶頭結點的迴圈連結串列表示佇列,並且只設一個指標指向隊尾結點,
但不設頭指標。編寫相應的入隊和出隊程式。
三、實驗預習的核心演算法虛擬碼
· 鏈棧操作
- 棧的定義:
struct Node {
int data;
Node* next;
};
- 進棧操作(push)
arduino
複製程式碼
push(stack, value):
create a new node
new_node.data = value
new_node.next = stack.top
stack.top = new_node
- 出棧操作(pop)
pop(stack):
if stack is empty:
output "Stack underflow" and return
top_node = stack.top
stack.top = stack.top.next
delete top_node
· 迴圈連結串列佇列操作
- 佇列的定義:
arduino
複製程式碼
struct Node {
int data;
Node* next;
};
- 入隊操作(enqueue)
enqueue(queue, value):
create a new node
new_node.data = value
if queue is empty:
new_node.next = new_node
queue.tail = new_node
else:
new_node.next = queue.tail.next
queue.tail.next = new_node
queue.tail = new_node
- 出隊操作(dequeue)
dequeue(queue):
if queue is empty:
output "Queue underflow" and return
front_node = queue.tail.next
if queue.tail == queue.tail.next:
queue.tail = NULL // last element
else:
queue.tail.next = front_node.next
delete front_node
四、實驗源程式
鏈棧實現
#include
using namespace std;
// 鏈棧節點定義
struct Node {
int data;
Node* next;
};
// 鏈棧類
class Stack {
private:
Node* top;
public:
Stack() : top(nullptr) {}
// 進棧操作
void push(int value) {
Node* newNode = new Node();
newNode->data = value;
newNode->next = top;
top = newNode;
cout << value << " pushed to stack" << endl;
}
// 出棧操作
void pop() {
if (top == nullptr) {
cout << "Stack underflow!" << endl;
return;
}
Node* temp = top;
cout << "Popped from stack: " << top->data << endl;
top = top->next;
delete temp;
}
// 列印棧
void print() {
Node* temp = top;
if (temp == nullptr) {
cout << "Stack is empty" << endl;
return;
}
while (temp != nullptr) {
cout << temp->data << " ";
temp = temp->next;
}
cout << endl;
}
};
int main() {
Stack s;
int num;
// 輸入資料
cout << "Enter integers to push to the stack. Enter -1 to pop." << endl;
while (true) {
cout << "Enter an integer: ";
cin >> num;
if (num == -1) {
s.pop();
} else {
s.push(num);
}
}
return 0;
}迴圈連結串列佇列實現
#include
using namespace std;
// 佇列節點定義
struct Node {
int data;
Node* next;
};
// 佇列類
class Queue {
private:
Node* tail;
public:
Queue() : tail(nullptr) {}
// 入隊操作
void enqueue(int value) {
Node* newNode = new Node();
newNode->data = value;
if (tail == nullptr) {
newNode->next = newNode; // 指向自己,表示佇列只有一個元素
tail = newNode;
} else {
newNode->next = tail->next;
tail->next = newNode;
tail = newNode;
}
cout << value << " enqueued to queue" << endl;
}
// 出隊操作
void dequeue() {
if (tail == nullptr) {
cout << "Queue underflow!" << endl;
return;
}
Node* frontNode = tail->next;
if (tail == frontNode) { // 只有一個元素
tail = nullptr;
} else {
tail->next = frontNode->next;
}
cout << "Dequeued from queue: " << frontNode->data << endl;
delete frontNode;
}
// 列印佇列
void print() {
if (tail == nullptr) {
cout << "Queue is empty" << endl;
return;
}
**
Node* temp = tail->next;
do {
cout << temp->data << " ";
temp = temp->next;
} while (temp != tail->next);
cout << endl;
}
};
int main() {
Queue q;
int num;
// 輸入資料
cout << "Enter integers to enqueue to the queue. Enter -1 to dequeue." << endl;
while (true) {
cout << "Enter an integer: ";
cin >> num;
if (num == -1) {
q.dequeue();
} else {
q.enqueue(num);
}
}
return 0;
}
五、實驗結果與分析
· 鏈棧:
- 棧的實現透過鏈式儲存來儲存資料,進棧時將新元素插入棧頂,出棧時刪除棧頂元素。
- 異常處理:當棧為空時,執行出棧操作會觸發Stack underflow提示。
· 迴圈連結串列佇列:
- 使用迴圈連結串列實現佇列,tail指向佇列的最後一個元素,tail.next即為隊頭元素。入隊操作將新元素插入到隊尾,出隊操作移除隊頭元素。
- 異常處理:當佇列為空時,執行出隊操作會觸發Queue underflow提示。
六、實驗心得
· 對棧和佇列的理解:
- 本實驗加深了我對棧和佇列的理解,特別是鏈式儲存方式如何實現這兩種資料結構。鏈棧和迴圈連結串列佇列是非常高效的動態儲存結構。
· 鏈式儲存結構的優勢:
- 相比於順序儲存,鏈式儲存結構在棧和佇列的操作中不需要提前分配固定大小的記憶體,動態性更強。
· 除錯經驗:
- 除錯過程中遇到過一些指標操作錯誤(如迴圈連結串列尾指標更新不當),透過細心檢查指標的修改,逐步排除問題,最終順利實現了功能。
· 擴充套件與實踐:
- 學會了如何實現鏈棧和迴圈連結串列佇列的基本操作,並能夠擴充套件實現其他功能,如佇列的大小監控、棧的最小值查詢等。
實驗三 二叉樹
一、實驗目的
二、實驗題目
編寫程式實現以下功能:
1.建立一棵二叉樹,所有值均為整數(採用二叉連結串列儲存方式);
2.輸出該二叉樹的前序、中序、後序遍歷序列;
3. 把所有值為負數的結點修改為正數。
三、實驗預習的核心演算法虛擬碼
· 二叉樹的基本結構
- 每個結點包含:資料域(int 型別)和兩個指標(左子樹指標、右子樹指標)。
cpp
複製程式碼
struct TreeNode {
int data;
TreeNode *left;
TreeNode *right;
TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};
· 前序遍歷(Pre-order Traversal) 虛擬碼:
scss
複製程式碼
PreOrderTraversal(root):
if root is not null:
visit(root)
PreOrderTraversal(root.left)
PreOrderTraversal(root.right)
· 中序遍歷(In-order Traversal) 虛擬碼:
scss
複製程式碼
InOrderTraversal(root):
if root is not null:
InOrderTraversal(root.left)
visit(root)
InOrderTraversal(root.right)
· 後序遍歷(Post-order Traversal) 虛擬碼:
scss
複製程式碼
PostOrderTraversal(root):
if root is not null:
PostOrderTraversal(root.left)
PostOrderTraversal(root.right)
visit(root)
· 修改所有負數結點為正數 虛擬碼:
kotlin
複製程式碼
ModifyNegativeNodes(root):
if root is not null:
if root.data < 0:
root.data = -root.data
ModifyNegativeNodes(root.left)
ModifyNegativeNodes(root.right)
四、實驗源程式
#include
using namespace std;
// 二叉樹結點結構體
struct TreeNode {
int data;
TreeNode *left, *right;
TreeNode(int val) : data(val), left(nullptr), right(nullptr) {}
};
// 前序遍歷
void PreOrderTraversal(TreeNode* root) {
if (root != nullptr) {
cout << root->data << " ";
PreOrderTraversal(root->left);
PreOrderTraversal(root->right);
}
}
// 中序遍歷
void InOrderTraversal(TreeNode* root) {
if (root != nullptr) {
InOrderTraversal(root->left);
cout << root->data << " ";
InOrderTraversal(root->right);
}
}
// 後序遍歷
void PostOrderTraversal(TreeNode* root) {
if (root != nullptr) {
PostOrderTraversal(root->left);
PostOrderTraversal(root->right);
cout << root->data << " ";
}
}
// 修改所有負數結點為正數
void ModifyNegativeNodes(TreeNode* root) {
if (root != nullptr) {
if (root->data < 0) {
root->data = -root->data;
}
ModifyNegativeNodes(root->left);
ModifyNegativeNodes(root->right);
}
}
// 建立一個簡單的二叉樹
TreeNode* CreateSampleTree() {
TreeNode* root = new TreeNode(-1);
root->left = new TreeNode(2);
root->right = new TreeNode(-3);
root->left->left = new TreeNode(-4);
root->left->right = new TreeNode(5);
root->right->right = new TreeNode(-6);
return root;
}
int main() {
// 建立二叉樹
TreeNode* root = CreateSampleTree();
// 輸出遍歷結果
cout << "前序遍歷: ";
PreOrderTraversal(root);
cout << endl;
cout << "中序遍歷: ";
InOrderTraversal(root);
cout << endl;
cout << "後序遍歷: ";
PostOrderTraversal(root);
cout << endl;
// 修改負數結點為正數
ModifyNegativeNodes(root);
cout << "修改後,前序遍歷: ";
PreOrderTraversal(root);
cout << endl;
return 0;
}
五、實驗結果與分析
· 在修改負數結點後,原本的負數值(如 -1, -4, -3, -6)都變為了對應的正數(如 1, 4, 3, 6)。
· 三種遍歷(前序、中序、後序)的輸出順序符合預期,能夠正確反映二叉樹的結構。
六、實驗心得
- 透過這個實驗,加深了對二叉樹結構和遍歷方式的理解。
- 實現過程中,遇到了一些問題,例如遍歷順序理解的細節,但透過查閱資料和不斷除錯,最終掌握瞭如何正確實現前序、中序和後序遍歷。
- 修改負數結點為正數的操作也讓我對遞迴操作有了更深的理解,遞迴是處理樹結構時常用且有效方法。
實驗四 圖
一、實驗目的
熟悉圖的儲存結構,掌握圖的遍歷及實現。
二、實驗題目
編寫程式實現以下功能:
1.建立一個無向圖(採用鄰接矩陣或者鄰接表方式儲存);
2.分別輸出從結點0開始的一個深度優先遍歷序列和一個廣度優先遍歷序列。
三、實驗預習的核心演算法虛擬碼
// 初始化鄰接矩陣
function initializeGraph(numNodes):
matrix = [[0 for i in range(numNodes)] for j in range(numNodes)]
return matrix
function DFS(graph, node, visited):
mark node as visited
print(node)
for each neighbor in graph[node]:
if neighbor is not visited:
DFS(graph, neighbor, visited)
function BFS(graph, startNode):
create a queue
enqueue startNode
mark startNode as visited
while queue is not empty:
node = dequeue from queue
print(node)
for each neighbor in graph[node]:
if neighbor is not visited:
enqueue neighbor
mark neighbor as visited
四、實驗源程式
#include
#include
#include
using namespace std;
const int NUM_NODES = 6;
// 初始化圖的鄰接矩陣
void initializeGraph(vector<vector
graph[0][1] = 1; graph[1][0] = 1;
graph[0][2] = 1; graph[2][0] = 1;
graph[1][2] = 1; graph[2][1] = 1;
graph[1][3] = 1; graph[3][1] = 1;
graph[2][4] = 1; graph[4][2] = 1;
graph[3][4] = 1; graph[4][3] = 1;
graph[3][5] = 1; graph[5][3] = 1;
graph[0][5] = 1; graph[5][0] = 1;
}
// 深度優先遍歷(DFS)
void DFS(const vector<vector
visited[node] = true;
cout << node << " ";
for (int i = 0; i < NUM_NODES; ++i) {
if (graph[node][i] == 1 && !visited[i]) {
DFS(graph, i, visited);
}
}
}
// 廣度優先遍歷(BFS)
void BFS(const vector<vector
vector
queue
q.push(startNode);
visited[startNode] = true;
while (!q.empty()) {
int node = q.front();
q.pop();
cout << node << " ";
for (int i = 0; i < NUM_NODES; ++i) {
if (graph[node][i] == 1 && !visited[i]) {
q.push(i);
visited[i] = true;
}
}
}
}
int main() {
vector<vector
initializeGraph(graph);
// 輸出從結點0開始的深度優先遍歷序列
cout << "DFS從節點0開始的遍歷序列: ";
vector
DFS(graph, 0, visited);
cout << endl;
// 輸出從結點0開始的廣度優先遍歷序列
cout << "BFS從節點0開始的遍歷序列: ";
BFS(graph, 0);
cout << endl;
return 0;
}
五、實驗結果與分析
· DFS遍歷序列(從結點0開始):
輸出順序可能為:0 1 2 4 3 5
· BFS遍歷序列(從結點0開始)
輸出順序可能為:`0 1 2 50 1 2 5 3 4
六、實驗心得
本次實驗透過鄰接矩陣的方式儲存無向圖,使圖的每條邊在矩陣中有對應的位置。理解了鄰接矩陣在圖的儲存中具有固定大小的特點,適合稠密圖的情況。
實驗五 查詢
一、實驗目的
熟悉查詢的基本過程,掌握常用查詢演算法設計技巧。
二、實驗題目
1.設計順序表的順序查詢演算法,將哨兵設在下標高階。
2.構造一棵二叉排序樹,從小到大輸出樹中所有值大於x的結點值。
三、實驗預習的核心演算法虛擬碼
· 順序查詢演算法(帶哨兵)虛擬碼:
less
複製程式碼
function SequentialSearchWithSentinel(arr, target):
n = len(arr)
arr[n] = target // 將目標值設為哨兵,放在陣列末尾
i = 0
while arr[i] != target:
i = i + 1
if i < n:
return i // 找到目標,返回索引
else:
return -1 // 沒有找到目標,返回-1
說明:透過將目標值放置在陣列的最後,避免了在每次迴圈中判斷目標是否為最後一個元素的情況,最佳化了效率。
· 二叉排序樹查詢大於x的節點值虛擬碼:
php
複製程式碼
function BSTFindGreaterThanX(root, x):
if root == null:
return
if root.value > x:
BSTFindGreaterThanX(root.left, x)
print(root.value) // 輸出當前節點值
BSTFindGreaterThanX(root.right, x)
else:
BSTFindGreaterThanX(root.right, x)
說明:二叉排序樹的特點是左子樹的值小於根節點,右子樹的值大於根節點。因此,在查詢大於x的值時,我們只需要訪問右子樹和符合條件的節點即可。
四、實驗源程式
- 順序查詢演算法實現:
python
複製程式碼
def SequentialSearchWithSentinel(arr, target):
n = len(arr)
arr.append(target) # 將目標值作為哨兵新增到末尾
i = 0
while arr[i] != target:
i += 1
if i < n:
return i # 找到目標,返回索引
else:
return -1 # 沒有找到目標,返回-1
# 測試順序查詢
arr = [10, 20, 30, 40, 50]
target = 30
result = SequentialSearchWithSentinel(arr, target)
print(f"Target {target} found at index: {result}")
- 二叉排序樹的構建與查詢大於x的節點值實現:
python
複製程式碼
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(root, value):
if root is None:
return TreeNode(value)
elif value < root.value:
root.left = insert(root.left, value)
else:
root.right = insert(root.right, value)
return root
def BSTFindGreaterThanX(root, x):
if root is None:
return
if root.value > x:
BSTFindGreaterThanX(root.left, x)
print(root.value) # 輸出當前節點值
BSTFindGreaterThanX(root.right, x)
else:
BSTFindGreaterThanX(root.right, x)
# 構建二叉排序樹
root = None
values = [20, 10, 30, 5, 15, 25, 35]
for value in values:
root = insert(root, value)
# 查詢大於 15 的節點
print("Nodes with values greater than 15:")
BSTFindGreaterThanX(root, 15)
五、實驗結果與分析
- 順序查詢結果:
- 輸入陣列:[10, 20, 30, 40, 50]
- 查詢目標:30
- 輸出:Target 30 found at index: 2
- 結果表明,順序查詢能夠正確找到目標元素的索引位置,並且由於採用了哨兵技術,查詢效率略有提高。
- 二叉排序樹查詢結果:
- 輸入二叉排序樹:包含節點值 [20, 10, 30, 5, 15, 25, 35]
- 查詢大於 15 的節點:
- 結果表明,二叉排序樹能夠正確地輸出所有大於給定值15的節點,且輸出是按升序排列的。
六、實驗心得
透過本次實驗,我對查詢演算法有了更加深入的理解。順序查詢演算法透過引入哨兵技術,減少了條件判斷的次數,使得查詢過程更加高效。而二叉排序樹則透過遞迴的方式,在樹結構中進行查詢,能夠快速定位大於某一值的節點,並按升序輸出。
- 順序查詢的最佳化:透過引入哨兵,可以使得順序查詢過程更加簡潔和高效,避免了每次判斷是否為最後元素的開銷。
- 二叉排序樹的優勢:二叉排序樹在查詢大於某一值的節點時,能夠根據樹的結構,有效地剪枝,減少不必要的遍歷。
總的來說,透過這次實驗,我加深了對查詢演算法的理解,並且透過實現這些演算法,提升了我的程式設計能力和問題分析能力。
實驗六 排序
一、實驗目的
掌握排序的基本概念,比較基於不同儲存結構下排序的演算法設計過程。
二、實驗題目
設待排序的記錄序列用單連結串列作儲存結構,編寫直接插入排序和簡單選擇排序的程式。
三、實驗預習的核心演算法虛擬碼
· 直接插入排序(Insertion Sort)虛擬碼
在連結串列中實現直接插入排序的關鍵是需要處理節點間的指標操作。
python
複製程式碼
function InsertionSort(head):
if head is None or head.next is None:
return head // 如果連結串列為空或只有一個元素,則不需要排序
**
sorted_head = None // 已排序的部分
current = head
while current is not None:
next_node = current.next // 儲存當前節點的下一個節點
if sorted_head is None or sorted_head.value >= current.value:
current.next = sorted_head
sorted_head = current
else:
sorted_ptr = sorted_head
while sorted_ptr.next is not None and sorted_ptr.next.value < current.value:
sorted_ptr = sorted_ptr.next
current.next = sorted_ptr.next
sorted_ptr.next = current
current = next_node
return sorted_head
說明:直接插入排序是將連結串列的節點逐個插入到已排序的部分。每次遍歷連結串列時,透過調整指標將當前節點插入到合適的位置。
· 簡單選擇排序(Selection Sort)虛擬碼
在連結串列中實現選擇排序時,我們需要尋找當前未排序部分中的最小元素,並將其移到已排序部分的末尾。
python
複製程式碼
function SelectionSort(head):
if head is None or head.next is None:
return head // 如果連結串列為空或只有一個元素,則不需要排序
**
current = head
while current is not None:
min_node = current
runner = current.next
while runner is not None:
if runner.value < min_node.value:
min_node = runner
runner = runner.next
**
// 交換當前節點和最小節點的值
if min_node != current:
current.value, min_node.value = min_node.value, current.value
**
current = current.next
return head
說明:簡單選擇排序是透過在未排序的部分選擇最小(或最大)元素,然後將其移到已排序部分的末尾。這裡的交換操作透過直接交換節點的值來實現,而不需要交換節點的指標。
四、實驗源程式
class ListNode:
def __init__(self, value=0, next=None):
self.value = value
self.next = next
# 直接插入排序
def InsertionSort(head):
if head is None or head.next is None:
return head # 如果連結串列為空或只有一個元素,則不需要排序
**
sorted_head = None # 已排序的部分
current = head
while current is not None:
next_node = current.next # 儲存當前節點的下一個節點
if sorted_head is None or sorted_head.value >= current.value:
current.next = sorted_head
sorted_head = current
else:
sorted_ptr = sorted_head
while sorted_ptr.next is not None and sorted_ptr.next.value < current.value:
sorted_ptr = sorted_ptr.next
current.next = sorted_ptr.next
sorted_ptr.next = current
current = next_node
return sorted_head
# 簡單選擇排序
def SelectionSort(head):
if head is None or head.next is None:
return head # 如果連結串列為空或只有一個元素,則不需要排序
**
current = head
while current is not None:
min_node = current
runner = current.next
while runner is not None:
if runner.value < min_node.value:
min_node = runner
runner = runner.next
**
# 交換當前節點和最小節點的值
if min_node != current:
current.value, min_node.value = min_node.value, current.value
**
current = current.next
return head
# 輔助函式:列印連結串列
def print_list(head):
current = head
while current:
print(current.value, end=" -> ")
current = current.next
print("None")
# 輔助函式:建立連結串列
def create_linked_list(values):
head = None
for value in reversed(values):
head = ListNode(value, head)
return head
# 測試程式碼
values = [4, 2, 1, 5, 3]
head = create_linked_list(values)
# 排序前連結串列
print("Original list:")
print_list(head)
# 使用直接插入排序
sorted_head = InsertionSort(head)
print("List after Insertion Sort:")
print_list(sorted_head)
# 重新建立連結串列並使用簡單選擇排序
head = create_linked_list(values)
sorted_head = SelectionSort(head)
print("List after Selection Sort:")
print_list(sorted_head)
五、實驗結果與分析
- 測試資料:
- 輸入連結串列:[4, 2, 1, 5, 3]
- 直接插入排序結果:
- 排序前:4 -> 2 -> 1 -> 5 -> 3 -> None
- 排序後:1 -> 2 -> 3 -> 4 -> 5 -> None
- 簡單選擇排序結果:
- 排序前:4 -> 2 -> 1 -> 5 -> 3 -> None
- 排序後:1 -> 2 -> 3 -> 4 -> 5 -> None
分析:
- 直接插入排序:直接插入排序透過不斷調整指標,確保每次插入的節點都在已排序部分的正確位置。對於連結串列,插入操作相對簡單,不需要移動元素,只需要調整指標。
- 簡單選擇排序:選擇排序每次都找到剩餘部分的最小節點,並將其值與當前節點交換。與直接插入排序相比,選擇排序在連結串列實現時的交換較為簡單,只需要交換節點的值而不是調整指標。
六、實驗心得
透過本次實驗,我進一步理解了排序演算法在連結串列中的實現方式和最佳化。在連結串列中實現排序時,需要特別注意指標的操作,避免對連結串列結構的破壞。
- 直接插入排序在連結串列中的實現相對簡單,每次透過指標調整將節點插入到已排序部分。
- 簡單選擇排序儘管與陣列中的實現類似,但由於連結串列的結構特性,節點的交換操作僅限於交換節點的值,而不是交換節點的位置,減少了複雜度。