python进阶篇-day08-数据结构与算法(线性结构介绍与链表实现)

数据的存储和组织形式

程序 = 数据结构 + 算法

一. 算法介绍

概述目的

都是可以提高程序的效率(性能), 面试高频考点

数据结构介绍

数据的存储和组织形式, 同样的空间, 不同的结构, 存储的数据不同, 操作方式也不同

算法介绍

为了解决实际的业务问题, 而考虑出来的方法和思路 => 算法

算法具有独立性, 即: 他是解决问题的思路和方法, 不依赖于语言, 用C能实现, 用Java能实现, 用python也能实现

算法5大特性

  1. 有输入 算法具有0个或者多个输入

  2. 有输出 算法至少有1个或多个输出

  3. 有穷性 算法在有限的步骤之后会自动结束而不会死循环, 并且每个步骤都在可接受的时间内完成

  4. 确定性 算法中的每一步都具有确定的含义, 不会出现二义性

  5. 可执行 算法的每一步都是可行的, 执行有限的次数完成

案例

经典案例: a + b + c = 1000, a^2 + b^2 = c^2

方式1: 穷举法, 155秒

import time
​
start = time.time()
for a in range(1001):
    for b in range(1001):
        for c in range(1001):
            if a ** 2 + b ** 2 == c ** 2 and a + b + c == 1000:
                print(a, b, c)
end = time.time()
print(f'执行了: {end - start}')

方式2: 穷举法, 88.9秒

import time
​
start = time.time()
for a in range(1001):
    for b in range(1001):
        for c in range(1001):
            if a + b + c == 1000 and a ** 2 + b ** 2 == c ** 2:
                print(a, b, c)
end = time.time()
print(f'执行了: {end - start}')

方式3: 代入法(从a, b可得c, 减少一层循环), 0.236秒

import time
​
start = time.time()
for a in range(1001):
    for b in range(1001):
        c = 1000 - a - b
        if a ** 2 + b ** 2 == c ** 2:
            print(a, b, c)
end = time.time()
print(f'执行了: {end - start}')

衡量算法的优略

不能单纯的只依靠程序的执行时间, 因为程序执行时间也会收到机器, 硬件, 其他硬件的影响.

所以可以假定 计算机执行每个步骤的时间都是固定的, 只考虑算法的 执行步骤即可.

代码执行总时间(T) = 操作步骤数量 * 操作步骤执行时间

时间复杂度

时间复杂度T 是关于n的函数

概述

可以反应一个算法的优略, 他表示一个算法随着问题规模的变化而表现出来的主要趋势.

大O标记法

忽略次要条件, 只考虑主要条件(随着问题规模变化而变化的内容), 就会得到: 大O标记法, 标记的效率

例如: 4中 方式1穷举法 的时间复杂度为 : O(n³)

例如: 4中 方式3代入法 的时间复杂度为 : O(n²)

时间复杂度计算

  1. 基本操作: O(1) => 固定步骤

  2. 顺序结构: 加法

  3. 循环结构: 乘法

  4. 分支结构: 取最大项

  5. 只考虑主要条件, 不考虑次要条件

细节

  1. 如非特别说明, 考虑时间复杂度都是考虑: 最坏时间复杂度, 它算是算法的一种保证

  2. 常见的时间复杂度, 效率从高到低分别是:

    O(1) > O(logn) > O(n) > O(nlogn) > O(n²) > O(n³)

空间复杂度(了解)

  1. 空间复杂度指的是: 算法的运算过程中, 临时占用的空间大小, 也用大O标记法标记, 具体如下:

    O(1) < O(logn) < O(n) < O(n²) < O(n³)

  2. 开发思想: 时空转换, 即: 用空间换空间 / 用空间换时间

  3. 数据结构是算法的载体

二. 数据结构

线性结构

特点:每个节点都只能有1个子节点(后继节点) 和 1个父节点(前驱节点)

代表:列表, 栈, 列表, 链表...

顺序表

  1. 概述: 属于线性结构的一种, 例如: 栈, 队列都属于顺序表

  2. 特点: 所有元素在内存中以 连续 的内存空间 来存

分类

一体式存储: 地址和数据一起存储

分离式存储: 地址和数据分离存储

扩容策略
  1. 每次扩容增加固定的条数目, 拿时间环空间

  2. 每次扩容容量翻倍, 拿空间换时间

细节:

顺序表主要存储 数据区 和信息区, 数据区和信息区在一起的叫: 一体式存储, 分开存储 的叫: 分离式存储

顺序表增删

方式1: 末尾增删: 时间复杂度: O(1)

方式2: 中间增删(非保序) 时间复杂度: O(1)

方式3: 中间增删(保序) 时间复杂度: O(n)

链表

  1. 概述: 他是由节点组成, 每个节点由 数值域地址域 组成

  2. 特点: 所有元素在内存中以 非连续 的内存空间来存储, 即: 有地就行

链表介绍
  1. 概述: 属于线性结构, 即: 每个节点都只能有1个子节点(后继节点) 和 1个父节点(前驱节点)

    链表可以看作是 用链条把所有节点连接起来的 一种结构

  2. 节点概述: 由元素域(数值域) 和地址域(连接域)组成, 其中 数值域存储的是 数值, 地址域存储的是下个节点的地址

分类(根据节点)

  1. 单向链表:

    每个节点由1个数值域和1个地址域组成, 且每个节点的地址域指向下个节点的地址, 最后1个节点的地址域为None

  2. 单项循环链表:

    每个节点由1个数值域和1个地址域组成, 且每个节点的地址域指向下个节点的地址, 最后1个节点的地址域为: 第1个节点的地址

  3. 双向链表

    每个节点由1个数值域和2个地址域组成, 且每个节点的地址域分别指向前1个节点的地址 和 后1个节点的地址.

    第1个节点的(前)地址域为: None, 最后1个节点的(后)地址域为: None

  4. 双项循环链表

    每个节点由1个数值域和2个地址域组成, 且每个节点的地址域分别指向前1个节点的地址 和 后1个节点的地址.

    第1个节点的(前)地址域为: 最后1个节点的地址,

    最后1个节点的(后)地址域为: 第1个节点的地址

单链表演示
分析

节点类: Single Node

属性:

item 表示: 数值域

next 表示: 地址域, 即: 指向下个节点的地址

链表类: SingleLinkedList

属性:

head 表示: 头节点, 即: 默认指向链表第一个节点的地址

行为:

is_empty(self)链表是否为空

length(self)链表长度

travel(self) 遍历整个链表

add(self,item)链表头部添加元素

append(self,item)链表尾部添加元素

insert(self,pos,item)指定位置添加元素

remove(self,item)删除节点

search(self,item)查找节点是否存在

代码
# 定义节点类
class SingleNode(object):
    # 初始化节点属性
    def __init__(self, item):
        self.item = item
        self.next = None
​
​
# 定义链表类
class SingleLinkedList(object):
    # 初始化链表属性
    def __init__(self, head=None):
        self.head = head
​
    # 判断链表是否为空
    def is_empty(self):
        return self.head is None
​
    # 链表长度
    def length(self):
        # 定义计数器
        count = 0
        # 定义当前节点, 初值指向头节点
        cur = self.head
        # 判断当前节点是否为空, 不为空遍历
        while cur is not None:
            # 计数器加1
            count += 1
            # 设置当前节点指向下个节点
            cur = cur.next
        # 返回链表长度计数器
        return count
​
    # 遍历链表
    def travel(self):
        # 定义当前节点, 初值指向头节点
        cur = self.head
        # 判断当前节点是否为空, 不为空遍历
        while cur is not None:
            print(cur.item)
            # 设置当前节点指向下个节点
            cur = cur.next
​
    # 头插法
    def add(self, item):
        # 将要添加的数据封装成节点
        new_node = SingleNode(item)
        # 设置新节点的地址域为头节点
        new_node.next = self.head
        # 将头节点为新节点
        self.head = new_node
​
    # 尾插
    def append(self, item):
        # 将要添加的节点封装成节点
        new_node = SingleNode(item)
        # 判断当前头节点是否为空
        if self.head is None:
            # 为空直接将头节点设置为新节点
            self.head = new_node
        else:
            # 不为空则遍历链表
            cur = self.head
            # 判断当前节点的地址域是否不为空
            while cur.next is not None:
                # 这里是当前节点的地址域不为空, 继续遍历
                cur = cur.next
            # 这里找到最后节点, 将最后节点的地址域设置为新节点
            cur.next = new_node
​
    # 插入到指定位置(索引从0开始)
    def insert(self, pos, item):
        # 判断插入的位置是否合法
        if pos <= 0:
            # 小于0, 则在头部插入
            self.add(item)
        elif pos >= self.length():
            # 大于链表长度, 则在尾部插入
            self.append(item)
        else:
            # 定义计数器
            count = 0
            # 定义存储当前的节点
            cur = self.head
            # 判断是否为要插入位置的前一个节点
            while count < pos - 1:
                cur = cur.next
                count += 1
            # 具体插入动作
            new_node = SingleNode(item)
            # 顺序不能变
            new_node.next = cur.next
            cur.next = new_node
​
    # 删除节点
    def remove(self, item):
        # 当前节点
        cur = self.head
        # 当前节点的前一个节点
        pre = None
        while cur is not None:
            if cur.item == item:
                # 被删除的为头节点
                if cur == self.head:
                    # 将头节点设为当前节点地址域
                    self.head = cur.next
                else:
                    # 设置当前的前一个节点为当前节点的地址域
                    pre.next = cur.next
                break
            else:
                # 将当前节点设为前一个节点
                pre = cur
                # 将当前节点的地址域设为当前节点
                cur = cur.next
​
    # 查询元素
    def search(self, item):
        # 定义变量存储当前节点
        cur = self.head
        # 遍历
        while cur is not None:
            if cur.item == item:
                return True
            else:
                cur = cur.next
        return False
​
​
if __name__ == '__main__':
    # 测试节点类
    node1 = SingleNode('张三')
    print(node1)
    print(node1.item)
    print(node1.next)
    print('-' * 21)
​
    # 测试创建空链表
    linkedlist1 = SingleLinkedList()
    print(linkedlist1.head)
    print('-' * 21)
​
    # 测试创建链表, 头节点指向node1节点
    linkedlist2 = SingleLinkedList(node1)
    print(linkedlist2.head)
    print(linkedlist2.head.item)
    print(linkedlist2.head.next)
    print('-' * 21)
​
    # 测试头添加
    linkedlist2.add('李四')
    linkedlist2.add('王五')
​
    # 测试尾部添加
    linkedlist2.append('麻子')
    linkedlist2.append('孙二')
​
    # 添加到指定位置
    linkedlist2.insert(-10, '-10')
    linkedlist2.insert(200, '200')
    linkedlist2.insert(2, '2')
​
    # 删除元素
    linkedlist2.remove('-10')
    linkedlist2.remove('2')
    linkedlist2.remove('200')
​
    # 查找
    print('查找')
    print(linkedlist2.search('-2'))
    print(linkedlist2.search('麻子'))
    print('-' * 21)
​
    # 测试是否为空
    print('空')
    print(linkedlist1.is_empty())
    print(linkedlist2.is_empty())
    print('-' * 21)
​
    # 链表长度
    print('长度')
    print(linkedlist1.length())
    print(linkedlist2.length())
    print('-' * 21)
​
    # 遍历链表
    print('遍历')
    print('')
    linkedlist1.travel()
    linkedlist2.travel()
    # print('-' * 21)
​

非线性结构

特点: 每个节点都可以有n个子节点(后继节点) 和 n个父节点(前驱节点)

代表: 树, 图......

  • 28
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值