[JLU] 資料結構與演算法上機題解思路分享-

陆爻齐發表於2024-07-04

前言

首先,請務必自己盡全力嘗試實現題目,直接看成品程式碼,思維就被拘束了,也很容易被查重。

這裡只是思路解析的部落格,程式碼倉庫在 JLU_Data_Structures_Record

希望你能在這裡找到你想要的:)

第三次上機


A 手撕BST

分數 50
作者 朱允剛
單位 吉林大學
對一棵初始為空的二叉查詢樹(Binary Search Tree, BST)進行若干插入或刪除操作,請輸出最後的二叉查詢樹。

bst.png

輸入格式:
輸入第一行為一個整數 T,表示運算元目。隨後 T 行,每行為Insert K(表示插入關鍵詞為K的結點,若樹中已有關鍵詞為K的結點,則不插入)或Remove K(表示刪除關鍵詞為K的結點,若樹中無關鍵詞為K的結點,則不刪除),其中K為整數。 T 不超過2×105,樹高不超過104。

輸出格式:
輸出經上述操作後得到的二叉查詢樹的中根序列和先根序列,序列中每個整數後一個空格,兩個序列之間用空行間隔。

輸入樣例:
16
Insert 17
Insert 31
Insert 13
Insert 11
Insert 20
Insert 35
Insert 25
Insert 8
Insert 4
Insert 11
Insert 24
Insert 40
Insert 27
Insert 9
Remove 17
Remove 13

輸出樣例:
4 8 9 11 20 24 25 27 31 35 40

20 11 8 4 9 31 25 24 27 35 40

程式碼長度限制
16 KB
時間限制
500 ms
記憶體限制
64 MB
棧限制
8192 KB


二叉查詢樹的插入和查詢都不算難,但刪除需要注意的是,如果要刪的節點有兩個子節點,則將該節點的右子樹中最小的節點與之交換,並執行從右子樹中刪除該最小點的任務。其餘與普通的二叉樹一樣操作。


B 手撕AVL樹(基礎版)

分數 50
作者 朱允剛
單位 吉林大學
對一棵初始為空的高度平衡樹(AVL樹)進行若干插入或刪除操作,請輸出最後得到的AVL樹。

備註:
(1)當有多種旋轉方案時,優先選擇旋轉次數少的方案。
(2)70%的測試點只包含插入操作,如果你只實現插入操作,也能獲得70%的分數。

輸入格式:
輸入第一行為一個整數 T,表示運算元目。隨後 T 行,每行為Insert K(表示插入關鍵詞為K的結點,若樹中已有關鍵詞為K的結點,則不插入)或Remove K(表示刪除關鍵詞為K的結點,若樹中無關鍵詞為K的結點,則不刪除),其中K為整數。 T 不超過2×10
5

輸出格式:
輸出經上述操作後得到的高度平衡樹的中根序列和先根序列,序列中每個整數後一個空格,兩個序列之間用空行間隔。

輸入樣例:
16
Insert 17
Insert 31
Insert 13
Insert 11
Insert 20
Insert 35
Insert 25
Insert 8
Insert 4
Insert 11
Insert 24
Insert 40
Insert 27
Insert 9
Remove 17
Remove 13
輸出樣例:
4 8 9 11 20 24 25 27 31 35 40

20 8 4 11 9 31 25 24 27 35 40
程式碼長度限制
16 KB
時間限制
100 ms
記憶體限制
64 MB
棧限制
8192 KB


二叉平衡樹的插入和刪除也是思維不復雜,但實現很麻煩的東西,我只能說,為了使程式的邏輯儘可能清晰,儘量把功能打包成函式,比如一個節點的高度什麼的


C 手撕紅黑樹

分數 50
作者 朱允剛
單位 吉林大學
對一棵初始為空的紅黑樹(Red-Black Tree, RBT)進行若干插入或刪除操作,請輸出最後的紅黑樹。

rbt.png

備註:
(1)當有多種旋轉方案時,優先選擇旋轉次數少的方案。
(2)70%的測試點只包含插入操作,如果你只實現插入操作,也能獲得70%的分數。

輸入格式:
輸入第一行為一個整數 T,表示運算元目。隨後 T 行,每行為Insert K(表示插入關鍵詞為K的結點,若樹中已有關鍵詞為K的結點,則不插入)或Remove K(表示刪除關鍵詞為K的結點,若樹中無關鍵詞為K的結點,則不刪除),其中K為整數。 T 不超過2×10
5

輸出格式:
輸出經上述操作後得到的紅黑樹的中根序列和先根序列,序列中對於每個結點輸出其關鍵詞和顏色(紅色用R表示,黑色用B表示),每個元素後一個空格,兩個序列之間用空行間隔。

輸入樣例:
5
Insert 8
Insert 9
Insert 3
Insert 4
Insert 5

輸出樣例:
3 R 4 B 5 R 8 B 9 B

8 B 4 B 3 R 5 R 9 B

程式碼長度限制
16 KB
時間限制
100 ms
記憶體限制
64 MB
棧限制
8192 KB


紅黑樹這題,也差一個點沒過,故不作思路解析


第四次上機


A 手撕STL sort(進階版)

分數 100
作者 朱允剛
單位 吉林大學
事實上,STL的sort函式在上一題(基礎版)的基礎上,還採用了下列最佳化手段,以進一步提升快速排序演算法的效率。

(3)“三數取中”選基準元素。不是選取第一個元素作為基準元素,而是在當前子陣列中選取3個元素,取中間大的那個元素作為基準元素。從而保證選出的基準元素不是子陣列的最小元素,也不是最大元素,避免Partition分到子陣列最邊上,以降低最壞情況發生的機率。
為確保本題答案唯一,本演算法的實現請以教材為準,即普林斯頓大學Sedgewick教授給出的方法:若當前子陣列是R[m]…R[n],選取R[m]、R[(m+n)/2]和R[n]的中位數作為基準元素。先將R[(m+n)/2]與R[m+1]交換;若R[m+1]比R[n]大,交換二者;若R[m]比R[n]大,交換二者;若R[m+1]比R[m]大,交換二者。

(4)尾遞迴轉為迴圈。即將傳統快速排序程式碼

void QuickSort(int R[],int m,int n){
   if(n - m + 1 > threshold){
        int j = Partition(R, m, n); 
        QuickSort(R, m, j-1);  //遞迴處理左區間
        QuickSort(R, j+1, n);  //遞迴處理右區間,尾遞迴
     }
}

轉換為

void QuickSort(int R[],int m,int n){
   while(n - m + 1 > threshold){    //注意此處不是if,而是while
        int j = Partition(R, m, n); 
        QuickSort(R, m, j-1);  //遞迴處理左區間
        m = j+1;  //透過while迴圈處理右區間,從而消除尾遞迴
     }
}

即先遞迴處理左區間,後迴圈處理右區間,從而消除一個尾遞迴,以減少遞迴呼叫帶來的時空消耗。
這裡需注意,尾遞迴轉迴圈後,轉入堆排序的時機不僅僅是遞迴深度達到2logn,而是遞迴深度和while迴圈迭代的次數加一起達到2logn時轉入堆排序。

(5)優先處理短區間。在上述策略(4)的基礎上進一步改進,不是按固定次序處理左右子區間(每次都先處理左區間、後處理右區間),而是先(透過遞迴)處理左右兩個子區間中“較短的那個區間”,然後再(透過迴圈)處理兩個子區間中“較長的那個區間”。從而使每次遞迴處理的子陣列長度至少縮減一半,使最壞情況下遞迴深度(演算法最壞情況空間複雜度)為logn。

(6)三路分劃(3-Way Partition)。當重複元素很多時,傳統快速排序效率較低。可修改Partition操作,不是把當前陣列劃分為兩部分,而是三部分:小於基準元素K的元素放在左邊,等於K的元素放在中間,大於K的元素在右邊。接下來僅需對小於K的左半部分子陣列和大於K的右半部分子陣列進行排序。中間等於K的所有元素都已就位,無需處理。
為確保本題答案唯一,此處請採用如下做法:若當前子陣列是R[m]…R[n],可設定3個指標,前指標i,中指標j,後指標k。初始時i和j指向第一個元素,k指向最後一個元素;指標j從左往右掃描陣列:

若R[j]小於基準元素,交換R[j]和R[i], i++, j++;
若R[j]大於基準元素,交換R[j]和R[k], k--;
若R[j]等於基準元素,j++;
透過指標j的遍歷,使小於基準元素的元素換到當前子陣列左側,大於基準元素的元素換到右側。當j掃描完當前子陣列後,R[m]…R[i-1]即小於基準元素的元素,R[i]…R[k]即等於基準元素的元素,R[k+1]…R[n]即大於基準元素的元素。

在本題中,請你在之前實現的STL sort()初級版的基礎上,進一步實現上述最佳化策略。

提示:本題只需把原來的Partition改為3-way Partition,並使用“三數取中”法選擇基準元素。
在快速排序函式里,改為“尾遞迴轉迴圈 + 先處理短區間”。你可以在Visual Studio裡對sort函式右鍵點選“轉到定義”,檢視VS中STL的sort()實現細節
vstudio sort轉到定義圖片.jpg

函式介面定義:
void sort(int *R, int n);
功能為對整數R[1]…R[n]遞增排序。

裁判測試程式樣例:

#include<iostream>
#include<stdlib.h>
#include<math.h>
using namespace std;
int threshold;

// 請在這裡補充你的程式碼,即你所實現的sort函式

int main()
{
    int n,i;
    int a[50010];
    scanf("%d %d", &n, &threshold);
    for (i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    
    sort(a,n);
    
    printf("Final:");
    for (i = 1; i <= n; i++)
        printf("%d ",a[i]);
    printf("\n");
    return 0;
}

備註:提交程式碼時,只需提交sort函式以及你自定義的其他函式,不用提交#include或者main函式等內容。

輸入格式:
輸入第一行為2個正整數n和threshold,n為待排序的元素個數,不超過50000,threshold為改用插入排序的閾值,不超過20,含義如上所述。第二行為n個空格間隔的整數。本題中讀入資料的操作無需你來實現,而由框架程式完成。

輸出格式:
輸出第一行為以depth_limit:開頭的整數,表示轉為堆排序的遞迴深度,即⌊2log
2n⌋。從第二行開始,輸出對某子陣列轉為堆排序後,該子陣列初始建堆的結果,每個元素後一個空格,每個堆佔一行,以Heap:開頭。注意,可能不止一個堆。接下來下一行,輸出n個整數,每個整數後一個空格,為快速排序所有遞迴退出後,插入排序執行前的陣列元素,以Intermediate:開頭。最後一行為n整數,每個整數後一個空格,表示排序後的陣列,以Final:開頭(最後一行由框架程式完成,無需你來輸出)。

輸入樣例1:
10 2
10 9 8 7 6 5 4 3 2 1

輸出樣例1:
depth_limit:6
Intermediate:1 2 3 5 4 6 7 8 9 10
Final:1 2 3 4 5 6 7 8 9 10

輸入樣例2:
60 2
66 61 92 22 50 80 39 2 25 60 49 17 37 19 24 57 40 82 11 52 45 0 33 78 32 25 19 42 92 50 39 87 74 87 56 79 63 63 80 83 50 3 87 2 91 77 87 10 59 23 25 6 49 85 9 95 60 16 28 1
輸出樣例2:
depth_limit:11
Intermediate:1 0 2 2 3 6 9 10 11 16 17 19 19 22 23 24 25 25 25 32 28 33 37 39 39 40 42 45 49 49 50 50 50 52 56 57 59 60 60 61 63 63 66 74 77 78 79 80 80 82 83 85 87 87 87 87 91 92 92 95
Final:0 1 2 2 3 6 9 10 11 16 17 19 19 22 23 24 25 25 25 28 32 33 37 39 39 40 42 45 49 49 50 50 50 52 56 57 59 60 60 61 63 63 66 74 77 78 79 80 80 82 83 85 87 87 87 87 91 92 92 95

程式碼長度限制
16 KB
時間限制
100 ms
記憶體限制
64 MB


此題基於課程設計第二次上機A題,而且改進方法都在題目裡,是在不行,朱老師的ppt裡有來著?


小結

好像又沒啥可結的,總之就這樣罷

相關文章