数据结构和算法

数据结构:列表 字典 元组 数组 链表 树
算法:在数据结构基础上执行的操作(增删改查)

一.算法

什么是算法?
一个计算过程,解决问题的方法
输入->算法->输出
时间复杂度:用来评估算法运行效率的一个东西
一般来说,时间复杂度高的算法比复杂度低的算法慢

常见的时间复杂度(按效率排序)
    O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^2logn)<O(n^3)

如何一眼判断时间复杂度?
    循环减半的过程 -> O(logn)
    几次循环就是n的几次方的复杂度
空间复杂度:用来评估算法内存占有大小的一个式子

列表查找

列表查找:从列表中查找指定元素
    输入:列表、待查找的元素
    输出:元素下标或未查找到元素

1.顺序查找

从列表第一个元素开始,顺序进行搜索,直到找到为止
def linear_search(li,value):
    for i in li:
        if li[i] == value:
            return i
    return 

时间复杂度:O(n)

2.二分查找

从有序列表的候选区开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半
def bin_search(li,value):
    low = 0
    high = len(li) - 1
    while low <= high:
        mid = (low + high) // 2
        if li[mid] == value:
            return mid
        elif li[mid] > value:
            high = mid - 1
        else:
            low = mid + 1

li = [1,2,3,4,5,6,7,8]
print(bin_search(li,3))
时间复杂度:O(logn)

列表排序

列表排序:将无序列表变为有序列表
应用场景:各种榜单,各种表格,给二分排序用,给其他算法用
输入:无序列表 输出:有序列表
升序与降序

1.冒泡排序

#冒泡排序
#思路:列表每两个相邻的数,如果前边的比后边的大,那么交换这两个数的位置
#代码关键点:趟 无序区
def bubble_sort(li):
    for i in range(len(li)-1):
        for j in range(len(li)-i-1):
            if li[j] > li[j+1]:
                li[j],li[j+1] = li[j+1],li[j]

li = [2,4,5,6,7,3,8,9,1]
bubble_sort(li)
print(li)

时间复杂度:O(n^2)
空间复杂度:1

2.选择排序

#选择排序
#思路:一趟遍历记录最小的数,放到第一个位置,再一趟遍历记录剩余列表中最小的数,继续放置
#代码关键点:无序区 最小数的位置
def select_sort(li):
    for i in range(len(li)-1):
        min_loc = i
        for j in range(i+1,len(li)):
            if li[j] < li[min_loc]:
                li[j],li[min_loc] = li[min_loc],li[j]

li = [2,4,6,5,7,3,8,9,1]
select_sort(li)
print(li)

时间复杂度:O(n^2)
空间复杂度:1

3.插入排序

#插入排序
#思路:列表分为有序区和无序区两个部分.最初有序区只有一个元素.每次从无序区中选择一个元素,插到有序区的位置,直到无序区变空.
#代码关键点:摸到的牌 手里的牌
def insert_sort(li):
    for i in range(1,len(li)):
        tmp = li[i]
        j = i - 1
        while j >= 0 and tmp < li[j]:
            li[j+1] = li[j]
            j = j - 1
        li[j+1] = tmp

li = [2,4,6,5,7,3,8,9,1]
insert_sort(li)
print(li)

时间复杂度:O(n^2)
空间复杂度:1

4.快速排序

好写的排序算法里最快的
快的排序算法里最好写的
思路:
    1.取一个元素p(第一个元素),使元素p归位;
    2.列表被p分为两部分,左边都比p小,右边都比p大;
    3.递归完成排序
算法关键点:1.整理 2.递归
#快速排序
def partition(li,left,right):
    tmp = li[left]
    while left < right:
        while left < right and li[right] >= tmp:
            right -= 1
        li[left] = li[right]
        while left < right and li[left] <= tmp:
            left += 1
        li[right] = li[left]
    li[left] = tmp
    return left
def quit_sort(li,left,right):
    if left < right:
        mid = partition(li,left,right)
        quit_sort(li,left,mid - 1)
        quit_sort(li,mid + 1,right)

li = [2,4,6,5,7,3,8,9,1]
quit_sort(li,0,len(li)-1)
print(li)

时间复杂度:O(nlogn)
空间复杂度:1

5.归并排序

分解:将列表越分越小,直至分成一个元素
一个元素是有序的
合并:将两个有序列表归并,列表越来越大
#归并排序
def merge(li,low,mid,high):
    i = low
    j = mid + 1
    ltmp = []
    while i <= mid and j <= high:
        if li[i] < li[j]:
            ltmp.append(li[i])
            i += 1
        else:
            ltmp.append(li[j])
            j += 1
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        ltmp.append(li[j])
        j += 1
    li[low:high + 1] = ltmp
def mergesort(li,low,high):
    if low < high:
        mid = (low + high) // 2
        mergesort(li,low,mid - 1)
        mergesort(li,mid + 1,high)
        merge(li,low,mid,high)

li = [2,4,6,5,7,3,8,9,1]
mergesort(li,0,len(li)-1)
print(li)

时间复杂度:O(nlogn)
空间复杂度:n

6.希尔排序

希尔排序是一种分组插入排序算法
希尔排序每趟并不是使某些元素有序,而是使整体数据越来越接近有序,最后一趟排序使得所有数据有序
时间复杂度:O(1,3T)

7.桶排序

-计数排序 :创建一个列表用来统计每个数出现的次数
def count_sort(li,max_num):
    count = [0 for i in range(max_num + 1)]
    for num in li:
        count[num] += 1
    li.clear()
    for index,val in enumerate(count):
        for i in range(val):
            li.append(index)

思想

Brute Force(暴力破解 枚举)
分治(分而治之)
动态规划
贪心算法:遵循某种规律,不断贪心的选取当前最优策略的算法设计方案 案例:分糖果

二.数据结构

程序 = 数据结构 + 算法

1.线性结构

数组(列表) 和 链表
区别:数组需要一块连续的内存空间来储存,对内存的要求比较高。如果我们申请一个 100MB 大小的数组,当内存中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于 100MB,仍然会申请失败。 而链表恰恰相反,它并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用,所以如果我们申请的是 100MB 大小的链表,根本不会有问题.

应用

栈:
一种实现'先进后出'的存储结构
只能操作栈顶的元素
栈的分类:
    静态栈:静态栈的核心是数组.类似于一个连续内存的数组,我们只能操作其栈顶的元素
    动态栈:动态栈的核心是链表
栈的应用:
     ①函数的调用:
        def f():
            print('FFF')
            g()
        def g():
            print('GGG')
            k()
        def k():
            print('KKK')
        if __name__ == "__main__":
            f()
        当有多个函数调用时,按照“先调用后返回”的原则,函数之间的信息传递和控制转移必须借助栈来实现,即系统将整个程序运行时所需要的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放他的存储区,即进行出栈操作,当前运行的函数永远在栈顶的位置

    ②浏览器的前进或后退:有两个栈
    
    ③表达式的求值:例如:4+5-2*3  有两个栈.一个是数字栈.一个是运算符栈,循环,将数字放入数字栈,将运算符放入运算符栈,放入运算符时要判断运算符的优先级,当此时运算符的优先级小于或等于栈顶的运算符优先级,将其放到栈顶,当此时运算符的优先级大于栈顶的运算符优先级,我们不会将其加入,而是将此时数字栈顶的数字弹出,与此运算符后的数字进行运算,然后将得到的值放入数字栈的栈顶.




队列:
一种可以实现“先进先出”的数据结构
队列的分类:链式队列 静态队列
生产者消费者模型不是python独有的,而是操作系统中提出的一种思想和概念,可以通过队列实现

①连续存储(数组)

数组,在其python语言中称为列表,是一种基本的数据结构类型
优点:存取速度快
缺点:事先需要知道数组的长度
    需要大块的连续内存
    插入删除非常的慢,效率极低
时间复杂度:插入删除:O(n) 随机访问:O(1)

②离散存储(链表)

时间复杂度:插入删除:O(1) 随机访问:O(n)

定义:
    n个节点离散分配
    彼此通过指针相连
    每个节点只有一个前驱节点,每个节点只有一个后续节点
    首节点没有前驱节点,尾节点没有后续节点

优点:空间没有限制,插入删除数据很快
缺点:查询比较慢

专业术语:
    首节点:第一个有效节点
    尾节点:最后一个有效节点
    头节点:第一个有效节点之前的那个节点,头节点并不存储任何数据,目的是为了方便对链表的操作
    头指针:指向头节点的指针变量
    尾指针:指向尾节点的指针变量
 
链表的分类:
    单链表
    双链表
    循环链表
    非循环链表  

单链表

#单链表
class Hero():
    def __init__(self,number = None, name = None, nickname = None, pNext = None):
        self.number = number
        self.name = name
        self.nickname = nickname
        self.pNext = pNext

#增加
def addHero(head,hero):
    cur = head #指针
    while cur.pNext != None:
        if cur.pNext.number > hero.number:
            break
        cur = cur.pNext
    hero.pNext = cur.pNext
    cur.pNext = hero

#查找
def get_all(head):
    cur = head
    while cur.pNext != None:
        cur = cur.pNext
        print('编号是:%s,名字是:%s,外号是:%s'%(cur.number,cur.name,cur.nickname))

#删除
def delete(head,hero):
    cur = head
    while cur.pNext != None:
        cur = cur.pNext
        if cur.pNext == hero:
            cur.pNext = cur.pNext.pNext
            break

#
def change(head,hero):
    cur = head
    while cur.pNext != None:
        cur = cur.pNext
        if cur.pNext.number == hero.number:
            cur.pNext = hero
            break

head = Hero()

h1 = Hero(1,'宋江','及时雨') #h1其实是一个地址
addHero(head,h1)

h2 = Hero(2,'卢俊义','玉麒麟')
addHero(head,h2)

h3 = Hero(3,'吴用','智多星')
addHero(head,h3)

h5 = Hero(5,'大刀','关胜')
addHero(head,h5)

h4 = Hero(4,'公孙胜','入云龙')
addHero(head,h4)

get_all(head)

2.非线性结构

递归的应用

树和森林就是以递归的方式定义的
树和图的算法就是以递归的方式实现的
很多数学公式就是以递归的方式实现的(斐波那楔序列)

n!
def f(n):
    if 1 == n:
        return 1
    else:
        return f(n-1)*n
    
1+2+3...+n
def sum(n):
    if 1 == n:
        return n
    else:
        return sum(n-1) + n

①树

树的专业术语:
    节点、父节点、子节点:没有节点的节点、子孙、堂兄弟、兄弟、深度:从根节点到最底层节点的层数被称为深度,根节点是第一层、叶子节点:没有子节点的节点、度:子节点的个数
                    
树的分类:
    一般树:任意一个节点的子节点的个数不受限制
    二叉数:定义:任意一个节点的子节点的个数最多是两个,且子节点的位置不可更改
            满二叉树:定义:在不增加层数的前提下,无法再多添加一个节点的二叉树
            完全二叉树:只是删除了满二叉树最底层最右边连续若干个节点
            一般二叉树
     森林:n个互不相交的数的集合

二叉树集体的操作
      A
   B  C
 D E F
1.二叉树的先序遍历(先访问根节点)
    -先访问根节点,再先序遍历左子树,再先序遍历右子数
    -A B D E C F
2.二叉树中序遍历(中间访问根节点)
    -先中序遍历左子树,再访问根节点,再中序遍历右子数
    -D B E A F C
3.二叉树后序遍历(最后访问根节点)
    -先后续遍历左子树,再后续遍历右子数,再访问根节点
    -D E B F C A

树的应用:
    树是数据库中数据组织的一种重要形式
    操作系统子父进程的关系本身就是一颗树
    面向对象语言中类的继承关系    

②图

posted @ 2019-05-27 22:39  Zhuang_Z  阅读(136)  评论(0)    收藏  举报