广州交通大学二年级算法实验题目(第一弹)
文章目錄
- 寫在前面
- 內容:
- 2. 棋盤覆蓋
- 3. 找眾數
- 4. 半數集
- 5. 重復元素的全排列
- 6. 整數因子分解
- 8. 線性時間選擇
- 寫在最后
寫在前面
這個學期學院給我們專業開了算法課,下星期五是專業第一次算法實驗課,晚上老師給我們先發了實驗內容讓我們去準備,題目有這些:
必選題:1.歸并排序 2.棋盤覆蓋
自選題:3.找眾數 4.半數集 5.重復元素的全排列 6. 整數因子分解
附加題:7.快速排序 8.線性時間選擇
現在是清明節放假的第一天,因為感覺回家路太堵,也只有短短三天,索性留在學校,昨晚把題目都看了一遍,覺得并沒有太難,寫完之后今天再來整理一下。
吐槽一下學校的圖書館,很熱,去到四樓發現插座沒有電,底樓在裝修,整棟樓都是噪音,倒霉啊。輾轉來了煎餅樓,沒有WIFI,將就將就。
內容:
這是一個鏈接 >幾分鐘初步學會歸并排序和快速排序<
歸并排序和快速排序可以看我之前的這篇文章,(雖然寫得不怎么樣),其余的題目我就按上面的順序放過來。
2. 棋盤覆蓋
給你一個有2k * 2k(k = 2, 3, 4…)個方格的棋盤,此棋盤上恰有一個特殊方格,用以下四種類型的L型骨牌,覆蓋棋盤上除特殊方格以外的區域。
一看,2 * 2 或 4 * 4不是很簡單?如果k超級大?嗯…但我還是可以將這個大棋盤切成很多個2 * 2的小棋盤的吧。(遞歸yyds)
這樣思路就來了:假設棋盤是8 * 8,我可以先將它分為四個 4 * 4的棋盤(象限1,2,3,4),然后判斷特殊方格所在的象限,將此象限繼續切成四個2 * 2的棋盤,進入遞歸。但為了將另外三個普通棋盤轉化為特殊棋盤,我們可以用一個L型骨牌覆蓋這3個較小棋盤的匯合處,這三個普通棋盤上被L型骨牌覆蓋的方格就成為該棋盤上的特殊方格,從而成功將大特殊棋盤8 * 8分為四個小特殊棋盤4 * 4,繼續按照此方式切分,直至棋盤變成1 * 1。
????????????????????????????
看了一下書上的偽碼傳參,分別是記錄棋盤左上角方格的行列數tr、tc(定位棋盤),還有定位特殊方格所在的行列dr、dc,接著是棋盤的大小size。我另外引入了棋盤的二維數組,然后開始寫代碼。
寫到這,已經經過了不知道多少次的調試、改傳參,調了差不多半小時,頭暈腦脹。然而測試結果是這個樣子:
再看一眼書上,代碼確實是這樣沒錯,是打開姿勢不對嗎,我細想一番,決定在遞歸出口再加一些代碼:
if (dividesize == 1) {// 象限1if (dr != tr || dc != tc + 1) {board[tr][tc + 1] = currentNum;}// 象限2if (dr != tr || dc != tc) {board[tr][tc] = currentNum;}// 象限3if (dr != tr + 1 || dc != tc) {board[tr + 1][tc] = currentNum;}// 象限4if (dr != tr + 1 || dc != tc + 1) {board[tr + 1][tc + 1] = currentNum;}return;}運行:
… 書上并沒有這段代碼,去網上找帖子,發現很多也沒有這一段,這是為啥…唉,先把問題放一放,有好兄弟懂的話評論區教一下俺這個小菜雞…
好!下一題!
9.32更新, 問題代碼修改:
public class chessBoard {static int num = 1;public static void chessBoard(int[][] board, int tr, int tc, int dr, int dc, int size){if (size == 1) {return;}int dividesize = size / 2;int currentNum = num++;// 象限1if (dr < dividesize + tr && dc >= dividesize + tc) {chessBoard(board, tr, tc + dividesize, dr, dc, dividesize);}else {board[dividesize - 1 + tr][dividesize + tc] = currentNum;chessBoard(board, tr, tc + dividesize, dividesize - 1 + tr, dividesize + tc, dividesize);}// 象限2if (dr < dividesize + tr && dc < dividesize + tc) {chessBoard(board, tr, tc, dr, dc, dividesize);}else {board[dividesize - 1 + tr][dividesize - 1 + tc] = currentNum;chessBoard(board, tr, tc, dividesize - 1 + tr, dividesize - 1 + tc, dividesize);}// 象限3if (dr >= dividesize + tr && dc < dividesize + tc) {chessBoard(board, tr + dividesize, tc, dr, dc, dividesize);}else {board[dividesize + tr][dividesize - 1 + tc] = currentNum;chessBoard(board, tr + dividesize, tc, dividesize + tr, dividesize - 1 + tc, dividesize);}// 象限4if (dr > dividesize + tr && dc > dividesize + tc) {chessBoard(board, tr + dividesize, tc + dividesize, dr, dc, dividesize);}else {board[dividesize + tr][dividesize + tc] = currentNum;chessBoard(board, tr + dividesize, tc + dividesize, dividesize + tr, dividesize + tc, dividesize);}}public static void main(String[] args){int[][] board = new int[8][8];chessBoard(board, 0, 0, 0, 3, 8);for (int i = 0; i < board.length; ++i) {for (int j = 0; j < board[0].length; ++j) {System.out.print(board[i][j] + "\t");}System.out.println();}}}3. 找眾數
給定一個數組(亂序),找出其中的眾數(出現次數最多的數)和重數(眾數的個數)。這題就很簡單啦!先排序,再一次遍歷,注意邊界條件:數組以眾數結尾或者非眾數結尾。
public static int[] searchMode(int[] nums){if (nums.length == 0) {return nums;}Arrays.sort(nums);int count = 0, maxCount = 0,mode = nums[0];for (int i = 0; i < nums.length - 1; ++i) {if (nums[i] == nums[i + 1]) {count++;if (i == nums.length - 2 && count >= maxCount) {// 以眾數結尾mode = nums[i]; // 記錄眾數maxCount = ++count;// 記錄重數}}else if (count >= maxCount) { // 不以眾數結尾mode = nums[i]; // 記錄眾數maxCount = count;// 記錄重數count = 0;}}return new int[] {mode, maxCount};}雖然簡單,但我覺得寫得有億點亂唉…復雜度是 O(n)。
但有沒有不排序的路子呢?我想應該是有的,遍歷第一次數組,用HashMap的key記錄元素值,用HashMap的value記錄元素出現次數,然后找出HashMap里value最大的key,我想應該可以,這里鴿掉,嘿嘿。
9.32更新:解法2 public static int[] searchMode(int[] nums){int mode = 0, modeNum = 0;// 定義哈希表,key對應眾數,value對應重數Map<Integer, Integer> map = new HashMap<Integer, Integer>();for (int i = 0; i < nums.length; ++i) {if (map.containsKey(nums[i])) {// 已存在key,將value加1map.put(nums[i], map.get(nums[i]) + 1);}else{// 不存在key,加入key將value設置為1map.put(nums[i], 1);}if (map.get(nums[i]) > modeNum) {mode = nums[i];// 獲得眾數modeNum = map.get(nums[i]);// 獲得重數}}return new int[] {mode, modeNum};}
4. 半數集
經典遞歸題,只要好好審題,代碼就不難理解。
半數集定義:
現要求傳入參數n,求出半數集set(n)中元素個數。
光看代碼想起來難,建議cv去debug,看看參數怎么傳的。
路子大概就是傳參為6的空間下有comp(1)、comp(2)、comp(3),然后這三個分別的子空間里:comp(1)沒有直接返回ans = 1,comp(2)有comp(1),comp(3)有comp(1)…
最后傳回comp(6)的空間中:
comp(1)= 1,comp(2) = 2,comp(3) = 2,再加上ans默認值1,返回6.所以半數集set(6)里有6個元素。
好家伙,不會有人看懂我在講什么吧,寫完我自己都不知道自己在講什么233。
5. 重復元素的全排列
就是力扣的全排列2,在全排列1的基礎上加上了原數組含有重復元素,所以首先要明白沒有重復元素時候,全排列怎么求。
我們使用深度優先搜索dfs,配合回溯(狀態重置)與剪枝(收集答案)
(上面偷力扣君視頻里的一張截圖,力扣君yyds)
黃色部分表示我們的深度優先搜索深度已經到達數組長度,此時進行剪枝(保存一個答案),例如【1、2、3】,然后撤銷當前操作,把3移出數組,再把2移出數組。再接著選3、再選2,形成【1、3、2】,如此類推。
關鍵是怎么撤銷和保存答案,用到了這幾個狀態變量:
path(棧:記錄路徑),used[](標記已經使用的元素),deepth(搜索深度)
全排列到此就完成啦!但這個代碼顯然不能解決含有重復元素的問題,怎么辦呢。很簡單,只要事先將數組進行排序,保證重復元素都在相鄰位置,然后在dfs里面循環的if語句中加入下面的條件:
一:i > 0:
二:nums[i] == nums[i - 1] :
三:!used[i - 1] :
完整代碼:
class Solution {public List<List<Integer>> permuteUnique(int[] nums) {Arrays.sort(nums);List<List<Integer>> res = new ArrayList<>();if (nums.length == 0){return res;}//狀態變量path(棧),used[],deepthDeque<Integer> path = new ArrayDeque<>();boolean[] used = new boolean[nums.length];int deepth = 0;dfs(nums, res, path, used, deepth);return res;}private void dfs(int[] nums, List<List<Integer>> res, Deque<Integer> path, boolean[] used, int deepth) {if (deepth == nums.length){//“剪枝”res.add(new ArrayList<>(path));return;}for (int i=0; i<nums.length; ++i){// nums[i] == nums[i - 1] // 保證重復序列首元進入、限制以除重復序列首元外的元素為首的遞歸進入// !used[i - 1]// 保證以首元開始的遞歸進行if(used[i] || (i > 0 && nums[i] == nums[i - 1] && !used[i - 1])){continue;}path.addLast(nums[i]);used[i] = true;dfs(nums, res, path, used, deepth+1);path.removeLast();//狀態重置used[i] = false;}} }6. 整數因子分解
這是書上練習的最后一題,我最喜歡的一題(可能是因為很快解出來2333)
問題描述:
大于1的正整數n可以分解為
n=a?b?c?...?zn=a*b*c*...*z n=a?b?c?...?z
當n = 6時,有3種不同的分解式:
6=6,6=2×3,6=3×26 = 6,6=2×3,6=3×2 6=6,6=2×3,6=3×2
對于給定的正整數n,計算有多少種不同的分解式。
這里用到判斷一個數是不是質數的算法思想,我們很快可以想到雙重遍歷,但其實只需要從2開始,到n的平方根即可。因為假設a×b=n,若a比n的平方根小,那么b一定比n的平方根大,我們搜索到了a×b=n,自然也就存在b×a=n,就可以直接將種類數加2。而邊界條件就是:n的平方根×n的平方根 = n,對于此邊界條件,我們可以只將種數加1。
是不是一下子就明白了呢,但不要忘了一點,還可能是n = a×b×c !但只要有上面的思想,我們可以將b遞歸下去,將b拆分成N個分解式,加入總數即可。
您的代碼已到賬,請注意查收~
8. 線性時間選擇
在線性時間里,從亂序數組中找出第k小的數。
一開始不怎么明白是要做什么,可以看看書上這段話:
書上的偽碼描述:
Type RandomizedSelect(Type[] a, int p, int r, int k) {if (p == r)return a[p];int i = RandomizedPartition(a, p, r);j = i - p + 1;if (k <= j)return RandomizedSelect(a, p, i, k);elsereturn RandomizedSelect(a, i + 1, r, k - j);}我基于此用快排實現的代碼:
import java.util.*; import java.io.File; import java.io.PrintWriter; import java.io.FileNotFoundException;public class Main{public static int randomizedSelected(int[] a, int p, int r, int k){if (p == r) {return a[p];}int i = randomizedPartition(a, p, r);int j = i - p + 1;if (k <= j) {return randomizedSelected(a, p, i, k);}else {return randomizedSelected(a, i + 1, r, k - j);}}public static int randomizedPartition(int[] a, int p, int r) {// 基于快排的一次基準歸位int i = p, j = r, base = a[p];while (i < j) {while (a[j] >= base && j > i) {--j;}while (a[i] <= base && i < j) {++i;}if (i < j) {int temp = a[j];a[j] = a[i];a[i] = temp;}}a[p] = a[i];a[i] = base;return i;}public static void main(String[] args){int[] nums = new int[] {4, 5, 3, 2, 1};System.out.print(randomizedSelected(nums, 0, nums.length - 1, 5)); } }與快排不同的是,每一次基準數歸位找出i,需要比較一次i與k的關系,若找到了第k小的元素即刻返回,未找到就繼續下一次的基準歸位。
(書上說每次歸位的基準數下標需要取隨機值(未實現))
寫在最后
整理完這第一波實驗題,覺得并沒有說非常難。
這次也是一個標題黨,我攤牌了,想騙大佬們的一鍵三連。現在已經是中午時分,肚子餓得咕咕叫,收拾好電腦去干飯。聽老師說這門課一共有三次實驗,不知道下一次實驗又會學到什么新東西呢?朋友說明天圖書館閉館,唉…
好了,下次見!
總結
以上是生活随笔為你收集整理的广州交通大学二年级算法实验题目(第一弹)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 监控不显示画面怎么办
- 下一篇: 合并两个有序数组(双/三指针)