大话数据结构(程杰)

数据结构绪论

数据结构学科定义:是一门研究非数值计算的程序设计中的操作对象,它们之间的关系和操作等相关问题的数据学科。

数据组成

数据元素:组成数据的基本单位。
数据项:数据元素可以优若干个数据项构成,数据不可分割的最小单位。
数据对象:数据相同的元素组成的集合,是数据的子集合。
example:
[类数据:人类, 元素:{张三, 李四,…}, 数据项:{眼睛,鼻子,…}, 数据对象(眼睛):{张三的眼睛, 李四的眼睛…}]

单独说说数据结构
不同的元素的数据元素之间不是相互独立的,而是存在关系的,这种关系称之为结构。

逻辑结构:集合、线性、树形、拓扑图
物理结构:数据的逻辑结构在计算机中的存储形式

顺序存储结构:数据存储在对地址连续的存储单元,逻辑关系与物理关系一致。
连式存储结构:数据元素位置任意,需要指针存放数据元素的地址信息。

算法

时间复杂度

这里仅仅记录下几个重要的定义:
算法的时间复杂度:T(n),随着n的变化,执行次数的变化,仅仅关注T(n)的最高阶。O(1):常数阶、O(n):线性阶、O($ n^2 $)平方阶 。下面分别给出不同等差数列求和为例:

# 1+2...+n
# O(1)
def sum_1(n):
    return int((1 + n) * n / 2)

# n
def sum_2(n):
    count = 0
    for i in range(1, n+1):
        count += i
    return count

# n*n
def sum_3(n):
    count = 0
    for i in range(1, n+1):
        for j in range(i):
            count += 1
    return count

常用的算法复杂度比较:
$$ O(1) < O(n) < O(logn) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) $$
最坏复杂度和平均复杂度:n个数顺序存储,现在需要插入某个元素,最好的情况1, 最坏的情况n+1,那么平均复杂度是多少了?,搜寻任意x的期望值(n+2)/2。一般没有特殊说明,复杂度都是指最坏的情况。

空间复杂度

定义和时间复杂度类似,重要的是我们可以通过空间复杂度来换取时间。
example:设计一个算法,复杂度为O($n^2$), 这里把算法的结果顺序存储下来,复杂度O(1)。

线性表

最大的确点插入和删除:算法时间复杂度大。为了解决这个问题就得牺牲下空间—连式存储

  • 连式存储
    每个数据储存叫节点Node,Node由数域和指针域,前者存储数值后者存储后继的地址信息。存在头指针,每次必须从头指针开始,最后一个指针域为空。

  • 搜寻
    想得到某个指定地址数据,每次必须从头开始,往下搜寻下去找到即停止,否者直到指针域为空停止说明数据不存在。时间复杂度O(n).

  • 插入
    p节点后插入s节点,s->next=p->next, p-next=s,p的后继赋值给s后继,p后继改为s

  • 删除
    p->next = p->next->next
    ps:静态链表,循环链表,双向链表

想象下弹夹

  1. 是线性单链表
  2. 仅仅允许在链表尾部,即栈顶进行插入和删除操作
  3. 1-n数依次进栈(仅仅是进去的顺序),出站顺序必须满足任意想编号x后面的编号小于x的必须倒序排列。
  4. 两个站共享内存,从中间某个位置作为两个栈顶, 两端为栈为。如果容积为n当两个站的顶部地址差为1时代表栈满。
  5. 链栈不存在栈满的情况???
  6. 栈的递归运用,斐波那契数列数列为例,每次递归到最底层就把数据压入栈的底部,慢慢向上回溯到顶部得到最后顶部值。
# 方法1
def Fib():
    a = 1
    yield a
    b = a
    yield b
    while 1:
        c = a + b
        yield c
        a = b
        b = c

# 这里采用递归的方法解决,稍微改变下需求,求第i个fib数列
def Fib_i(i):
    if i <= 2:
        return 1
    else:
        return Fib_i(i-1) + Fib_i(i-2)

串(string)

  • 朴素的模式匹配方法
    001 匹配000000001,从第一个起点index=0开始001,发现前两个00能匹配到第1个不行,然后index=1,直到尾部,如果两个长度分别为m, n (n >= m),则算法复杂度为(n – m + 1)* m。

  • KMP模式匹配
    abcd和abcabcd进行匹配

step1:
abcabcd
abcd
index=3时不匹配
step2
abcabcd
`abcd
index=1终止
step3
abcabcd
“abcd
index=2终止
!!!!!!其实上面step2 step3都是无效
因为在step1时a=a,向后一个个平移到abcabcd第二个a时才可以进一步可能,因此step2可以直接跳到下面
abcabcd
···abcd

二叉树

  • 二叉树的性质
  1. 第i层节点数最多可能:$2^{i-1}$
  2. 深度为n的二叉树至多:$2^{n}-1$个节点
  3. 叶节点为$n_{leaf}$,度为2 的节点数$n_{node2}$,则$n_{leaf}=n_{node2}+1$
  4. n个节点完全二叉树的深度$[log_2(n)] + 1$,[]为取整运算
  5. 把完全二叉树n个节点按层进行编号。如果i=1则为root,i>1这这个节点的父节点为$[i/2]$;如果节点$2i > n$就没有左孩子,否则左孩子为$2i$;如果$2 * i +1 > n$则无孩子,否则其有孩子为$2 * i +1$.
  • 满树

n个节点,除了每个叶节点,每个节点都有左右左右子树,并且所有的叶节点都在最底层,层次m与节点n存在 $2^m – 1=n$关系。

  • 完全二叉树

对应一个二叉树按层次对所以节点编号包括叶节点,如果所有节点编号与同样深度的满二叉树编号结果一致,那么该树为满二叉树。
性质、属性:

  1. 叶节点只能在最下面2层,倒数第二层存在叶节点则,一定集中在树右侧且连续,二最下面一层则集中在左侧且连续。
  2. 同样节点数的树,完全二叉树深度最小

满树是完全二叉树的子集

  • 二叉树的遍历
  1. 前序:根节点左孩子右孩子
  2. 中序:左孩子根节点右孩子
  3. 后续:左孩子右孩子根节点
  4. 层次遍历:顾名思义吧

单独成文

查找

单独成文

排序

内排序:与下相反
外排序:需要辅助空间

排序方法:
1.冒泡排序:两两比较,不符合则交换,复杂度$O(n^2)$。
下面看看这两者的时间对比:# out:0.34375643730163574; 0.10623884201049805

def buddleSort(L):
    n = len(L)
    for i in range(n):
        count = 0
        for j in range(n - 1):
            if L[j] > L[j + 1]:
                c = L[j]
                L[j] = L[j + 1]
                L[j + 1] = c
                count = 1
            else:
                pass
        if count == 0:
            # 如果序列不在发生改变跳出循环
            break
    return L


print(buddleSort([4, 3, 1, 2]))

# 升级算法:比如:
# 3, 2, 1, 4, 5   初始状态
# 2, 1, 3, 4, 5   第一个外层循环后,内层在j=2时后面的顺序不在改变
# 1, 2, 3, 4, 5   在第二外层,j=2和后面都不要在遍历了,前面一个外层已经知道
def buddleSort2(L):
    n = len(L)
    m = n
    for i in range(n):
        count = 1
        # print('m', m, i)
        flag = None
        for j in range(m - 1):
            if L[j] > L[j + 1]:
                c = L[j]
                L[j] = L[j + 1]
                L[j + 1] = c
                count = 0
                flag = j + 1
            else:
                pass
        # print('flag:', flag)
        m = flag
        if count:
            # 如果序列不在发生改变跳出循环
            break
    return L


Z = [1, 4, 3, 2] + list(range(10, 1000000))
print(Z[:7])
s = time.time()
res = buddleSort(Z)
cost = time.time() - s
print(Z[:7])    # 行数调用的时候吧Z顺序已经改变了,fuck

Z = [1, 4, 3, 2] + list(range(10, 1000000))
s = time.time()
res2 = buddleSort2(Z)
cost2 = time.time() - s
print(cost, cost2)
print(res2[:7])
  1. 简单排序法:找到最小的排在第一位,在剩下的元素中在找最小的排在第二位。。。。。复杂度$O(n^2)$

  2. 直接插入排序:(利用链式存储)复杂度$O(n^2)$。参考
    (http://bubkoo.com/2014/01/15/sort-algorithm/shell-sort/)

def insert(i, j, L):
    # i < j
    c = L[j]
    for z in range(j, i - 1, -1):
        L[z] = L[z - 1]
    L[i] = c
    return L
x = [1, 2, 4, 5]
print(insert(0, 3, x))

def InsertSort(L):
    n = len(L)
    if L[0] > L[1]:
        L = insert(0, 1, L)
    for i in range(2, n):
        for z in range(i - 1, -1, -1):
            if (L[i] < L[z]) and (L[i] >= L[z-1]):
                L = insert(z, i, L)
    return L

x = [1, 4, 1, 3, 4, 6, 2]
print(InsertSort(x))
  1. 希尔排序:将数据分为d组,然后组内进行插入排序,d递减步长为1直到循环到1即停止,复杂度$O(n^{3/2})$。[参考]
  2. 堆排序:将数据编号按层次构建完全二叉树,利用满二叉树性质,从叶节点从树的下面向上寻,吧大的数给父节点,最后root就为最大的数,如此反复,就会得到序列。复杂度$O(nlog_2n)$
  3. 归并排序:分而治之复杂度$O(nlog_2n)$
  4. 快速排序:参考**复杂度$O(nlog_2n)$
    原文作者:Kean_L_C
    原文地址: https://www.jianshu.com/p/c2c712132085
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞