Bootstrap

(9)Python 数据结构:构建高效算法与金融应用的基石

1. 前言

当我们谈论数据结构时,我们实际上是在探讨一种组织、存储和管理数据的系统方法。在编程中,数据结构的选择对程序的性能、可读性和可维护性都有着深远的影响。Python,作为一种功能强大且易于学习的编程语言,提供了丰富的数据结构供我们使用。

1.1 为什么学习数据结构?

  • 性能优化:了解不同数据结构的特性和适用场景,可以帮助我们编写出更高效、更快速的代码。例如,当我们需要频繁地进行数据的查找、插入或删除操作时,选择合适的数据结构可以显著提升这些操作的效率。
  • 问题解决:很多实际编程问题都可以通过选择合适的数据结构来解决。通过掌握各种数据结构的特点和使用方法,我们可以更灵活地应对各种编程挑战。
  • 算法基础:数据结构和算法是计算机科学的核心内容之一。学习数据结构有助于我们理解算法的实现原理和效率评估方法,为后续学习更高级的算法知识打下坚实的基础。

1.2 Python 中的数据结构

Python 提供了一些内建的数据结构,如列表(List)、元组(Tuple)、集合(Set)和字典(Dictionary)等。这些数据结构各有特点,适用于不同的场景。例如,列表适用于需要频繁修改元素顺序的场景,而字典则适用于需要快速查找键值对的场景。

除了内建的数据结构外,Python 还支持自定义数据结构。通过继承 Python 中的基本类(如 object、list、dict 等)或实现特定的接口(如 Iterable、Container 等),我们可以创建出符合自己需求的数据结构。

1.3 如何学习数据结构?

  • 理论学习:首先,我们需要了解各种数据结构的基本概念、特性、操作方法和应用场景。这可以通过阅读教材、观看视频教程或参加在线课程等方式进行。
  • 实践应用:理论学习是基础,但真正掌握数据结构还需要通过实践来加深理解。我们可以尝试编写一些基于不同数据结构的算法和程序,以便更直观地理解数据结构在实际编程中的应用。
  • 算法练习:学习数据结构的过程中,我们还需要了解与数据结构相关的算法。通过练习各种算法题目,我们可以更深入地理解数据结构和算法之间的关系,提高编程能力和问题解决能力。

数据结构是编程中不可或缺的一部分。通过学习数据结构,我们可以更好地理解数据的组织和管理方式,提高程序的性能和可读性。Python 作为一种功能强大且易于学习的编程语言,为我们提供了丰富的数据结构支持。希望通过本文的介绍,您能对 Python 数据结构有一个初步的了解,并激发您学习数据结构的兴趣。

2. 线性数据结构

Python中的线性数据结构主要包括列表(List)、元组(Tuple)、栈(Stack,通常由列表或其他集合类型模拟)和队列(Queue,Python的标准库中没有直接提供队列类型,但可以使用collections.deque或列表来模拟)等。这些数据结构在内存中占据连续或离散但有序的存储空间,数据元素之间存在一对一的线性关系。

2.1 列表(List)

在Python中,列表(List)是一种非常常见且强大的数据结构,它允许我们存储一系列的项目(可以是不同类型的元素)。下面,我将展示如何在Python中进行列表的增(添加)、删(删除)、改(修改)和查(查询)操作,并提供一个金融示例来演示这些操作的实际应用。

2.1.1 列表的增删改查

  • 增(添加)

    • 使用 append() 方法在列表末尾添加一个元素。
    • 使用 insert() 方法在指定位置插入一个元素。
    • 使用 extend() 方法将另一个列表的元素添加到当前列表的末尾。
  • 删(删除)

    • 使用 remove() 方法删除指定值的第一个匹配项。
    • 使用 pop() 方法删除并返回指定索引位置的元素(默认为最后一个元素)。
    • 使用 del 语句删除一个元素或一段子列表。
    • 使用列表推导式或 filter() 函数等创建新列表,同时过滤掉不需要的元素。
  • 改(修改)

    • 直接使用索引访问并修改元素值。
  • 查(查询)

    • 使用 index() 方法查找元素的索引位置。
    • 使用 in 关键字检查元素是否存在于列表中。

2.1.2 示例1

假设我们有一个简单的金融应用,其中包含一个股票列表,我们想要对这个列表进行增删改查操作。

# 初始化股票列表
stocks = ['AAPL', 'GOOGL', 'MSFT', 'AMZN']

# 增(添加)
# 添加一只新股票
stocks.append('TSLA')
print("增加后的股票列表:", stocks)

# 插入一只新股票到指定位置(比如第二位)
stocks.insert(1, 'FB')
print("插入后的股票列表:", stocks)

# 删(删除)
# 删除指定股票(比如'GOOGL')
stocks.remove('GOOGL')
print("删除后的股票列表:", stocks)

# 弹出并返回最后一个股票(假设是卖出了)
sold_stock = stocks.pop()
print(f"卖出的股票: {sold_stock}")
print("弹出后的股票列表:", stocks)

# 使用 del 删除指定位置的股票
del stocks[1]
print("使用 del 删除后的股票列表:", stocks)

# 改(修改)
# 假设我们买入了更多的 AMZN 股票,数量翻倍(这里只是模拟,所以简单地把'AMZN'修改为'AMZN*2')
stocks[stocks.index('AMZN')] = 'AMZN*2'
print("修改后的股票列表:", stocks)

# 查(查询)
# 查询股票是否在列表中
if 'AAPL' in stocks:
    print("AAPL 在股票列表中")

# 查询股票索引位置
print("AMZN 的索引位置:", stocks.index('AMZN*2'))

2.1.3 示例2

以下是一个模拟股票投资组合的管理过程的代码:

# 引入需要的库
from collections import namedtuple

# 定义一个股票信息的命名元组
Stock = namedtuple('Stock', ['ticker', 'quantity', 'price_per_share'])

# 初始化股票投资组合
portfolio = [
    Stock('AAPL', 100, 150),
    Stock('GOOGL', 50, 2000),
    Stock('MSFT', 200, 250),
    Stock('AMZN', 75, 3000)
]

# 定义一个函数,用于添加新的股票到投资组合
def add_stock_to_portfolio(portfolio, ticker, quantity, price_per_share):
    new_stock = Stock(ticker, quantity, price_per_share)
    portfolio.append(new_stock)
    return portfolio

# 添加一个新的股票到投资组合
portfolio = add_stock_to_portfolio(portfolio, 'TSLA', 25, 700)
print("增加后的投资组合:")
for stock in portfolio:
    print(stock)

# 定义一个函数,用于从投资组合中删除股票
def remove_stock_from_portfolio(portfolio, ticker):
    for stock in list(portfolio):
        if stock.ticker == ticker:
            portfolio.remove(stock)
            print(f"已删除股票: {stock.ticker}")
            return
    print(f"未找到股票: {ticker}")

# 从投资组合中删除一只股票
remove_stock_from_portfolio(portfolio, 'GOOGL')

# 定义一个函数,用于修改投资组合中股票的数量
def update_stock_quantity(portfolio, ticker, new_quantity):
    for stock in portfolio:
        if stock.ticker == ticker:
            stock = stock._replace(quantity=new_quantity)
            print(f"已更新股票 {ticker} 的数量为: {new_quantity}")
            return
    print(f"未找到股票: {ticker}")

# 更新一只股票的数量
update_stock_quantity(portfolio, 'AMZN', 100)

# 查询并打印投资组合的总价值
def calculate_portfolio_value(portfolio):
    total_value = 0
    for stock in portfolio:
        total_value += stock.quantity * stock.price_per_share
    return total_value

print(f"当前投资组合的总价值为: {calculate_portfolio_value(portfolio)}")

这个版本使用了collections.namedtuple来定义一个更结构化的Stock对象,它包含了股票代码(ticker)、持有数量(quantity)和每股价格(price_per_share)。此外,还定义了一些函数来模拟对投资组合的增删改查操作,并计算了投资组合的总价值。

2.2 栈(Stack)

在Python中,栈(Stack)是一种遵循后进先出(LIFO, Last In First Out)原则的数据结构。常用的栈操作包括入栈(push)、出栈(pop)、查看栈顶元素(peek)以及检查栈是否为空(is_empty)等。

以下是一个简单的Python栈实现及其金融示例:

2.2.1 示例1

class Stack:
    def __init__(self):
        self.stack = []

    def push(self, item):
        """入栈操作"""
        self.stack.append(item)

    def pop(self):
        """出栈操作,如果栈为空则抛出异常"""
        if not self.is_empty():
            return self.stack.pop()
        else:
            raise IndexError("Pop from an empty stack")

    def peek(self):
        """查看栈顶元素,如果栈为空则返回None或抛出异常"""
        if not self.is_empty():
            return self.stack[-1]
        else:
            return None  # 或者抛出异常

    def is_empty(self):
        """检查栈是否为空"""
        return len(self.stack) == 0

    def size(self):
        """返回栈的大小"""
        return len(self.stack)

2.2.2 示例2

假设我们在管理一个证券交易的历史记录,我们可以使用栈来保存最近买入的股票记录。当我们要卖出股票时,我们希望按照最近的买入顺序来卖出。

# 创建一个栈实例来保存股票交易记录
stock_trades = Stack()

# 模拟买入股票操作
stock_trades.push(('AAPL', 100, 150))  # 股票代码,数量,价格
stock_trades.push(('MSFT', 50, 200))
stock_trades.push(('GOOGL', 25, 1800))

# 查看当前栈中的股票交易记录
print("当前股票交易记录:")
for trade in reversed(stock_trades.stack):  # 因为栈是反序的,所以这里反转列表来查看
    print(trade)

# 假设我们要卖出一些股票,我们按照最近买入的顺序来卖出
print("\n卖出操作:")
while stock_trades.size() > 0 and input("是否要继续卖出股票?(y/n): ").lower() == 'y':
    trade = stock_trades.pop()
    print(f"卖出了 {trade[0]} {trade[1]} 股,每股价格 {trade[2]}")

# 查看卖出后的股票交易记录
print("\n卖出后的股票交易记录:")
for trade in reversed(stock_trades.stack):
    print(trade)

2.3 队列(Queue)

队列(Queue)是一种先进先出(FIFO, First In First Out)的数据结构,其中一端插入数据(队尾),另一端删除数据(队头)。

2.3.1 Deque 双端队列

Python标准库中的collections.deque是一个双端队列,但它也可以用来模拟普通队列。此外,第三方库如queue也提供了更丰富的队列实现。

from collections import deque

# 使用deque模拟队列
queue = deque()

# 入队操作
queue.append('a')
queue.append('b')

# 查看队首元素
print(queue[0])  # 输出:'a'

# 出队操作
popped_item = queue.popleft()
print(popped_item)  # 输出:'a'

2.3.2 Queue 队列

Python标准库中的queue模块提供了多种队列的实现,其中最常用的是FIFOQueue(也称为Queue)和LIFOQueue(又称为stack,但在queue模块中更常见的是作为队列的变种存在)。
以下我们将以queue.Queue为例,介绍队列的基本操作。

  • 导入队列模块
import queue
  • 创建队列
q = queue.Queue()
  • 入队(put)

将元素添加到队列的尾部。

q.put('A')
q.put('B')
q.put('C')
  • 出队(get)

从队列的头部移除并返回元素。如果队列为空,则此操作将阻塞,直到有元素可用。

print(q.get())  # 输出:'A'
print(q.get())  # 输出:'B'
  • 查看队列大小(qsize)

返回队列中的元素个数。

print(q.qsize())  # 输出:1(因为还有'C'未出队)
  • 检查队列是否为空(empty)

如果队列为空,返回True;否则返回False。

print(q.empty())  # 输出:False(因为还有'C'未出队)
  • 获取队列的头部元素(但不移除)(get_nowait)

如果队列为空,则引发queue.Empty异常。

try:
    print(q.get_nowait())  # 输出:'C'
except queue.Empty:
    print("队列为空")

注意:在Python 3.7及以上版本中,get_nowait方法已被重命名为get_nowait(),而在之前的版本中,它可能是Queue.Queue.get(block=False)

金融应用示例

在金融领域中,队列常常被用于处理交易订单、消息队列、事件驱动的系统等场景。

以下是一个简单的金融交易订单处理的示例:

假设我们有一个股票交易平台,用户提交的买卖订单被保存在队列中,等待处理。平台按照订单提交的先后顺序处理这些订单。

  • 订单队列

使用队列来保存用户提交的订单。

import queue

order_queue = queue.Queue()

# 假设这是用户提交的订单,订单包含用户ID、股票代码、交易类型(买入/卖出)、数量和价格
order_queue.put(('User1', 'AAPL', 'BUY', 100, 150))
order_queue.put(('User2', 'MSFT', 'SELL', 50, 200))
order_queue.put(('User3', 'AAPL', 'SELL', 25, 155))
  • 订单处理

平台按照订单提交的顺序处理这些订单。

while not order_queue.empty():
    order = order_queue.get()
    user_id, stock_code, order_type, quantity, price = order
    print(f"处理订单:用户 {user_id} 对股票代码 {stock_code} 进行 {order_type} 操作,数量 {quantity},价格 {price}")
    
    # 在这里可以添加实际的订单处理逻辑,比如与股票市场的实时价格进行匹配、执行交易等。
    # ...

# 输出类似:
# 处理订单:用户 User1 对股票代码 AAPL 进行 BUY 操作,数量 100,价格 150
# 处理订单:用户 User2 对股票代码 MSFT 进行 SELL 操作,数量 50,价格 200
# 处理订单:用户 User3 对股票代码 AAPL 进行 SELL 操作,数量 25,价格 155

注意:这个示例仅用于说明队列在金融交易订单处理中的应用,实际的金融交易平台会有更复杂的逻辑和安全性考虑。

2.4 链表

链表(Linked List)是一种常见的数据结构,它包含一系列按照顺序排列的元素,但每个元素并不存储在连续的内存空间中,而是存储在各自独立的空间中,并通过指针(在Python中通常是引用)连接在一起。链表中的每个元素被称为节点(Node),节点包含两个部分:数据部分和指向下一个节点的指针部分。

Python中的链表不像其他语言那样有内置的链表类型,但我们可以使用类(Class)来模拟链表。

链表常用操作

  • 插入节点:在链表的开头、末尾或指定位置插入一个新的节点。
  • 删除节点:删除链表中的指定节点或头节点、尾节点。
  • 遍历链表:从头节点开始,访问链表中的每个节点,直到到达尾节点的下一个节点(通常是None)。
  • 查找节点:根据节点的值在链表中查找节点。
  • 修改节点值:修改链表中指定节点的值。

2.4.1 单链表

在这里插入图片描述

我们来定义一个单链表节点类和一个单链表类。单链表节点类通常包含一个数据域和一个指向下一个节点的指针。单链表类则提供了添加、删除、遍历等基本操作。

示例1

class ListNode:
    def __init__(self, value=0, next=None):
        self.value = value
        self.next = next

class LinkedList:
    def __init__(self):
        self.head = None

    def append(self, value):
        if not self.head:
            self.head = ListNode(value)
        else:
            cur = self.head
            while cur.next:
                cur = cur.next
            cur.next = ListNode(value)

    def delete(self, value):
        if self.head is None:
            return
        if self.head.value == value:
            self.head = self.head.next
            return
        cur = self.head
        while cur.next:
            if cur.next.value == value:
                cur.next = cur.next.next
                return
            cur = cur.next

    def display(self):
        elements = []
        cur = self.head
        while cur:
            elements.append(cur.value)
            cur = cur.next
        return elements

    def is_empty(self):
        return self.head is None

示例2

在金融领域,单链表可以应用于许多场景,例如记录交易历史、模拟订单处理等。以下是一个简单的金融应用示例,使用单链表来模拟股票交易历史。

# 假设我们有一个股票交易系统,使用单链表来记录交易历史
class StockTransaction:
    def __init__(self, timestamp, stock_code, quantity, price):
        self.timestamp = timestamp
        self.stock_code = stock_code
        self.quantity = quantity
        self.price = price

# 使用LinkedList来存储交易记录
trade_history = LinkedList()

# 模拟几笔交易
trade_history.append(StockTransaction(1620000000, 'AAPL', 100, 150))  # 时间戳为UNIX时间,例如2021-05-01 00:00:00
trade_history.append(StockTransaction(1620086400, 'MSFT', 50, 200))   # 2021-05-01 20:00:00
trade_history.append(StockTransaction(1620172800, 'AAPL', -25, 155))  # 卖出25股AAPL

# 显示交易历史
print("交易历史:")
for trade in trade_history.display():
    print(f"时间戳: {trade.timestamp}, 股票代码: {trade.stock_code}, 数量: {trade.quantity}, 价格: {trade.price}")

# 假设我们要查找某只股票的所有交易记录
def find_stock_transactions(history, stock_code):
    results = []
    cur = history.head
    while cur:
        if cur.value.stock_code == stock_code:
            results.append(cur.value)
        cur = cur.next
    return results

# 查找AAPL的交易记录
aapl_transactions = find_stock_transactions(trade_history, 'AAPL')
print("\nAAPL的交易记录:")
for trade in aapl_transactions:
    print(f"时间戳: {trade.timestamp}, 股票代码: {trade.stock_code}, 数量: {trade.quantity}, 价格: {trade.price}")

在这个示例中,StockTransaction类代表一笔交易,包含了交易的时间戳、股票代码、交易数量和价格。我们使用LinkedList来存储这些交易记录,并提供了显示所有交易和查找某只股票交易记录的功能。通过单链表,我们可以很方便地按交易发生的顺序来存储和检索交易记录。

2.4.2 双链表

双链表(Doubly Linked List)与单链表(Singly Linked List)类似,但是每个节点除了有一个指向下一个节点的指针外,还有一个指向前一个节点的指针。这使得双链表在插入和删除操作时更为灵活。

下面是一个 Python 双链表的实现:

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

class DoublyLinkedList:
    def __init__(self):
        self.head = None

    def append(self, data):
        if not self.head:
            self.head = Node(data)
        else:
            cur = self.head
            while cur.next:
                cur = cur.next
            cur.next = Node(data)
            cur.next.prev = cur

    def display(self):
        elements = []
        cur = self.head
        while cur:
            elements.append(cur.data)
            cur = cur.next
        return elements

    # 插入节点到头部等其他方法可以在这里实现
    # ...

    # 假设添加了一个删除节点的方法
    def delete_node(self, key):
        cur = self.head
        while cur:
            if cur.data == key:
                if cur.prev:
                    cur.prev.next = cur.next
                else:
                    self.head = cur.next
                if cur.next:
                    cur.next.prev = cur.prev
                return True
            cur = cur.next
        return False

    # 检查链表是否为空
    def is_empty(self):
        return self.head is None

金融应用示例

在金融领域,双链表的一个潜在应用是模拟一个具有双向引用关系的金融交易系统。例如,我们可以使用双链表来跟踪一个投资者的买入和卖出订单,其中每个订单节点都包含了对前一个和后一个订单的引用。

以下示例,展示了如何使用双链表来记录投资者的股票交易历史,并支持查询和回溯操作。

# 假设我们有一个表示交易订单的类
class TradeOrder:
    def __init__(self, timestamp, symbol, quantity, price, action):
        self.timestamp = timestamp
        self.symbol = symbol
        self.quantity = quantity
        self.price = price
        self.action = action  # 'BUY' 或 'SELL'

# 使用双链表来存储交易历史
trade_history = DoublyLinkedList()

# 假设投资者进行了一些交易
trade_history.append(TradeOrder(1620000000, 'AAPL', 100, 150, 'BUY'))
trade_history.append(TradeOrder(1620086400, 'MSFT', 50, 200, 'BUY'))
trade_history.append(TradeOrder(1620172800, 'AAPL', 25, 155, 'SELL'))

# 显示交易历史
print("交易历史:")
for trade in trade_history.display():
    print(f"时间戳: {trade.timestamp}, 股票代码: {trade.symbol}, 数量: {trade.quantity}, 价格: {trade.price}, 操作: {trade.action}")

# 假设我们想要查找并删除一个特定的交易记录
def find_and_delete_trade(history, symbol, action):
    cur = history.head
    while cur:
        if cur.data.symbol == symbol and cur.data.action == action:
            history.delete_node(cur.data)
            print(f"找到并删除了股票代码 {symbol}, 操作 {action} 的交易记录。")
            return True
        cur = cur.next
    print(f"未找到股票代码 {symbol}, 操作 {action} 的交易记录。")
    return False

# 查找并删除一个 'AAPL' 的卖出记录
find_and_delete_trade(trade_history, 'AAPL', 'SELL')

# 再次显示交易历史,确认记录已被删除
print("\n更新后的交易历史:")
for trade in trade_history.display():
    print(f"时间戳: {trade.timestamp}, 股票代码: {trade.symbol}, 数量: {trade.quantity}, 价格: {trade.price}, 操作: {trade.action}")以下是上述代码的完整实现,包括修复了`DoublyLinkedList`类中`append`方法的错误,并确保它能够正确地添加新节点到双链表的末尾。同时,我也提供了一个`insert_after`方法作为双链表特性的一个例子。

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

class DoublyLinkedList:
    def __init__(self):
        self.head = None

    def append(self, data):
        new_node = Node(data)
        if self.head is None:
            self.head = new_node
        else:
            cur = self.head
            while cur.next:
                cur = cur.next
            cur.next = new_node
            new_node.prev = cur

    def insert_after(self, key, new_data):
        if self.head is None:
            return

        cur = self.head
        while cur:
            if cur.data == key:
                new_node = Node(new_data)
                new_node.next = cur.next
                if cur.next:
                    cur.next.prev = new_node
                cur.next = new_node
                new_node.prev = cur
                return
            cur = cur.next
        print(f"没有找到键为 {key} 的节点")

    def display(self):
        elements = []
        cur = self.head
        while cur:
            elements.append(cur.data)
            cur = cur.next
        return elements

    def delete_node(self, key):
        cur = self.head
        while cur:
            if cur.data == key:
                if cur.prev:
                    cur.prev.next = cur.next
                else:
                    self.head = cur.next
                if cur.next:
                    cur.next.prev = cur.prev
                return True
            cur = cur.next
        return False

    def is_empty(self):
        return self.head is None

# 假设我们有一个表示交易订单的类
class TradeOrder:
    def __init__(self, timestamp, symbol, quantity, price, action):
        self.timestamp = timestamp
        self.symbol = symbol
        self.quantity = quantity
        self.price = price
        self.action = action  # 'BUY' 或 'SELL'

    def __str__(self):
        return f"时间戳: {self.timestamp}, 股票代码: {self.symbol}, 数量: {self.quantity}, 价格: {self.price}, 操作: {self.action}"

# 使用双链表来存储交易历史
trade_history = DoublyLinkedList()

# 假设投资者进行了一些交易
trade_history.append(TradeOrder(1620000000, 'AAPL', 100, 150, 'BUY'))
trade_history.append(TradeOrder(1620086400, 'MSFT', 50, 200, 'BUY'))
trade_history.append(TradeOrder(1620172800, 'AAPL', 25, 155, 'SELL'))

# 显示交易历史
print("交易历史:")
for trade in trade_history.display():
    print(trade)

# 插入一个新的交易记录到'AAPL'的'BUY'记录之后
new_trade = TradeOrder(1620259200, 'AAPL', 50, 152, 'BUY')
trade_history.insert_after(new_trade.symbol + new_trade.action, new_trade)

# 再次显示交易历史,确认记录已被插入
print("\n插入新交易后的历史:")
for trade in trade_history.display():
    print(trade)

# 查找一个特定的交易记录
def find_and_delete_trade(history, symbol, action):
    for trade in history.display():
        if trade.symbol == symbol and trade.action == action:
            trade_data = trade
            return True;
    else:
        print(f"未找到股票代码 {symbol}, 操作 {action} 的交易记录。")
        return False

2.4.3 循环链表

循环链表(Circular Linked List)是一种特殊的链表结构,其中最后一个节点的next指针指向头节点,从而使链表形成一个环。这种结构使得循环链表在某些场景(如环形缓冲区、约瑟夫环问题)中非常有用。循环链表和单链表的主要区别在于循环链表的最后一个节点指向头节点,而单链表的最后一个节点指向None

不包含了删除节点的循环链表示例

# 定义节点
# item:数据
# next:下一个节点
class Node:
    def __init__(self, data=None):
        self.data = data
        self.next = None

# 定义循环链表
class CircularLinkedList:
    def __init__(self):
        self.head = None

    # 插入节点到链表末尾
    def append(self, data):
        new_node = Node(data)
        if self.head is None:
            new_node.next = new_node  # 形成闭环
            self.head = new_node
        else:
            cur = self.head
            while cur.next != self.head:  # 找到最后一个节点
                cur = cur.next
            cur.next = new_node  # 将新节点添加到末尾
            new_node.next = self.head  # 新节点的next指向头节点,形成闭环

    # 插入节点到链表开头
    def insert_at_beginning(self, data):
        new_node = Node(data)
        if self.head is None:
            new_node.next = new_node  # 形成闭环
            self.head = new_node
        else:
            cur = self.head
            while cur.next != self.head:  # 找到最后一个节点
                cur = cur.next
            new_node.next = self.head  # 新节点的next指向头节点
            cur.next = new_node  # 最后一个节点的next指向新节点
            self.head = new_node  # 新节点成为头节点

    # 删除节点
    def delete(self, key):
        if self.head is None:
            return

        if self.head.data == key:
            if self.head.next == self.head:  # 如果链表只有一个节点
                self.head = None
            else:
                cur = self.head
                while cur.next != self.head:  # 找到倒数第二个节点
                    cur = cur.next
                cur.next = self.head.next  # 跳过头节点
                self.head = self.head.next  # 更新头节点
                if self.head:
                    self.head.prev = None  # 假设我们也有prev指针(在这个简单示例中没有,但可以作为扩展)
            return

        cur = self.head
        while cur.next != self.head:  # 遍历链表寻找节点
            if cur.next.data == key:
                cur.next = cur.next.next  # 跳过要删除的节点
                return
            cur = cur.next

        # 如果没有找到要删除的节点
        print(f"Key {key} not found in circular linked list")

    # 遍历链表并打印每个节点的值
    def print_list(self):
        if self.head is None:
            return
        cur = self.head
        while True:
            print(cur.data, end=" ")
            cur = cur.next
            if cur == self.head:  # 回到头节点,说明遍历完成
                break
        print()


# 使用示例
cll = CircularLinkedList()
cll.append(1)
cll.append(2)
cll.append(3)
cll.insert_at_beginning(0)
cll.print_list()  # 输出: 0 1 2 3

包含了删除节点的循环链表示例

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


class CircularLinkedList:
    def __init__(self):
        self.head = None

    # 插入节点到链表末尾
    def append(self, data):
        new_node = Node(data)
        if self.head is None:
            new_node.next = new_node
            self.head = new_node
        else:
            cur = self.head
            while cur.next != self.head:
                cur = cur.next
            cur.next = new_node
            new_node.next = self.head

    # 删除节点
    def delete(self, key):
        if self.head is None:
            print("List is empty")
            return

        if self.head.data == key:
            if self.head.next == self.head:  # 只有一个节点
                self.head = None
            else:
                temp = self.head
                while temp.next != self.head:  # 找到最后一个节点
                    temp = temp.next
                temp.next = self.head.next  # 跳过头节点
                self.head = self.head.next
        else:
            cur = self.head
            while cur.next != self.head:
                if cur.next.data == key:
                    cur.next = cur.next.next
                    break
                cur = cur.next
            else:  # 如果循环结束没有找到节点,说明不存在该节点
                print(f"Key {key} not found in circular linked list")

    # 遍历链表并打印每个节点的值
    def print_list(self):
        if self.head is None:
            print("List is empty")
            return
        cur = self.head
        while True:
            print(cur.data, end=" ")
            cur = cur.next
            if cur == self.head:
                break
        print()


# 使用示例
cll = CircularLinkedList()
cll.append(1)
cll.append(2)
cll.append(3)
cll.print_list()  # 输出: 1 2 3 

cll.delete(2)
cll.print_list()  # 输出: 1 3 

cll.delete(1)
cll.print_list()  # 输出: 3 

cll.delete(3)
cll.print_list()  # 输出: List is empty

这个示例中,我们实现了循环链表的append(在链表末尾添加节点)、delete(删除指定值的节点)和print_list(遍历并打印链表的所有节点)方法。delete方法现在可以正确地处理删除头节点和链表中任意位置节点的情况。在删除操作中,我们首先检查要删除的节点是否是头节点,然后相应地调整链表结构。如果删除的不是头节点,我们遍历链表找到要删除的节点,并更新前一个节点的next指针来跳过要删除的节点。

2.5 元组(Tuple)

在Python中,元组(tuple)是一个不可变序列类型,通常用于存储一组相关的值。在金融领域,元组可以用来表示多个与金融相关的数据点,比如股票价格、货币对汇率、投资组合的构成等。

以下是一个关于Python元组及其在金融领域应用的简单示例:

2.5.1 示例:股票报价

假设我们有一个股票报价系统,它实时提供股票的最新价格和交易量。我们可以使用元组来存储这些信息,因为一旦股票报价被确定,通常不会频繁更改(至少在短时间内)。

# 定义一个函数来获取股票报价
def get_stock_quote(stock_symbol):
    # 在实际应用中,这里可能会从数据库、API或其他数据源获取数据
    # 这里仅作示例,直接返回模拟数据
    if stock_symbol == 'AAPL':
        return (150.25, 12345678)  # (价格, 交易量)
    elif stock_symbol == 'GOOGL':
        return (2050.75, 6543210)
    else:
        return None

# 使用函数获取股票报价
aapl_quote = get_stock_quote('AAPL')
googl_quote = get_stock_quote('GOOGL')

# 检查并打印股票报价
if aapl_quote:
    print(f"AAPL: 价格 = {aapl_quote[0]}, 交易量 = {aapl_quote[1]}")

if googl_quote:
    print(f"GOOGL: 价格 = {googl_quote[0]}, 交易量 = {googl_quote[1]}")

# 注意:由于元组是不可变的,你不能修改其中的值
# 例如,尝试修改元组会抛出一个异常
# aapl_quote[0] = 160.00  # 这行代码会引发 TypeError

2.5.2 示例:投资组合构成

在另一个金融场景中,你可能需要跟踪投资组合的构成,即其中各种资产的种类和数量。元组可以用来表示每一种资产及其持有量。

# 定义一个函数来创建投资组合
def create_portfolio(assets_and_quantities):
    # assets_and_quantities 是一个元组列表,其中每个元组表示一种资产和它的数量
    # 例如: [('AAPL', 100), ('GOOGL', 50), ('GOLD', 10)]
    return assets_and_quantities

# 创建一个投资组合
portfolio = create_portfolio([('AAPL', 100), ('GOOGL', 50), ('GOLD', 10)])

# 打印投资组合的构成
for asset, quantity in portfolio:
    print(f"持有 {quantity}{asset}")

# 注意:由于元组是不可变的,你不能直接修改投资组合中的资产或数量
# 但你可以创建一个新的元组列表来表示投资组合的变化

在这些示例中,元组提供了一个简洁且不可变的方式来存储和传递与金融相关的数据。不过,对于需要频繁修改的数据集,你可能需要使用列表或其他可变的数据结构。

3. 非线性数据结构

Python 中支持多种非线性数据结构,这些结构在数据处理和算法实现中非常有用。以下是一些常见的非线性数据结构及其简要说明:

3.1 树(Tree)

3.1.1 树是什么?

Python中的树(Tree)是一种非线性的数据结构,用于模拟具有树状结构性质的数据集合。树结构包含以下主要概念和特点:

  • 定义

    • 树是n(n>=0)个元素的集合。当n=0时,称为空树。
    • 树有一个特殊的元素,称为根(root),它没有前驱元素,但有零个或多个后继元素。
    • 树中除根节点外,其余元素只能有一个前驱,但可以有零个或多个后继。
  • 递归定义

    • 树T是n(n>=0)个元素的集合,n=0时称为空树。
    • 树有一个特殊的元素作为根,剩余元素可以被划分为m个互不相交的集合T1、T2、…、Tm,每个集合本身也是一棵树,称为T的子树。
  • 主要术语

    • 节点(Node):树中的数据元素。
    • 根节点(Root Node):树的顶部节点,没有前驱节点。
    • 父节点(Parent Node):一个节点包含的子树的顶部节点。
    • 子节点(Child Node):一个节点所包含的树中的节点。
    • 叶节点(Leaf Node):没有子节点的节点。
    • 子树(Subtree):由节点和它的后代构成的树。
    • 节点的度(Degree of a Node):一个节点含有的子树的个数。
    • 树的度(Degree of a Tree):一棵树中,最大的节点的度。
    • 高度或深度(Height or Depth):树中节点的最大层次,从根节点开始定义,根为第1层。
  • 树的种类

    • 无序树(Unordered Tree):树中任意节点的子节点之间没有顺序关系。
    • 有序树(Ordered Tree):树中任意节点的子节点之间有顺序关系。
    • 二叉树(Binary Tree):每个节点最多含有两个子树的树。
      • 完全二叉树(Complete Binary Tree):除了最后一层外,每一层的节点数均达到最大值,且最后一层的节点都靠左排列。
      • 平衡二叉树(Balanced Binary Tree):任意节点的两棵子树的高度差不大于1。
      • 排序二叉树(Binary Search Tree):也称为二叉查找树,对于树中的任意节点,其左子树上所有节点的值都小于它的值,右子树上所有节点的值都大于它的值。
    • 霍夫曼树(Huffman Tree):用于信息编码,是一种带权路径最短的二叉树。
    • B树(B-Tree):一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于两个子树。

在Python中,可以使用类(class)和对象(object)的概念来实现树结构,并定义相应的属性和方法来操作树,如插入、删除节点、遍历等。理解树的结构和性质对于编写和分析涉及树形数据结构的算法非常重要。

3.1.2 示例1 公司的组织结构树

在金融领域,树形结构可以用于表示组织结构、投资决策树、风险管理框架等多种场景。下面是一个简单的Python树形结构示例及其在金融领域的一个应用场景。

Python树形结构基础
首先,我们定义一个简单的树节点类:

class TreeNode:
    def __init__(self, value):
        self.value = value
        self.children = []
    
    def add_child(self, node):
        self.children.append(node)

# 创建一个树形结构示例
# 假设这是一个公司的组织结构树

root = TreeNode("CEO")
cto = TreeNode("CTO")
cfo = TreeNode("CFO")
dev1 = TreeNode("Developer 1")
dev2 = TreeNode("Developer 2")
acc1 = TreeNode("Accountant 1")

# 构建树形结构
cto.add_child(dev1)
cto.add_child(dev2)
root.add_child(cto)
root.add_child(cfo)
cfo.add_child(acc1)

3.1.3 示例2 投资决策树

在金融中,投资决策树可以帮助我们模拟和分析不同的投资决策及其结果。下面是一个简单的投资决策树示例,用于决策是否投资某个项目:

class InvestmentNode(TreeNode):
    def __init__(self, value, profit_if_success, profit_if_failure=0):
        super().__init__(value)
        self.profit_if_success = profit_if_success
        self.profit_if_failure = profit_if_failure
 
    def calculate_expected_profit(self, success_rate):
        # 假设我们知道每个节点的成功概率
        expected_profit = success_rate * self.profit_if_success + (1 - success_rate) * self.profit_if_failure
        return expected_profit
 
# 创建一个投资决策树
root = InvestmentNode("投资决策", 0)  # 根节点不产生利润
invest = InvestmentNode("投资项目", 100000, -50000)  # 投资10万,成功赚10万,失败亏5万
not_invest = InvestmentNode("不投资", 0)  # 不投资则没有利润
 
# 构建投资决策树
root.add_child(invest)
root.add_child(not_invest)
 
# 假设投资成功的概率为60%
success_rate = 0.6
 
# 计算预期利润
expected_profit_invest = invest.calculate_expected_profit(success_rate)
expected_profit_not_invest = not_invest.calculate_expected_profit(success_rate)  # 实际上这个值总是0

print(f"投资项目的预期利润为: {expected_profit_invest}")
print(f"不投资的预期利润为: {expected_profit_not_invest}")

在上面的示例中,InvestmentNode类继承自TreeNode类,并增加了与投资相关的属性(如成功和失败的利润)。然后,我们创建了一个简单的投资决策树,包括“投资项目”和“不投资”两个选项,并计算了每个选项的预期利润。通过比较这些预期利润,我们可以做出更明智的投资决策。

3.2 图(Graph)

在Python中,图(Graph)是一种非线性数据结构,由顶点和边组成。在金融领域,图常用于表示各种复杂的关系,如社交网络分析、投资组合分析、市场趋势预测等。下面是一个Python图的基本示例及其在金融领域的应用。

3.2.1 Python图的基本结构

首先,我们需要一个图的基本表示。在Python中,有多种库可以实现图,如networkxmatplotlib等。以下是一个使用networkx库创建图的基本示例:

import networkx as nx
import matplotlib.pyplot as plt

# 创建一个空的无向图
G = nx.Graph()

# 添加节点
G.add_node("A")
G.add_node("B")
G.add_node("C")
G.add_node("D")

# 添加边
G.add_edge("A", "B")
G.add_edge("A", "C")
G.add_edge("B", "D")
G.add_edge("C", "D")

# 绘制图形
nx.draw(G, with_labels=True)
plt.show()

3.2.2 示例:股票关联网络

在金融领域,我们可以使用图来表示股票之间的关联关系,比如它们是否属于同一行业、它们之间的价格波动是否相似等。以下是一个使用图来表示股票关联网络的示例:

import networkx as nx
import pandas as pd
import matplotlib.pyplot as plt

# 假设我们有一个DataFrame,其中包含股票之间的关联得分
# 这里为了示例,我们手动创建一个简单的DataFrame
stock_correlations = pd.DataFrame({
    'stock1': ['AAPL', 'AAPL', 'MSFT', 'MSFT'],
    'stock2': ['MSFT', 'GOOGL', 'GOOGL', 'AAPL'],
    'correlation': [0.8, 0.6, 0.7, 0.5]
})

# 创建一个无向图
G = nx.Graph()

# 根据DataFrame中的关联得分添加边和权重
for index, row in stock_correlations.iterrows():
    G.add_edge(row['stock1'], row['stock2'], weight=row['correlation'])

# 可视化图形,可以使用节点大小和颜色来表示边的权重(这里仅使用颜色)
edge_colors = [G[u][v]['weight'] for u, v in G.edges()]
nx.draw(G, with_labels=True, node_color='skyblue', edge_color=edge_colors, cmap=plt.cm.Blues)
plt.colorbar(label='Correlation Score')
plt.show()

# 我们可以进一步分析图来识别紧密关联的股票群或预测未来的股票价格变动

在这个示例中,我们首先创建了一个表示股票间关联得分的DataFrame。然后,我们使用这个DataFrame来创建一个图,其中股票是节点,它们之间的关联得分是边的权重。最后,我们使用matplotlib库和networkx的绘图功能来可视化这个图。在实际应用中,你可以根据股票的实际数据和关联分析方法来构建更复杂的股票关联网络。

注意,上面的示例仅用于演示如何在Python中使用图来表示金融数据。在真实的金融应用中,你可能需要处理大量的数据,并使用更复杂的图算法和分析技术来提取有用的信息。

3.3 堆(Heap)

在Python中,堆(Heap)是一种特殊的树形数据结构,通常被实现为完全二叉树,它满足堆属性:父节点的值总是大于或等于(最大堆)或小于或等于(最小堆)其子节点的值。在金融领域,堆数据结构可以用于多种优化问题,比如投资组合优化、资产排序等。

下面是一个使用Python内置的heapq模块来实现堆的基本示例,并给出一个金融领域中的可能应用场景:

3.3.1 Python堆的基本结构

import heapq

# 创建一个最小堆
min_heap = []
heapq.heappush(min_heap, 3)
heapq.heappush(min_heap, 1)
heapq.heappush(min_heap, 4)

# 弹出并打印最小元素
print(heapq.heappop(min_heap))  # 输出: 1

# 查看堆顶元素(最小元素)
print(min_heap[0])  # 输出: 3

# 插入新元素
heapq.heappush(min_heap, 2)

# 堆排序(通过不断弹出最小元素实现)
sorted_list = []
while min_heap:
    sorted_list.append(heapq.heappop(min_heap))
print(sorted_list)  # 输出: [1, 2, 3, 4]

3.3.2 示例:资产优先排序

在金融领域,假设我们有一组资产,每个资产都有一个预期收益率和风险值。我们可能希望基于某种指标(比如风险调整后收益)对资产进行排序,以优化我们的投资组合。这里可以使用堆数据结构来高效地实现这一排序过程。

import heapq

# 假设我们有一组资产,每个资产都有一个预期收益率和风险值
assets = [
    {'name': 'Asset A', 'return': 0.1, 'risk': 0.05},
    {'name': 'Asset B', 'return': 0.08, 'risk': 0.03},
    {'name': 'Asset C', 'return': 0.12, 'risk': 0.07},
    # ... 其他资产
]

# 计算风险调整后收益(比如夏普比率)并作为排序依据
# 这里简单使用(收益 - 无风险利率)/ 风险 作为示例,假设无风险利率为0.02
def sharpe_ratio(asset):
    return (asset['return'] - 0.02) / asset['risk']

# 使用最小堆(因为我们想要最大化夏普比率)
# 但由于Python的heapq实现的是最小堆,我们需要取负值来实现最大化效果
max_heap = []
for asset in assets:
    heapq.heappush(max_heap, (-sharpe_ratio(asset), asset))

# 弹出并打印具有最高夏普比率的资产(取负后变为最小堆的最小值)
top_asset = heapq.heappop(max_heap)[1]
print(f"Asset with highest Sharpe ratio: {top_asset['name']}, Sharpe ratio: {sharpe_ratio(top_asset)}")

# 如果需要所有资产的排序结果,可以逐个弹出并处理
sorted_assets = []
while max_heap:
    _, asset = heapq.heappop(max_heap)
    sorted_assets.append(asset)

# sorted_assets现在包含了按夏普比率从高到低排序的资产列表

在这个示例中,我们首先定义了一组资产和计算夏普比率的函数。然后,我们使用Python的heapq模块创建了一个最小堆,并将资产的负夏普比率作为堆的排序依据(因为我们想要最大化夏普比率)。最后,我们弹出并打印了具有最高夏普比率的资产,并可以进一步处理整个排序后的资产列表。

;