数据结构回顾:栈、链表与二叉查找树

 1. 栈(Stack)

1.1 概念

栈是一种后进先出(LIFO, Last In First Out)的数据结构。可以将其想象成一叠盘子,最后放上去的盘子最先被取走。

1.2 基本操作

Push(入栈):将元素添加到栈顶。
Pop(出栈):移除栈顶的元素。
Peek(查看栈顶):查看栈顶的元素,但不移除。
isEmpty(判空):检查栈是否为空。

 2. 链表(Linked List)

2.1 概念

链表是一种线性数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针(或引用)。

2.2 链表的类型

单向链表:每个节点只有一个指向下一个节点的指针。
双向链表:每个节点有两个指针,分别指向前一个节点和后一个节点。
循环链表:最后一个节点指向第一个节点,形成一个环。

 2.3 链表的基本操作

插入(Insert):在链表的指定位置插入一个新节点。
删除(Delete):从链表中删除指定节点。
查找(Search):在链表中查找指定值的节点。
遍历(Traverse):访问链表中的每个节点。

2.4 链表删除节点的问题

在单向链表中,删除节点需要找到待删除节点的前一个节点。如果只知道待删除节点,而无法直接访问前一个节点,删除操作会变得复杂。

问题描述

假设我们有一个单向链表,现在要删除一个已知节点,但无法直接访问该节点的前一个节点。

 解决方案

1. 使用双向链表:双向链表的每个节点都有指向前一个节点的指针,可以方便地找到前一个节点。
2. 遍历链表:从头节点开始遍历链表,找到待删除节点的前一个节点,然后执行删除操作。

 2.5 代码示例

以下是一个单向链表的 Python 实现,包括插入、删除和显示操作。

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

class LinkedList:
    def __init__(self):
        self.head = Node()  # 初始化一个虚拟头节点

    # 在链表尾部插入数据
    def append(self, data):
        node = Node(data)
        # 如果链表为空,则直接插入
        if self.head.next is None:
            self.head.next = node
        else:
            # 做遍历,获取第一个数据节点
            temp = self.head.next
            # 判断数据节点后面是否还存在节点,如果存在节点指针向后移动。否则认为是最后一个节点,将新节点链接到最后一个节点后边
            while temp.next is not None:
                temp = temp.next
            temp.next = node

    def prepend(self, data):
        node = Node(data)
        # 将新节点插入到虚拟头节点之后
        node.next = self.head.next
        self.head.next = node

    def display(self):
        if self.head.next is None:
            print("LinkedList is empty")
            return
        temp = self.head.next
        while temp is not None:
            print(temp.data)
            temp = temp.next

    def remove(self, data):
        if self.head.next is None:
            raise Exception('LinkedList is empty')

        temp = self.head
        while temp.next is not None and temp.next.data != data:
            temp = temp.next
        if temp.next is not None:
            temp.next = temp.next.next

if __name__ == '__main__':
    ls = LinkedList()
    ls.append(4)
    ls.append(5)
    ls.prepend(3)
    ls.display()  # 输出: 3 4 5
    ls.remove(4)
    ls.display()  # 输出: 3 5

3. 二叉查找树(Binary Search Tree)

3.1 概念

二叉查找树(BST)是一种特殊的二叉树,具有以下性质:
1. 每个节点都有一个键值(key)。
2. 左子树中的所有节点的键值都小于该节点的键值。
3. 右子树中的所有节点的键值都大于该节点的键值。
4. 左子树和右子树也分别是二叉查找树。

3.2 基本操作

 3.2.1 插入节点
def insert(self, key):
    if self.root is None:
        self.root = TreeNode(key)
    else:
        self._insert(self.root, key)

def _insert(self, node, key):
    if key < node.key:
        if node.left is None:
            node.left = TreeNode(key)
        else:
            self._insert(node.left, key)
    elif key > node.key:
        if node.right is None:
            node.right = TreeNode(key)
        else:
            self._insert(node.right, key)
3.2.2 删除节点
def delete(self, key):
    self.root = self._delete(self.root, key)

def _delete(self, node, key):
    if node is None:
        return node

    if key < node.key:
        node.left = self._delete(node.left, key)
    elif key > node.key:
        node.right = self._delete(node.right, key)
    else:
        # 找到要删除的节点
        if node.left is None:
            return node.right
        elif node.right is None:
            return node.left
        temp = self._min_value_node(node.right)
        node.key = temp.key
        node.right = self._delete(node.right, temp.key)
    return node
3.2.3 查找节点
def search(self, key):
    return self._search(self.root, key)

def _search(self, node, key):
    if node is None or node.key == key:
        return node
    if key < node.key:
        return self._search(node.left, key)
    return self._search(node.right, key)

3.2.4 遍历

中序遍历:左子树 -> 根节点 -> 右子树。
前序遍历:根节点 -> 左子树 -> 右子树。
后序遍历:左子树 -> 右子树 -> 根节点。

 4. 线程安全与队列

4.1 队列(Queue)

队列是一种先进先出(FIFO, First In First Out)的数据结构。Python 标准库中的 `queue` 模块提供了多种队列实现,包括普通队列、双端队列和优先队列。所以只要记得基本的模块是queue就好

 4.1.1 普通队列
import queue

q = queue.Queue()
q.put(1)
q.put(2)
print(q.get())  # 输出: 1
4.1.2 双端队列
from collections import deque

q = deque()
q.append(1)
q.appendleft(2)
print(q.pop())  # 输出: 1
print(q.popleft())  # 输出: 2
4.1.3 优先队列
import queue

q = queue.PriorityQueue()
q.put((1, 'Task 1'))
q.put((3, 'Task 3'))
print(q.get())  # 输出: (1, 'Task 1')

 4.2 线程安全问题

在多线程环境中,操作数据结构时可能会遇到线程安全问题。例如,`heapq` 模块不是线程安全的,如果多个线程同时操作堆,可能会导致数据不一致。为了解决这个问题,可以使用锁(Lock)来确保线程安全。

import heapq
import threading

heap = []
lock = threading.Lock()

def safe_push(item):
    with lock:
        heapq.heappush(heap, item)

def safe_pop():
    with lock:
        return heapq.heappop(heap)

课后作业

 1. 数据筛选:loc 的妙用

`loc` 方法是 Pandas 中用于基于标签(label)进行数据选择的强大工具。它可以帮助我们根据条件筛选行和列,提取所需的数据子集。

df['Age'] > 30:这是一个布尔索引,用于选择 'Age' 列中大于 30 的行。
['ID', 'Name']:指定要选择的列。
df.loc[...]:使用 `loc` 方法将两者结合,筛选出满足条件的 'ID' 和 'Name'。

 df = pd.DataFrame(data)
    print("公司员工的基本数据:\n",df)
    df2 = df.loc[df['Age'] > 30, ['ID', 'Name']]
    print("年龄大于30岁的员工:\n", df2)

2. 数据删除与添加:drop`和 concat

在数据处理过程中,经常需要删除不需要的行或列,并添加新的数据。 Pandas 提供了 `drop` 方法用于删除数据,`concat` 方法用于连接数据。

df.drop(df[df['ID'] == 2].index):首先找到 'ID' 列中值为 2 的行,然后使用 `drop` 方法删除这些行。`.index` 用于获取要删除的行的索引。
pd.concat([df, pd.DataFrame([new_student])], ignore_index=True)`:创建一个包含新学生信息的 DataFrame,然后使用 `pd.concat` 方法将其添加到原始 DataFrame 中。`ignore_index=True` 参数用于重新生成索引。

    df2= df.drop(df[df['ID'] == 2].index)
    print("删除ID为2的学生记录\n",df2)
    ad_student = {'ID': 6, 'Name': 'Frank', 'Score': 85}
    #添加新行axis: 指定连接的轴,0 或 'index' 表示按行连接,1 或 'columns' 表示按列连接。
    df3 = pd.concat([df2, pd.DataFrame([ad_student])], axis=0)

3. 数据列操作:添加与删除列

除了删除行,我们还可以添加或删除列。

df['Job Title'] = ['Manager', 'Analyst', 'Developer']`:创建一个新的列 'Job Title' 并为其赋值。
df.drop('Department', axis=1)`:删除 'Department' 列。`axis=1` 参数指定删除列,而不是行。

 #添加新列,直接赋值添加
    df['Job Title'] = ['Manager', 'Analyst', 'Developer']
    print("添加职工头衔后的信息\n",df)
    #axis指定删除的方向。axis=0 或 axis='index' 表示删除行,axis=1 或 axis='columns' 表示删除列。
    df = df.drop('Department', axis=1)
    print("\n删除'Department'后的信息:\n", df)

 4. 数据分组与聚合:groupby

`groupby` 方法是 Pandas 中用于数据分组和聚合的关键工具。它可以将数据按照指定的列进行分组,然后对每个组应用聚合函数(如平均值、总和、计数等)。

df.groupby('Team'):按 'Team' 列对数据进行分组。
['Age']`:选择要计算平均值的 'Age' 列。
.mean()`:计算每个组的平均值。

 df2 = df.groupby('Team')['Age'].mean()

 5. 数据合并:merge

`merge` 函数用于将两个 DataFrame 按照指定的键进行合并。它可以执行多种类型的连接,如内连接、外连接、左连接和右连接。

pd.merge(left_df, right_df, on='Key', how='inner')`:使用 'Key' 列作为连接键,执行内连接。`how='inner'` 参数指定只保留两个 DataFrame 中都存在的键。

 m_df = pd.merge(l_df, r_df, on='Key', how='inner')

6. 问题 1:** 我们使用 `groupby` 方法按 'Product' 列分组,然后分别计算每组 'Sales' 列的总和和平均值。
*   **问题 2:** 我们使用 `groupby` 方法按 'Region' 和 'Product' 列分组,然后计算每组 'Sales' 列的平均值。

    product_sales_sum = df.groupby('Product')['Sales'].sum()
    product_sales_mean = df.groupby('Product')['Sales'].mean()
    # 将两个series合并为一个dataframe
    product_sales = pd.DataFrame({'sum': product_sales_sum, 'mean': product_sales_mean})
    # product_sales = df.groupby('Product')['Sales'].agg(['sum', 'mean'])
    print("\n按商品分组的总销售额和平均销售额:\n", product_sales)
    # 2. 按照地区分组,求出该地区每个商品的平均销售额
    region_product_sales = df.groupby(['Region', 'Product'])['Sales'].mean()

你可能感兴趣的:(python,pandas,开发语言,数据结构)