列表、棧、隊列、鏈表、哈希表


數據結構:

線性結構,元素存在一對一的關系,例如列表
樹結構,元素存在一對多的關系,例如層級結構
圖結構,元素存在多對多的關系,例如地圖

列表:

列表:
1、列表中的元素是怎么存儲的?
是順序存儲的,是一塊連續的內存
2、列表的操作:按下標查找,插入元素,刪除元素


先說C中的數組:
查找的時間復雜度是O(1),因為知道首地址+每個元素的大小*index就找到了地址
數組與列表有兩點不同:
1、數組元素類型要相同
2、數組長度固定

python中的列表如何實現?
Python中列表的類型可以不同
Python中的列表存放的不是值而是地址,32位機器一個地址占4個字節,地址的長度是固定的
列表長度不固定
Python解釋器自動維護的,發現長度不夠會新開辟一塊內存,把之前的列表進行拷貝
python下標查找和append復雜度是O(1)
python列表的插入和刪除的復雜度是O(n)

棧:

棧(stack)是一個數據集合,可以理解為只能在一端進行插入或刪除操作的列表
棧的特點:LIFO
棧的概念:棧頂,棧底
棧的基本操作:(用列表可以實現)
進棧:li.append
出棧: li.pop()
取棧頂: li[-1]
class Stack(object):
def __init__(self):
self.stack = []
def pop(self):
return self.stack.pop()
def push(self, element):
return self.stack.append(element)
def get_top(self):
if self.stack:
return self.stack[-1]
else:
return None

棧的應用:括號匹配問題 '{[()[]{}]}'遇到左括號就入棧,遇到右括號就看棧頂的左括號,把匹配的左括號出棧,匹配完后棧為空說明匹配。

隊列:

隊列的實現:

class Queue:
    def __init__(self, size=100):
        self.queue = [0 for _ in range(size)]
        self.size = size
        self.rear = 0
        self.front = 0

    def push(self, element):
        if self.is_filled():
            raise IndexError("Queue is filled.")
        self.rear = (self.rear + 1) % self.size
        self.queue[self.rear] = element

    def pop(self):
        if self.is_empty():
            raise IndexError("Queue is empty.")
        self.front = (self.front + 1) % self.size
        return self.queue[self.front]

    def is_empty(self):
        return self.rear == self.front

    def is_filled(self):
        return (self.rear + 1) % self.size == self.front

q = Queue(5)
for i in range(4):
    q.push(i)
print(q.is_filled())
隊列簡單實現

 

Python隊列內置模塊

python中有線程queue,進程queue,這里是普通的雙向queue

使用方法:from collections import deque
創建隊列:q = deque([1, 2, 3], maxlen=5)
進隊:append()
出隊:popleft()
隊首進隊:appendleft()
隊尾出隊:pop()

 棧和隊列的應用:迷宮問題

 

# 用棧實現迷宮問題:找到的不一定是最短路徑
# 深度優先搜索,又叫回溯法
# 棧里存放走的路徑,先選一個方向,走一步,入棧,再選方向,走一步,入棧....,當前所在位置就是棧頂的值
# 如果某一步走不通,退一步,出棧,再換方向走,循環,直到到達終點

# 用棧實現迷宮問題:
# 深度優先搜索,又叫回溯法
# 棧里存放走的路徑,先選一個方向,走一步,入棧,再選方向,走一步,入棧....,當前所在位置就是棧頂的值
# 如果某一步走不通,退一步,出棧,再換方向走,循環,直到到達終點
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],
]
# 四個方向的坐標
dirs = [
    lambda x, y: (x + 1, y),
    lambda x, y: (x - 1, y),
    lambda x, y: (x, y - 1),
    lambda x, y: (x, y + 1),
]

def maze_path(x1, y1, x2, y2):
    stack = []
    stack.append((x1, y1))
    while (len(stack) > 0):
        curNode = stack[-1]
        if curNode[0] == x2 and curNode[1] == y2:
            # 走到終點了
            print(stack)
            return
        # 四個方向,隨便選一個能走通的路走,走過的標記為2,下次不走
        for dir in dirs:
            nextNode = dir(curNode[0], curNode[1])
            # 如果下個節點能走
            if maze[nextNode[0]][nextNode[1]] == 0:
                stack.append(nextNode)
                maze[nextNode[0]][nextNode[1]] = 2
                break
        else:
            stack.pop()
    else:
        print('沒有路')

maze_path(1, 1, 8, 8)
用棧解決迷宮問題

 

# 使用隊列實現迷宮問題,廣度優先搜索,找到的一定是最短路徑
# 使用隊列存儲當前正在考慮的節點


# 為了計算路徑,還需要用一個列表來保存“哪個點讓這個點進隊列的”

 

 

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],
]
# 四個方向的坐標
dirs = [
    lambda x, y: (x + 1, y),
    lambda x, y: (x - 1, y),
    lambda x, y: (x, y - 1),
    lambda x, y: (x, y + 1),
]
from collections import deque


def print_path(path):
    '''
    打印路徑
    '''
    curNode = path[-1]
    realpath = []
    while curNode[2] != -1:
        realpath.append(curNode[0:2])
        curNode = path[curNode[2]]
    realpath.append(curNode[0:2])  # 起點
    realpath.reverse()
    print(realpath)


def maze_path_queue(x1, y1, x2, y2):
    queue = deque()
    queue.append((x1, y1, -1))  # 第三個數表示誰引發它進隊列的,存path中的下標
    path = []
    while len(queue) > 0:
        curNode = queue.popleft()
        path.append(curNode)    # 出隊列的放入path,用於計算路徑
        if curNode[0] == x2 and curNode[1] == y2:
            # 走到終點了
            print_path(path)
            return
        for dir in dirs:
            nextNode = dir(curNode[0], curNode[1])
            if maze[nextNode[0]][nextNode[1]] == 0:
                queue.append((nextNode[0], nextNode[1], len(path) - 1))  # path中最后一個元素就是引發它進隊列的點
                maze[nextNode[0]][nextNode[1]] = 2
    else:
        print("沒有路")

maze_path_queue(1, 1, 8, 8)
用隊列解決迷宮問題

 

棧和隊列的應用 :廣度優先和深度優先遍歷文件夾

 

import os


def bfs_scan(dir):
    """廣度優先"""
    queue = []  # 先進先出:queue.append()  queue.pop(0)
    queue.append(dir)
    while len(queue) > 0:
        tmp = queue.pop(0)
        if os.path.isdir(tmp):
            for f in os.listdir(tmp):
                abs_path = os.path.join(tmp, f)
                if os.path.isdir(abs_path):
                    queue.append(abs_path)
                else:
                    print("找到文件>>", abs_path)
        else:
            print("找到文件>>", tmp)


if __name__ == '__main__':
    dir = "C:\\Users\\Administrator\\Desktop\\test"
    bfs_scan(dir)
廣度優先遍歷目錄【用隊列】

 

 

import os


def dfs_scan(dir):
    """深度優先"""
    stack = []  # 后進先出:queue.append()  queue.pop()
    stack.append(dir)
    while len(stack) > 0:
        tmp = stack.pop()
        if os.path.isdir(tmp):
            for f in os.listdir(tmp):
                abs_path = os.path.join(tmp, f)
                if os.path.isdir(abs_path):
                    stack.append(abs_path)
                else:
                    print("找到文件>>", abs_path)
        else:
            print("找到文件>>", tmp)


if __name__ == '__main__':
    dir = "C:\\Users\\Administrator\\Desktop\\test"
    dfs_scan(dir)
深度優先遍歷目錄【用堆棧】

 

 

鏈表:

 

 

 

順序表與鏈表時間復雜度對比:
  按元素值查找:O(n)
  按下標查找:O(1) O(n)
  在某元素后插入:O(n) O(1)
  刪除某元素:O(n) O(1)
總結:
  1.鏈表在插入和刪除的操作上明顯快於順序表
  2.鏈表的內存可以更靈活的分配
  3.可以實現堆和棧

 哈希表

哈希表:
哈希表通過哈希函數來計算數據存儲位置的數據結構,通常支持如下操作
insert:插入鍵值對
get:如果存在鍵為key的返回其value,否則返回空
delete:刪除為key的鍵值對
直接尋址表:

 

 

直接尋址技術缺點:
  域U很大時,需要消耗大量內存,不實際
  域U很大而實際出現的key很少,則大量空間被浪費
  無法處理關鍵字不是數字的情況
直接尋址表:key=k的元素放在列表的k位置

直接尋址表改為哈希表:
  1、構建大小為m的尋址表T
  2、key=k的元素放到h(k)位置上
  3、h(k)是一個函數,作用是將域U映射到表T[0,1,2,...,m-1]

哈希表
哈希表又稱散列表,是一種線性表的存儲結構。哈希表由一個
直接尋址表和一個哈希函數組成。哈希函數h(k)將元素關鍵字作為自變量,
返回元素的存儲下標。

假設有一個長度為7的哈希表,哈希函數h(k)=k%7,元素集合
{14,22,3,5}的存儲方式如下圖:

哈希沖突:
  由於哈希表的大小是有限的,而要存儲的值的總數量是無限的,
  因此對於任何哈希函數,都會出現兩個不同元素映射到同一個位置上的情況,
  這種情況叫做哈希沖突.
比如h(k)=k%7, h(0)=h(7)=h(14)=...
解決hash沖突的兩種方式:
一、開放尋址法:如果哈希函數返回的位置已經有值,則可以向后探查新的位置來存儲這個值。
  線性探查:如果位置i被占用,則探查i+1,i+2,...查找的時候,也要進行線性探查
  二次探查:如果位置i被占用,則探查i+1²,i-1²,i+2²,i-2²,...
  二度哈希:有n個哈希函數,當使用第1個哈希函數h1發生沖突時,則嘗試使用h2,h3...
  這個方式大家不太喜歡,有可能hash表滿了,就肯定沒空間了

二、拉鏈法
  哈希表每個位置都連接一個鏈表,當沖突發生時,沖突的元素將被加到該位置鏈表的最后。
  查找的時候,先找到hash函數計算的位置,再在鏈表里找

 常見的哈希函數:

 

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

    class LinkListIterator:
        def __init__(self, node):
            self.node = node

        def __next__(self):
            if self.node:
                cur_node = self.node
                self.node = cur_node.next
                return cur_node.item
            else:
                raise StopIteration

        def __iter__(self):
            return self

    def __init__(self, iterable=None):
        self.head = None
        self.tail = None
        if iterable:
            self.extend(iterable)

    def append(self, obj):
        s = LinkList.Node(obj)
        if not self.head:
            self.head = s
            self.tail = s
        else:
            self.tail.next = s
            self.tail = s

    def extend(self, iterable):
        for obj in iterable:
            self.append(obj)

    def find(self, obj):
        for n in self:
            if n == obj:
                return True
        return False

    def __iter__(self):
        return self.LinkListIterator(self.head)

    def __repr__(self):
        return "<<" + ", ".join(map(str, self)) + ">>"


# 類似於集合的結構
class HashTable:
    def __init__(self, size=101):
        self.size = size
        self.T = [LinkList() for _ in range(self.size)]

    def h(self, k):
        '''
        hash函數
        '''
        return k % self.size

    def insert(self, k):
        i = self.h(k)
        if self.find(k):
            print('Duplicated Insert')
        else:
            self.T[i].append(k)

    def find(self, k):
        i = self.h(k)
        return self.T[i].find(k)

    def __repr__(self):
        return ",".join(map(str, self.T))


ht = HashTable()
ht.insert(0)
ht.insert(1)
ht.insert(0)
ht.insert(202)
print(ht)
print(ht.find(202))
哈希表實現集合

 

 

 

 

 

 

 

 

 

23223


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM