1. 程式人生 > >演算法2——連結串列,陣列,和選擇排序

演算法2——連結串列,陣列,和選擇排序

首先我們來認識一下計算機的記憶體工作原理:

計算機的記憶體就像是我們平時存放東西的儲物櫃一樣,我們把揹包之類的物品放入櫃子裡,每個櫃子有一個專屬的編號,我們記住這個編號,然後根據編號就可以找到櫃子拿回屬於我們的物體。

在計算機裡,就有很多這種櫃子,叫記憶體單元,每個櫃子的編號就是地址。那麼如何將資料存放到相應的地址內呢,有兩種基本方式,連結串列和陣列,接下來我們分別介紹他們並比較其優缺點。

什麼是陣列(array)?

假如我們有一個四乘四的格子,即16個小格子,相當於計算機的記憶體,從左到右,從上到小分別編號0-15。每個格子代表一個地址。此時,我有一個包含四個數的陣列a[4],我們想把它放到這16個格子當中,如果16格子全部為空,我可以放到任意的位置上,比如0-3或者4-7,這個編號就代表陣列所存放的地址。但是這四個數必須是相鄰的,不能有跳躍。這就是陣列儲存在計算機記憶體的方式。

但是,如果這16個格子不全為空,編號為3,7,11,15的格子裡本身存有資料,我們不能佔用,那我們還可以將陣列a[4]存入這16個格子中嗎?答案是不能,因為我們發現已經沒有相鄰的4個格子了,連在一起的格子最多隻有3個,那這時我們要怎麼儲存資料呢,這就引出了連結串列。

什麼是連結串列(linked list)?

連結串列也是資料儲存在計算機記憶體裡的一種方式,不同於陣列的是,當我們儲存一個數的時候,我們還會記下下一個數所在的地址。這有什麼好處呢,回到上面的例子,現在編號3,7,11,15的格子已經被佔用了,假設我們將前三個數儲存在0-2編號的格子裡,儲存第一個數時會包括第二個數的地址,第二個數會包括第三個數的地址,儲存放在編號2的格子裡的第三個數還包括第四個數的地址。假設我們將第四個數存在編號4的格子裡(因為編號3的格子已經被佔用),當我們讀到第三個數時,由於有了第四個數的地址,我們可以直接跳到編號4的格子中讀取裡面的數字。而陣列由於不儲存下一個資料所在的地址,所以我們必須將他們相鄰排列,前一個地址加一就是後一個數的地址所在。

陣列和連結串列的比較

現在我們知道了陣列和連結串列的工作原理,那麼誰更好呢。當然世界上沒有絕對的優勢與劣勢,連結串列和陣列也有各自擅長的情形,接下來我們考慮幾個操作,讀取,插入,刪除。

讀取 (陣列 優於 連結串列)

假設我們想從數組裡讀取第四個元素,由於陣列是相鄰排列,我們只需要將頭元素的地址加4就可以直接訪問到第四個元素,只是簡單的加法運算即可得到,相當於直接查表,所以執行時間是O(1)。

假設我們想從連結串列裡讀取第四個元素,連結串列不一定是相鄰排列,我們必須從第一個查到第二個的地址,從第二個再查第三個,從第三個再查第四個,無論我們查第幾個元素,都要遍歷一遍這個元素前面的所有元素才可以找到我們想要的,所以執行時間是O(n)。

插入(連結串列 優於 陣列)

假設我們想在包含四個資料的陣列再插入一個數據,則陣列的大小發生了變化,首先我們需要確保加了一個數據之後,記憶體中還有五個相鄰的格子,其次,如果我們把新資料插在頭一個,那麼所有後面的數都要相應往後挪一位,每一個數據本身的地址都發生了變化,所以執行時間是O(n)。

假設我們想在包含四個資料的連結串列裡插入一個新資料,假如插在第二位,我們可以將新資料放入到任意一個空的格子裡,然後讓第一個數記下這個新資料格子的地址,這樣第一個數之後我們就查詢到第二個數,也就是新資料,我們再讓新資料儲存原本第二個數的地址,這樣從新數可以查到舊的第二位數,這樣新資料就被加進了原本的第一位數和第二位數之間。那麼後面的資料呢?我們完全不需要動,對吧。所以執行時間是O(1)。

刪除(連結串列 優於 陣列)

刪除的操作和插入的操作很相像。對於陣列而言,刪除一個元素也是改變了陣列本身的大小,如果刪除第一個元素,後面所有元素都要向前挪一位,所有元素地址都要變,執行時間是O(n)。

而對於連結串列,一樣原理,連結串列有一個叫頭結點(head)的東西,顧名思義它是開始,它儲存著第一個數的地址,如果我們要刪除第一個元素的時候,只需要將頭結點裡的地址改為原本第二個數的地址,這樣從頭節點我們就直接跳到了原來的第二個數上,原本的第一個數自然而然就被刪除掉了,而後面的數字則全部不需要動,所以執行時間是O(1)。

我想下面這幅圖可以更好的幫助理解關於連結串列的操作。綠色箭頭代表指向下一個元素的地址,紫色箭頭是跨過E直接指向了A的地址,也就是相當於刪除元素E的操作了。連結串列操作比較難理解,我會在之後放入更為詳細的講解。


選擇排序

在演算法1裡我們講到了二分查詢,每次先找中間的然後比較,但這必須是有序的數列,如果是無序的數列呢?這裡我們將一種比較簡單直觀,效率可能較低,但很基本的排序方法,選擇排序。

假如我們有5個學生,他們是數學成績如下,很明顯這是無序的,我們想要按照成績從高到低的順序進行排列,要如何做呢?

  ->  ->...

最簡單的方法就是,先從頭到尾看一遍找到最高的挑出來,然後再從頭到尾查一遍挑出第二高,...,然後第三高,第四高,直至最後。很簡單對吧,這就是選擇排序。很簡單易懂,當然花費時間也很高,來讓我們算算它的執行時間。

假設有n個同學需要排序,第一次需要檢視n個同學找到最高,第二次只需要檢視n-1個同學,然後n-2個同學,直至查到最後一名,所以總時間t = n + (n-1) +(n-2) + ... + 1 = n(n+1)/2 = (n^2+n)/2 ,而時間複雜度我們一般忽略常數項取最高次的多項,所以這裡選擇排序的執行時間是O(n^2).

程式碼實現

def findSmallest(lst):
  smallest = lst[0]
  smallest_index = 0
  for i in range(len(lst)):
    print('com',lst[i],smallest)
    if lst[i] < smallest:
      smallest = lst[i]
      smallest_index = i
  return smallest_index
  
def selectionSort(lst):
  new_list = []
  while len(lst) > 0:
    smallest_index = findSmallest(lst)
    new_list.append(lst.pop(smallest_index))
    print(lst)
  return new_list

mylst = [1,2,3,4,1,2]
mynew_lst = []
mynew_lst = selectionSort(mylst)
print(mynew_lst)
Reference: 理解來源於《演算法圖解》