1. 程式人生 > 實用技巧 >資料結構期末極限複習

資料結構期末極限複習

選擇題填空:書本課後題
//我的程式碼可能是錯的,沒有經過ide執行,直接寫的


程式填空題:順序表插入
    統計二叉樹結點個數
    堆排序(調整堆)

順序表插入(簡單):
//課本程式碼
public void insert (int i , Object x) throws Exception
    {
        if (curLen == listElem.length)
            throw new Excetpion("順序表已滿");
        if (i<0 || i>curLen)
    /:        throw new Excetpion("
插入位置合法"); //核心程式碼,就是後移嘛 for (int j = curLen; j>i, j--) listElem[j] = listElem[j-1]; listElem[i] = x; curLem++; } 統計二叉樹結點個數: //自己打的,遍歷左子樹右子樹加上根結點就是總結點數了 protected int rcountLeaves(BinNode<Elem> root) { if (root == null) { return
0; } if (root.right == null && root.left == null) { return 1; } return rcountLeaves(root.left) + rcountLeaves(root.right); } public int countLeaves() { return rcountLeaves(root); } public int countNodes() { return countLeaves() + 1
; } //核心程式碼:percolateDown /*如果考的是二叉堆(用完全二叉樹儲存資料的堆)的話就是下面的程式碼*/ //用陣列實現,下面考慮小頂堆的操作(程式碼自己打的) //堆的調整核心操作就兩個:向下過濾 向上過濾 //為什麼要過濾?因為插入/刪除的操作會改變堆的結構 //為了保持堆的結構,我們需要向下向上過濾 //當插入一個元素的時候我們需要percolateup,往上過濾 //當刪除一個元素的時候我們需要percolatedown,往下過 //過濾可以看作是一個比較的過程,這個詞比較拗口 //實踐發現當陣列從1開始儲存資料時,i/2為父親位置,i*2是左兒子 //i*2+1就是右兒子 //當陣列從0開始儲存,父親(i+1)/2 - 1,左 2* i + 1 //堆排序全部程式碼:(c++.. 都差不多的) #include<stdlib.h> #include<cstdio> using namespace std; /*寫在最前面:我們所學的是二叉堆(用完全二叉樹(陣列)儲存) 但是我們還有二項堆、Fib堆,這兩種堆就不是用完全二叉樹實現的 二叉堆只是prorityqueue這個結構實現的一種方式,而不是說優先佇列等同於二叉堆 priortiyqueue還能用二項堆、avl樹、線段樹還有二進位制分組的vector實現 */ /* 堆的其他操作: 全部操作都首先要看這個堆是最大堆還是最小堆,細節上有些差異 1.提高元素優先順序:提高優先順序 + percolateup(因為提高優先順序之後,這個位置不合適了,向上過濾) 2.降低元素優先順序:降低優先順序 + percolatedown(和提高優先順序的的理由一致) 3.刪除元素(不是堆頂的元素): 欲想讓其滅亡,先讓其膨脹,不是堆頂就提高它的優先順序變成堆頂,那麼我們的操作就變成了刪除堆頂hh */ /*刪除操作:堆頂元素為最大或者最小 所以刪除顯然是刪除堆頂比較方便 本測試程式中以大頂堆為示例,刪除最大的元素 考慮堆是空的時候,丟擲異常 */ /*如果是刪除堆頂,會導致堆頂空了一個位置,接著堆頂這個空的位置會與它的 左兒子和右兒子進行交換,把這個空位給交換下去,導致一塊地方給空了出來 最終會破壞掉整棵完全二叉樹的結構 (二叉樹的深度為k,除第 k 層外,其它各層 (1~k-1) 的結點數都達到最大個數,第k 層所有的結點都連續集中在最左邊,這就是完全二叉樹) 只要刪除的是最後一個元素我們的結構就不會發生變化了,那麼我們只需要刪除堆頂的值而不刪除它的位置,怎麼做? 將最後一個元素的值和堆頂元素的值交換一下,然後堆頂向下滲透完事。 */ //滲透是一個動作,沒什麼特殊含義(我自己的觀點),就往下進行元素交換交換去調整元//素排列順序 //從而符合二叉堆的結構 void percolateDown(int k, int* array, int size){ //向下滲透 int temp; temp = array[k]; int i, child; //i要用來找要刪掉的位置,所以要設在for外面 //i*2 + 1 < size 表示左兒子存在 for(i = k; i*2+1 < size; i=child){ //左兒子 child = i * 2 + 1; //child+1右兒子 //現在做的是升序排列,每次找最大的那個出堆然後放最後面去 //所以判斷條件找大的 //下面判斷右兒子是不是比左兒子更大 因為最大位置在size - 1 //接上一句,所以child+1不會在child < size - 1的情況下越界 if(child != size - 1 && array[child+1] > array[child]){ child ++; } //堆頂比它小就要去挪資料 if(temp < array[child]){ array[i] = array[child]; } //else 找到一個大於等於的就退出不用動了 else break; } array[i] = temp; } /*Heap排序的實質:假設堆裡有n個元素,連續刪除n次堆頂(存下來)就是排序了 因為每次刪除堆頂的時候都會對這個堆進行調整,保證刪的元素是最大or最小 時間複雜度上:刪除一個元素的時間複雜度是logn,還要進行n次,所以就是nlogn 那麼我們需要一個另外的n元陣列來儲存出堆的元素 而實際上,由於我們每次刪除堆去調整堆的時候,會用一個下面的元素去頂替堆頂 此時,那個用來頂替堆頂的元素的位置會空缺的,也就是說我們能借用這個地方來快取我們刪除的堆頂 這樣子的話,小頂堆在不斷進行刪除之後我們就會得到一個升序的序列 */ void heapSort(int* array, int size){ /*我們只是應用堆排序的思想,不必要真的將陣列元素拷進堆裡再排序再返回 甚至刪除我們也不需要,因為刪除只要放進最後一個就好了,我們只需要用到percolatedown 新的堆排序應用在陣列上,陣列以0開始,現在要改變尋子尋父的 公式,左兒子變成了2*i+1,而右兒子鄰接左兒子,所以右兒子是2*i+1+1 原先樹根從1開始,左右兒子:左兒子能被2整除,右兒子餘1所以都能直接/2找到父親 現在樹根從0開始,在不斷實踐中發現,把這個1加回去再除以2,然後再減掉1 (i+1)/2 - 1這個公式就都符合左右兒子找父親的公式 percolatedown 中找最後一個擁有子節點的父節點 那麼現在陣列從0->size-1,size-1就是最後一個節點的位置 那麼(size-1 +1)/2 - 1 = size / 2 - 1就是父親了 當前的點向下過濾完後,自減找前面一個點進行過濾 pps:謹記這裡的元素節點是有順序的,所以自減就能找到前面一個點 負數的地方已經越界,不能再向下過濾,所以i>=0 */ for(int i = size/2 - 1; i >= 0; i --){ //for迴圈作用:調整元素排列順序進行建堆(抽象意義上) //從最後一個有兒子的點開始不斷向下過濾 percolateDown(i, array, size); } //把堆頂元素和最後一個做一下交換,然後進行向下過濾,一共要交換n-1次(n個元素前提下) //i=0時候已經全部出堆了,不需要等於0 for(int i = size - 1; i > 0; i --){ int temp = array[size - 1]; array[size - 1] = array[0]; array[0] = temp; //拿出去一個元素之後,堆在變小,比如拿出來一個之後就是size-1,所以堆的size和i一致 percolateDown(0, array, i); } } void printArray(int * array, int size){ for(int i = 0; i < size; i ++){ printf("%d ",array[i]); } printf("\n"); } int main(){ //pppps:原始序列有序,堆排序照樣要找最大的,不會(正/負)影響它的效率 int array[6] = {11, 2, 33, 44, 55, 66}; printArray(array, 6); heapSort(array,6); printArray(array, 6); return 0; } 問答題: 排序 最小生成樹 建立BST Hash線性探測 DFS BFS 排序:自己看書 最小生成樹:看書 BSTree:記住左子樹、父親、右子樹 三個點的數值是從小到大 Hash線性探測: 有手就行 DFS BFS:DFS用棧 BFS用佇列(程式碼參照藍橋杯複習https://www.cnblogs.com/GDUFdebuger/p/13829462.html 綜合題:1.根據前中後序遍歷 畫出二叉樹 轉成森林 2.帶頭結點單迴圈連結串列合併成一個單鏈表 綜合題解題方法: 1.根據前中後序遍歷的特點(根節點位置、左右子樹的遍歷順序)即可劃分區間 注意邊界即可 前序遍歷:先根節點,後左子樹,在右子樹 中序遍歷:先左子樹,後根節點,再右子樹 後序遍歷:先左子樹,後右子樹,再根節點 前 1 2 4 5 3 6 74 5 2 6 7 3 1 前:1為root 245為左子樹 367為右子樹 後: 1為root 452左子樹 673右子樹 再遞迴劃分 就不說了 1 2 3 4 5 6 7 其他組合同理 中序 9 3 15 20 7 後序 9 15 7 20 3 3為root 3 9 20 15 7 樹轉換成二叉樹: 左孩子右兄弟 二叉樹轉森林:按順序連root即可 //程式碼:思路,遍歷陣列找根節點劃分左右子樹區間,遞迴遞迴遞迴 //程式碼肯定不會全部讓人手寫,寫不完的 //填空照著上面思路吧- - 2./*獲取兩個連結串列的最後一個node p1 p2 後賦值給s1 s2 s1連第二鏈L2的頭結點,s2連第一鏈L1的頭結點 */ Node p1 = new Node(); Node p2 = new Node(); Node s1 = new Node(); Node s2 = new Node(); p1 = L1.Head.next; p2 = L2.Head.next; //獲取最後一個結點 while(p1 != L1.Head){ if(p1->next == L1.Head){ s1 = p1;break; } p1 = p1->next; } while(p2 != L2.Head){ if(p2->next == L2.Head){ s2 = p2;break; } p2 = p2->next; } //合併 s1->next = L2.Head->next; s2->next = L1.Head; }