python學習-09(查找、排序和淺談數據結構)

查找的方法:算法

排序的方法:shell

簡單的數據結構:編程

1、算計基礎

1.一、什麼是算法:

算法(Algorithm)是指解題方案的準確而完整的描述,是一系列解決問題的清晰指令,算法表明着用系統的方法描述解決問題的策略機制。也就是說,可以對必定規範的輸入,在有限時間內得到所要求的輸出。若是一個算法有缺陷,或不適合於某個問題,執行這個算法將不會解決這個問題。不一樣的算法可能用不一樣的時間、空間或效率來完成一樣的任務。一個算法的優劣能夠用空間複雜度與時間複雜度來衡量。數組

簡單的說,算法就是一個計算的過程,解決問題的辦法。數據結構

一個算法應該具備如下七個重要的特徵:app

①有窮性(Finiteness):算法的有窮性是指算法必須能在執行有限個步驟以後終止;dom

②確切性(Definiteness):算法的每一步驟必須有確切的定義;編程語言

③輸入項(Input):一個算法有0個或多個輸入,以刻畫運算對象的初始狀況,所謂0個輸入是指算法自己定出了初始條件;ide

④輸出項(Output):一個算法有一個或多個輸出,以反映對輸入數據加工後的結果。沒有輸出的算法是毫無心義的;函數

⑤可行性(Effectiveness):算法中執行的任何計算步驟都是能夠被分解爲基本的可執行的操做步,即每一個計算步均可以在有限時間內完成(也稱之爲有效性);

⑥高效性(High efficiency):執行速度快,佔用資源少;

⑦健壯性(Robustness):對數據響應正確。

 

1.二、時間複雜度

定義:若是一個問題的規模是n,解這一問題的某一算法所須要的時間爲T(n),它是n的某一函數 T(n)稱爲這一算法的「時間複雜性」。 

當輸入量n逐漸加大時,時間複雜性的極限情形稱爲算法的「漸近時間複雜性」。

此外,一個問題自己也有它的複雜性,若是某個算法的複雜性到達了這個問題複雜性的下界,那就稱這樣的算法是最佳算法。

 

計算機科學中,算法的時間複雜度是一個函數,它定量描述了該算法的運行時間,時間複雜度經常使用大O符號(大O符號(Big O notation)是用於描述函數漸進行爲的數學符號。更確切地說,它是用另外一個(一般更簡單的)函數來描述一個函數數量級的漸近上界。在數學中,它通常用來刻畫被截斷的無窮級數尤爲是漸近級數的剩餘項;在計算機科學中,它在分析算法複雜性的方面很是有用。)表述,使用這種方式時,時間複雜度可被稱爲是漸近的,它考察當輸入值大小趨近無窮時的狀況。

大O,簡而言之能夠認爲它的含義是「order of」(大約是)

無窮大漸近
大O符號在分析算法效率的時候很是有用。舉個例子,解決一個規模爲 n 的問題所花費的時間(或者所需步驟的數目)能夠被求得:T(n) = 4n^2 - 2n + 2。
當 n 增大時,n^2; 項將開始占主導地位,而其餘各項能夠被忽略——舉例說明:當 n = 500,4n^2; 項是 2n 項的1000倍大,所以在大多數場合下,省略後者對錶達式的值的影響將是能夠忽略不計的。

時間複雜度的計算:

1).一個算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但咱們不可能也沒有必要對每一個算法都上機測試,只需知道哪一個算法花費的時間多,哪一個算法花費的時間少就能夠了。而且一個算法花費的時間與算法中語句的執行次數成正比例,哪一個算法中語句執行次數多,它花費時間就多。

一個算法中的語句執行次數稱爲語句頻度或時間頻度。記爲T(n)

2.通常狀況下,算法的基本操做重複執行的次數是模塊n的某一個函數f(n),所以,算法的時間複雜度記作:T(n)=O(f(n))。隨着模塊n的增大,算法執行的時間的增加率和f(n)的增加率成正比,因此f(n)越小,算法的時間複雜度越低,算法的效率越高。

在計算時間複雜度的時候,先找出算法的基本操做,而後根據相應的各語句肯定它的執行次數,再找出T(n)的同數量級(它的同數量級有如下:1,Log2n ,n ,nLog2n ,n的平方,n的三次方,2的n次方,n!),找出後,f(n)=該數量級,若T(n)/f(n)求極限可獲得一常數c,則時間複雜度T(n)=O(f(n))。

3.常見的時間複雜度

按數量級遞增排列,常見的時間複雜度有:

常數階O(1),  對數階O(log2n),  線性階O(n),  線性對數階O(nlog2n),  平方階O(n2), 立方階O(n3),..., k次方階O(nk), 指數階O(2n) 。

其中,

1.O(n),O(n2), 立方階O(n3),..., k次方階O(nk) 爲多項式階時間複雜度,分別稱爲一階時間複雜度,二階時間複雜度。。。。

2.O(2n),指數階時間複雜度,該種不實用

3.對數階O(log2n),   線性對數階O(nlog2n),除了常數階之外,該種效率最高

例:算法:

 1   for(i=1;i<=n;++i)
 2 
 3   {
 4 
 5      for(j=1;j<=n;++j)
 6 
 7      {
 8 
 9          c[ i ][ j ]=0; //該步驟屬於基本操做 執行次數:n^2
10 
11           for(k=1;k<=n;++k)
12 
13                c[ i ][ j ]+=a[ i ][ k ]*b[ k ][ j ]; //該步驟屬於基本操做 執行次數:n^3
14 
15      }
16 
17   }

  則有 T(n)= n^2+n^3,根據上面括號裏的同數量級,咱們能夠肯定 n^3爲T(n)的同數量級

  則有f(n)= n^3,而後根據T(n)/f(n)求極限可獲得常數c

  則該算法的 時間複雜度:T(n)=O(n^3)

時間複雜度爲:O(1) 

1 Temp=i;
2 i=j;
3 j=temp;                 

以上三條單個語句的頻度均爲1,該程序段的執行時間是一個與問題規模n無關的常數。算法的時間複雜度爲常數階,記做T(n)=O(1)。若是算法的執行時間不隨着問題規模n的增長而增加,即便算法中有上千條語句,其執行時間也不過是一個較大的常數。此類算法的時間複雜度是O(1)。 

時間複雜度爲:O(n2

交換i和j的內容

1      sum=0;                 (一次)
2      for(i=1;i<=n;i++)       (n次 )
3         for(j=1;j<=n;j++) (n^2次 )
4          sum++;       (n^2次 )

解:T(n)=2n^2+n+1 =O(n^2) 

例子    

1   for (i=1;i<n;i++)
2     {
3         y=y+1;         ①  
4         for (j=0;j<=(2*n);j++)   
5            x++;        ②     
6     }        

解: 語句1的頻度是n-1

          語句2的頻度是(n-1)*(2n+1)=2n^2-n-1

          f(n)=2n^2-n-1+(n-1)=2n^2-2

          該程序的時間複雜度T(n)=O(n^2).         

時間複雜度爲:O(n)                                                        

1     a=0;
2     b=1;                      ①
3     for (i=1;i<=n;i++) ②
4     { 
5        s=a+b;    ③
6        b=a;     ④ 
7        a=s;     ⑤
8     }

解:語句1的頻度:2,       

           語句2的頻度: n,       

          語句3的頻度: n-1,       

          語句4的頻度:n-1,   

          語句5的頻度:n-1,                                 

          T(n)=2+n+3(n-1)=4n-1=O(n).                                                                                              

時間複雜度爲:O(log2n ) 

1      i=1;       ①
2     while (i<=n)
3        i=i*2; ②

解: 語句1的頻度是1, 

          設語句2的頻度是f(n),   則:2^f(n)<=n;f(n)<=log2n   

          取最大值f(n)= log2n,

          T(n)=O(log2n )

時間複雜度爲:O(n3)   

1   for(i=0;i<n;i++)
2     { 
3        for(j=0;j<i;j++) 
4        {
5           for(k=0;k<j;k++)
6              x=x+2; 
7        }
8     }

解:當i=m, j=k的時候,內層循環的次數爲k當i=m時, j 能夠取 0,1,...,m-1 , 因此這裏最內循環共進行了0+1+...+m-1=(m-1)m/2次因此,i從0取到n, 則循環共進行了: 0+(1-1)*1/2+...+(n-1)n/2=n(n+1)(n-1)/6因此時間複雜度爲O(n^3).                            

咱們還應該區分算法的最壞狀況的行爲和指望行爲。如快速排序的最 壞狀況運行時間是 O(n^2),但指望時間是 O(nlogn)。經過每次都仔細 地選擇基準值,咱們有可能把平方狀況 (即O(n^2)狀況)的機率減少到幾乎等於 0。在實際中,精心實現的快速排序通常都能以 (O(nlogn)時間運行。

下面是一些經常使用的記法: 

訪問數組中的元素是常數時間操做,或說O(1)操做。一個算法如 果能在每一個步驟去掉一半數據元素,如二分檢索,一般它就取 O(logn)時間。用strcmp比較兩個具備n個字符的串須要O(n)時間。常規的矩陣乘算法是O(n^3),由於算出每一個元素都須要將n對 元素相乘並加到一塊兒,全部元素的個數是n^2。

指數時間算法一般來源於須要求出全部可能結果。例如,n個元 素的集合共有2n個子集,因此要求出全部子集的算法將是O(2n)的。指數算法通常說來是太複雜了,除非n的值很是小,由於,在 這個問題中增長一個元素就致使運行時間加倍。不幸的是,確實有許多問題 (如著名的「巡迴售貨員問題」 ),到目前爲止找到的算法都是指數的。若是咱們真的遇到這種狀況,一般應該用尋找近似最佳結果的算法替代之。

 

時間複雜度小結:

間複雜度是用來估計算法運行時間的一個式子(單位)。

通常來講,時間複雜度高的算法比複雜度低的算法快。

常見的時間複雜度(按效率排序)

O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n2log2n)<O(n3)

不常見的時間複雜度(看看就好)

O(n!) O(2n) O(nn) …

 

如何一眼判斷時間複雜度?

循環減半的過程O(logn)

幾回循環就是n的幾回方的複雜度

 

三、空間複雜度

空間複雜度(Space Complexity)是對一個算法在運行過程當中臨時佔用存儲空間大小的量度,記作S(n)=O(f(n))。好比直接插入排序的時間複雜度是O(n^2),空間複雜度是O(1) 。而通常的遞歸算法就要有O(n)的空間複雜度了,由於每次遞歸都要存儲返回信息。一個算法的優劣主要從算法的執行時間和所須要佔用的存儲空間兩個方面衡量。

一個算法的空間複雜度S(n)定義爲該算法所耗費的存儲空間,它也是問題規模n的函數。漸近空間複雜度也經常簡稱爲空間複雜度。空間複雜度(SpaceComplexity)是對一個算法在運行過程當中臨時佔用存儲空間大小的量度。一個算法在計算機存儲器上所佔用的存儲空間,包括存儲算法自己所佔用的存儲空間,算法的輸入輸出數據所佔用的存儲空間和算法在運行過程當中臨時佔用的存儲空間這三個方面。算法的輸入輸出數據所佔用的存儲空間是由要解決的問題決定的,是經過參數表由調用函數傳遞而來的,它不隨本算法的不一樣而改變。存儲算法自己所佔用的存儲空間與算法書寫的長短成正比,要壓縮這方面的存儲空間,就必須編寫出較短的算法。算法在運行過程當中臨時佔用的存儲空間隨算法的不一樣而異,有的算法只須要佔用少許的臨時工做單元,並且不隨問題規模的大小而改變,咱們稱這種算法是「就地\"進行的,是節省存儲的算法,有的算法須要佔用的臨時工做單元數與解決問題的規模n有關,它隨着n的增大而增大,當n較大時,將佔用較多的存儲單元,例如快速排序和歸併排序算法就屬於這種狀況。

分析一個算法所佔用的存儲空間要從各方面綜合考慮。如對於遞歸算法來講,通常都比較簡短,算法自己所佔用的存儲空間較少,但運行時須要一個附加堆棧,從而佔用較多的臨時工做單元;若寫成非遞歸算法,通常可能比較長,算法自己佔用的存儲空間較多,但運行時將可能須要較少的存儲單元。

一個算法的空間複雜度只考慮在運行過程當中爲局部變量分配的存儲空間的大小,它包括爲參數表中形參變量分配的存儲空間和爲在函數體中定義的局部變量分配的存儲空間兩個部分。若一個算法爲 遞歸算法,其空間複雜度爲遞歸所使用的堆棧空間的大小,它等於一次調用所分配的臨時存儲空間的大小乘以被調用的次數(即爲遞歸調用的次數加1,這個1表示開始進行的一次非遞歸調用)。算法的空間複雜度通常也以數量級的形式給出。如當一個算法的空間複雜度爲一個常量,即不隨被處理數據量n的大小而改變時,可表示爲O(1);當一個算法的空間複雜度與以2爲底的n的對數成正比時,可表示爲O(log2n);當一個算法的空間複雜度與n成線性比例關係時,可表示爲O(n).若形參爲數組,則只須要爲它分配一個存儲由實參傳送來的一個地址指針的空間,即一個機器字長空間;若形參爲引用方式,則也只須要爲其分配存儲一個地址的空間,用它來存儲對應實參變量的地址,以便由系統自動引用實參變量。

時間複雜度與空間複雜度比較:

對於一個算法,其時間複雜度和空間複雜度每每是相互影響的。當追求一個較好的時間複雜度時,可能會使空間複雜度的性能變差,便可能致使佔用較多的存儲空間;反之,當追求一個較好的空間複雜度時,可能會使時間複雜度的性能變差,便可能致使佔用較長的運行時間。另外,算法的全部性能之間都存在着或多或少的相互影響。所以,當設計一個算法(特別是大型算法)時,要綜合考慮算法的各項性能,算法的使用頻率,算法處理的數據量的大小,算法描述語言的特性,算法運行的機器系統環境等各方面因素,纔可以設計出比較好的算法。算法的時間複雜度和空間複雜度合稱爲算法的複雜度。

2、查找

列表查找:從列表中查找指定元素

輸入:列表、待查找元素

輸出:元素下標或未查找到元素

常見的查找方法有兩種: 

2.1順序查找

從列表第一個元素開始,順序進行搜索,直到找到爲止。

查找成功時的平均查找長度爲:(假設每一個數據元素的機率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;

當查找不成功時,須要n+1次比較,時間複雜度爲O(n);

因此, 順序查找的時間複雜度爲O(n ) 。

順序查找的實現:

 1 import random
 2 def improveseque(la,x):
 3     la[0]=x
 4     k=len(la)-1
 5     while x!=la[k]:
 6         k=k-1
 7     return k
 8 
 9 data=list(range(100))
10 random.shuffle(data)
11 print(data)
12 
13 while True:
14     key = int(input("input your want number"))
15     n = improveseque(data,key)
16     if n == 0:
17         print("not found!")
18     else:
19         print(key,"IS",n+1,"element")
順序查找

2.2二分查找(折半查找)

從有序列表的候選區data[0:n]開始,經過對待查找的值與候選區中間值的比較,可使候選區減小一半。

說明:元素必須是有序的,若是是無序的則要先進行排序操做。

基本思想:也稱爲是折半查找,屬於有序查找算法。用給定值k先與中間結點的關鍵字比較,中間結點把線形表分紅兩個子表,若相等則查找成功;若不相等,再根據k與該中間結點關鍵字的比較結果肯定下一步查找哪一個子表,這樣遞歸進行,直到查找到或查找結束髮現表中沒有這樣的結點。

複雜度分析: 最壞狀況下,關鍵詞比較次數爲log2(n+1),且 指望時間複雜度爲O(log2n) ;

注: 折半查找的前提條件是須要有序表順序存儲,對於靜態查找表,一次排序後再也不變化,折半查找能獲得不錯的效率。但對於須要 頻繁執行插入或刪除操做的數據集來講,維護有序的排序會帶來不小的工做量,那就不建議使用。

二分查找的實現:

 1 #Author:ajun
 2 import random
 3 def binary_search(dataset, find_num):
 4     if len(dataset) > 1:
 5         mid = int(len(dataset) / 2)
 6         if dataset[mid] == find_num:
 7             #print("Find it")
 8             return dataset[mid]
 9         elif dataset[mid] > find_num:
10             return binary_search(dataset[0:mid], find_num)
11         else:
12             return binary_search(dataset[mid + 1:], find_num)
13     else:
14         if dataset[0] == find_num:
15             #print("Find it")
16             return dataset[0]
17         else:
18             pass
19             #print("Cannot find it.")
20 
21 data=list(range(0,100,4))
22 print(data)
23 print(binary_search(data,30))
二分查找

2.3其它查找算法

還有不少其它的查找算法,等學好了,再給你們補充出來。

1). 插值查找

2). 斐波那契查找

3). 樹表查找

4). 分塊查找

5). 哈希查找

3、排序算法

在編程語言中,提到算法就會說到排序算法,下面簡單的用Python跟你們探討一下常見的算法!

排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納所有的排序記錄,在排序過程當中須要訪問外存。

咱們這裏說說排序就是內部排序。

 

3.1插入排序—直接插入排序(Straight Insertion Sort)

基本思想:

將一個記錄插入到已排序好的有序表中,從而獲得一個新,記錄數增1的有序表。即:先將序列的第1個記錄當作是一個有序的子序列,而後從第2個記錄逐個進行插入,直至整個序列有序爲止。

要點:設立哨兵,做爲臨時存儲和判斷數組邊界之用。

直接插入排序示例:

 

若是遇見一個和插入元素相等的,那麼插入元素把想插入的元素放在相等元素的後面。因此,相等元素的先後順序沒有改變,從原無序序列出去的順序就是排好序後的順序,因此插入排序是穩定的。

算法的實現:

 1 #Author:ajun
 2 import random
 3 def insert_sort(li):
 4     for i in range(1,len(li)):
 5         tmp=li[i]
 6         j=i-1
 7         while j >= 0 and li[j] > tmp:
 8             li[j+1]=li[j]
 9             j=j-1
10         li[j+1]=tmp
11 data=list(range(100))
12 random.shuffle(data)
13 print(data)
14 insert_sort(data)
15 print(data)
插入排序

3.2選擇排序—簡單選擇排序(Simple Selection Sort)

基本思想:

在要排序的一組數中,選出最小(或者最大)的個數與第1個位置的數交換;而後在剩下的數當中再找最小(或者最大)的與第2個位置的數交換,依次類推,直到第n-1個元素(倒數第二個數)和第n個元素(最後個數)比較爲止。

簡單選擇排序的示例:

 

操做方法:

第一趟,從n 個記錄中找出關鍵碼最小的記錄與第一個記錄交換;

第二趟,從第二個記錄開始的n-1 個記錄中再選出關鍵碼最小的記錄與第二個記錄交換;

以此類推.....

第i 趟,則從第i 個記錄開始的n-i+1 個記錄中選出關鍵碼最小的記錄與第i 個記錄交換,

直到整個序列按關鍵碼有序。

選擇排序算法:

 1 #Author:ajun
 2 import random
 3 def select_sort(li):
 4     for i in range(len(li)-1):
 5         min_loc=i
 6         for j in range(i+1,len(li)):
 7             if li[min_loc] > li[j]:
 8                 min_loc=j
 9         li[i],li[min_loc]=li[min_loc],li[i]
10 
11 data=list(range(100))
12 random.shuffle(data)
13 print(data)
14 select_sort(data)
15 print(data)
選擇排序

3.3 交換排序—冒泡排序(Bubble Sort)

基本思想:

在要排序的一組數中,對當前還未排好序的範圍內的所有數,自上而下對相鄰的兩個數依次進行比較和調整,讓較大的數往下沉,較小的往上冒。即:每當兩相鄰的數比較後發現它們的排序與排序要求相反時,就將它們互換。

冒泡排序的示例:

 

 冒泡排序的實現:

 1 #Author:ajun
 2 import random
 3 
 4 def bubble_sort(li):
 5     for i in range(len(li)):
 6         for j in range(len(li)-i-1):
 7             if li[j] > li[j+1]:
 8                 li[j],li[j+1]=li[j+1],li[j]
 9 
10 data=list(range(100))
11 random.shuffle(data)
12 print(data)
13 bubble_sort(data)
14 print(data)
冒泡排序

3.4交換排序—快速排序(Quick Sort)

基本思想:

1)選擇一個基準元素,一般選擇第一個元素或者最後一個元素,

2)經過一趟排序講待排序的記錄分割成獨立的兩部分,其中一部分記錄的元素值均比基準元素值小。另外一部分記錄的 元素值比基準值大。

3)此時基準元素在其排好序後的正確位置

4)而後分別對這兩部分記錄用一樣的方法繼續進行排序,直到整個序列有序。

快速排序的示例:

(a)一趟排序的過程:

(b)排序的全過程

算法的實現:

 快速排序

分析:

快速排序是一般被認爲在同數量級(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按關鍵碼有序或基本有序時,快排序反而蛻化爲冒泡排序。

 3.5堆排序

堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。

堆排序基礎知識:(樹和二叉樹)

樹是一種數據結構          好比:目錄結構

樹是一種能夠遞歸定義的數據結構

樹是由n個節點組成的集合:

若是n=0,那這是一棵空樹;

若是n>0,那存在1個節點做爲樹的根節點,其餘節點能夠分爲m個集合,每一個集合自己又是一棵樹。

一些概念:

根節點、葉子節點:

樹的深度(高度):

樹的度:

孩子節點/父節點:

子樹:

特殊的樹--二叉樹:

二叉樹:度不超過2的樹(節點最多有兩個叉)

滿二叉樹和徹底二叉樹:

滿二叉樹是指這樣的一種二叉樹:除最後一層外,每一層上的全部結點都有兩個子結點。在滿二叉樹中,每一層上的結點數都達到最大值,即在滿二叉樹的第k層上有2k-1個結點,且深度爲m的滿二叉樹有2m-1個結點。

徹底二叉樹是指這樣的二叉樹:除最後一層外,每一層上的結點數均達到最大值;在最後一層上只缺乏右邊的若干結點。 

對於徹底二叉樹來講,葉子結點只可能在層次最大的兩層上出現:對於任何一個結點,若其右分支下的子孫結點的最大層次爲p,則其左分支下的子孫結點的最大層次或爲p,或爲p+1。 

徹底二叉樹具備如下兩個性質: 

性質5:具備n個結點的徹底二叉樹的深度爲[log2n]+1。 

性質6:設徹底二叉樹共有n個結點。若是從根結點開始,按層次(每一層從左到右)用天然數1,2,……,n給結點進行編號,則對於編號爲k(k=1,2,……,n)的結點有如下結論: 

①若k=1,則該結點爲根結點,它沒有父結點;若k>1,則該結點的父結點編號爲INT(k/2)。 

②若2k≤n,則編號爲k的結點的左子結點編號爲2k;不然該結點無左子結點(顯然也沒有右子結點)。 

③若2k+1≤n,則編號爲k的結點的右子結點編號爲2k+1;不然該結點無右子結點。 

滿二叉樹確定是徹底二叉樹,徹底二叉樹不必定是滿二叉樹。

二叉樹的存儲方式:

二叉樹是非線性結構,其存儲結構能夠分爲兩種,即順序存儲結構鏈式存儲結構

一、順序存儲結構

---- 二叉樹的順序存儲,就是用一組連續的存儲單元存放二叉樹中的結點。即用一維數組存儲二叉樹中的結點。

所以,必須把二叉樹的全部結點安排成一個恰當的序列,結點在這個序列中的相互位置能反映出結點之間的邏輯關係。

用編號的方法從樹根起,自上層至下層,每層自左至右地給全部結點編號。

---- 依據二叉樹的性質,徹底二叉樹滿二叉樹採用順序存儲比較合適,樹中結點的序號能夠惟一地反映出結點之間的邏輯關係,

這樣既可以最大可能地節省存儲空間,又能夠利用數組元素的下標值肯定結點在二叉樹中的位置,以及結點之間的關係。

---- 一棵徹底二叉樹(滿二叉樹)以下圖所示:

                       

將這棵二叉樹存入到數組中,相應的下標對應其一樣的位置,以下圖所示:

                

可是對於通常的非徹底二叉樹來講,若是仍然按照從上到下、從左到右的次序存儲在一維數組中,則數組下標之間不能準確反映

樹中結點間的邏輯關係,能夠在非徹底二叉樹中添加一些並不存在的空結點使之變成徹底二叉樹,(把不存在的結點設置爲「^」)

不過這樣作有可能會形成空間的浪費,以下圖所示,而後再用一維數組順序存儲二叉樹。

         

             

 

缺點是:有可能對存儲空間形成極大的浪費,在最壞的狀況下,一棵深度爲k的右斜樹,它只有k個結點,卻須要2^k-1個結點存儲空間

這顯然是對存儲空間的嚴重浪費,因此順序存儲結構通常只用於徹底二叉樹或滿二叉樹

二、鏈式存儲結構

---- 二叉樹的鏈式存儲結構是指用鏈表來表示一棵二叉樹,即用鏈來指示元素的邏輯關係。

---- 二叉樹的每一個結點最多有兩個孩子,所以,每一個結點除了存儲自身的數據外,還應設置兩個指針分別指向左、右孩子結點。

結點結構以下圖所示:

        

其中data是數據域,lchild和rchild都是指針域,分別存放指向左孩子和右孩子的指針。由上圖所示的結點構成的鏈表稱做二叉鏈表。

當沒有孩子結點時,相應的指針域置爲空。

二叉樹小結:

二叉樹是度不超過2的樹

滿二叉樹與徹底二叉樹

(徹底)二叉樹能夠用列表來存儲,經過規律能夠從父親找到孩子或從孩子找到父親

 

基本思想:

堆的定義以下:具備n個元素的序列(k1,k2,...,kn),當且僅當知足

時稱之爲堆。由堆的定義能夠看出,堆頂元素(即第一個元素)必爲最小項(小頂堆)。
若以一維數組存儲一個堆,則堆對應一棵徹底二叉樹,且全部非葉結點的值均不大於(或不小於)其子女的值,根結點(堆頂元素)的值是最小(或最大)的。

大根堆:一棵徹底二叉樹,知足任一節點都比其孩子節點大。

小根堆:一棵徹底二叉樹,知足任一節點都比其孩子節點小。

如:

(a)大頂堆序列:(96, 83,27,38,11,09)

  (b)  小頂堆序列:(12,36,24,85,47,30,53,91)

 

初始時把要排序的n個數的序列看做是一棵順序存儲的二叉樹(一維數組存儲二叉樹),調整它們的存儲序,使之成爲一個堆,將堆頂元素輸出,獲得n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。而後對前面(n-1)個元素從新調整使之成爲堆,輸出堆頂元素,獲得n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們做交換,最後獲得有n個節點的有序序列。稱這個過程爲堆排序

所以,實現堆排序需解決兩個問題:
1. 如何將n 個待排序的數建成堆;
2. 輸出堆頂元素後,怎樣調整剩餘n-1 個元素,使其成爲一個新堆。


首先討論第二個問題:輸出堆頂元素後,對剩餘n-1元素從新建成堆的調整過程。
調整小頂堆的方法:

1)設有m 個元素的堆,輸出堆頂元素後,剩下m-1 個元素。將堆底元素送入堆頂((最後一個元素與堆頂進行交換),堆被破壞,其緣由僅是根結點不知足堆的性質。

2)將根結點與左、右子樹中較小元素的進行交換。

3)若與左子樹交換:若是左子樹堆被破壞,即左子樹的根結點不知足堆的性質,則重複方法 (2).

4)若與右子樹交換,若是右子樹堆被破壞,即右子樹的根結點不知足堆的性質。則重複方法 (2).

5)繼續對不知足堆性質的子樹進行上述交換操做,直到葉子結點,堆被建成。

稱這個自根結點到葉子結點的調整過程爲篩選。如圖:


再討論對n 個元素初始建堆的過程。
建堆方法:對初始序列建堆的過程,就是一個反覆進行篩選的過程。

1)n 個結點的徹底二叉樹,則最後一個結點是第個結點的子樹。

2)篩選從第個結點爲根的子樹開始,該子樹成爲堆。

3)以後向前依次對各結點爲根的子樹進行篩選,使之成爲堆,直到根結點。

如圖建堆初始過程:無序序列:(49,38,65,97,76,13,27,49)
                              


                              

 

 算法的實現:

從算法描述來看,堆排序須要兩個過程,一是創建堆,二是堆頂與堆的最後一個元素交換位置。因此堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。

 1 #Author:ajun
 2 import random
 3 def sift(data, low, high):
 4     i = low
 5     j = 2 * i + 1
 6     tmp = data[i]
 7     while j <= high:    #孩子在堆裏
 8         if j + 1 <= high and data[j] < data[j+1]:   #若是有右孩子且比左孩子大
 9             j += 1  #j指向右孩子
10         if data[j] > tmp:   #孩子比最高領導大
11             data[i] = data[j]   #孩子填到父親的空位上
12             i = j               #孩子成爲新父親
13             j = 2 * i +1        #新孩子
14         else:
15             break
16     data[i] = tmp           #最高領導放到父親位置
17 
18 
19 def heap_sort(data):
20     n = len(data)
21     for i in range(n // 2 - 1, -1, -1):
22         sift(data, i, n - 1)
23     #堆建好了
24     for i in range(n-1, -1, -1):            #i指向堆的最後
25         data[0], data[i] = data[i], data[0] #領導退休,刁民上位
26         sift(data, 0, i - 1)                #調整出新領導
27 
28 data=list(range(100))
29 random.shuffle(data)
30 print(data)
31 heap_sort(data)
32 print(data)
堆排序

3.6歸併排序

基本思想:

歸併(Merge)排序法是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列。

歸併排序示例:

 

 合併方法:

設r[i…n]由兩個有序子表r[i…m]和r[m+1…n]組成,兩個子表長度分別爲n-i +一、n-m。

    1. j=m+1;k=i;i=i; //置兩個子表的起始下標及輔助數組的起始下標
    2. 若i>m 或j>n,轉⑷ //其中一個子表已合併完,比較選取結束
    3. //選取r[i]和r[j]較小的存入輔助數組rf
      若是r[i]<r[j],rf[k]=r[i]; i++; k++; 轉⑵
      不然,rf[k]=r[j]; j++; k++; 轉⑵
    4. //將還沒有處理完的子表中元素存入rf
      若是i<=m,將r[i…m]存入rf[k…n] //前一子表非空
      若是j<=n ,  將r[j…n] 存入rf[k…n] //後一子表非空
    5. 合併結束

歸併排序算法實現:

 1 #Author:ajun
 2 import random
 3 def merge(li, low, mid, high):
 4     i = low
 5     j = mid + 1
 6     ltmp = []
 7     while i <= mid and j <= high:
 8         if li[i] < li[j]:
 9             ltmp.append(li[i])
10             i += 1
11         else:
12             ltmp.append(li[j])
13             j += 1
14     while i <= mid:
15         ltmp.append(li[i])
16         i += 1
17     while j <= high:
18         ltmp.append(li[j])
19         j += 1
20     li[low:high+1] = ltmp
21 
22 def _mergesort(li, low, high):
23     if low < high:
24         mid = (low + high) // 2
25         _mergesort(li,low, mid)
26         _mergesort(li, mid+1, high)
27         merge(li, low, mid, high)
28 
29 def mergesort(li):
30     _mergesort(li, 0, len(li) - 1)
31 
32 
33 data=list(range(100))
34 random.shuffle(data)
35 print(data)
36 mergesort(data)
37 print(data)
歸併排序

3.7希爾排序

希爾排序是1959 年由D.L.Shell 提出來的,相對直接排序有較大的改進。希爾排序又叫縮小增量排序

基本思想:

先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,待整個序列中的記錄「基本有序」時,再對全體記錄進行依次直接插入排序。

操做方法:

  1. 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列個數k,對序列進行k 趟排序;
  3. 每趟排序,根據對應的增量ti,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列做爲一個表來處理,表長度即爲整個序列的長度。

希爾排序的示例:

 算法實現: 

咱們簡單處理增量序列:增量序列d = {n/2 ,n/4, n/8 .....1} n爲要排序數的個數

即:先將要排序的一組記錄按某個增量dn/2,n爲要排序數的個數)分紅若干組子序列,每組中記錄的下標相差d.對每組中所有元素進行直接插入排序,而後再用一個較小的增量(d/2)對它進行分組,在每組中再進行直接插入排序。繼續不斷縮小增量直至爲1,最後使用直接插入排序完成排序。

希爾排序的實現:

 1 #Author:ajun
 2 import random
 3 def shell_sort(li):
 4     gap=int(len(li)/2)
 5     while gap >=1:
 6         for i in range(gap,len(li)):
 7             tmp=li[i]
 8             j=i-gap
 9             while j >= 0 and li[j] > tmp:
10                 li[j+gap]=li[j]
11                 j=j-gap
12             li[j+gap]=tmp
13         gap=int(gap/2)
14 
15 data=list(range(100))
16 random.shuffle(data)
17 print(data)
18 shell_sort(data)
19 print(data)
希爾排序

3.8其它排序-基數排序

簡單瞭解 略過!

3.9總結

各類排序的穩定性,時間複雜度和空間複雜度總結:

 咱們比較時間複雜度函數的狀況:

 

                             時間複雜度函數O(n)的增加狀況

因此對n較大的排序記錄。通常的選擇都是時間複雜度爲O(nlog2n)的排序方法。 

時間複雜度來講:

(1)平方階(O(n2))排序
  各種簡單排序:插入、選擇和冒泡排序;

時間複雜度:O(n2)

空間複雜度:O(1)


 (2)線性對數階(O(nlog2n))排序
  快速排序、堆排序和歸併排序;

三種排序算法的時間複雜度都是O(nlog2n)

通常狀況下,就運行時間而言:

快速排序 < 歸併排序 < 堆排序

三種排序算法的缺點:

快速排序:極端狀況下排序效率低

歸併排序:須要額外的內存開銷

堆排序:在快的排序算法中相對較


 (3)O(n1+§))排序,§是介於0和1之間的常數。

       希爾排序
(4)線性階(O(n))排序
  基數排序

說明:

當原表有序或基本有序時,直接插入排序和冒泡排序將大大減小比較次數和移動記錄的次數,時間複雜度可降至O(n);

而快速排序則相反,當原表基本有序時,將蛻化爲冒泡排序,時間複雜度提升爲O(n2);

原表是否有序,對簡單選擇排序、堆排序、歸併排序和基數排序的時間複雜度影響不大。

穩定性:

排序算法的穩定性:若待排序的序列中,存在多個具備相同關鍵字的記錄,通過排序, 這些記錄的相對次序保持不變,則稱該算法是穩定的;若經排序後,記錄的相對 次序發生了改變,則稱該算法是不穩定的。 
     穩定性的好處:排序算法若是是穩定的,那麼從一個鍵上排序,而後再從另外一個鍵上排序,第一個鍵排序的結果能夠爲第二個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,若是排序算法穩定,能夠避免多餘的比較;

穩定的排序算法:冒泡排序、插入排序、歸併排序和基數排序

不是穩定的排序算法:選擇排序、快速排序、希爾排序、堆排序

 選擇排序算法準則:

每種排序算法都各有優缺點。所以,在實用時需根據不一樣狀況適當選用,甚至能夠將多種方法結合起來使用。

選擇排序算法的依據

影響排序的因素有不少,平均時間複雜度低的算法並不必定就是最優的。相反,有時平均時間複雜度高的算法可能更適合某些特殊狀況。同時,選擇算法時還得考慮它的可讀性,以利於軟件的維護。通常而言,須要考慮的因素有如下四點:

1.待排序的記錄數目n的大小;

2.記錄自己數據量的大小,也就是記錄中除關鍵字外的其餘信息量的大小;

3.關鍵字的結構及其分佈狀況;

4.對排序穩定性的要求。

設待排序元素的個數爲n.

1)當n較大,則應採用時間複雜度爲O(nlog2n)的排序方法:快速排序、堆排序或歸併排序序。

       快速排序:是目前基於比較的內部排序中被認爲是最好的方法,當待排序的關鍵字是隨機分佈時,快速排序的平均時間最短;
       堆排序 :  若是內存空間容許且要求穩定性的,

       歸併排序:它有必定數量的數據移動,因此咱們可能過與插入排序組合,先得到必定長度的序列,而後再合併,在效率上將有所提升。

2)  當n較大,內存空間容許,且要求穩定性 =》歸併排序

3)當n較小,可採用直接插入或直接選擇排序。

    直接插入排序:當元素分佈有序,直接插入排序將大大減小比較次數和移動記錄的次數。

    直接選擇排序 :元素分佈有序,若是不要求穩定性,選擇直接選擇排序

5)通常不使用或不直接使用傳統的冒泡排序。

6)基數排序
它是一種穩定的排序算法,但有必定的侷限性:
  一、關鍵字可分解。
  二、記錄的關鍵字位數較少,若是密集更好
  三、若是是數字時,最好是無符號的,不然將增長相應的映射覆雜度,可先將其正負分開排序。

3.10排序算法的應用

3.10.1計數排序

如今有一個列表,列表中的數範圍都在0到100之間,列表長度大約爲100萬。設計算法在O(n)時間複雜度內將列表進行排序。

 1 #Author:ajun
 2 import random
 3 def count_sort(li, max_num):
 4     count = [0 for i in range(max_num + 1)]
 5     for num in li:
 6         count[num] += 1
 7     i = 0
 8     for num,m in enumerate(count):
 9         for j in range(m):
10             li[i] = num
11             i += 1
12 
13 data=[]
14 for i in range(1000):
15     data.append(random.randint(0,100))
16 random.shuffle(data)
17 print(data)
18 count_sort(data,100)
19 print(data)
計數排序

經過申請100個空間,經過空間計數,從而節約時間。

  1 import random
  2 import time
  3 import copy
  4 import sys
  5 def cal_time(func):
  6     def wrapper(*args, **kwargs):
  7         t1 = time.time()
  8         result = func(*args, **kwargs)
  9         t2 = time.time()
 10         print("%s running time: %s secs." % (func.__name__, t2 - t1))
 11         return result
 12     return wrapper
 13 @cal_time
 14 def bubble_sort(li):
 15     for i in range(len(li) - 1):
 16         for j in range(len(li) - i - 1):
 17             if li[j] > li[j+1]:
 18                 li[j], li[j+1] = li[j+1], li[j]
 19 @cal_time
 20 def bubble_sort_1(li):
 21     for i in range(len(li) - 1):
 22         exchange = False
 23         for j in range(len(li) - i - 1):
 24             if li[j] > li[j+1]:
 25                 li[j], li[j+1] = li[j+1], li[j]
 26                 exchange = True
 27         if not exchange:
 28             break
 29 def select_sort(li):
 30     for i in range(len(li) - 1):
 31         min_loc = i
 32         for j in range(i+1,len(li)):
 33             if li[j] < li[min_loc]:
 34                 min_loc = j
 35         li[i], li[min_loc] = li[min_loc], li[i]
 36 def insert_sort(li):
 37     for i in range(1, len(li)):
 38         tmp = li[i]
 39         j = i - 1
 40         while j >= 0 and li[j] > tmp:
 41             li[j+1]=li[j]
 42             j = j - 1
 43         li[j + 1] = tmp
 44 def quick_sort_x(data, left, right):
 45     if left < right:
 46         mid = partition(data, left, right)
 47         quick_sort_x(data, left, mid - 1)
 48         quick_sort_x(data, mid + 1, right)
 49 
 50 def partition(data, left, right):
 51     tmp = data[left]
 52     while left < right:
 53         while left < right and data[right] >= tmp:
 54             right -= 1
 55         data[left] = data[right]
 56         while left < right and data[left] <= tmp:
 57             left += 1
 58         data[right] = data[left]
 59     data[left] = tmp
 60     return left
 61 @cal_time
 62 def quick_sort(data):
 63     return quick_sort_x(data, 0, len(data) - 1)
 64 @cal_time
 65 def sys_sort(data):
 66     return data.sort()
 67 def sift(data, low, high):
 68     i = low
 69     j = 2 * i + 1
 70     tmp = data[i]
 71     while j <= high:    #孩子在堆裏
 72         if j + 1 <= high and data[j] < data[j+1]:   #若是有右孩子且比左孩子大
 73             j += 1  #j指向右孩子
 74         if data[j] > tmp:   #孩子比最高領導大
 75             data[i] = data[j]   #孩子填到父親的空位上
 76             i = j               #孩子成爲新父親
 77             j = 2 * i +1        #新孩子
 78         else:
 79             break
 80     data[i] = tmp           #最高領導放到父親位置
 81 @cal_time
 82 def heap_sort(data):
 83     n = len(data)
 84     for i in range(n // 2 - 1, -1, -1):
 85         sift(data, i, n - 1)
 86     #堆建好了
 87     for i in range(n-1, -1, -1):            #i指向堆的最後
 88         data[0], data[i] = data[i], data[0] #領導退休,刁民上位
 89         sift(data, 0, i - 1)                #調整出新領導
 90 
 91 def merge(li, low, mid, high):
 92     i = low
 93     j = mid + 1
 94     ltmp = []
 95     while i <= mid and j <= high:
 96         if li[i] < li[j]:
 97             ltmp.append(li[i])
 98             i += 1
 99         else:
100             ltmp.append(li[j])
101             j += 1
102     while i <= mid:
103         ltmp.append(li[i])
104         i += 1
105     while j <= high:
106         ltmp.append(li[j])
107         j += 1
108     li[low:high+1] = ltmp
109 
110 def _mergesort(li, low, high):
111     if low < high:
112         mid = (low + high) // 2
113         _mergesort(li,low, mid)
114         _mergesort(li, mid+1, high)
115         merge(li, low, mid, high)
116 
117 @cal_time
118 def mergesort(li):
119     _mergesort(li, 0, len(li) - 1)
120 
121 @cal_time
122 
123 def insert_sort(li):
124     for i in range(1,len(li)):
125         tmp=li[i]
126         j=i-1
127         while j >= 0 and li[j] > tmp:
128             li[j+1]=li[j]
129             j=j-1
130         li[j+1]=tmp
131 @cal_time
132 def shell_sort(li):
133     gap = int(len(li) // 2)
134     while gap >= 1:
135         for i in range(gap, len(li)):
136             li[j+1]=li[j]
137             j = j - 1
138             tmp = li[i]
139             j = i - gap
140             while j >= 0 and tmp < li[j]:
141                 li[j + gap] = li[j]
142                 j -= gap
143             li[i - gap] = tmp
144         gap = gap // 2
145 
146 
147 @cal_time
148 def count_sort(li, max_num):
149     count = [0 for i in range(max_num + 1)]
150     for num in li:
151         count[num] += 1
152     i = 0
153     for num,m in enumerate(count):
154         for j in range(m):
155             li[i] = num
156             i += 1
157 
158 @cal_time
159 def insert_sort(li):
160     for i in range(1, len(li)):
161         tmp = li[i]
162         j = i - 1
163         while j >= 0 and li[j] > tmp:
164             li[j+1]=li[j]
165             j = j - 1
166         li[j + 1] = tmp
167 
168 def topn(li, n):
169     heap = li[0:n]
170     for i in range(n // 2 - 1, -1, -1):
171         sift(heap, i, n - 1)
172     #遍歷
173     for i in range(n, len(li)):
174         if li[i] > heap[0]:
175             heap[0] = li[i]
176             sift(heap, 0, n - 1)
177     for i in range(n - 1, -1, -1):  # i指向堆的最後
178         heap[0], heap[i] = heap[i], heap[0]  # 領導退休,刁民上位
179         sift(heap, 0, i - 1)  # 調整出新領導
180     return heap
181 
182 data_data = []
183 sys.setrecursionlimit(100000)#增長遞歸的層數
184 for i in range(100000):
185      data_data.append(random.randint(0,100))
186 # # data.sort()
187 random.shuffle(data_data)
188 data1 = copy.deepcopy(data_data)
189 data2 = copy.deepcopy(data_data)
190 data3 = copy.deepcopy(data_data)
191 data4 = copy.deepcopy(data_data)
192 # #
193 #bubble_sort(data3)
194 quick_sort(data2)
195 count_sort(data1, 100)
196 sys_sort(data4)
前面全部排序算法的綜合比較

結果

經過比較發現,快速排序用了7秒,計數排序用了0.02秒,系統的自帶的(通常用C實現的排序),用了0.03秒。

3.10.2 TOP10--海量數據取最大的十個數。

如今有n個數(n>10000),設計算法,按大小順序獲得前10大的數。

 1 import random
 2 
 3 def insert(li, i):
 4     tmp = li[i]
 5     j = i - 1
 6     while j >= 0 and li[j] > tmp:
 7         li[j + 1] = li[j]
 8         j = j - 1
 9     li[j + 1] = tmp
10 
11 def insert_sort(li):
12     for i in range(1, len(li)):
13         insert(li, i)
14 
15 
16 def topk(li, k):
17     top = li[0:k + 1]
18     insert_sort(top)
19     for i in range(k+1, len(li)):
20         top[k] = li[i]
21         insert(top, k)
22     return top[:-1]
23 
24 
25 def sift(data, low, high):
26     i = low
27     j = 2 * i + 1
28     tmp = data[i]
29     while j <= high:    #孩子在堆裏
30         if j + 1 <= high and data[j] < data[j+1]:   #若是有右孩子且比左孩子大
31             j += 1  #j指向右孩子
32         if data[j] > tmp:   #孩子比最高領導大
33             data[i] = data[j]   #孩子填到父親的空位上
34             i = j               #孩子成爲新父親
35             j = 2 * i +1        #新孩子
36         else:
37             break
38     data[i] = tmp           #最高領導放到父親位置
39 
40 def topn(li, n):
41     heap = li[0:n]
42     for i in range(n // 2 - 1, -1, -1):
43         sift(heap, i, n - 1)
44     #遍歷
45     for i in range(n, len(li)):
46         if li[i] < heap[0]:
47             heap[0] = li[i]
48             sift(heap, 0, n - 1)
49     for i in range(n - 1, -1, -1):  # i指向堆的最後
50         heap[0], heap[i] = heap[i], heap[0]  # 領導退休,刁民上位
51         sift(heap, 0, i - 1)  # 調整出新領導
52     return heap
53 
54 data = list(range(10000))
55 random.shuffle(data)
56 print(topn(data, 10))
57 #print(topk(data, 10))
top10源代碼

思路:

1)topk方法就是插入排序。插入排序法:先將前10個排序做爲初步結果,而後對剩餘990個進行循環,每一個值與結果t中的值比較,若是比裏面的某個值大,那麼刪掉裏面最小的。保持只有10個值。等循環完畢,那麼最終結果TOP10。

2)topn方法就是堆排序思路,解決這類問題的最佳思路。

取列表前10個元素創建一個小根堆。堆頂就是目前第10大的數。

依次向後遍歷原列表,對於列表中的元素,若是小於堆頂,則忽略該元素;若是大於堆頂,則將堆頂更換爲該元素,而且對堆進行一次調整;

遍歷列表全部元素後,倒序彈出堆頂。

3.10.3練習題

1)給定一個列表和一個整數,設計算法找到兩個數的下標,使得兩個數之和爲給定的整數。保證確定僅有一個結果。例如,列表[1,2,5,4]與目標整數3,1+2=3,結果爲(0, 1).

 1 import copy
 2 li = [1, 5, 4, 2]
 3 target = 3
 4 max_num = 100
 5 
 6 def func1():
 7     for i in range(len(li)):
 8         for j in range(i+1, len(li)):
 9             if li[i] + li[j] == target:
10                 return (i,j)
11 
12 def bin_search(data_set, val, low, high):
13     while low <= high:
14         mid = (low+high)//2
15         if data_set[mid] == val:
16             return mid
17         elif data_set[mid] < val:
18             low = mid + 1
19         else:
20             high = mid - 1
21     return
22 
23 def func2():
24     li2 = copy.deepcopy(li)
25     li2.sort()
26     for i in range(len(li2)):
27         a = i
28         b = bin_search(li2, target - li2[a], i+1, len(li2)-1)
29         if b:
30             return (li.index(li2[a]),li.index(li2[b]))
31 
32 def func3():
33     a = [None for i in range(max_num+1)]
34     for i in range(len(li)):
35         a[li[i]] = i
36         if a[target-li[i]] != None:
37             return (a[li[i]], a[target-li[i]])
38 
39 
40 print(func1())
參考答案

三種思路

(1):窮舉法,所有測試。

(2):經過二分查找的思路

(3):時間複雜度最少,經過列表的查找。

2)給定一個升序列表和一個整數,返回該整數在列表中的下標範圍。例如:列表[1,2,3,3,3,4,4,5],若查找3,則返回(2,4);若查找1,則返回(0,0)。

 1 def bin_search(data_set, val):
 2     low = 0
 3     high = len(data_set) - 1
 4     while low <= high:
 5         mid = (low+high)//2
 6         if data_set[mid] == val:
 7             left = mid
 8             right = mid
 9             while left >= 0 and data_set[left] == val:
10                 left -= 1
11             while right < len(data_set) and data_set[right] == val:
12                 right += 1
13             return (left + 1, right - 1)
14         elif data_set[mid] < val:
15             low = mid + 1
16         else:
17             high = mid - 1
18     return (-1, -1)
19 
20 
21 def bin_search(data_set, val):
22     low = 0
23     high = len(data_set) - 1
24     while low <= high:
25         mid = (low+high)//2
26         if data_set[mid] == val:
27             left = mid
28             right = mid
29             while left >= 0 and data_set[left] == val:
30                 left -= 1
31             while right < len(data_set) and data_set[right] == val:
32                 right += 1
33             return (left + 1, right - 1)
34         elif data_set[mid] < val:
35             low = mid + 1
36         else:
37             high = mid - 1
38     return
39 
40 
41 
42 li = [1,2,3,3,3,4,4,5]
43 print(bin_search(li, 3))
參考答案

4、簡單數據結構

數據結構就是設計數據以何種方式組織並存儲在計算機中。

好比:列表、集合與字典等都是一種數據結構。 「程序=數據結構+算法」

Python中特有的-----列表:在其餘編程語言中稱爲「數組」,是一種基本的數據結構類型。

關於列表的問題:

列表中元素使如何存儲的?

列表提供了哪些基本的操做?

這些操做的時間複雜度是多少?

4.1棧

4.1 棧的定義

棧(Stack)是一個數據集合,能夠理解爲只能在一端進行插入或刪除操做的列表。棧的特色:後進先出(last-in, first-out)。以下所示:

棧的基本運算有六種:

構造空棧:建立列表、

判棧空: 判斷列表是否爲空、

判棧滿:

進棧:可形象地理解爲壓入,這時棧中會多一個元素

退棧:  可形象地理解爲彈出,彈出後棧中就無此元素了。

取棧頂元素:不一樣與彈出,只是使用棧頂元素的值,該元素仍在棧頂不會改變。

 對於Python來講,棧用列表實現的。棧的操做通常只有三種,出棧(pop),進棧(append),查看棧頂元素([-1])。

棧的應用:

1)括號匹配問題:給一個字符串,其中包含小括號、中括號、大括號,求該字符串中的括號是否匹配。

例如:()()[]{}               匹配                         ([{()}])               匹配

           [](             不匹配                               [(])            不匹配

 1 def cheak_kuohao(s):
 2     stack = []
 3     for char in s:
 4         if char in {'(','[', '{'}:
 5             stack.append(char)
 6         elif char == ')':
 7             if len(stack)>0 and stack[-1]=='(':
 8                 stack.pop()
 9             else:
10                 return False
11         elif char == ']':
12             if len(stack) > 0 and stack[-1] == '[':
13                 stack.pop()
14             else:
15                 return False
16         elif char == '}':
17             if len(stack)>0 and stack[-1]=='{':
18                 stack.pop()
19             else:
20                 return False
21     if len(stack) == 0:
22         return True
23     else:
24         return False
25 
26 
27 print(cheak_kuohao('()[]{{[]}}'))
括號匹配

2)迷宮問題

給一個二維列表,表示迷宮(0表示通道,1表示圍牆)。給出算法,求一條走出迷宮的路徑。

 

maze = [

    [1,1,1,1,1,1,1,1,1,1],
    [1,0,0,1,0,0,0,1,0,1],
    [1,0,0,1,0,0,0,1,0,1],
    [1,0,0,0,0,1,1,0,0,1],
    [1,0,1,1,1,0,0,0,0,1],
    [1,0,0,0,1,0,0,0,0,1],
    [1,0,1,0,0,0,1,0,0,1],
    [1,0,1,1,1,0,1,1,0,1],
    [1,1,0,0,0,0,0,0,0,1],
    [1,1,1,1,1,1,1,1,1,1]

 

 1 maze = [
 2     [1,1,1,1,1,1,1,1,1,1],
 3     [1,0,0,1,0,0,0,1,0,1],
 4     [1,0,0,1,0,0,0,1,0,1],
 5     [1,0,0,0,0,1,1,0,0,1],
 6     [1,0,1,1,1,0,0,0,0,1],
 7     [1,0,0,0,1,0,0,0,0,1],
 8     [1,0,1,0,0,0,1,0,0,1],
 9     [1,0,1,1,1,0,1,1,0,1],
10     [1,1,0,0,0,0,0,1,0,1],
11     [1,1,1,1,1,1,1,1,1,1]
12 ]
13 
14 dirs = [lambda x, y: (x + 1, y),
15         lambda x, y: (x - 1, y),
16         lambda x, y: (x, y - 1),
17         lambda x, y: (x, y + 1)]
18 
19 def mpath(x1, y1, x2, y2):
20     stack = []
21     stack.append((x1, y1))
22     while len(stack) > 0:
23         curNode = stack[-1]
24         if curNode[0] == x2 and curNode[1] == y2:
25             #到達終點
26             for p in stack:
27                 print(p)
28             return True
29         for dir in dirs:
30             nextNode = dir(curNode[0], curNode[1])
31             if maze[nextNode[0]][nextNode[1]] == 0:
32                 #找到了下一個
33                 stack.append(nextNode)
34                 maze[nextNode[0]][nextNode[1]] = -1  # 標記爲已經走過,防止死循環
35                 break
36         else:#四個方向都沒找到
37             maze[curNode[0]][curNode[1]] = -1  # 死路一條,下次別走了
38             stack.pop() #回溯
39     print("沒有路")
40     return False
41 
42 mpath(1,1,8,8)
棧實現迷宮問題

3)漢諾塔的問題:

解決:

1)若是有一個盤子,直接從X移到Z便可。
2)若是有n個盤子要從X移到Z,Y做爲輔助。問題能夠轉化爲,先將上面n-1個從X移動到Y,Z做爲輔助,而後將第n個從X移動到Z,最後將剩餘的n-1個從Y移動到Z,X做爲輔助。

 

4.2隊列

Queue是一種先進先出的數據結構,和Stack同樣,他也有鏈表和數組兩種實現,理解了Stack的實現後,Queue的實現就比較簡單了。

queue

隊列(Queue)是一個數據集合,僅容許在列表的一端進行插入,另外一端進行刪除。

進行插入的一端稱爲隊尾(rear),插入動做稱爲進隊或入隊

進行刪除的一端稱爲隊頭(front),刪除動做稱爲出隊

隊列的性質:先進先出(First-in, First-out)

 

雙向隊列:隊列的兩端都容許進行進隊和出隊操做。

 

 

隊列可否簡單用列表實現?爲何?

 

使用方法:from collections import deque

建立隊列:queue = deque(li)

進隊:append

出隊:popleft

雙向隊列隊首進隊:appendleft

雙向隊列隊尾進隊:pop

 

隊列的實現原理:

初步設想:列表+兩個下標指針

建立一個列表和兩個變量,front變量指向隊首,rear變量指向隊尾。初始時,front和rear都爲0。

進隊操做:元素寫到li[rear]的位置,rear自增1。

出隊操做:返回li[front]的元素,front自減1。

 

這種實現的問題?

 

 

隊列的實現原理——環形隊列

改進方案:將列表首尾邏輯上鍊接起來。

 

 

環形隊列:當隊尾指針front == Maxsize + 1時,再前進一個位置就自動到0。

實現方式:求餘數運算

隊首指針前進1:front = (front + 1) % MaxSize

隊尾指針前進1:rear = (rear + 1) % MaxSize

隊空條件:rear == front

隊滿條件:(rear + 1) % MaxSize == front

 

1 from collections import deque
2 
3 queue = deque()
4 queue.append(1)
5 queue.append(2)
6 print(queue.popleft())

 

隊列的應用:

1)迷宮問題

在解棧的迷宮問題,使用隊列的方法:

 1 from collections import  deque
 2 
 3 mg = [
 4     [1,1,1,1,1,1,1,1,1,1],
 5     [1,0,0,1,0,0,0,1,0,1],
 6     [1,0,0,1,0,0,0,1,0,1],
 7     [1,0,0,0,0,1,1,0,0,1],
 8     [1,0,1,1,1,0,0,0,0,1],
 9     [1,0,0,0,1,0,0,0,0,1],
10     [1,0,1,0,0,0,1,0,0,1],
11     [1,0,1,1,1,0,1,1,0,1],
12     [1,1,0,0,0,0,0,1,0,1],
13     [1,1,1,1,1,1,1,1,1,1]
14 ]
15 
16 dirs = [lambda x, y: (x + 1, y),
17         lambda x, y: (x - 1, y),
18         lambda x, y: (x, y - 1),
19         lambda x, y: (x, y + 1)]
20 
21 def print_p(path):
22     curNode = path[-1]
23     realpath = []
24     print('迷宮路徑爲:')
25     while curNode[2] != -1:
26         realpath.append(curNode[0:2])
27         curNode = path[curNode[2]]
28     realpath.append(curNode[0:2])
29     realpath.reverse()
30     print(realpath)
31 
32 def mgpath(x1, y1, x2, y2):
33     queue = deque()
34     path = []
35     queue.append((x1, y1, -1))
36     while len(queue) > 0:
37         curNode = queue.popleft()
38         path.append(curNode)
39         if curNode[0] == x2 and curNode[1] == y2:
40             #到達終點
41             print_p(path)
42             return True
43         for dir in dirs:
44             nextNode = dir(curNode[0], curNode[1])
45             if mg[nextNode[0]][nextNode[1]] == 0:  # 找到下一個方塊
46                 queue.append((*nextNode, len(path) - 1))
47                 mg[nextNode[0]][nextNode[1]] = -1  # 標記爲已經走過
48     return False
49 
50 
51 mgpath(1,1,8,8)
隊列解決迷宮問題

2)銀行排隊
3)模擬打印機緩衝區。
在主機將數據輸出到打印機時,會出現主機速度與打印機的打印速度不匹配的問題。這時主機就要停下來等待打印機。顯然,這樣會下降主機的使用效率。爲此人們設想了一種辦法:爲打印機設置一個打印數據緩衝區,當主機須要打印數據時,先將數據依次寫入這個緩衝區,寫滿後主機轉去作其餘的事情,而打印機就從緩衝區中按照先進先出的原則依次讀取數據並打印,這樣作即保證了打印數據的正確性,又提升了主機的使用效率。因而可知,打印機緩衝區實際上就是一個隊列結構。
4)CPU分時系統
在一個帶有多個終端的計算機系統中,同時有多個用戶須要使用CPU運行各自的應用程序,它們分別經過各自的終端向操做系統提出使用CPU的請求,操做系統一般按照每一個請求在時間上的前後順序,將它們排成一個隊列,每次把CPU分配給當前隊首的請求用戶,即將該用戶的應用程序投入運行,當該程序運行完畢或用完規定的時間片後,操做系統再將CPU分配給新的隊首請求用戶,這樣便可以知足每一個用戶的請求,又可使CPU正常工做。

4.3鏈表

 

鏈表中每個元素都是一個對象,每一個對象稱爲一個節點,包含有數據域key和指向下一個節點的指針next。經過各個節點之間的相互鏈接,最終串聯成一個鏈表。

節點的定義:

class Node(object):
    def __init__(self, item):
        self.item = item
        self.next = None

頭結點:

 

遍歷鏈表:

def traversal(head):
    curNode = head  # 臨時用指針 
    while curNode is not None:
        print(curNode.data)
        curNode = curNode.next

 

 

插入:

p.next = curNode.next

curNode.next = p

 

刪除:

p = curNode.next

curNode.next = curNode.next.next

del p

 頭插法:

def createLinkListF(li):
    l = Node()
    for num in li:
        s = Node(num)
        s.next = l.next
        l.next = s
    return l

 

 

尾插法:

def createLinkListR(li):
    l = Node()
    r = l       #r指向尾節點
    for num in li:
        s = Node(num)
        r.next = s
        r = s

 

單鏈表的基本操做:

 1 class Node(object):
 2     def __init__(self, item=None):
 3         self.item = item
 4         self.next = None
 5 
 6 head = Node()
 7 head.next = Node(20)
 8 head.next.next = Node(30)
 9 
10 def traversal(head):
11     curNode = head  # 臨時用指針
12     while curNode is not None:
13         print(curNode.item)
14         curNode = curNode.next
15 
16 traversal(head)

 

雙鏈表:

  • 雙鏈表中每一個節點有兩個指針:一個指向後面節點、一個指向前面節點。
  • 節點定義:

節點的定義:

class Node(object):
    def __init__(self, item=None):
        self.item = item
        self.next = None
       
self.prior = None

 

雙鏈表的插入:

p.next = curNode.next

curNode.next.prior = p

p.prior = curNode

curNode.next = p

雙鏈表的刪除:

p = curNode.next

curNode.next = p.next

p.next.prior = curNode

del p

 

創建雙鏈表:

尾插法:

def createLinkListR(li):
    l = Node()
    r = l
    fornum inli:
        s = Node(num)
        r.next = s
        s.prior = r
        r = s
    returnl, r

列表與鏈表

1)按元素值查找

2)按下標查找

3)在某元素後插入

4)刪除某元素

4.4哈希查找

哈希值是一段由其餘某個值(標識) 映射 成爲的一段佔用更小的內存更少的數據空間的值。 
   而哈希表是 根據 指定的某個hash函數H(key),和處理衝突的方法,將一組關鍵字映射到另外一個有限的區間上,映射獲得的值(根據算法和解決衝突方法獲得) 就能夠做爲記錄在哈希表中的存放的具體位置。 
   哈希查找是經過計算數據元素的存儲地址進行查找的一種方法。

好比」5「是一個要保存的數,而後我丟給哈希函數,哈希函數給我返回一個」2」,那麼此時的」5「和「2」就創建一種對應關係,這種關係就是所謂的「哈希關係」,在實際應用中也就造成了」2「是key,」5「是value。

哈希必需要遵照兩點原則: 
①: key儘量的分散,也就是我丟一個「6」和「5」給你,你都返回一個「2」,那麼這樣的哈希函數不盡完美。 
②: 哈希函數儘量的簡單,也就是說丟一個「6」給你,你哈希函數要搞1小時才能給我,這樣也是很差的。

經常使用的哈希函數構造方法:

  • 直接定址法:很容易理解,key=Value+C; 這個「C」是常量。Value+C其實就是一個簡單的哈希函數。
  • 除法取餘法: 很容易理解, key=value%C;解釋同上。
  • 數字分析法:這種蠻有意思,好比有一組value1=112233,value2=112633,value3=119033,針對這樣的數咱們分析數中間兩個數比較波動,其餘數不變。那麼咱們取key的值就能夠是key1=22,key2=26,key3=90。
  • 平方取中法。此處忽略,見名識意。
  • 摺疊法:這種蠻有意思,好比value=135790,要求key是2位數的散列值。那麼咱們將value變爲13+57+90=160,而後去掉高位「1」,此時key=60,哈哈,這就是他們的哈希關係,這樣作的目的就是key與每一位value都相關,來作到「散列地址」儘量分散的目地。·

當兩個不一樣的數據元素的哈希值相同時,就會發生衝突。解決衝突經常使用的手法有2種:

    • 開放地址法:若是兩個數據元素的哈希值相同,則在哈希表中爲後插入的數據元素另外選擇一個表項。當程序查找哈希表時,若是沒有在第一個對應的哈希表項中找到符合查找要求的數據元素,程序就會繼續日後查找,直到找到一個符合查找要求的數據元素,或者遇到一個空的表項。
    • 連接法:將哈希值相同的數據元素存放在一個鏈表中,在查找哈希表的過程當中,當查找到這個鏈表時,必須採用線性查找方法。


   一樣的長度,把標識的數據項給壓縮了,而且加上衝突處理方法,那就是另 外一串查找起來至關簡單同時不會出錯的線性表,並且咱們是省略了一大堆無用的查找,首先直接找到有可能正確的數據項來比較,那就說明正確的那個項必定就在咱們起始找的那個元素的附近,或者就是它,這不就是咱們想要的嗎? 
    
   哈希這種映射的方法將查找數的時間複雜度變成了一個算法常數,不管你查找什麼數,先執行指定的hash算法,以後按照解決衝突的方法去查找,這個效率比起線性表和隊列這些同爲線性數據結構的傢伙,那但是快了不僅一點點。 

 1 #Author:ajun
 2 
 3 #除法取餘法實現的哈希函數
 4 def myHash(data,hashLength,):
 5     return data % hashLength
 6 #哈希表檢索數據
 7 def searchHash(hash,hashLength,data):
 8     hashAddress=myHash(data,hashLength)
 9    #指定hashAddress存在,但並不是關鍵值,則用開放尋址法解決
10     while hash.get(hashAddress) and hash[hashAddress]!=data:
11         hashAddress+=1
12         hashAddress=hashAddress%hashLength
13     if hash.get(hashAddress)==None:
14         return None
15     return hashAddress
16 
17 #數據插入哈希表
18 def insertHash(hash,hashLength,data):
19     hashAddress=myHash(data,hashLength)
20     #若是key存在說明應經被別人佔用, 須要解決衝突
21     while(hash.get(hashAddress)):
22         #用開放尋執法
23         hashAddress+=1
24         hashAddress=myHash(data,hashLength)
25     hash[hashAddress]=data
26 
27 if __name__ == '__main__':
28     hashLength=20
29     L=[13, 29, 27, 28, 26, 30, 38 ]
30     hash={}
31     for i in L:
32         insertHash(hash,hashLength,i)
33     result=searchHash(hash,hashLength,22)
34     if result:
35         print("數據已找到,索引位置在",result)
36         print(hash[result])
37     else:
38         print("沒有找到數據")
哈希-

hash還普遍應用於加密和數據校驗中,hash的128位加密具備不可逆性,像MD5,我們一般用的WAP/WAP2協議也是應用到了hash技術

相關文章
相關標籤/搜索