排序算法 和數據結構

各類排序算法時間複雜度及空間複雜度對比

Timsort與其餘比較排序算法時間複雜度(time complexity)的比較python

空間複雜度(space complexities)比較算法

各類排序算法

同等硬件條件下,對相同長度的的列表排序,全部時常以下:shell

# 打印結果:
'''
quick_sort:0.03600168228149414
sorted:0.003000020980834961
sorted:0.003000020980834961
bubble_sort:11.830676555633545
select_sort:7.196411609649658
insert_sort:6.566375494003296
shell_sort:0.06900382041931152
count_sort:14.809847116470337
merge_sort: 0.06600356101989746
'''

1.python內置方法sorted()和list.sort()排序

內置數據類型list的方法sort(),內置函數sorted()
這個的底層實現就是歸併排序,只是使用了Python沒法編寫的底層實現,從而避免了Python自己附加的大量開銷,速度比咱們本身寫的歸併排序要快不少(10~20倍),因此說咱們通常排序都儘可能使用sorted和sort編程

# Python內置函數sorted()
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
sorted(li)
print('sorted:%s' % (time.time() - start))

# python內置數據類型list.sort()
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
li.sort()
print('sorted:%s' % (time.time() - start))

'''
sorted:0.003000020980834961
sorted:0.003000020980834961
'''

2.冒泡排序

# 冒泡排序
# 最壞的時間複雜度是:(O(n^2)),最優的時間複雜度是:O(n)  有序的列表在內層循環一趟就就return了。
def bubble_sort(l):
    for i in range(len(l)-1):
        # 定一個標誌位,作一個排序的優化。若是在循環第一趟的時候,
        # 發現列表是一個有序的列表,就不會走內層循環裏面的if條件
        # 判斷裏面的代碼,進行交換數字,flag標誌位就不會被置換
        # 成False,進而走內層循環外面的if判斷,終止整個函數的運行。

        flag = True
        for j in range(len(l)-i-1):
            if l[j] > l[j+1]:
                l[j],l[j+1] = l[j+1],l[j]
                flag = False
        if flag:
            return
# 冒泡排序
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
bubble_sort(li)
print('bubble_sort:%s' % (time.time() - start))


'''
bubble_sort:11.830676555633545
'''

3.選擇排序

# 選擇排序
def select_sort(l):
    for i in range(len(l)):
        minloc = i
        for j in range(i+1,len(l)):
            if l[j] < l[minloc]:
                l[j],l[minloc] = l[minloc],l[j]
     return l
# 選擇排序
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
select_sort(li)
print('select_sort:%s' % (time.time() - start))

'''
select_sort:7.196411609649658
'''

4.插入排序

# 插入排序
# 時間複雜度 O(n^2)
def insert_sort(l):
    for i in range(1,len(l)):
        j = i-1
        temp = l[i]

        while j >= 0 and l[j] > temp:

            l[j+1] = l[j]
            j = j-1
        l[j+1] = temp

# l=[5,7,4,6,3,1,2,9,8]
# insert_sort(l)
# print(l)


# 插入排序
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
insert_sort(li)
print('insert_sort:%s' % (time.time() - start))

'''
insert_sort:6.566375494003296
'''

5.快速排序

def partition(l,left,right):
    tmp = l[left]
    while left < right:
        while left < right and l[right] >= tmp:
            right = right - 1
        l[left] = l[right]

        while left < right and l[left] <= tmp:
            left =left + 1
        l[right] = l[left]

    l[left] = tmp
    return left

#快速排序
# 時間複雜度:O(nlogn)
def quick_sort(l,left,right):
    if left < right:
        mid = partition(l,left,right)
        quick_sort(l,left,mid-1)
        quick_sort(l,mid+1,right)

# 快排
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
quick_sort(li, 0, len(li)-1)
print('quick_sort:%s' % (time.time() - start))

'''
quick_sort:0.03600168228149414
'''

6.希爾排序

# 希爾排序
'''
希爾排序是一種分組插入排序算法
首先取一個整數d1=n/2,將元素分爲d1個組,魅族相鄰元素之間的距離爲d1,在各組內進行直接插入排序;
取第二個整數d2=d1/2,重複上述分組排序過程,直到d1=1,即全部元素在同一組內進行直接插入排序。
希爾排序每趟並不使某些元素有序,而是使總體數據愈來愈接近有序;最後一趟排序使得全部數據有序。
'''

def shell_sort(l):
    gap = len(l) // 2
    while gap > 0:
        for i in range(gap, len(l)):
            tmp = l[i]
            j = i - gap
            while j >= 0 and tmp < l[j]:
                l[j + gap] = l[j]
                j -= gap

            l[j + gap] = tmp
        gap //= 2
# 希爾排序
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
shell_sort(li)
print('shell_sort:%s' % (time.time() - start))

'''
shell_sort:0.06900382041931152
'''

7.計數排序

# 計數排序
# 方式1: 時間複雜度O(n+k) k表明數組中的最大值  線性時間的排序
# 優勢:穩定,適用於最大值不是很大的整數序列,在k值較小時突破了基於比較的排序的算法下限
# 缺點:存在前提條件,k值較大時,須要大量額外空間
l = [3, 4, 4, 5, 5, 61, 1, 2, 2, 3, 3, 3, 3, 6, 7, 7, 7, 7, 8, 9,0]

max_num = l[0]
for num in l:
    if num > max_num:
        max_num = num
l1 = [0] * (max_num +1)
for num in l:
    l1[num] += 1
l.clear()
for i in range(len(l1)):
        while l1[i] > 0:
            l.append(i)
            l1[i] -= 1
print(l)

# 方式2:時間複雜度:O(n^2)
def count_sort(l):
    n = len(l)
    res = [None] * n
    # 首次循環遍歷, 每一個列表的數都統計
    for i in range(n):
        # p 表示 a[i] 大於列表其餘數 的次數
        p = 0
        # q 表示 等於 a[i] 的次數
        q = 0
        # 二次循環遍歷, 列表中的每一個數都和首次循環的數比較
        for j in range(n):
            if l[i] > l[j]:
                p += 1
            elif l[i] == l[j]:
                q += 1
        for k in range(p, p+q):  # q表示 相等的次數,就表示, 從 P 開始索引後, 連續 q 次,都是一樣的 數
            res[k] = l[i]
    return res

# 計數排序
li = [random.randint(0,100000) for i in range(10000)]
start = time.time()
count_sort(li)
print('count_sort:%s' % (time.time() - start))

'''
count_sort:14.809847116470337
'''

8.歸併排序

歸併排序仍然是利用徹底二叉樹實現,它是創建在歸併操做上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。將已有序的子序列合併,獲得徹底有序的序列。數組

基本過程:假設初始序列含有n個記錄,則能夠當作是n個有序的子序列,每一個子序列的長度爲1,而後兩兩歸併,獲得n/2個長度爲2或1的有序子序列,再兩兩歸併,最終獲得一個長度爲n的有序序列爲止,這稱爲2路歸併排序。數據結構

下面的截圖來自《大話數據結構》相關章節,便於理解整個代碼的實現過程。併發

圖中主要代表了實例代碼中的兩部分,分別爲原始序列的拆分和合並兩部分。app

# 歸併排序
def merge_sort(lst):
    if len(lst) <= 1:
        return lst          # 從遞歸中返回長度爲1的序列

    middle = len(lst) // 2
    left = merge_sort(lst[:middle])     # 經過不斷遞歸,將原始序列拆分紅n個小序列
    right = merge_sort(lst[middle:])
    return merge(left, right)

def merge(left, right):
    i, j = 0, 0
    result = []
    while i < len(left) and j < len(right):  # 比較傳入的兩個子序列,對兩個子序列進行排序
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])         # 將排好序的子序列合併
    result.extend(right[j:])
    return result

# 歸併排序
li = [random.randint(0, 100000) for i in range(10000)]
start = time.time()
lst = merge_sort(li)
print("merge_sort:", time.time() - start)

'''
merge_sort: 0.06600356101989746
'''

9.桶排序

優勢:桶排序的時間複雜度是O(n+m),m是數列中最大值與最小值的插值,可實現快速排序。
缺點:若是差值過大,內存消耗巨大dom

#簡單的桶排序
def bucksort(A):

    bucks = dict()      # 定義一個桶變量,類型爲字典
    for i in A:
        bucks.setdefault(i,[])  # 每一個桶默認爲空列表
        bucks[i].append(i)      # 往對應的桶中添加對應相同元素
    print(bucks)
    A_sort = []
    for i in range(min(A), max(A)+1):
        if i in bucks:                  # 檢查是否存在對應數字的桶
            A_sort.extend(bucks[i])     # 合併桶中數據
    return A_sort

A = [2,3,5,4,6,7,3,3,0,8,5]
a = bucksort(A)
print(a)

# 包含其餘元素的桶排序
# element:人名,分數
class Person:
    def __init__(self,name,score):
        self.name = name
        self.score = score
    def __repr__(self):     # 覆寫打印輸出 print Person()
        return self.name + "-" + str(self.score)


def bucksort2(A):
    bucks = dict()
    for a in A:
        bucks.setdefault(a.score,[])  # 以我的分數爲評價排列標準
        bucks[a.score].append(a)      # 在相同分數的桶中添加人

    A_sort = []
    scorelist = [a.score for a in A]    # 將人員列表中全部人的分數取出
    for i in range(min(scorelist), max(scorelist)+1):
        if i in bucks:
            A_sort.extend(bucks[i])

    return A_sort


# 對人進行排序
B = [Person('huhu',5),Person('haha',3),Person('xixi',5),Person('hengheng',2),Person('gaoshou',8)]
b = bucksort2(B)
print(b)
'''
[hengheng-2, haha-3, huhu-5, xixi-5, gaoshou-8]
'''

列表查找

列表查找:從列表中查找指定元素數據結構和算法

​ 輸入:列表、待查找元素

​ 輸出:元素下標或爲查找到元素

順序查找:

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

二分查找:

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

# 二分查找
def binary_search(l,low,high,value):
    mid = (low + high) // 2
    if low <= high:
        if l[mid] == value:
            return mid
        elif l[mid] > value:
            return binary_search(l,low,mid-1,value)
        else:
            return binary_search(l,mid+1,high,value)

    else:
        return "此數不存在!"

l = [1,2,3,4,5,6,7,8,9,10,11,12,13]
res = binary_search(l,0,len(l)-1,9)
print(res)

數據結構

malloc()到底如何申請內存空間?

malloc()到底從哪裏獲得了內存空間?

答案是從堆裏面得到空間。也就是說函數返回的指針是指向堆裏面的一塊內存。

      操做系統中有一個記錄空閒內存地址的鏈表。當操做系統收到程序的申請時,就會遍歷該鏈表,而後就尋找第一個空間大於所申請空間的堆結點,而後就將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。

     malloc函數的實質體如今,它有一個將可用的內存塊鏈接爲一個長長的列表的所謂空閒鏈表(Free List)。調用malloc函數時,它沿鏈接表尋找一個大到足以知足用戶請求所須要的內存塊(根據不一樣的算法而定(將最早找到的不小於申請的大小內存塊分配給請求者,將最合適申請大小的空閒內存分配給請求者,或者是分配最大的空閒塊內存塊)。而後,將該內存塊一分爲二(一塊的大小與用戶請求的大小相等,另外一塊的大小就是剩下的字節)。接下來,將分配給用戶的那塊內存傳給用戶,並將剩下的那塊(若是有的話)返回到鏈接表上。

     調用free函數時,它將用戶釋放的內存塊鏈接到空閒鏈上。到最後,空閒鏈會被切成不少的小內存片斷,若是這時用戶申請一個大的內存片斷,那麼空閒鏈上可能沒有能夠知足用戶要求的片斷了。因而,malloc函數請求延時,並開始在空閒鏈上翻箱倒櫃地檢查各內存片斷,對它們進行整理,將相鄰的小空閒塊合併成較大的內存塊。若是沒法得到符合要求的內存塊,malloc函數會返回NULL指針,所以在調用malloc動態申請內存塊時,必定要進行返回值的判斷。

在此也要說明就是由於new和malloc須要符合大衆的申請內存空間的要求,針對泛型提供的,分配內存設計到分配算法和查找,此外還要避免內存碎片,因此其效率比較低下,所以有時程序猿會本身重寫new和delete,或者建立一個內存池來管理內存,提升程序運行的效率。

數組和鏈表

轉至:http://www.javashuo.com/article/p-pqwhlclj-et.html

1.鏈表是什麼

鏈表是一種上一個元素的引用指向下一個元素的存儲結構,鏈表經過指針來鏈接元素與元素;

鏈表是線性表的一種,所謂的線性表包含順序線性表和鏈表,順序線性表是用數組實現的,在內存中有順序排列,經過改變數組大小實現。而鏈表不是用順序實現的,用指針實現,在內存中不連續。意思就是說,鏈表就是將一系列不連續的內存聯繫起來,將那種碎片內存進行合理的利用,解決空間的問題。

因此,鏈表容許插入和刪除表上任意位置上的節點,可是不容許隨即存取。鏈表有不少種不一樣的類型:單向鏈表、雙向鏈表及循環鏈表。

2.單向鏈表

單向鏈表包含兩個域,一個是信息域,一個是指針域。也就是單向鏈表的節點被分紅兩部分,一部分是保存或顯示關於節點的信息,第二部分存儲下一個節點的地址,而最後一個節點則指向一個空值。

3.雙向鏈表

從上圖能夠很清晰的看出,每一個節點有2個連接,一個是指向前一個節點(當此連接爲第一個連接時,指向的是空值或空列表),另外一個則指向後一個節點(當此連接爲最後一個連接時,指向的是空值或空列表)。意思就是說雙向鏈表有2個指針,一個是指向前一個節點的指針,另外一個則指向後一個節點的指針。

4.循環鏈表

循環鏈表就是首節點和末節點被鏈接在一塊兒。循環鏈表中第一個節點以前就是最後一個節點,反之亦然。

5.數組和鏈表的區別?

不一樣:鏈表是鏈式的存儲結構;數組是順序的存儲結構。

鏈表經過指針來鏈接元素與元素,數組則是把全部元素按次序依次存儲。

鏈表的插入刪除元素相對數組較爲簡單,不須要移動元素,且較爲容易實現長度擴充,可是尋找某個元素較爲困難;

數組尋找某個元素較爲簡單,但插入與刪除比較複雜,因爲最大長度須要再編程一開始時指定,故當達到最大長度時,擴充長度不如鏈表方便。
相同:兩種結構都可實現數據的順序存儲,構造出來的模型呈線性結構。

6.鏈表的應用、代碼實踐

約瑟夫問題:

傳說在公園1世紀的猶太戰爭中,猶太約瑟夫是公元一世紀著名的歷史學家。在羅馬人佔領喬塔帕特後,39 個猶太人與約瑟夫及他的朋友躲到一個洞中,39個猶太人決定寧願死也不要被敵人俘虜,因而決定了一個流傳千古的自殺方式,41我的排成一個圓圈,由第1我的開始報數,每報到第3人該人就必須自殺,而後再由下一我的從新報數,直到全部人都自殺身亡爲止。然而約瑟夫和他的朋友並不想聽從這個約定,約瑟夫要他的朋友先僞裝聽從,他將朋友與本身安排在第_個和第_個位置,因而逃過了這場死亡遊戲,你知道安排在了第幾個嘛?

針對以上問題,使用單向循環鏈表的方式求解:

# 控制參數:
nums = 41
call = 3
# 參數定義:
peoples = [True for _ in range(nums)]

# append的方法性能很差
# for _ in range(nums):
#    peoples.append(True)

result = []
num = 1

while (any(peoples)):
    for index, people in enumerate(peoples):
        if people:
            if num == call:
                peoples[index] = False
                result.append(index + 1)
                #                print(index+1)#每輪的出局者
                #                print(peoples)#每次的隊列狀態
                num = 1
            else:
                num += 1

print('\n總數爲%d,報數爲%d' % (nums, call))
print('約瑟夫序列爲:\n%s\n' % result)

組織代碼的方式要學習體會;

7.自我理解

1)數組便於查詢和修改,可是不方便新增和刪除

2)鏈表適合新增和刪除,可是不適合查詢,根據業務狀況使用合適的數據結構和算法是在大數據量和高併發時必需要考慮的問題

棧和隊列的應用

是一種能夠實現「先進後出」的存儲結構

相似於一個箱子,先放進去的書,最後才能取出來,後放進去的書,先去出來。由於在棧中永遠只能先操做棧頂的元素。對棧頂的元素進行push和pop。

函數調用在棧中的應用

函數在調用的時候會在內存中開闢一塊棧空間,在調用一個函數的時候,會將這個函數壓入咱們申請的棧內,若是此時調用的還有其餘函數,按照「先調用後返回」的原則,先入棧(push)的這個函數會將函數執行的控制權交給後入棧(push)的函數,依次類推,將這些函數壓入棧中,直到將執行函數的控制權交給棧頂的那個函數。棧頂的那個函數執行完以後,會出棧(pop),而後將函數的控制權交給當前棧頂的元素(函數),依次出棧,直到程序判斷棧空,全部代碼執行完畢。

隊列

是一種能夠實現「先進先出」的存儲結構。

相似於一個管道,先入管道的,先從管道出來。

應用:生產者消費者模型(起源於操做系統)

相關文章
相關標籤/搜索