数据结构与算法 python

绪论

  • 常用的渐进复杂度函数 O(1), O(log n), O(n), O(n log n), O(n²), O(n³), O(2^n)
  • 基本循环程序的算法分析,
    1. 基本操作, 认为其时间复杂度是 O(1)
    2. 加法规则,其复杂性是两个分的复杂性之和, T(n)=O(max(T1(n),T2(n)))
    3. 乘法规则(循环结构), T(n)=O(T1(n) x T2(n))
    4. 取最大规则(分支结构),T(n) = O(max(T1(n), T2(n)))
  • 一些情况的时间开销
    1. 基本算术运算是常量时间操作, 逻辑预算吗是常量时间运算
    2. 组合操作:复制和切片操作通常是线性时间, list的元素访问和袁术赋值是常量时间的, 列表的最后加入和删除元素效率高,其他地方删除和加入元素效率低
    3. 字符串也应该看做是组合操作, 许多操作不是常量时间的
    4. 创建对象也需要付出空间和时间, 空间和时间代价都与对象大小有关
    5. 构造新结构, 如构造新的list, set等.构造新的空结构是常量时间操作,而构造一个包含n个元素的结构,则至少需要O(n)时间
    6. 一些list操作的效率: 表元素访问和元素修改是常量时间操作, 但一般的加入/删除元素操作(即使只加入一个元素)都是O(n)时间操作
    7. 字典dict操作的效率:最坏时间复杂度是O(n), 但平均复杂度是O(1). 也就是说,一般而言字典效率很高,但是偶然也会出现效率低的情况
  • 一些情况的空间开销
    1. 假设程序里建了一个表,而后不断加入元素导致表变得很大,而后不断删除元素,虽然元素变少,但是占用的存储空间并不减少
  • 一些不同写法的时间开销
def test1(n):
    lst = []
    for i in range(n*10000):
        lst = lst + [i]
        return lst


def test2(n):
    lst = []
    for i in range(n*10000):
        lst.append(i)
    return lst


def test3(n):
    return [i for i in range(n*10000)]


def test4(n):
    return list(range(n*10000))
#n=10时运行结果
#test1时间3.0994415283203125e-06
#test2时间0.010317087173461914
#test3时间0.0054988861083984375
#test4时间0.00350189208984375
  • 一些特别有用的典型数据结构: 集合结构, 序列结构,层次结构,数据结构,图结构.这些数据结构称为结构性的数据结构,因为他们的元素之间要满足某种关系.还有一类是功能性的数据结构,包括栈,队列,优先队列,字典
  • python标准函数id取得对象的标识, 内置操作is 和is not 就是通过比较标识的方式怕段是否是同一个对象
  • 已知对象标识 访问相应对象的操作可以直接映射到已知地址访问内存单元,这种操作可以在常量时间完成
  • 计算机内存里表示数据元素之间的联系有两种基本技术:
    1. 利用数据袁术的存储位置隐式表示, 典型例子就是简单字符串
    2. 吧数据元素之间的联系也看做一种数据,显示的保存在内存中
  • python语言中用变量的引用语义, 变量所需的存储空间大小一致,只需要保存一个引用,而c语言是值语义

线性表

  • 线性表有两种实现模型, 一个是顺序表,一个是链接表
  • 顺序表的实现方式很简单,表中元素顺序存放在一片足够大的连续存储区里,元素之间的逻辑顺序关系通过元素在存储区里的物理位置标示(隐式表示元素间的关系).有两种实现方式,一个是一体式结构,一个是分离式结构.而python里的list实现就是采用分离式技术的动态顺序表3
  • list的实现策略:在建立空表时,系统分配一块能容纳8个元素的存储区.插入过程中,如果元素区满了,换一块4倍大的存储区, 但表达到50000时,就变换策略,换存储区时容量加倍
  • 采用链接技术实现线性表称为链接表或链表,基本想法如下
    1. 把表中的元素分别存储在一批独立的存储块里
    2. 保证从组成表结构中的任一个节点可找到与其相关的下一个节点
    3. 在前一节点里用链接的方式显式的记录与下一个节点之间的关联
  • 单链表:一个单链表由一些具体的表节点构成, 每个节点就是一个对象,有自己的标识, 节点之间通过节点链接建立起单向的顺序联系
  • 链表操作复杂度
    1. 创建空表: O(1)
    2. 删除表: O(1)
    3. 判断空表: O(1)
    4. 首端加入元素: O(1)
    5. 尾端加入元素: O(n)
    6. 定位加入元素: O(n)
    7. 首端删除元素: O(1)
    8. 尾端删除元素: O(n)
    9. 定位删除元素: O(n)
    10. 其他删除: O(n)
  • r

字符串

  • 字符串采用的是一体式顺序表形式
  • 匹配算法: 朴素匹配算法, 无回溯匹配算法(KMP算法)
# kmp算法 复杂度O(n)
def matching_kmp(t, p, pnext):
    j, i = 0, 0
    n, m = len(t), len(p)
    while j < n and i <m:
        if i == -1 or t[i] == p[i]:
            j, i = j+1, i+1
        else:
            i = pnext[i]
    if i == m:
        return j - i
    else:
        return -1
  • 匹配算法还有一个BM算法,虽然复杂度也是O(n), 但是实际效率要比kmp算法高很多(书上说的)

树 二叉树

  • 二叉树: 是一种最简单的树型结构,其特点是树中每个节点至多关联到两个后继节点,另一个特点是关联的后继节点明确的分左右.
    image
    1. 不包含任何节点的二叉树称为空树;只包含一个节点的二叉树是一颗单点树
    2. 一颗二叉树的根节点称为该树的子树的父节点
    3. 有些节点没有子节点, 这种节点称为树叶, 树中其他节点称为分支节点,
    4. 一个节点的子节点个数称为该节点的度数, 显然, 树叶节点的度数是0, 分支节点的度数是1或者2
    5. 从一个祖先节点到其任何子孙节点都存在一系列的边,形成从前者到后者的联系,这样一系列首尾相连的边称为树中的一条路径,路径中边的条数称为该路径的长度
    6. 二叉树根的层数为0, 对于位于k层的节点,其子节点是k+1层的元素
    7. 一颗二叉树的高度是树中节点的最大层数
  • 二叉树性质
    1. 在非空二叉树第i层中之多有2^i个节点(i>=0)
    2. 高度位h的二叉树至多有2^(h+1) -1 个节点
  • 满二叉树:如果二叉树中所有分直接点的度数都是2,则称它位一颗满二叉树
  • 完全二叉树: 对于一颗高度为h的二叉树,如果其第0层至第h-1层的节点都满,如果最下一层节点不满,则所有节点在最左边连续排列,空位都在右边,这样的二叉树为完全二叉树
    1. n个节点的完全二叉树高度h, 不大于log2n的最大整数
    2. 如果n个节点的完全二叉树的节点按层次并按从左到右的顺序从0开始编号,
      那么对于任意一个节点i(0<=i<=n-1)都有:
      • 序号为0的是根
      • 对于i>0的,它的父节点是(i-1)/2
      • 2*i+1<n其左子节点是2*i+1, 否则无左子节点
      • 2*i+2<n其左子节点是2*i+2, 否则无左子节点
    1. 深度优先遍历分:先根续,中根序,后根序
    2. 宽度优先遍历又称层次优先遍历,常见的是在每一层里都从左到右逐个访问
  • 优先队列:作为缓存结构,优先队列与栈和队列类似考科一将数据元素保存其中,可以访问和弹出,优先队列的特点是存入其中的每项数据都另附有一个数值,表示这个项的优先程度称为其优先级.基于list可以实现优先队列,但无论是顺序表还是链表,效率都不高,所以考虑用堆来实现优先队列.
  • 堆: 从结构上看,堆就是节点里存储数据的完全二叉树,但堆中的数据的存储要满足一种特殊的堆序:任一个节点里所存储的数据先于或等于其子节点里的数据
  • 堆的应用:堆排序
  • 当用 n 个结点(都做叶子结点且都有各自的权值)试图构建一棵树时,如果构建的这棵树的带权路径长度最小,称这棵树为“最优二叉树”,有时也叫“赫夫曼树”或者“哈夫曼树”。
    在构建哈弗曼树时,要使树的带权路径长度最小,只需要遵循一个原则,那就是:权重越大的结点离树根越近。在图 1 中,因为结点 a 的权值最大,所以理应直接作为根结点的孩子结点。
    python代码实现
class HTNode(BinTNode):
# BinTNode 是一个自定义的二叉树类
    def  __lt__(self, othernode):
        return self.data < othernode.data

class HuffmanPrioQ(PrioQueue):
    """优先队列类"""
    def number(self);
        return len(self._elems)

def huffmanTree(weights):
    trees= HuffmanPrioQ()
    for w in weights:
        trees.enqueue(HTNode(w)) # 入队列,因为是优先队列,所有弹出时是有顺序的
    while trees.number() > 1:    #如果只剩一个节点,那么跳出循环
        t1 = trees.dequeue()
        t2 = trees.dequeue()
        x = t1.data + t2.data
        trees.enqueue(HTNode(x, t1, t2)
    return trees.dequeue()
```d
# 排序
![640.png](https://upload-images.jianshu.io/upload_images/9254930-fa57d7e67e3ef259.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 插入排序

def insert_sort(lst):
for i in range(1, len(lst)):
current = lst[i] # 当前的值
j -= 1
while j > 0 and current < lst[j]: # 用i 和j比较, 并且每次循环j减1
lst[j+1] = lst[j] # 交换, 将位置j 的数据 放到j+1,
j -= 1
lst[j+1] = current # 当j = 0 ,或者找到了该插入的地方,那么把current插入, lst[j]比current大,所以要插入到j+1的位置

- 冒泡排序

def bubble_sort(numbers):
n = len(numbers)
for i in range(n-1, 0, -1): # j表示每次遍历需要比较的次数,是逐渐减小的
for j in (range(i)):
if numbers[j] > numbers[j+1]:
numbers[j], numbers[j+1] = numbers[j+1], numbers[j]

- 选择排序

def select_sort(lists):
n = len(numbers)
for i in range(n):
minIndex = i
for j in range(i+1, n):
if lists[minIndex] > lists[j]:
minIndex = j
lists[i], lists[minIndex] = lists[minIndex], lists[i]
return lists

- 快速排序

def quick_sort(array, l, r):
if l < r:
q = partition(array, l, r)
quick_sort(array, l, q - 1)
quick_sort(array, q + 1, r)

def partition(array, l, r):
x = array[r]
i = l - 1
for j in range(l, r):
if array[j] <= x:
i += 1
array[i], array[j] = array[j], array[i]
array[i + 1], array[r] = array[r], array[i+1]
return i + 1
111

字典和集合

字典

  • 基于顺序表和二分检索实现的字典, 检索速度快O(log n), 但是插入和删除都是O(n),需要连续存储, 所以不适合很大的动态字典
  • 以散列表思想实现字典,具体做法是:
    1. 选定一个整数的下标范围(通常以0或者1开始的), 建立一个包括相应元素位置范围的顺序表
    2. 选定一个从实际关键码集合到上述下标范围的适当映射h: 在需要存入关键码为key的数据时, 将其存入表中第h(key)个位置, 遇到以key为关键码检索数据时,直接去找表中第h(key)个位置的元素
      这个h称为散列函数, 也常被称为哈希(hash)函数或杂凑函数,它就是从可能的关键码集合到一个整数区间(下标区间)的映射
  • 关键码实现字典时, 所用下标集合通常都远远小于关键码集合的规模,|key| >> |index|, 也就是说: 散列函数h 是从一个大集合到一个小集合的映射
  • 冲突是散列表中必然会出现的情况, 负载因子α = (散列表中当时的实际数据项数) / (散列表的基本存储区能容纳的元素个数)

散列函数

  • 常见设计散列函数的方法
    1. 用于整数关键码的若干散列方法
      a. 数字分析法
      b. 折叠法
      c. 中平方法
    2. 常见散列函数:
      a. 除余法
      b. 基数转换法
  • 解决冲突的方法:
    1. 内消解:
      a. 开地址法: 基本想法是, 唉混呗插入数据并发现冲突时, 设法在基本存储区(顺序表)里为插入的数据项另行安排一个位置,为此需要设计一种系统的且易于计算的位置安排方式,称为探查方式,分为线性探查 和双散列探查
    2. 外消解:
      a. 溢出区方法: 如果冲突较少, 溢出区里的实际数据非常少, 这种方式效果不 错, 当随和溢出区中数据的增长, 字典的性能将趋向线性

      b. 桶散列: 另一种可能的做法是数据项不存放在散列表的基本存储区里,二十另外存放. 在散列表里保存对数据项的引用,基于这种想法可以开发出很多不同的设计. 这种设计被称为桶散列, 其中一种最简单的设计 是拉链法
      拉链法.jpg

集合

  • 定义: 一个集合S就是一些个体的汇集. 如果集合S里有个体e, 就说e是S的一个元素, 或说e数据集合S, 用e∈S表示这个事实. 个体e不属于S用 e∉S表示, 包含所有个体的集合称为全集
  • 描述集合的两个方法:
    1. 一种是明确列出其中的所有元素, 这种写法称为集合的外延表示,例如 {1, 3, 5}, 显然, 这种外延表示 还能描述有穷集合
    2. 另一种描述方法时给出集合中的元素应该满足的性质, 这种表示称为集合的描述式, 或者集合的内涵表示, 具体写法是{e|p}, 例如{x+2y | x, y ∈N ʌ x mode y= 3}
  • 一个集合中元素的个数称为该集合的基数, 或说该集合的大小
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • 个人看法:数据结构的重要性对于码农而言就像盖房子的图纸,做饭的菜谱,没有它,也许也能盖得成房子,也能做的熟菜,但是...
    小曼blog阅读 902评论 1 1
  • 一、数据结构 数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等的学科。 二、...
    JaJian阅读 142评论 0 0
  • 基本概念 动态规划(英语:Dynamic programming,简称DP)是一种在数学、管理科学、计算机科学、经...
    复旦猿阅读 2,083评论 2 5
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,068评论 1 32
  • 前言 二叉树是数据结构中一种重要的数据结构,也是树表家族最为基础的结构,包括完全二叉树、满二叉树、二叉查找树、AV...
    复旦猿阅读 411评论 0 1