快速排序(quick sort)
首先任意選取一個數據(通常選用數組的第一個數)作為關鍵數據,然后將所有比它小的數都放到它前面,所有比它大的數都放到它后面,這個過程稱為一趟快速排序。
將數組分割成兩個數組之后再分別對剩下的兩個數組執行排序,這樣循環,直到剩一個元素。
import time,random import copy def cal_time(func): #該裝飾器用來測量函數運行的時間 def wrapper(*args,**kwargs): t1 = time.time() res = func(*args,**kwargs) t2 = time.time() print("%s running time: %s secs." % (func.__name__, t2 - t1)) return res return wrapper def query_sort(data,left,right): if left < right: mid = partition(data,left,right) query_sort(data,left,mid-1) query_sort(data,mid+1,right) def partition(data,left,right): temp = data[left] while left < right: while left<right and data[right] >= temp: right -=1 data[left] = data[right] while left<right and data[left] <= temp: left +=1 data[right] = data[left] data[left] = temp return left @cal_time def query_sort_h(data): query_sort(data,0,len(data)-1) @cal_time def sort_fun(data): data.sort() data = list(range(10000)) random.shuffle(data) data1 = copy.deepcopy(data) print(data) query_sort_h(data) sort_fun(data1) print(data)
二叉樹
樹的特征和定義
樹是一種重要的非線性 數據結構,直觀地看,它是 數據元素(在樹中稱為結點)按分支關系組織起來的結構,很象自然界中的樹那樣。 樹結構在客觀世界中廣泛存在,如人類社會的族譜和各種社會組織機構都可用樹形象表示。樹在計算機領域中也得到廣泛應用,如在編譯源程序時,可用樹表示源程序的語法結構。又如在 數據庫系統中,樹型結構也是信息的重要組織形式之一。一切具有層次關系的問題都可用樹來描述。
樹(Tree)是元素的集合。我們先以比較直觀的方式介紹樹。下面的數據結構是一個樹:
樹有多個節點(node),用以儲存元素。某些節點之間存在一定的關系,用連線表示,連線稱為邊(edge)。邊的上端節點稱為父節點,下端稱為子節點。樹像是一個不斷分叉的樹根。
每個節點可以有多個子節點(children),而該節點是相應子節點的父節點(parent)。比如說,3,5是6的子節點,6是3,5的父節點;1,8,7是3的子節點, 3是1,8,7的父節點。樹有一個沒有父節點的節點,稱為根節點(root),如圖中的6。沒有子節點的節點稱為葉節點(leaf),比如圖中的1,8,9,5節點。從圖中還可以看到,上面的樹總共有4個層次,6位於第一層,9位於第四層。樹中節點的最大層次被稱為深度。也就是說,該樹的深度(depth)為4。
如果我們從節點3開始向下看,而忽略其它部分。那么我們看到的是一個以節點3為根節點的樹:
三角形代表一棵樹
再進一步,如果我們定義孤立的一個節點也是一棵樹的話,原來的樹就可以表示為根節點和子樹(subtree)的關系:
上述觀察實際上給了我們一種嚴格的定義樹的方法:
1. 樹是元素的集合。
2. 該集合可以為空。這時樹中沒有元素,我們稱樹為空樹 (empty tree)。
3. 如果該集合不為空,那么該集合有一個根節點,以及0個或者多個子樹。根節點與它的子樹的根節點用一個邊(edge)相連。
上面的第三點是以遞歸的方式來定義樹,也就是在定義樹的過程中使用了樹自身(子樹)。由於樹的遞歸特征,許多樹相關的操作也可以方便的使用遞歸實現。我們將在后面看到。
樹的實現
樹的示意圖已經給出了樹的一種內存實現方式: 每個節點儲存元素和多個指向子節點的指針。然而,子節點數目是不確定的。一個父節點可能有大量的子節點,而另一個父節點可能只有一個子節點,而樹的增刪節點操作會讓子節點的數目發生進一步的變化。這種不確定性就可能帶來大量的內存相關操作,並且容易造成內存的浪費。
一種經典的實現方式如下:
二叉樹:
二叉樹是由n(n≥0)個結點組成的有限集合、每個結點最多有兩個子樹的有序樹。它或者是空集,或者是由一個根和稱為左、右子樹的兩個不相交的二叉樹組成。
特點:
(1)二叉樹是有序樹,即使只有一個子樹,也必須區分左、右子樹;
(2)二叉樹的每個結點的度不能大於2,只能取0、1、2三者之一;
(3)二叉樹中所有結點的形態有5種:空結點、無左右子樹的結點、只有左子樹的結點、只有右子樹的結點和具有左右子樹的結點。
二叉樹(binary)是一種特殊的樹。二叉樹的每個節點最多只能有2個子節點:
二叉樹
由於二叉樹的子節點數目確定,所以可以直接采用上圖方式在內存中實現。每個節點有一個左子節點(left children)和右子節點(right children)。左子節點是左子樹的根節點,右子節點是右子樹的根節點。
如果我們給二叉樹加一個額外的條件,就可以得到一種被稱作二叉搜索樹(binary search tree)的特殊二叉樹。二叉搜索樹要求:每個節點都不比它左子樹的任意元素小,而且不比它的右子樹的任意元素大。
(如果我們假設樹中沒有重復的元素,那么上述要求可以寫成:每個節點比它左子樹的任意節點大,而且比它右子樹的任意節點小)
二叉搜索樹,注意樹中元素的大小
二叉搜索樹可以方便的實現搜索算法。在搜索元素x的時候,我們可以將x和根節點比較:
1. 如果x等於根節點,那么找到x,停止搜索 (終止條件)
2. 如果x小於根節點,那么搜索左子樹
3. 如果x大於根節點,那么搜索右子樹
二叉搜索樹所需要進行的操作次數最多與樹的深度相等。n個節點的二叉搜索樹的深度最多為n,最少為log(n)。
二叉樹的遍歷
遍歷即將樹的所有結點訪問且僅訪問一次。按照根節點位置的不同分為前序遍歷,中序遍歷,后序遍歷。
前序遍歷:根節點->左子樹->右子樹
中序遍歷:左子樹->根節點->右子樹
后序遍歷:左子樹->右子樹->根節點
例如:求下面樹的三種遍歷
前序遍歷:abdefgc
中序遍歷:debgfac
后序遍歷:edgfbca
二叉樹的類型
如何判斷一棵樹是完全二叉樹?按照定義,
教材上的說法:一個深度為k,節點個數為 2^k - 1 的二叉樹為滿二叉樹。這個概念很好理解,
就是一棵樹,深度為k,並且沒有空位。
首先對滿二叉樹按照廣度優先遍歷(從左到右)的順序進行編號。
一顆深度為k二叉樹,有n個節點,然后,也對這棵樹進行編號,如果所有的編號都和滿二叉樹對應,那么這棵樹是完全二叉樹。
堆排序
堆排序,顧名思義,就是基於堆。因此先來介紹一下堆的概念。
堆分為最大堆和最小堆,其實就是完全二叉樹。最大堆要求節點的元素都要大於其孩子,最小堆要求節點元素都小於其左右孩子,兩者對左右孩子的大小關系不做任何要求,其實很好理解。有了上面的定義,我們可以得知,處於最大堆的根節點的元素一定是這個堆中的最大值。其實我們的堆排序算法就是抓住了堆的這一特點,每次都取堆頂的元素,將其放在序列最后面,然后將剩余的元素重新調整為最大堆,依次類推,最終得到排序的序列。
堆排序就是把堆頂的最大數取出,
將剩余的堆繼續調整為最大堆,具體過程在第二塊有介紹,以遞歸實現
剩余部分調整為最大堆后,再次將堆頂的最大數取出,再將剩余部分調整為最大堆,這個過程持續到剩余數只有一個時結束
import time,random,copy from cal_time import cal_time,nor_sort def sift(data,low,high): #來一次調整 i= low #low為根節點,父節點 j= 2 * i + 1 #i的左孩子 tmp = data[i] while j <= high: #沒有超過數組范圍時 if j < high and data[j] < data[j + 1 ]:#如果j小於high也就是說左孩子小於最邊界的high那么這個節點肯定有右孩子, j +=1 #這個時候對比左孩子和右孩子的大小,如果右孩子大於左孩子,那么這個時候把J置為右孩子,J的左右就是表示兩個孩子里面最大的那個 if tmp < data[j]: #如果父親的值小於孩子的值,這個時候開始換位置 data[i] = data[j] #j的值換為父親,i的值換為孩子 i =j #把I的位置調整到之前比父親大的那個孩子的位置, j = 2* i + 1 #開始比較當前這個孩子和他自己孩子的大小,找到它的左孩子,定義為j else: #如果兩個孩子都沒父親大 break #什么也不干,跳出當前循環 data[i] = tmp #把最初始i的值賦給調整之后的位置 @cal_time def heap_sort(data): n = len(data) for i in range(n //2 -1,-1,-1):#取最后一個有孩子的父節點,倒序遍歷 sift(data,i,n-1) #調整 for i in range(n-1,-1,-1): data[0],data[i] = data[i],data[0] sift(data,0,i-1) data = list(range(10000)) random.shuffle(data) data1 = copy.deepcopy(data) print(data) heap_sort(data) print(data) data1 = nor_sort(data1) print(data1)
歸並排序
假設現在的列表分兩段有序,如何將其合成為一個有序列表
將序列垂直分割成兩個有序序列,然后取每個序列的第一項對比,小的取出來然后繼續對比,當某一序列取完之后就把另一個序列剩下的全部放到新的序列里面
將數組細分,先分解到數個有序的序列,比如只包含一個元素的序列是有序的,然后再開始合並,所以稱該算法為歸並排序。
代碼示例:
import time,random,copy from cal_time import cal_time,nor_sort def merge(data,low,mid,high): i = low j = mid + 1 ltmp = [] while i <= mid and j <= high: if data[i]<data[j]: ltmp.append(data[i]) i += 1 else: ltmp.append(data[j]) j += 1 while i <= mid: ltmp.append(data[i]) i += 1 while j <= high: ltmp.append(data[j]) j +=1 data[low:high+1] = ltmp def _merge_sort(data,low,high): if low<high: mid = (low+high)//2 _merge_sort(data,low,mid) _merge_sort(data,mid+1,high) merge(data,low,mid,high) @cal_time def merge_sort(data): _merge_sort(data,0,len(data)-1) data = list(range(10000)) random.shuffle(data) data1 = copy.deepcopy(data) print(data) merge_sort(data) nor_sort(data1) print(data)
快速排序、堆排序、歸並排序的區別
三種排序算法的時間復雜度都是O(nlogn) 一般情況下,就運行時間而言: 快速排序 < 歸並排序 < 堆排序
三種排序算法的缺點: 快速排序:極端情況下排序效率低 歸並排序:需要額外的內存開銷 堆排序:在快的排序算法中相對較慢
希爾排序(shell sort)
希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序算法的一種更高效的改進版本,該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然后依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因為直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率比直接插入排序有較大提高,希爾排序每趟並不使某些元素有序,而是使整體數據越來越接近有序;最后一趟排序使得所有數據有序。
首先要明確一下增量的取法:
第一次增量的取法為: d=count/2;
第二次增量的取法為: d=(count/2)/2;
最后一直到: d=1;
看上圖觀測的現象為:
d=3時:將40跟50比,因50大,不交換。
將20跟30比,因30大,不交換。
將80跟60比,因60小,交換。
d=2時:將40跟60比,不交換,拿60跟30比交換,此時交換后的30又比前面的40小,又要將40和30交換,如上圖。
將20跟50比,不交換,繼續將50跟80比,不交換。
d=1時:這時就是前面講的插入排序了,不過此時的序列已經差不多有序了,所以給插入排序帶來了很大的性能提高。
代碼示例:
import random,copy from cal_time import cal_time,nor_sort def shell_sort(data): gap = len(data) // 2 print(gap) while gap >=1: for i in range(gap,len(data)): tmp = data[i] j = i-gap while j >= 0 and tmp < data[j]: data[j + gap] = data[j] j -=gap data[i-gap] = tmp gap //=2 data = list(range(10000)) random.shuffle(data) data1 = copy.deepcopy(data) print(data) shell_sort(data) print(data) data1 = nor_sort(data1) print(data1)
各排序方法總結:
備注:1、深度遞歸調用有時會出錯需要設置遞歸保護代碼:
sys.setrecursionlimit(100000)
2、切記不能給遞歸函數加裝飾器,這樣的話每次遞歸裝飾器都會執行一次