數據結構與算法總結與易錯分析

排序算法總結和易錯點分析

冒泡排序

  • 冒泡排序有兩層循環,外面的循環是記錄循環次數而已,我建議用i=1來算次數,這樣更容易理解,如果寫成i=0當然也行容易加大思維負擔

  • 還有一點,我有時候寫算法時候,寫着寫着一不留神就把冒泡的交換

    int temp = arr[j];
    arr[j] = arr[j+1];
    arr[j+1] = temp;
    

    寫成了如下

    int temp = arr[j];
    arr[j] = arr[j+1];
    arr[j+1] = arr[j];
    

    這個稍微留心一下習慣

  • 最後方法類型是int[]別忘了返回數組

快速排序

  • 快排的思想容易記偏,快排最重要的是中軸,記住重要的並不是找中軸,而是擺放中軸,中軸很好找,就是直接令最後一個元素就是中軸,所以重點根本就不是找中軸,而是怎麼把中軸擺放到這個序列中間的某個位置,使得中軸左邊的數都比中軸小,中軸右邊的數比中軸大。之後便是左遞歸和右遞歸了
  • 快排怎麼排出中軸呢,通過多輪交換,每一輪交換隻涉及到左邊換一次,右邊換一次
  • 最外層 while 循環條件儘量寫成left < right,這是因爲要保證 left 等於 right 時候可以出循環,所以我們要注意裏頭的條件要加上left < right,包括左遍歷遍歷,右邊遍歷,這樣裏頭的循環也可以在left = right時候或者 left 還沒到 right 的時候可以出來,就不會發生left > right比較難處理的現象了
  • 裏頭循環我們無需關注和中軸相等的元素,不管是左邊還是右邊,都可以無需關注
  • 循環都結束後,我們把中軸放在 left 處或者 right 處,因爲 left 和 right 從循環來的時候就已經相等了

簡單選擇排序

  • 外層循環代表經過幾輪,可以從i = 1開始
  • 內層循環找到最小(大)值之後表下標,我建議從小到大排序,每個最大的排在最後面,符合思考習慣一點,然後交換元素可以直接用最外層循環的變量

堆排序

  • 堆排序要記住先將無序序列創建爲最小堆或者最大堆,然後再把最小堆或者最大堆的根和尾部替換位置,這樣就把最小或者最大的元素放到了最末尾,然後將根部元素下沉,下沉到不能再下沉爲止,這樣就又構造出一個新的最小堆或者最大堆,然後不斷循環這樣的操作,直到序列變成從大到小排序或者從小到大排序。簡化來說就是,創建堆,然後循環的將根和尾部替換,最後就可以完成排序。如果創建最大堆,就是從小到大排序,如果是創建最小堆,就是從大到小排序
  • 如何創建大(小)頂堆?將一個無序的完全二叉樹,從最後一個非葉子結點開始進行下沉操作,然後循環到根結點

直接插入排序

  • 直接插入要注意將後面元素插入到前面一個有序序列中時候什麼時候出來,只有當不滿足某個循環條件時候出來,再插入下一個元素,怎麼插入呢?從有序序列最後一個開始不斷後移即可

折半插入排序

  • 折半插入排序和直接插入排序很像,裏頭用到了折半查找的思想,折半查找注意循環條件,我喜歡設置循環條件當 left = right 時候出循環,再比較下元素是必這個位置元素大還是小即可

希爾排序

  • 裏面是直接插入排序,外層進行縮小增量,縮小增量是有意義的,因爲我們知道直接插入排序當元素基本有序的時候,排序的時間複雜度就很小(內層循環幾乎不執行),所以縮小增量就是起了這個意義,希望通過某種操作先讓元素基本有序,怎麼做呢,縮小增量,我們假如使用樸素的希爾排序,開始增量爲數組長度的一半,之後不斷縮小爲一半,一直到一,一其實就是常規的直接插入排序了,假如一個開始增量是 n,那麼這裏需要一個循環,增量爲 n 的時候有 n 組數據可以進行插入排序,那麼又可以寫一個循環爲表示這 n 組,裏頭再寫一個直接插入即可,這樣會發現是是 4 個循環,網上有的 人寫 3 個循環,這是因爲他們把第二個和第三個和在一起了,我喜歡分開,因爲這樣可以很清晰的表示分組進行插入排序,不分開表示,則是這 n 組交替的循環

數據結構總結和易錯點分析

鏈表

  • 單鏈表的創建,頭插法和尾插法,頭插法最簡單,尾插法可以先讓 head 表示最尾部,然後我們每次插入新結點,往尾部插,然後再把最後結點賦給尾部即可
  • 不管是單鏈表還是雙向循環鏈表的插入,我們都遵守一個這樣的規律,那就是率先把要插入的結點的左邊和右邊連在表上,然後我們再去考慮動表中的連接,先動後面再動前面

  • 只用注意一點,不管是單鏈表還是棧,我們都把頭結點作爲一個未存數據的結點,因爲方便我們指向後續結點

隊列

  • 隊列最開始時候 head 等於 tail 且指向空,現在創建的時候我建議 tail 往後移動一下再存,而不是存了之後再往後移,我們始終應該要保持 head 爲一個空的結點指向的是存在的結點的形式
  • 順序循環隊列有幾種方式來做,要麼多設置一個記錄數量的變量,要麼犧牲一個數組中的空間,存我們都是 tail 存了之後,tail 後移,如果用一個計數變量的話,隊空隊滿的條件都是head==tail,能保證數組空間都被使用,如果我們犧牲數組空間的話,隊空的條件是head==tail,隊滿的條件是(tail+1)%arr.legnth==head,即 tail 下一個元素是 head 的時候表示隊滿,要知道當前這個 tail 所指的空間還是一個空的,這就相當於犧牲了一個數組空間。head 後移是(head+1)%arr.length,tail 後移是(tail+1)%arr.length

二叉樹

  • 遍歷的遞歸很容易,其中層次遍歷是 BFS,BFS 一般不用遞歸形式,DFS 可以自然想到遞歸的形式,二叉樹的非遞歸遍歷算法,對於先中後序遍歷採用棧,層次遍歷採用隊列。
  • 後序遍歷二叉樹的非遞歸算法比較麻煩,後序與先序和中序不同的是,只有當左右孩子都找過了之後,才能將根結點拋棄,這就會導致一個問題,假如一個結點左右都找過都不存在,那麼我們把這個結點數據輸出,並從棧中彈出這個結點,然後棧頂又變成父節點,若剛剛彈出的結點是左結點還好,因爲下一個是右結點,若剛剛彈出的是右結點,那麼父節點找左結點找不到又會找右結點,這樣又是剛纔那一步了,所以我們需要增加一個變量,來保證只有當又結點不存在並且右結點不是剛剛訪問過的結點時候,才能指向右邊,否則繼續彈出
  • 層次遍歷二叉樹粗略的理解可能是一層一層的遍歷,實際上這句話是有問題的,不是一層一層的遍歷,按照這句話的理解就成了循環裏每個循環中要遍歷完一層,其實並不是這樣,而是說按照從上到下,從左至右的順序遍歷,每次循環中只需要控制一個結點出隊就行了,而不是該層所有節點出隊,一旦一個結點出隊了,同時就需要它的左右孩子結點入隊
  • 複製二叉樹,求二叉樹深度,二叉樹葉子結點數,非葉子結點數都可以用遞歸很方便的求解
  • 線索二叉樹的創建,用先中後序遞歸來創建,注意有一個 pre 是全局變量,表示前驅,代碼中有前驅指定與後繼指定,涉及到兩個元素,分別是當前元素和 pre 指向的元素,二者之間就可以寫後繼和前驅了
  • 線索二叉樹的遍歷,後序遍歷很麻煩,可以嘗試用左右根的方式寫一下,很難寫出,最後要轉化爲根右左的形式,也就是傳統後序的倒序,這個根右左就和先序很類似了,在按照先序的思想來寫就行了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章